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