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     public int capacity() {
128         return this.buffer.length;
129     }
130 
131     /**
132      * @since 4.1
133      */
134     public int length() {
135         return this.bufferlen - this.bufferpos;
136     }
137 
138     /**
139      * @since 4.1
140      */
141     public int available() {
142         return capacity() - length();
143     }
144 
145     protected int fillBuffer() throws IOException {
146         // compact the buffer if necessary
147         if (this.bufferpos > 0) {
148             final int len = this.bufferlen - this.bufferpos;
149             if (len > 0) {
150                 System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len);
151             }
152             this.bufferpos = 0;
153             this.bufferlen = len;
154         }
155         final int l;
156         final int off = this.bufferlen;
157         final int len = this.buffer.length - off;
158         l = this.instream.read(this.buffer, off, len);
159         if (l == -1) {
160             return -1;
161         } else {
162             this.bufferlen = off + l;
163             this.metrics.incrementBytesTransferred(l);
164             return l;
165         }
166     }
167 
168     protected boolean hasBufferedData() {
169         return this.bufferpos < this.bufferlen;
170     }
171 
172     public int read() throws IOException {
173         int noRead;
174         while (!hasBufferedData()) {
175             noRead = fillBuffer();
176             if (noRead == -1) {
177                 return -1;
178             }
179         }
180         return this.buffer[this.bufferpos++] & 0xff;
181     }
182 
183     public int read(final byte[] b, final int off, final int len) throws IOException {
184         if (b == null) {
185             return 0;
186         }
187         if (hasBufferedData()) {
188             final int chunk = Math.min(len, this.bufferlen - this.bufferpos);
189             System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
190             this.bufferpos += chunk;
191             return chunk;
192         }
193         // If the remaining capacity is big enough, read directly from the
194         // underlying input stream bypassing the buffer.
195         if (len > this.minChunkLimit) {
196             final int read = this.instream.read(b, off, len);
197             if (read > 0) {
198                 this.metrics.incrementBytesTransferred(read);
199             }
200             return read;
201         } else {
202             // otherwise read to the buffer first
203             while (!hasBufferedData()) {
204                 final int noRead = fillBuffer();
205                 if (noRead == -1) {
206                     return -1;
207                 }
208             }
209             final int chunk = Math.min(len, this.bufferlen - this.bufferpos);
210             System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
211             this.bufferpos += chunk;
212             return chunk;
213         }
214     }
215 
216     public int read(final byte[] b) throws IOException {
217         if (b == null) {
218             return 0;
219         }
220         return read(b, 0, b.length);
221     }
222 
223     private int locateLF() {
224         for (int i = this.bufferpos; i < this.bufferlen; i++) {
225             if (this.buffer[i] == HTTP.LF) {
226                 return i;
227             }
228         }
229         return -1;
230     }
231 
232     /**
233      * Reads a complete line of characters up to a line delimiter from this
234      * session buffer into the given line buffer. The number of chars actually
235      * read is returned as an integer. The line delimiter itself is discarded.
236      * If no char is available because the end of the stream has been reached,
237      * the value {@code -1} is returned. This method blocks until input
238      * data is available, end of file is detected, or an exception is thrown.
239      * <p>
240      * This method treats a lone LF as a valid line delimiters in addition
241      * to CR-LF required by the HTTP specification.
242      *
243      * @param      charbuffer   the line buffer.
244      * @return     one line of characters
245      * @exception  IOException  if an I/O error occurs.
246      */
247     public int readLine(final CharArrayBuffer charbuffer) throws IOException {
248         Args.notNull(charbuffer, "Char array buffer");
249         int noRead = 0;
250         boolean retry = true;
251         while (retry) {
252             // attempt to find end of line (LF)
253             final int i = locateLF();
254             if (i != -1) {
255                 // end of line found.
256                 if (this.linebuffer.isEmpty()) {
257                     // the entire line is preset in the read buffer
258                     return lineFromReadBuffer(charbuffer, i);
259                 }
260                 retry = false;
261                 final int len = i + 1 - this.bufferpos;
262                 this.linebuffer.append(this.buffer, this.bufferpos, len);
263                 this.bufferpos = i + 1;
264             } else {
265                 // end of line not found
266                 if (hasBufferedData()) {
267                     final int len = this.bufferlen - this.bufferpos;
268                     this.linebuffer.append(this.buffer, this.bufferpos, len);
269                     this.bufferpos = this.bufferlen;
270                 }
271                 noRead = fillBuffer();
272                 if (noRead == -1) {
273                     retry = false;
274                 }
275             }
276             if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) {
277                 throw new IOException("Maximum line length limit exceeded");
278             }
279         }
280         if (noRead == -1 && this.linebuffer.isEmpty()) {
281             // indicate the end of stream
282             return -1;
283         }
284         return lineFromLineBuffer(charbuffer);
285     }
286 
287     /**
288      * Reads a complete line of characters up to a line delimiter from this
289      * session buffer. The line delimiter itself is discarded. If no char is
290      * available because the end of the stream has been reached,
291      * {@code null} is returned. This method blocks until input data is
292      * available, end of file is detected, or an exception is thrown.
293      * <p>
294      * This method treats a lone LF as a valid line delimiters in addition
295      * to CR-LF required by the HTTP specification.
296      *
297      * @return HTTP line as a string
298      * @exception  IOException  if an I/O error occurs.
299      */
300     private int lineFromLineBuffer(final CharArrayBuffer charbuffer)
301             throws IOException {
302         // discard LF if found
303         int len = this.linebuffer.length();
304         if (len > 0) {
305             if (this.linebuffer.byteAt(len - 1) == HTTP.LF) {
306                 len--;
307             }
308             // discard CR if found
309             if (len > 0) {
310                 if (this.linebuffer.byteAt(len - 1) == HTTP.CR) {
311                     len--;
312                 }
313             }
314         }
315         if (this.ascii) {
316             charbuffer.append(this.linebuffer, 0, len);
317         } else {
318             final ByteBuffer bbuf =  ByteBuffer.wrap(this.linebuffer.buffer(), 0, len);
319             len = appendDecoded(charbuffer, bbuf);
320         }
321         this.linebuffer.clear();
322         return len;
323     }
324 
325     private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position)
326             throws IOException {
327         final int off = this.bufferpos;
328         int i = position;
329         this.bufferpos = i + 1;
330         if (i > off && this.buffer[i - 1] == HTTP.CR) {
331             // skip CR if found
332             i--;
333         }
334         int len = i - off;
335         if (this.ascii) {
336             charbuffer.append(this.buffer, off, len);
337         } else {
338             final ByteBuffer bbuf =  ByteBuffer.wrap(this.buffer, off, len);
339             len = appendDecoded(charbuffer, bbuf);
340         }
341         return len;
342     }
343 
344     private int appendDecoded(
345             final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException {
346         if (!bbuf.hasRemaining()) {
347             return 0;
348         }
349         if (this.decoder == null) {
350             this.decoder = this.charset.newDecoder();
351             this.decoder.onMalformedInput(this.onMalformedCharAction);
352             this.decoder.onUnmappableCharacter(this.onUnmappableCharAction);
353         }
354         if (this.cbuf == null) {
355             this.cbuf = CharBuffer.allocate(1024);
356         }
357         this.decoder.reset();
358         int len = 0;
359         while (bbuf.hasRemaining()) {
360             final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true);
361             len += handleDecodingResult(result, charbuffer, bbuf);
362         }
363         final CoderResult result = this.decoder.flush(this.cbuf);
364         len += handleDecodingResult(result, charbuffer, bbuf);
365         this.cbuf.clear();
366         return len;
367     }
368 
369     private int handleDecodingResult(
370             final CoderResult result,
371             final CharArrayBuffer charbuffer,
372             final ByteBuffer bbuf) throws IOException {
373         if (result.isError()) {
374             result.throwException();
375         }
376         this.cbuf.flip();
377         final int len = this.cbuf.remaining();
378         while (this.cbuf.hasRemaining()) {
379             charbuffer.append(this.cbuf.get());
380         }
381         this.cbuf.compact();
382         return len;
383     }
384 
385     public String readLine() throws IOException {
386         final CharArrayBuffer charbuffer = new CharArrayBuffer(64);
387         final int l = readLine(charbuffer);
388         if (l != -1) {
389             return charbuffer.toString();
390         } else {
391             return null;
392         }
393     }
394 
395     public HttpTransportMetrics getMetrics() {
396         return this.metrics;
397     }
398 
399 }