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