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;
29  
30  import java.io.IOException;
31  import java.nio.channels.SelectionKey;
32  import java.nio.charset.CharsetDecoder;
33  import java.nio.charset.CharsetEncoder;
34  
35  import org.apache.http.HttpEntity;
36  import org.apache.http.HttpEntityEnclosingRequest;
37  import org.apache.http.HttpException;
38  import org.apache.http.HttpRequest;
39  import org.apache.http.HttpResponse;
40  import org.apache.http.HttpResponseFactory;
41  import org.apache.http.annotation.NotThreadSafe;
42  import org.apache.http.config.MessageConstraints;
43  import org.apache.http.entity.ContentLengthStrategy;
44  import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriter;
45  import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriterFactory;
46  import org.apache.http.impl.nio.codecs.DefaultHttpResponseParser;
47  import org.apache.http.impl.nio.codecs.DefaultHttpResponseParserFactory;
48  import org.apache.http.nio.NHttpClientEventHandler;
49  import org.apache.http.nio.NHttpClientHandler;
50  import org.apache.http.nio.NHttpClientIOTarget;
51  import org.apache.http.nio.NHttpMessageParser;
52  import org.apache.http.nio.NHttpMessageParserFactory;
53  import org.apache.http.nio.NHttpMessageWriter;
54  import org.apache.http.nio.NHttpMessageWriterFactory;
55  import org.apache.http.nio.reactor.EventMask;
56  import org.apache.http.nio.reactor.IOSession;
57  import org.apache.http.nio.reactor.SessionInputBuffer;
58  import org.apache.http.nio.reactor.SessionOutputBuffer;
59  import org.apache.http.nio.util.ByteBufferAllocator;
60  import org.apache.http.params.HttpParamConfig;
61  import org.apache.http.params.HttpParams;
62  import org.apache.http.util.Args;
63  
64  /**
65   * Default implementation of the {@link org.apache.http.nio.NHttpClientConnection}
66   * interface.
67   *
68   * @since 4.0
69   */
70  @SuppressWarnings("deprecation")
71  @NotThreadSafe
72  public class DefaultNHttpClientConnection
73      extends NHttpConnectionBase implements NHttpClientIOTarget {
74  
75      protected final NHttpMessageParser<HttpResponse> responseParser;
76      protected final NHttpMessageWriter<HttpRequest> requestWriter;
77  
78      /**
79       * Creates a new instance of this class given the underlying I/O session.
80       *
81       * @param session the underlying I/O session.
82       * @param responseFactory HTTP response factory.
83       * @param allocator byte buffer allocator.
84       * @param params HTTP parameters.
85       *
86       * @deprecated (4.3) use {@link DefaultNHttpClientConnection#DefaultNHttpClientConnection(
87       *   IOSession, int, int, ByteBufferAllocator, CharsetDecoder, CharsetEncoder,
88       *   MessageConstraints, ContentLengthStrategy, ContentLengthStrategy,
89       *   NHttpMessageWriterFactory, NHttpMessageParserFactory)}
90       */
91      @Deprecated
92      public DefaultNHttpClientConnection(
93              final IOSession session,
94              final HttpResponseFactory responseFactory,
95              final ByteBufferAllocator allocator,
96              final HttpParams params) {
97          super(session, allocator, params);
98          Args.notNull(responseFactory, "Response factory");
99          this.responseParser = createResponseParser(this.inbuf, responseFactory, params);
100         this.requestWriter = createRequestWriter(this.outbuf, params);
101         this.hasBufferedInput = false;
102         this.hasBufferedOutput = false;
103         this.session.setBufferStatus(this);
104     }
105 
106     /**
107      * Creates new instance DefaultNHttpClientConnection given the underlying I/O session.
108      *
109      * @param session the underlying I/O session.
110      * @param buffersize buffer size. Must be a positive number.
111      * @param fragmentSizeHint fragment size hint.
112      * @param allocator memory allocator.
113      *   If <code>null</code> {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE}
114      *   will be used.
115      * @param chardecoder decoder to be used for decoding HTTP protocol elements.
116      *   If <code>null</code> simple type cast will be used for byte to char conversion.
117      * @param charencoder encoder to be used for encoding HTTP protocol elements.
118      *   If <code>null</code> simple type cast will be used for char to byte conversion.
119      * @param constraints Message constraints. If <code>null</code>
120      *   {@link MessageConstraints#DEFAULT} will be used.
121      * @param incomingContentStrategy incoming content length strategy. If <code>null</code>
122      *   {@link org.apache.http.impl.entity.LaxContentLengthStrategy#INSTANCE} will be used.
123      * @param outgoingContentStrategy outgoing content length strategy. If <code>null</code>
124      *   {@link org.apache.http.impl.entity.StrictContentLengthStrategy#INSTANCE} will be used.
125      *
126      * @since 4.3
127      */
128     public DefaultNHttpClientConnection(
129             final IOSession session,
130             final int buffersize,
131             final int fragmentSizeHint,
132             final ByteBufferAllocator allocator,
133             final CharsetDecoder chardecoder,
134             final CharsetEncoder charencoder,
135             final MessageConstraints constraints,
136             final ContentLengthStrategy incomingContentStrategy,
137             final ContentLengthStrategy outgoingContentStrategy,
138             final NHttpMessageWriterFactory<HttpRequest> requestWriterFactory,
139             final NHttpMessageParserFactory<HttpResponse> responseParserFactory) {
140         super(session, buffersize, fragmentSizeHint,  allocator, chardecoder, charencoder,
141                 incomingContentStrategy, outgoingContentStrategy);
142         this.requestWriter = (requestWriterFactory != null ? requestWriterFactory :
143             DefaultHttpRequestWriterFactory.INSTANCE).create(this.outbuf);
144         this.responseParser = (responseParserFactory != null ? responseParserFactory :
145             DefaultHttpResponseParserFactory.INSTANCE).create(this.inbuf, constraints);
146     }
147 
148     /**
149      * @since 4.3
150      */
151     public DefaultNHttpClientConnection(
152             final IOSession session,
153             final int buffersize,
154             final CharsetDecoder chardecoder,
155             final CharsetEncoder charencoder,
156             final MessageConstraints constraints) {
157         this(session, buffersize, buffersize, null, chardecoder, charencoder, constraints,
158                 null, null, null, null);
159     }
160 
161     /**
162      * @since 4.3
163      */
164     public DefaultNHttpClientConnection(final IOSession session, final int buffersize) {
165         this(session, buffersize, buffersize, null, null, null, null, null, null, null, null);
166     }
167 
168     /**
169      * Creates an instance of {@link NHttpMessageParser} to be used
170      * by this connection for parsing incoming {@link HttpResponse} messages.
171      * <p>
172      * This method can be overridden in a super class in order to provide
173      * a different implementation of the {@link NHttpMessageParser} interface.
174      *
175      * @return HTTP response parser.
176      *
177      * @deprecated (4.3) use constructor.
178      */
179     @Deprecated
180     protected NHttpMessageParser<HttpResponse> createResponseParser(
181             final SessionInputBuffer buffer,
182             final HttpResponseFactory responseFactory,
183             final HttpParams params) {
184         // override in derived class to specify a line parser
185         final MessageConstraints constraints = HttpParamConfig.getMessageConstraints(params);
186         return new DefaultHttpResponseParser(buffer, null, responseFactory, constraints);
187     }
188 
189     /**
190      * Creates an instance of {@link NHttpMessageWriter} to be used
191      * by this connection for writing out outgoing {@link HttpRequest} messages.
192      * <p>
193      * This method can be overridden by a super class in order to provide
194      * a different implementation of the {@link NHttpMessageWriter} interface.
195      *
196      * @return HTTP response parser.
197      *
198      * @deprecated (4.3) use constructor.
199      */
200     @Deprecated
201     protected NHttpMessageWriter<HttpRequest> createRequestWriter(
202             final SessionOutputBuffer buffer,
203             final HttpParams params) {
204         // override in derived class to specify a line formatter
205         return new DefaultHttpRequestWriter(buffer, null);
206     }
207 
208     /**
209      * @since 4.2
210      */
211     protected void onResponseReceived(final HttpResponse response) {
212     }
213 
214     /**
215      * @since 4.2
216      */
217     protected void onRequestSubmitted(final HttpRequest request) {
218     }
219 
220     public void resetInput() {
221         this.response = null;
222         this.contentDecoder = null;
223         this.responseParser.reset();
224     }
225 
226     public void resetOutput() {
227         this.request = null;
228         this.contentEncoder = null;
229         this.requestWriter.reset();
230     }
231 
232     public void consumeInput(final NHttpClientEventHandler handler) {
233         if (this.status != ACTIVE) {
234             this.session.clearEvent(EventMask.READ);
235             return;
236         }
237         try {
238             if (this.response == null) {
239                 int bytesRead;
240                 do {
241                     bytesRead = this.responseParser.fillBuffer(this.session.channel());
242                     if (bytesRead > 0) {
243                         this.inTransportMetrics.incrementBytesTransferred(bytesRead);
244                     }
245                     this.response = this.responseParser.parse();
246                 } while (bytesRead > 0 && this.response == null);
247                 if (this.response != null) {
248                     if (this.response.getStatusLine().getStatusCode() >= 200) {
249                         final HttpEntity entity = prepareDecoder(this.response);
250                         this.response.setEntity(entity);
251                         this.connMetrics.incrementResponseCount();
252                     }
253                     onResponseReceived(this.response);
254                     handler.responseReceived(this);
255                     if (this.contentDecoder == null) {
256                         resetInput();
257                     }
258                 }
259                 if (bytesRead == -1) {
260                     handler.endOfInput(this);
261                 }
262             }
263             if (this.contentDecoder != null && (this.session.getEventMask() & SelectionKey.OP_READ) > 0) {
264                 handler.inputReady(this, this.contentDecoder);
265                 if (this.contentDecoder.isCompleted()) {
266                     // Response entity received
267                     // Ready to receive a new response
268                     resetInput();
269                 }
270             }
271         } catch (final HttpException ex) {
272             resetInput();
273             handler.exception(this, ex);
274         } catch (final Exception ex) {
275             handler.exception(this, ex);
276         } finally {
277             // Finally set buffered input flag
278             this.hasBufferedInput = this.inbuf.hasData();
279         }
280     }
281 
282     public void produceOutput(final NHttpClientEventHandler handler) {
283         try {
284             if (this.status == ACTIVE) {
285                 if (this.contentEncoder == null) {
286                     handler.requestReady(this);
287                 }
288                 if (this.contentEncoder != null) {
289                     handler.outputReady(this, this.contentEncoder);
290                     if (this.contentEncoder.isCompleted()) {
291                         resetOutput();
292                     }
293                 }
294             }
295             if (this.outbuf.hasData()) {
296                 final int bytesWritten = this.outbuf.flush(this.session.channel());
297                 if (bytesWritten > 0) {
298                     this.outTransportMetrics.incrementBytesTransferred(bytesWritten);
299                 }
300             }
301             if (!this.outbuf.hasData()) {
302                 if (this.status == CLOSING) {
303                     this.session.close();
304                     this.status = CLOSED;
305                     resetOutput();
306                 }
307                 if (this.contentEncoder == null && this.status != CLOSED) {
308                     this.session.clearEvent(EventMask.WRITE);
309                 }
310             }
311         } catch (final Exception ex) {
312             handler.exception(this, ex);
313         } finally {
314             // Finally set the buffered output flag
315             this.hasBufferedOutput = this.outbuf.hasData();
316         }
317     }
318 
319     public void submitRequest(final HttpRequest request) throws IOException, HttpException {
320         Args.notNull(request, "HTTP request");
321         assertNotClosed();
322         if (this.request != null) {
323             throw new HttpException("Request already submitted");
324         }
325         onRequestSubmitted(request);
326         this.requestWriter.write(request);
327         this.hasBufferedOutput = this.outbuf.hasData();
328 
329         if (request instanceof HttpEntityEnclosingRequest
330                 && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
331             prepareEncoder(request);
332             this.request = request;
333         }
334         this.connMetrics.incrementRequestCount();
335         this.session.setEvent(EventMask.WRITE);
336     }
337 
338     public boolean isRequestSubmitted() {
339         return this.request != null;
340     }
341 
342     public void consumeInput(final NHttpClientHandler handler) {
343         consumeInput(new NHttpClientEventHandlerAdaptor(handler));
344     }
345 
346     public void produceOutput(final NHttpClientHandler handler) {
347         produceOutput(new NHttpClientEventHandlerAdaptor(handler));
348     }
349 
350 }