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