View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.io;
29  
30  import java.io.IOException;
31  import java.io.OutputStream;
32  import java.nio.ByteBuffer;
33  import java.nio.CharBuffer;
34  import java.nio.charset.Charset;
35  import java.nio.charset.CharsetEncoder;
36  import java.nio.charset.CoderResult;
37  import java.nio.charset.CodingErrorAction;
38  
39  import org.apache.http.annotation.NotThreadSafe;
40  import org.apache.http.io.BufferInfo;
41  import org.apache.http.io.SessionOutputBuffer;
42  import org.apache.http.io.HttpTransportMetrics;
43  import org.apache.http.params.CoreConnectionPNames;
44  import org.apache.http.params.HttpParams;
45  import org.apache.http.params.HttpProtocolParams;
46  import org.apache.http.protocol.HTTP;
47  import org.apache.http.util.ByteArrayBuffer;
48  import org.apache.http.util.CharArrayBuffer;
49  
50  /**
51   * Abstract base class for session output buffers that stream data to
52   * an arbitrary {@link OutputStream}. This class buffers small chunks of
53   * output data in an internal byte array for optimal output performance.
54   * <p>
55   * {@link #writeLine(CharArrayBuffer)} and {@link #writeLine(String)} methods
56   * of this class use CR-LF as a line delimiter.
57   * <p>
58   * The following parameters can be used to customize the behavior of this
59   * class:
60   * <ul>
61   *  <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
62   *  <li>{@link org.apache.http.params.CoreConnectionPNames#MIN_CHUNK_LIMIT}</li>
63   * </ul>
64   * <p>
65   *
66   * @since 4.0
67   */
68  @NotThreadSafe
69  public abstract class AbstractSessionOutputBuffer implements SessionOutputBuffer, BufferInfo {
70  
71      private static final Charset ASCII = Charset.forName("US-ASCII");
72      private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF};
73  
74      private OutputStream outstream;
75      private ByteArrayBuffer buffer;
76  
77      private Charset charset;
78      private CharsetEncoder encoder;
79      private ByteBuffer bbuf;
80      private boolean ascii = true;
81      private int minChunkLimit = 512;
82  
83      private HttpTransportMetricsImpl metrics;
84  
85      private CodingErrorAction onMalformedInputAction;
86      private CodingErrorAction onUnMappableInputAction;
87  
88      /**
89       * Initializes this session output buffer.
90       *
91       * @param outstream the destination output stream.
92       * @param buffersize the size of the internal buffer.
93       * @param params HTTP parameters.
94       */
95      protected void init(final OutputStream outstream, int buffersize, final HttpParams params) {
96          if (outstream == null) {
97              throw new IllegalArgumentException("Input stream may not be null");
98          }
99          if (buffersize <= 0) {
100             throw new IllegalArgumentException("Buffer size may not be negative or zero");
101         }
102         if (params == null) {
103             throw new IllegalArgumentException("HTTP parameters may not be null");
104         }
105         this.outstream = outstream;
106         this.buffer = new ByteArrayBuffer(buffersize);
107         this.charset = Charset.forName(HttpProtocolParams.getHttpElementCharset(params));
108         this.ascii = this.charset.equals(ASCII);
109         this.encoder = null;
110         this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512);
111         this.metrics = createTransportMetrics();
112         this.onMalformedInputAction = HttpProtocolParams.getMalformedInputAction(params);
113         this.onUnMappableInputAction = HttpProtocolParams.getUnmappableInputAction(params);
114     }
115 
116     /**
117      * @since 4.1
118      */
119     protected HttpTransportMetricsImpl createTransportMetrics() {
120         return new HttpTransportMetricsImpl();
121     }
122 
123     /**
124      * @since 4.1
125      */
126     public int capacity() {
127         return this.buffer.capacity();
128     }
129 
130     /**
131      * @since 4.1
132      */
133     public int length() {
134         return this.buffer.length();
135     }
136 
137     /**
138      * @since 4.1
139      */
140     public int available() {
141         return capacity() - length();
142     }
143 
144     protected void flushBuffer() throws IOException {
145         int len = this.buffer.length();
146         if (len > 0) {
147             this.outstream.write(this.buffer.buffer(), 0, len);
148             this.buffer.clear();
149             this.metrics.incrementBytesTransferred(len);
150         }
151     }
152 
153     public void flush() throws IOException {
154         flushBuffer();
155         this.outstream.flush();
156     }
157 
158     public void write(final byte[] b, int off, int len) throws IOException {
159         if (b == null) {
160             return;
161         }
162         // Do not want to buffer large-ish chunks
163         // if the byte array is larger then MIN_CHUNK_LIMIT
164         // write it directly to the output stream
165         if (len > this.minChunkLimit || len > this.buffer.capacity()) {
166             // flush the buffer
167             flushBuffer();
168             // write directly to the out stream
169             this.outstream.write(b, off, len);
170             this.metrics.incrementBytesTransferred(len);
171         } else {
172             // Do not let the buffer grow unnecessarily
173             int freecapacity = this.buffer.capacity() - this.buffer.length();
174             if (len > freecapacity) {
175                 // flush the buffer
176                 flushBuffer();
177             }
178             // buffer
179             this.buffer.append(b, off, len);
180         }
181     }
182 
183     public void write(final byte[] b) throws IOException {
184         if (b == null) {
185             return;
186         }
187         write(b, 0, b.length);
188     }
189 
190     public void write(int b) throws IOException {
191         if (this.buffer.isFull()) {
192             flushBuffer();
193         }
194         this.buffer.append(b);
195     }
196 
197     /**
198      * Writes characters from the specified string followed by a line delimiter
199      * to this session buffer.
200      * <p>
201      * This method uses CR-LF as a line delimiter.
202      *
203      * @param      s   the line.
204      * @exception  IOException  if an I/O error occurs.
205      */
206     public void writeLine(final String s) throws IOException {
207         if (s == null) {
208             return;
209         }
210         if (s.length() > 0) {
211             if (this.ascii) {
212                 for (int i = 0; i < s.length(); i++) {
213                     write(s.charAt(i));
214                 }
215             } else {
216                 CharBuffer cbuf = CharBuffer.wrap(s);
217                 writeEncoded(cbuf);
218             }
219         }
220         write(CRLF);
221     }
222 
223     /**
224      * Writes characters from the specified char array followed by a line
225      * delimiter to this session buffer.
226      * <p>
227      * This method uses CR-LF as a line delimiter.
228      *
229      * @param      charbuffer the buffer containing chars of the line.
230      * @exception  IOException  if an I/O error occurs.
231      */
232     public void writeLine(final CharArrayBuffer charbuffer) throws IOException {
233         if (charbuffer == null) {
234             return;
235         }
236         if (this.ascii) {
237             int off = 0;
238             int remaining = charbuffer.length();
239             while (remaining > 0) {
240                 int chunk = this.buffer.capacity() - this.buffer.length();
241                 chunk = Math.min(chunk, remaining);
242                 if (chunk > 0) {
243                     this.buffer.append(charbuffer, off, chunk);
244                 }
245                 if (this.buffer.isFull()) {
246                     flushBuffer();
247                 }
248                 off += chunk;
249                 remaining -= chunk;
250             }
251         } else {
252             CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length());
253             writeEncoded(cbuf);
254         }
255         write(CRLF);
256     }
257 
258     private void writeEncoded(final CharBuffer cbuf) throws IOException {
259         if (!cbuf.hasRemaining()) {
260             return;
261         }
262         if (this.encoder == null) {
263             this.encoder = this.charset.newEncoder();
264             this.encoder.onMalformedInput(this.onMalformedInputAction);
265             this.encoder.onUnmappableCharacter(this.onUnMappableInputAction);
266         }
267         if (this.bbuf == null) {
268             this.bbuf = ByteBuffer.allocate(1024);
269         }
270         this.encoder.reset();
271         while (cbuf.hasRemaining()) {
272             CoderResult result = this.encoder.encode(cbuf, this.bbuf, true);
273             handleEncodingResult(result);
274         }
275         CoderResult result = this.encoder.flush(this.bbuf);
276         handleEncodingResult(result);
277         this.bbuf.clear();
278     }
279 
280     private void handleEncodingResult(final CoderResult result) throws IOException {
281         if (result.isError()) {
282             result.throwException();
283         }
284         this.bbuf.flip();
285         while (this.bbuf.hasRemaining()) {
286             write(this.bbuf.get());
287         }
288         this.bbuf.compact();
289     }
290 
291     public HttpTransportMetrics getMetrics() {
292         return this.metrics;
293     }
294 
295 }