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.InputStream;
32  import java.nio.ByteBuffer;
33  import java.nio.CharBuffer;
34  import java.nio.charset.Charset;
35  import java.nio.charset.CharsetDecoder;
36  import java.nio.charset.CoderResult;
37  import java.nio.charset.CodingErrorAction;
38  
39  import org.apache.http.Consts;
40  import org.apache.http.io.BufferInfo;
41  import org.apache.http.io.HttpTransportMetrics;
42  import org.apache.http.io.SessionInputBuffer;
43  import org.apache.http.params.CoreConnectionPNames;
44  import org.apache.http.params.CoreProtocolPNames;
45  import org.apache.http.params.HttpParams;
46  import org.apache.http.protocol.HTTP;
47  import org.apache.http.util.Args;
48  import org.apache.http.util.ByteArrayBuffer;
49  import org.apache.http.util.CharArrayBuffer;
50  
51  /**
52   * Abstract base class for session input buffers that stream data from
53   * an arbitrary {@link InputStream}. This class buffers input data in
54   * an internal byte array for optimal input performance.
55   * <p>
56   * {@link #readLine(CharArrayBuffer)} and {@link #readLine()} methods of this
57   * class treat a lone LF as valid line delimiters in addition to CR-LF required
58   * by the HTTP specification.
59   *
60   * @since 4.0
61   *
62   * @deprecated (4.3) use {@link SessionInputBufferImpl}
63   */
64  @Deprecated
65  public abstract class AbstractSessionInputBuffer implements SessionInputBuffer, BufferInfo {
66  
67      private InputStream instream;
68      private byte[] buffer;
69      private ByteArrayBuffer linebuffer;
70      private Charset charset;
71      private boolean ascii;
72      private int maxLineLen;
73      private int minChunkLimit;
74      private HttpTransportMetricsImpl metrics;
75      private CodingErrorAction onMalformedCharAction;
76      private CodingErrorAction onUnmappableCharAction;
77  
78      private int bufferpos;
79      private int bufferlen;
80      private CharsetDecoder decoder;
81      private CharBuffer cbuf;
82  
83      public AbstractSessionInputBuffer() {
84      }
85  
86      /**
87       * Initializes this session input buffer.
88       *
89       * @param instream the source input stream.
90       * @param buffersize the size of the internal buffer.
91       * @param params HTTP parameters.
92       */
93      protected void init(final InputStream instream, final int buffersize, final HttpParams params) {
94          Args.notNull(instream, "Input stream");
95          Args.notNegative(buffersize, "Buffer size");
96          Args.notNull(params, "HTTP parameters");
97          this.instream = instream;
98          this.buffer = new byte[buffersize];
99          this.bufferpos = 0;
100         this.bufferlen = 0;
101         this.linebuffer = new ByteArrayBuffer(buffersize);
102         final String charset = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET);
103         this.charset = charset != null ? Charset.forName(charset) : Consts.ASCII;
104         this.ascii = this.charset.equals(Consts.ASCII);
105         this.decoder = null;
106         this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1);
107         this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512);
108         this.metrics = createTransportMetrics();
109         final CodingErrorAction a1 = (CodingErrorAction) params.getParameter(
110                 CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION);
111         this.onMalformedCharAction = a1 != null ? a1 : CodingErrorAction.REPORT;
112         final CodingErrorAction a2 = (CodingErrorAction) params.getParameter(
113                 CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION);
114         this.onUnmappableCharAction = a2 != null? a2 : CodingErrorAction.REPORT;
115     }
116 
117     /**
118      * @since 4.1
119      */
120     protected HttpTransportMetricsImpl createTransportMetrics() {
121         return new HttpTransportMetricsImpl();
122     }
123 
124     /**
125      * @since 4.1
126      */
127     @Override
128     public int capacity() {
129         return this.buffer.length;
130     }
131 
132     /**
133      * @since 4.1
134      */
135     @Override
136     public int length() {
137         return this.bufferlen - this.bufferpos;
138     }
139 
140     /**
141      * @since 4.1
142      */
143     @Override
144     public int available() {
145         return capacity() - length();
146     }
147 
148     protected int fillBuffer() throws IOException {
149         // compact the buffer if necessary
150         if (this.bufferpos > 0) {
151             final int len = this.bufferlen - this.bufferpos;
152             if (len > 0) {
153                 System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len);
154             }
155             this.bufferpos = 0;
156             this.bufferlen = len;
157         }
158         final int l;
159         final int off = this.bufferlen;
160         final int len = this.buffer.length - off;
161         l = this.instream.read(this.buffer, off, len);
162         if (l == -1) {
163             return -1;
164         } else {
165             this.bufferlen = off + l;
166             this.metrics.incrementBytesTransferred(l);
167             return l;
168         }
169     }
170 
171     protected boolean hasBufferedData() {
172         return this.bufferpos < this.bufferlen;
173     }
174 
175     @Override
176     public int read() throws IOException {
177         int noRead;
178         while (!hasBufferedData()) {
179             noRead = fillBuffer();
180             if (noRead == -1) {
181                 return -1;
182             }
183         }
184         return this.buffer[this.bufferpos++] & 0xff;
185     }
186 
187     @Override
188     public int read(final byte[] b, final int off, final int len) throws IOException {
189         if (b == null) {
190             return 0;
191         }
192         if (hasBufferedData()) {
193             final int chunk = Math.min(len, this.bufferlen - this.bufferpos);
194             System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
195             this.bufferpos += chunk;
196             return chunk;
197         }
198         // If the remaining capacity is big enough, read directly from the
199         // underlying input stream bypassing the buffer.
200         if (len > this.minChunkLimit) {
201             final int read = this.instream.read(b, off, len);
202             if (read > 0) {
203                 this.metrics.incrementBytesTransferred(read);
204             }
205             return read;
206         } else {
207             // otherwise read to the buffer first
208             while (!hasBufferedData()) {
209                 final int noRead = fillBuffer();
210                 if (noRead == -1) {
211                     return -1;
212                 }
213             }
214             final int chunk = Math.min(len, this.bufferlen - this.bufferpos);
215             System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
216             this.bufferpos += chunk;
217             return chunk;
218         }
219     }
220 
221     @Override
222     public int read(final byte[] b) throws IOException {
223         if (b == null) {
224             return 0;
225         }
226         return read(b, 0, b.length);
227     }
228 
229     private int locateLF() {
230         for (int i = this.bufferpos; i < this.bufferlen; i++) {
231             if (this.buffer[i] == HTTP.LF) {
232                 return i;
233             }
234         }
235         return -1;
236     }
237 
238     /**
239      * Reads a complete line of characters up to a line delimiter from this
240      * session buffer into the given line buffer. The number of chars actually
241      * read is returned as an integer. The line delimiter itself is discarded.
242      * If no char is available because the end of the stream has been reached,
243      * the value {@code -1} is returned. This method blocks until input
244      * data is available, end of file is detected, or an exception is thrown.
245      * <p>
246      * This method treats a lone LF as a valid line delimiters in addition
247      * to CR-LF required by the HTTP specification.
248      *
249      * @param      charbuffer   the line buffer.
250      * @return     one line of characters
251      * @throws  IOException  if an I/O error occurs.
252      */
253     @Override
254     public int readLine(final CharArrayBuffer charbuffer) throws IOException {
255         Args.notNull(charbuffer, "Char array buffer");
256         int noRead = 0;
257         boolean retry = true;
258         while (retry) {
259             // attempt to find end of line (LF)
260             final int i = locateLF();
261             if (i != -1) {
262                 // end of line found.
263                 if (this.linebuffer.isEmpty()) {
264                     // the entire line is preset in the read buffer
265                     return lineFromReadBuffer(charbuffer, i);
266                 }
267                 retry = false;
268                 final int len = i + 1 - this.bufferpos;
269                 this.linebuffer.append(this.buffer, this.bufferpos, len);
270                 this.bufferpos = i + 1;
271             } else {
272                 // end of line not found
273                 if (hasBufferedData()) {
274                     final int len = this.bufferlen - this.bufferpos;
275                     this.linebuffer.append(this.buffer, this.bufferpos, len);
276                     this.bufferpos = this.bufferlen;
277                 }
278                 noRead = fillBuffer();
279                 if (noRead == -1) {
280                     retry = false;
281                 }
282             }
283             if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) {
284                 throw new IOException("Maximum line length limit exceeded");
285             }
286         }
287         if (noRead == -1 && this.linebuffer.isEmpty()) {
288             // indicate the end of stream
289             return -1;
290         }
291         return lineFromLineBuffer(charbuffer);
292     }
293 
294     /**
295      * Reads a complete line of characters up to a line delimiter from this
296      * session buffer. The line delimiter itself is discarded. If no char is
297      * available because the end of the stream has been reached,
298      * {@code null} is returned. This method blocks until input data is
299      * available, end of file is detected, or an exception is thrown.
300      * <p>
301      * This method treats a lone LF as a valid line delimiters in addition
302      * to CR-LF required by the HTTP specification.
303      *
304      * @return HTTP line as a string
305      * @throws  IOException  if an I/O error occurs.
306      */
307     private int lineFromLineBuffer(final CharArrayBuffer charbuffer)
308             throws IOException {
309         // discard LF if found
310         int len = this.linebuffer.length();
311         if (len > 0) {
312             if (this.linebuffer.byteAt(len - 1) == HTTP.LF) {
313                 len--;
314             }
315             // discard CR if found
316             if (len > 0) {
317                 if (this.linebuffer.byteAt(len - 1) == HTTP.CR) {
318                     len--;
319                 }
320             }
321         }
322         if (this.ascii) {
323             charbuffer.append(this.linebuffer, 0, len);
324         } else {
325             final ByteBuffer bbuf =  ByteBuffer.wrap(this.linebuffer.buffer(), 0, len);
326             len = appendDecoded(charbuffer, bbuf);
327         }
328         this.linebuffer.clear();
329         return len;
330     }
331 
332     private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position)
333             throws IOException {
334         final int off = this.bufferpos;
335         int i = position;
336         this.bufferpos = i + 1;
337         if (i > off && this.buffer[i - 1] == HTTP.CR) {
338             // skip CR if found
339             i--;
340         }
341         int len = i - off;
342         if (this.ascii) {
343             charbuffer.append(this.buffer, off, len);
344         } else {
345             final ByteBuffer bbuf =  ByteBuffer.wrap(this.buffer, off, len);
346             len = appendDecoded(charbuffer, bbuf);
347         }
348         return len;
349     }
350 
351     private int appendDecoded(
352             final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException {
353         if (!bbuf.hasRemaining()) {
354             return 0;
355         }
356         if (this.decoder == null) {
357             this.decoder = this.charset.newDecoder();
358             this.decoder.onMalformedInput(this.onMalformedCharAction);
359             this.decoder.onUnmappableCharacter(this.onUnmappableCharAction);
360         }
361         if (this.cbuf == null) {
362             this.cbuf = CharBuffer.allocate(1024);
363         }
364         this.decoder.reset();
365         int len = 0;
366         while (bbuf.hasRemaining()) {
367             final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true);
368             len += handleDecodingResult(result, charbuffer, bbuf);
369         }
370         final CoderResult result = this.decoder.flush(this.cbuf);
371         len += handleDecodingResult(result, charbuffer, bbuf);
372         this.cbuf.clear();
373         return len;
374     }
375 
376     private int handleDecodingResult(
377             final CoderResult result,
378             final CharArrayBuffer charbuffer,
379             final ByteBuffer bbuf) throws IOException {
380         if (result.isError()) {
381             result.throwException();
382         }
383         this.cbuf.flip();
384         final int len = this.cbuf.remaining();
385         while (this.cbuf.hasRemaining()) {
386             charbuffer.append(this.cbuf.get());
387         }
388         this.cbuf.compact();
389         return len;
390     }
391 
392     @Override
393     public String readLine() throws IOException {
394         final CharArrayBuffer charbuffer = new CharArrayBuffer(64);
395         final int l = readLine(charbuffer);
396         if (l != -1) {
397             return charbuffer.toString();
398         } else {
399             return null;
400         }
401     }
402 
403     @Override
404     public HttpTransportMetrics getMetrics() {
405         return this.metrics;
406     }
407 
408 }