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.nio.reactor;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.CharBuffer;
33  import java.nio.channels.ReadableByteChannel;
34  import java.nio.channels.WritableByteChannel;
35  import java.nio.charset.CharacterCodingException;
36  import java.nio.charset.Charset;
37  import java.nio.charset.CharsetDecoder;
38  import java.nio.charset.CoderResult;
39  import java.nio.charset.CodingErrorAction;
40  
41  import org.apache.http.MessageConstraintException;
42  import org.apache.http.annotation.NotThreadSafe;
43  import org.apache.http.config.MessageConstraints;
44  import org.apache.http.nio.reactor.SessionInputBuffer;
45  import org.apache.http.nio.util.ByteBufferAllocator;
46  import org.apache.http.nio.util.ExpandableBuffer;
47  import org.apache.http.nio.util.HeapByteBufferAllocator;
48  import org.apache.http.params.CoreProtocolPNames;
49  import org.apache.http.params.HttpParams;
50  import org.apache.http.protocol.HTTP;
51  import org.apache.http.util.Args;
52  import org.apache.http.util.CharArrayBuffer;
53  import org.apache.http.util.CharsetUtils;
54  
55  /**
56   * Default implementation of {@link SessionInputBuffer} based on
57   * the {@link ExpandableBuffer} class.
58   *
59   * @since 4.0
60   */
61  @SuppressWarnings("deprecation")
62  @NotThreadSafe
63  public class SessionInputBufferImpl extends ExpandableBuffer implements SessionInputBuffer {
64  
65      private final CharsetDecoder chardecoder;
66      private final MessageConstraints constraints;
67      private final int lineBuffersize;
68  
69      private CharBuffer charbuffer;
70  
71      /**
72       *  Creates SessionInputBufferImpl instance.
73       *
74       * @param buffersize input buffer size
75       * @param lineBuffersize buffer size for line operations. Has effect only if
76       *   <code>chardecoder</code> is not <code>null</code>.
77       * @param chardecoder chardecoder to be used for decoding HTTP protocol elements.
78       *   If <code>null</code> simple type cast will be used for byte to char conversion.
79       * @param constraints Message constraints. If <code>null</code>
80       *   {@link MessageConstraints#DEFAULT} will be used.
81       * @param allocator memory allocator.
82       *   If <code>null</code> {@link HeapByteBufferAllocator#INSTANCE} will be used.
83       *
84       * @since 4.4
85       */
86      public SessionInputBufferImpl(
87              final int buffersize,
88              final int lineBuffersize,
89              final MessageConstraints constraints,
90              final CharsetDecoder chardecoder,
91              final ByteBufferAllocator allocator) {
92          super(buffersize, allocator != null ? allocator : HeapByteBufferAllocator.INSTANCE);
93          this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
94          this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
95          this.chardecoder = chardecoder;
96      }
97  
98      /**
99       *  Creates SessionInputBufferImpl instance.
100      *
101      * @param buffersize input buffer size
102      * @param lineBuffersize buffer size for line operations. Has effect only if
103      *   <code>chardecoder</code> is not <code>null</code>.
104      * @param chardecoder chardecoder to be used for decoding HTTP protocol elements.
105      *   If <code>null</code> simple type cast will be used for byte to char conversion.
106      * @param allocator memory allocator.
107      *   If <code>null</code> {@link HeapByteBufferAllocator#INSTANCE} will be used.
108      *
109      * @since 4.3
110      */
111     public SessionInputBufferImpl(
112             final int buffersize,
113             final int lineBuffersize,
114             final CharsetDecoder chardecoder,
115             final ByteBufferAllocator allocator) {
116         this(buffersize, lineBuffersize, null, chardecoder, allocator);
117     }
118 
119     /**
120      * @deprecated (4.3) use
121      *   {@link SessionInputBufferImpl#SessionInputBufferImpl(int, int, CharsetDecoder,
122      *     ByteBufferAllocator)}
123      */
124     @Deprecated
125     public SessionInputBufferImpl(
126             final int buffersize,
127             final int lineBuffersize,
128             final ByteBufferAllocator allocator,
129             final HttpParams params) {
130         super(buffersize, allocator);
131         this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
132         final String charsetName = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET);
133         final Charset charset = CharsetUtils.lookup(charsetName);
134         if (charset != null) {
135             this.chardecoder = charset.newDecoder();
136             final CodingErrorAction a1 = (CodingErrorAction) params.getParameter(
137                     CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION);
138             this.chardecoder.onMalformedInput(a1 != null ? a1 : CodingErrorAction.REPORT);
139             final CodingErrorAction a2 = (CodingErrorAction) params.getParameter(
140                     CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION);
141             this.chardecoder.onUnmappableCharacter(a2 != null? a2 : CodingErrorAction.REPORT);
142         } else {
143             this.chardecoder = null;
144         }
145         this.constraints = MessageConstraints.DEFAULT;
146     }
147 
148     /**
149      * @deprecated (4.3) use
150      *   {@link SessionInputBufferImpl#SessionInputBufferImpl(int, int, Charset)}
151      */
152     @Deprecated
153     public SessionInputBufferImpl(
154             final int buffersize,
155             final int linebuffersize,
156             final HttpParams params) {
157         this(buffersize, linebuffersize, HeapByteBufferAllocator.INSTANCE, params);
158     }
159 
160     /**
161      * @since 4.3
162      */
163     public SessionInputBufferImpl(
164             final int buffersize,
165             final int lineBuffersize,
166             final Charset charset) {
167         this(buffersize, lineBuffersize, null,
168                 charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE);
169     }
170 
171     /**
172      * @since 4.3
173      */
174     public SessionInputBufferImpl(
175             final int buffersize,
176             final int lineBuffersize,
177             final MessageConstraints constraints,
178             final Charset charset) {
179         this(buffersize, lineBuffersize, constraints,
180                 charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE);
181     }
182 
183     /**
184      * @since 4.3
185      */
186     public SessionInputBufferImpl(
187             final int buffersize,
188             final int lineBuffersize) {
189         this(buffersize, lineBuffersize, null, null, HeapByteBufferAllocator.INSTANCE);
190     }
191 
192     /**
193      * @since 4.3
194      */
195     public SessionInputBufferImpl(final int buffersize) {
196         this(buffersize, 256, null, null, HeapByteBufferAllocator.INSTANCE);
197     }
198 
199     @Override
200     public int fill(final ReadableByteChannel channel) throws IOException {
201         Args.notNull(channel, "Channel");
202         setInputMode();
203         if (!this.buffer.hasRemaining()) {
204             expand();
205         }
206         return channel.read(this.buffer);
207     }
208 
209     @Override
210     public int read() {
211         setOutputMode();
212         return this.buffer.get() & 0xff;
213     }
214 
215     @Override
216     public int read(final ByteBuffer dst, final int maxLen) {
217         if (dst == null) {
218             return 0;
219         }
220         setOutputMode();
221         final int len = Math.min(dst.remaining(), maxLen);
222         final int chunk = Math.min(this.buffer.remaining(), len);
223         if (this.buffer.remaining() > chunk) {
224             final int oldLimit = this.buffer.limit();
225             final int newLimit = this.buffer.position() + chunk;
226             this.buffer.limit(newLimit);
227             dst.put(this.buffer);
228             this.buffer.limit(oldLimit);
229             return len;
230         } else {
231             dst.put(this.buffer);
232         }
233         return chunk;
234     }
235 
236     @Override
237     public int read(final ByteBuffer dst) {
238         if (dst == null) {
239             return 0;
240         }
241         return read(dst, dst.remaining());
242     }
243 
244     @Override
245     public int read(final WritableByteChannel dst, final int maxLen) throws IOException {
246         if (dst == null) {
247             return 0;
248         }
249         setOutputMode();
250         final int bytesRead;
251         if (this.buffer.remaining() > maxLen) {
252             final int oldLimit = this.buffer.limit();
253             final int newLimit = oldLimit - (this.buffer.remaining() - maxLen);
254             this.buffer.limit(newLimit);
255             bytesRead = dst.write(this.buffer);
256             this.buffer.limit(oldLimit);
257         } else {
258             bytesRead = dst.write(this.buffer);
259         }
260         return bytesRead;
261     }
262 
263     @Override
264     public int read(final WritableByteChannel dst) throws IOException {
265         if (dst == null) {
266             return 0;
267         }
268         setOutputMode();
269         return dst.write(this.buffer);
270     }
271 
272     @Override
273     public boolean readLine(
274             final CharArrayBuffer linebuffer,
275             final boolean endOfStream) throws CharacterCodingException {
276 
277         setOutputMode();
278         // See if there is LF char present in the buffer
279         int pos = -1;
280         for (int i = this.buffer.position(); i < this.buffer.limit(); i++) {
281             final int b = this.buffer.get(i);
282             if (b == HTTP.LF) {
283                 pos = i + 1;
284                 break;
285             }
286         }
287 
288         final int maxLineLen = this.constraints.getMaxLineLength();
289         if (maxLineLen > 0) {
290             final int currentLen = (pos > 0 ? pos : this.buffer.limit()) - this.buffer.position();
291             if (currentLen >= maxLineLen) {
292                 throw new MessageConstraintException("Maximum line length limit exceeded");
293             }
294         }
295 
296         if (pos == -1) {
297             if (endOfStream && this.buffer.hasRemaining()) {
298                 // No more data. Get the rest
299                 pos = this.buffer.limit();
300             } else {
301                 // Either no complete line present in the buffer
302                 // or no more data is expected
303                 return false;
304             }
305         }
306         final int origLimit = this.buffer.limit();
307         this.buffer.limit(pos);
308 
309         final int requiredCapacity = this.buffer.limit() - this.buffer.position();
310         // Ensure capacity of len assuming ASCII as the most likely charset
311         linebuffer.ensureCapacity(requiredCapacity);
312 
313         if (this.chardecoder == null) {
314             if (this.buffer.hasArray()) {
315                 final byte[] b = this.buffer.array();
316                 final int off = this.buffer.position();
317                 final int len = this.buffer.remaining();
318                 linebuffer.append(b, off, len);
319                 this.buffer.position(off + len);
320             } else {
321                 while (this.buffer.hasRemaining()) {
322                     linebuffer.append((char) (this.buffer.get() & 0xff));
323                 }
324             }
325         } else {
326             if (this.charbuffer == null) {
327                 this.charbuffer = CharBuffer.allocate(this.lineBuffersize);
328             }
329             this.chardecoder.reset();
330 
331             for (;;) {
332                 final CoderResult result = this.chardecoder.decode(
333                         this.buffer,
334                         this.charbuffer,
335                         true);
336                 if (result.isError()) {
337                     result.throwException();
338                 }
339                 if (result.isOverflow()) {
340                     this.charbuffer.flip();
341                     linebuffer.append(
342                             this.charbuffer.array(),
343                             this.charbuffer.position(),
344                             this.charbuffer.remaining());
345                     this.charbuffer.clear();
346                 }
347                 if (result.isUnderflow()) {
348                     break;
349                 }
350             }
351 
352             // flush the decoder
353             this.chardecoder.flush(this.charbuffer);
354             this.charbuffer.flip();
355             // append the decoded content to the line buffer
356             if (this.charbuffer.hasRemaining()) {
357                 linebuffer.append(
358                         this.charbuffer.array(),
359                         this.charbuffer.position(),
360                         this.charbuffer.remaining());
361             }
362 
363         }
364         this.buffer.limit(origLimit);
365 
366         // discard LF if found
367         int l = linebuffer.length();
368         if (l > 0) {
369             if (linebuffer.charAt(l - 1) == HTTP.LF) {
370                 l--;
371                 linebuffer.setLength(l);
372             }
373             // discard CR if found
374             if (l > 0) {
375                 if (linebuffer.charAt(l - 1) == HTTP.CR) {
376                     l--;
377                     linebuffer.setLength(l);
378                 }
379             }
380         }
381         return true;
382     }
383 
384     @Override
385     public String readLine(final boolean endOfStream) throws CharacterCodingException {
386         final CharArrayBuffer buffer = new CharArrayBuffer(64);
387         final boolean found = readLine(buffer, endOfStream);
388         if (found) {
389             return buffer.toString();
390         } else {
391             return null;
392         }
393     }
394 
395 }