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