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  
33  import org.apache.http.HttpEntity;
34  import org.apache.http.HttpEntityEnclosingRequest;
35  import org.apache.http.HttpException;
36  import org.apache.http.HttpRequest;
37  import org.apache.http.HttpResponse;
38  import org.apache.http.HttpResponseFactory;
39  import org.apache.http.annotation.NotThreadSafe;
40  import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriter;
41  import org.apache.http.impl.nio.codecs.DefaultHttpResponseParser;
42  import org.apache.http.nio.NHttpClientConnection;
43  import org.apache.http.nio.NHttpClientIOTarget;
44  import org.apache.http.nio.NHttpClientHandler;
45  import org.apache.http.nio.NHttpClientEventHandler;
46  import org.apache.http.nio.NHttpMessageParser;
47  import org.apache.http.nio.NHttpMessageWriter;
48  import org.apache.http.nio.reactor.EventMask;
49  import org.apache.http.nio.reactor.IOSession;
50  import org.apache.http.nio.reactor.SessionInputBuffer;
51  import org.apache.http.nio.reactor.SessionOutputBuffer;
52  import org.apache.http.nio.util.ByteBufferAllocator;
53  import org.apache.http.params.HttpParams;
54  
55  /**
56   * Default implementation of the {@link NHttpClientConnection} interface.
57   * <p>
58   * The following parameters can be used to customize the behavior of this
59   * class:
60   * <ul>
61   *  <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
62   *  <li>{@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
63   *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
64   *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
65   * </ul>
66   *
67   * @since 4.0
68   */
69  @SuppressWarnings("deprecation")
70  @NotThreadSafe
71  public class DefaultNHttpClientConnection
72      extends NHttpConnectionBase implements NHttpClientIOTarget {
73  
74      protected final NHttpMessageParser<HttpResponse> responseParser;
75      protected final NHttpMessageWriter<HttpRequest> requestWriter;
76  
77      /**
78       * Creates a new instance of this class given the underlying I/O session.
79       *
80       * @param session the underlying I/O session.
81       * @param responseFactory HTTP response factory.
82       * @param allocator byte buffer allocator.
83       * @param params HTTP parameters.
84       */
85      public DefaultNHttpClientConnection(
86              final IOSession session,
87              final HttpResponseFactory responseFactory,
88              final ByteBufferAllocator allocator,
89              final HttpParams params) {
90          super(session, allocator, params);
91          if (responseFactory == null) {
92              throw new IllegalArgumentException("Response factory may not be null");
93          }
94          this.responseParser = createResponseParser(this.inbuf, responseFactory, params);
95          this.requestWriter = createRequestWriter(this.outbuf, params);
96          this.hasBufferedInput = false;
97          this.hasBufferedOutput = false;
98          this.session.setBufferStatus(this);
99      }
100 
101     /**
102      * Creates an instance of {@link NHttpMessageParser} to be used
103      * by this connection for parsing incoming {@link HttpResponse} messages.
104      * <p>
105      * This method can be overridden in a super class in order to provide
106      * a different implementation of the {@link NHttpMessageParser} interface.
107      *
108      * @return HTTP response parser.
109      */
110     protected NHttpMessageParser<HttpResponse> createResponseParser(
111             final SessionInputBuffer buffer,
112             final HttpResponseFactory responseFactory,
113             final HttpParams params) {
114         // override in derived class to specify a line parser
115         return new DefaultHttpResponseParser(buffer, null, responseFactory, params);
116     }
117 
118     /**
119      * Creates an instance of {@link NHttpMessageWriter} to be used
120      * by this connection for writing out outgoing {@link HttpRequest} messages.
121      * <p>
122      * This method can be overridden by a super class in order to provide
123      * a different implementation of the {@link NHttpMessageWriter} interface.
124      *
125      * @return HTTP response parser.
126      */
127     protected NHttpMessageWriter<HttpRequest> createRequestWriter(
128             final SessionOutputBuffer buffer,
129             final HttpParams params) {
130         // override in derived class to specify a line formatter
131         return new DefaultHttpRequestWriter(buffer, null, params);
132     }
133 
134     /**
135      * @since 4.2
136      */
137     protected void onResponseReceived(final HttpResponse response) {
138     }
139     
140     /**
141      * @since 4.2
142      */
143     protected void onRequestSubmitted(final HttpRequest request) {
144     }
145     
146     public void resetInput() {
147         this.response = null;
148         this.contentDecoder = null;
149         this.responseParser.reset();
150     }
151 
152     public void resetOutput() {
153         this.request = null;
154         this.contentEncoder = null;
155         this.requestWriter.reset();
156     }
157 
158     public void consumeInput(final NHttpClientEventHandler handler) {
159         if (this.status != ACTIVE) {
160             this.session.clearEvent(EventMask.READ);
161             return;
162         }
163         try {
164             if (this.response == null) {
165                 int bytesRead;
166                 do {
167                     bytesRead = this.responseParser.fillBuffer(this.session.channel());
168                     if (bytesRead > 0) {
169                         this.inTransportMetrics.incrementBytesTransferred(bytesRead);
170                     }
171                     this.response = this.responseParser.parse();
172                 } while (bytesRead > 0 && this.response == null);
173                 if (this.response != null) {
174                     if (this.response.getStatusLine().getStatusCode() >= 200) {
175                         HttpEntity entity = prepareDecoder(this.response);
176                         this.response.setEntity(entity);
177                         this.connMetrics.incrementResponseCount();
178                     }
179                     onResponseReceived(this.response);
180                     handler.responseReceived(this);
181                     if (this.contentDecoder == null) {
182                         resetInput();
183                     }
184                 }
185                 if (bytesRead == -1) {
186                     handler.endOfInput(this);
187                 }
188             }
189             if (this.contentDecoder != null && (this.session.getEventMask() & SelectionKey.OP_READ) > 0) {
190                 handler.inputReady(this, this.contentDecoder);
191                 if (this.contentDecoder.isCompleted()) {
192                     // Response entity received
193                     // Ready to receive a new response
194                     resetInput();
195                 }
196             }
197         } catch (HttpException ex) {
198             resetInput();
199             handler.exception(this, ex);
200         } catch (Exception ex) {
201             handler.exception(this, ex);
202         } finally {
203             // Finally set buffered input flag
204             this.hasBufferedInput = this.inbuf.hasData();
205         }
206     }
207 
208     public void produceOutput(final NHttpClientEventHandler handler) {
209         try {
210             if (this.outbuf.hasData()) {
211                 int bytesWritten = this.outbuf.flush(this.session.channel());
212                 if (bytesWritten > 0) {
213                     this.outTransportMetrics.incrementBytesTransferred(bytesWritten);
214                 }
215             }
216             if (!this.outbuf.hasData()) {
217                 if (this.status == CLOSING) {
218                     this.session.close();
219                     this.status = CLOSED;
220                     resetOutput();
221                     return;
222                 } else {
223                     if (this.contentEncoder != null) {
224                         handler.outputReady(this, this.contentEncoder);
225                         if (this.contentEncoder.isCompleted()) {
226                             resetOutput();
227                         }
228                     }
229                 }
230 
231                 if (this.contentEncoder == null && !this.outbuf.hasData()) {
232                     if (this.status == CLOSING) {
233                         this.session.close();
234                         this.status = CLOSED;
235                     }
236                     if (this.status != CLOSED) {
237                         this.session.clearEvent(EventMask.WRITE);
238                         handler.requestReady(this);
239                     }
240                 }
241             }
242         } catch (Exception ex) {
243             handler.exception(this, ex);
244         } finally {
245             // Finally set buffered output flag
246             this.hasBufferedOutput = this.outbuf.hasData();
247         }
248     }
249 
250     public void submitRequest(final HttpRequest request) throws IOException, HttpException {
251         if (request == null) {
252             throw new IllegalArgumentException("HTTP request may not be null");
253         }
254         assertNotClosed();
255         if (this.request != null) {
256             throw new HttpException("Request already submitted");
257         }
258         onRequestSubmitted(request);
259         this.requestWriter.write(request);
260         this.hasBufferedOutput = this.outbuf.hasData();
261 
262         if (request instanceof HttpEntityEnclosingRequest
263                 && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
264             prepareEncoder(request);
265             this.request = request;
266         }
267         this.connMetrics.incrementRequestCount();
268         this.session.setEvent(EventMask.WRITE);
269     }
270 
271     public boolean isRequestSubmitted() {
272         return this.request != null;
273     }
274 
275     public void consumeInput(final NHttpClientHandler handler) {
276         consumeInput(new NHttpClientEventHandlerAdaptor(handler));
277     }
278 
279     public void produceOutput(final NHttpClientHandler handler) {
280         produceOutput(new NHttpClientEventHandlerAdaptor(handler));
281     }
282 
283 }