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} {@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} 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} simple type cast will be used for char to byte conversion.
119      * @param constraints Message constraints. If {@code null}
120      *   {@link MessageConstraints#DEFAULT} will be used.
121      * @param incomingContentStrategy incoming content length strategy. If {@code null}
122      *   {@link org.apache.http.impl.entity.LaxContentLengthStrategy#INSTANCE} will be used.
123      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
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                 constraints, 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     @Override
221     public void resetInput() {
222         this.response = null;
223         this.contentDecoder = null;
224         this.responseParser.reset();
225     }
226 
227     @Override
228     public void resetOutput() {
229         this.request = null;
230         this.contentEncoder = null;
231         this.requestWriter.reset();
232     }
233 
234     public void consumeInput(final NHttpClientEventHandler handler) {
235         if (this.status != ACTIVE) {
236             this.session.clearEvent(EventMask.READ);
237             return;
238         }
239         try {
240             if (this.response == null) {
241                 int bytesRead;
242                 do {
243                     bytesRead = this.responseParser.fillBuffer(this.session.channel());
244                     if (bytesRead > 0) {
245                         this.inTransportMetrics.incrementBytesTransferred(bytesRead);
246                     }
247                     this.response = this.responseParser.parse();
248                 } while (bytesRead > 0 && this.response == null);
249                 if (this.response != null) {
250                     if (this.response.getStatusLine().getStatusCode() >= 200) {
251                         final HttpEntity entity = prepareDecoder(this.response);
252                         this.response.setEntity(entity);
253                         this.connMetrics.incrementResponseCount();
254                     }
255                     this.hasBufferedInput = this.inbuf.hasData();
256                     onResponseReceived(this.response);
257                     handler.responseReceived(this);
258                     if (this.contentDecoder == null) {
259                         resetInput();
260                     }
261                 }
262                 if (bytesRead == -1) {
263                     handler.endOfInput(this);
264                 }
265             }
266             if (this.contentDecoder != null && (this.session.getEventMask() & SelectionKey.OP_READ) > 0) {
267                 handler.inputReady(this, this.contentDecoder);
268                 if (this.contentDecoder.isCompleted()) {
269                     // Response entity received
270                     // Ready to receive a new response
271                     resetInput();
272                 }
273             }
274         } catch (final HttpException ex) {
275             resetInput();
276             handler.exception(this, ex);
277         } catch (final Exception ex) {
278             handler.exception(this, ex);
279         } finally {
280             // Finally set buffered input flag
281             this.hasBufferedInput = this.inbuf.hasData();
282         }
283     }
284 
285     public void produceOutput(final NHttpClientEventHandler handler) {
286         try {
287             if (this.status == ACTIVE) {
288                 if (this.contentEncoder == null) {
289                     handler.requestReady(this);
290                 }
291                 if (this.contentEncoder != null) {
292                     handler.outputReady(this, this.contentEncoder);
293                     if (this.contentEncoder.isCompleted()) {
294                         resetOutput();
295                     }
296                 }
297             }
298             if (this.outbuf.hasData()) {
299                 final int bytesWritten = this.outbuf.flush(this.session.channel());
300                 if (bytesWritten > 0) {
301                     this.outTransportMetrics.incrementBytesTransferred(bytesWritten);
302                 }
303             }
304             if (!this.outbuf.hasData()) {
305                 if (this.status == CLOSING) {
306                     this.session.close();
307                     this.status = CLOSED;
308                     resetOutput();
309                 }
310                 if (this.contentEncoder == null && this.status != CLOSED) {
311                     this.session.clearEvent(EventMask.WRITE);
312                 }
313             }
314         } catch (final Exception ex) {
315             handler.exception(this, ex);
316         } finally {
317             // Finally set the buffered output flag
318             this.hasBufferedOutput = this.outbuf.hasData();
319         }
320     }
321 
322     @Override
323     public void submitRequest(final HttpRequest request) throws IOException, HttpException {
324         Args.notNull(request, "HTTP request");
325         assertNotClosed();
326         if (this.request != null) {
327             throw new HttpException("Request already submitted");
328         }
329         onRequestSubmitted(request);
330         this.requestWriter.write(request);
331         this.hasBufferedOutput = this.outbuf.hasData();
332 
333         if (request instanceof HttpEntityEnclosingRequest
334                 && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
335             prepareEncoder(request);
336             this.request = request;
337         }
338         this.connMetrics.incrementRequestCount();
339         this.session.setEvent(EventMask.WRITE);
340     }
341 
342     @Override
343     public boolean isRequestSubmitted() {
344         return this.request != null;
345     }
346 
347     @Override
348     public void consumeInput(final NHttpClientHandler handler) {
349         consumeInput(new NHttpClientEventHandlerAdaptor(handler));
350     }
351 
352     @Override
353     public void produceOutput(final NHttpClientHandler handler) {
354         produceOutput(new NHttpClientEventHandlerAdaptor(handler));
355     }
356 
357 }