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.net.InetAddress;
32  import java.net.InetSocketAddress;
33  import java.net.Socket;
34  import java.net.SocketAddress;
35  import java.nio.channels.ReadableByteChannel;
36  import java.nio.channels.WritableByteChannel;
37  import java.nio.charset.Charset;
38  import java.nio.charset.CharsetDecoder;
39  import java.nio.charset.CharsetEncoder;
40  import java.nio.charset.CodingErrorAction;
41  
42  import org.apache.http.ConnectionClosedException;
43  import org.apache.http.Consts;
44  import org.apache.http.Header;
45  import org.apache.http.HttpConnectionMetrics;
46  import org.apache.http.HttpEntity;
47  import org.apache.http.HttpException;
48  import org.apache.http.HttpInetConnection;
49  import org.apache.http.HttpMessage;
50  import org.apache.http.HttpRequest;
51  import org.apache.http.HttpResponse;
52  import org.apache.http.annotation.NotThreadSafe;
53  import org.apache.http.config.MessageConstraints;
54  import org.apache.http.entity.BasicHttpEntity;
55  import org.apache.http.entity.ContentLengthStrategy;
56  import org.apache.http.impl.HttpConnectionMetricsImpl;
57  import org.apache.http.impl.entity.LaxContentLengthStrategy;
58  import org.apache.http.impl.entity.StrictContentLengthStrategy;
59  import org.apache.http.impl.io.HttpTransportMetricsImpl;
60  import org.apache.http.impl.nio.codecs.ChunkDecoder;
61  import org.apache.http.impl.nio.codecs.ChunkEncoder;
62  import org.apache.http.impl.nio.codecs.IdentityDecoder;
63  import org.apache.http.impl.nio.codecs.IdentityEncoder;
64  import org.apache.http.impl.nio.codecs.LengthDelimitedDecoder;
65  import org.apache.http.impl.nio.codecs.LengthDelimitedEncoder;
66  import org.apache.http.impl.nio.reactor.SessionInputBufferImpl;
67  import org.apache.http.impl.nio.reactor.SessionOutputBufferImpl;
68  import org.apache.http.io.HttpTransportMetrics;
69  import org.apache.http.nio.ContentDecoder;
70  import org.apache.http.nio.ContentEncoder;
71  import org.apache.http.nio.NHttpConnection;
72  import org.apache.http.nio.reactor.EventMask;
73  import org.apache.http.nio.reactor.IOSession;
74  import org.apache.http.nio.reactor.SessionBufferStatus;
75  import org.apache.http.nio.reactor.SessionInputBuffer;
76  import org.apache.http.nio.reactor.SessionOutputBuffer;
77  import org.apache.http.nio.reactor.SocketAccessor;
78  import org.apache.http.nio.util.ByteBufferAllocator;
79  import org.apache.http.params.CoreConnectionPNames;
80  import org.apache.http.params.CoreProtocolPNames;
81  import org.apache.http.params.HttpParams;
82  import org.apache.http.protocol.HTTP;
83  import org.apache.http.protocol.HttpContext;
84  import org.apache.http.util.Args;
85  import org.apache.http.util.CharsetUtils;
86  import org.apache.http.util.NetUtils;
87  
88  /**
89   * This class serves as a base for all {@link NHttpConnection} implementations and provides
90   * functionality common to both client and server HTTP connections.
91   *
92   * @since 4.0
93   */
94  @SuppressWarnings("deprecation")
95  @NotThreadSafe
96  public class NHttpConnectionBase
97          implements NHttpConnection, HttpInetConnection, SessionBufferStatus, SocketAccessor {
98  
99      protected final ContentLengthStrategy incomingContentStrategy;
100     protected final ContentLengthStrategy outgoingContentStrategy;
101 
102     protected final SessionInputBufferImpl inbuf;
103     protected final SessionOutputBufferImpl outbuf;
104     private final int fragmentSizeHint;
105     private final MessageConstraints constraints;
106 
107     protected final HttpTransportMetricsImpl inTransportMetrics;
108     protected final HttpTransportMetricsImpl outTransportMetrics;
109     protected final HttpConnectionMetricsImpl connMetrics;
110 
111     protected HttpContext context;
112     protected IOSession session;
113     protected SocketAddress remote;
114     protected volatile ContentDecoder contentDecoder;
115     protected volatile boolean hasBufferedInput;
116     protected volatile ContentEncoder contentEncoder;
117     protected volatile boolean hasBufferedOutput;
118     protected volatile HttpRequest request;
119     protected volatile HttpResponse response;
120 
121     protected volatile int status;
122 
123     /**
124      * Creates a new instance of this class given the underlying I/O session.
125      *
126      * @param session the underlying I/O session.
127      * @param allocator byte buffer allocator.
128      * @param params HTTP parameters.
129      *
130      * @deprecated (4.3) use
131      *   {@link NHttpConnectionBase#NHttpConnectionBase(IOSession, int, int, ByteBufferAllocator,
132      *   CharsetDecoder, CharsetEncoder, ContentLengthStrategy, ContentLengthStrategy)}
133      */
134     @Deprecated
135     public NHttpConnectionBase(
136             final IOSession session,
137             final ByteBufferAllocator allocator,
138             final HttpParams params) {
139         super();
140         Args.notNull(session, "I/O session");
141         Args.notNull(params, "HTTP params");
142 
143         int buffersize = params.getIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1);
144         if (buffersize <= 0) {
145             buffersize = 4096;
146         }
147         int linebuffersize = buffersize;
148         if (linebuffersize > 512) {
149             linebuffersize = 512;
150         }
151 
152         CharsetDecoder decoder = null;
153         CharsetEncoder encoder = null;
154         Charset charset = CharsetUtils.lookup(
155                 (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET));
156         if (charset != null) {
157             charset = Consts.ASCII;
158             decoder = charset.newDecoder();
159             encoder = charset.newEncoder();
160             final CodingErrorAction malformedCharAction = (CodingErrorAction) params.getParameter(
161                     CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION);
162             final CodingErrorAction unmappableCharAction = (CodingErrorAction) params.getParameter(
163                     CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION);
164             decoder.onMalformedInput(malformedCharAction).onUnmappableCharacter(unmappableCharAction);
165             encoder.onMalformedInput(malformedCharAction).onUnmappableCharacter(unmappableCharAction);
166         }
167         this.inbuf = new SessionInputBufferImpl(buffersize, linebuffersize, decoder, allocator);
168         this.outbuf = new SessionOutputBufferImpl(buffersize, linebuffersize, encoder, allocator);
169         this.fragmentSizeHint = buffersize;
170         this.constraints = MessageConstraints.DEFAULT;
171 
172         this.incomingContentStrategy = createIncomingContentStrategy();
173         this.outgoingContentStrategy = createOutgoingContentStrategy();
174 
175         this.inTransportMetrics = createTransportMetrics();
176         this.outTransportMetrics = createTransportMetrics();
177         this.connMetrics = createConnectionMetrics(
178                 this.inTransportMetrics,
179                 this.outTransportMetrics);
180 
181         setSession(session);
182         this.status = ACTIVE;
183     }
184 
185     /**
186      * Creates new instance NHttpConnectionBase given the underlying I/O session.
187      *
188      * @param session the underlying I/O session.
189      * @param buffersize buffer size. Must be a positive number.
190      * @param fragmentSizeHint fragment size hint.
191      * @param allocator memory allocator.
192      *   If {@code null} {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE}
193      *   will be used.
194      * @param chardecoder decoder to be used for decoding HTTP protocol elements.
195      *   If {@code null} simple type cast will be used for byte to char conversion.
196      * @param charencoder encoder to be used for encoding HTTP protocol elements.
197      *   If {@code null} simple type cast will be used for char to byte conversion.
198      * @param constraints Message constraints. If {@code null}
199      *   {@link MessageConstraints#DEFAULT} will be used.
200      * @param incomingContentStrategy incoming content length strategy. If {@code null}
201      *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
202      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
203      *   {@link StrictContentLengthStrategy#INSTANCE} will be used.
204      *
205      * @since 4.4
206      */
207     protected NHttpConnectionBase(
208             final IOSession session,
209             final int buffersize,
210             final int fragmentSizeHint,
211             final ByteBufferAllocator allocator,
212             final CharsetDecoder chardecoder,
213             final CharsetEncoder charencoder,
214             final MessageConstraints constraints,
215             final ContentLengthStrategy incomingContentStrategy,
216             final ContentLengthStrategy outgoingContentStrategy) {
217         Args.notNull(session, "I/O session");
218         Args.positive(buffersize, "Buffer size");
219         int linebuffersize = buffersize;
220         if (linebuffersize > 512) {
221             linebuffersize = 512;
222         }
223         this.inbuf = new SessionInputBufferImpl(buffersize, linebuffersize, chardecoder, allocator);
224         this.outbuf = new SessionOutputBufferImpl(buffersize, linebuffersize, charencoder, allocator);
225         this.fragmentSizeHint = fragmentSizeHint >= 0 ? fragmentSizeHint : buffersize;
226 
227         this.inTransportMetrics = new HttpTransportMetricsImpl();
228         this.outTransportMetrics = new HttpTransportMetricsImpl();
229         this.connMetrics = new HttpConnectionMetricsImpl(this.inTransportMetrics, this.outTransportMetrics);
230         this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
231         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
232             LaxContentLengthStrategy.INSTANCE;
233         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
234             StrictContentLengthStrategy.INSTANCE;
235 
236         setSession(session);
237         this.status = ACTIVE;
238     }
239 
240     /**
241      * Creates new instance NHttpConnectionBase given the underlying I/O session.
242      *
243      * @param session the underlying I/O session.
244      * @param buffersize buffer size. Must be a positive number.
245      * @param fragmentSizeHint fragment size hint.
246      * @param allocator memory allocator.
247      *   If {@code null} {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE}
248      *   will be used.
249      * @param chardecoder decoder to be used for decoding HTTP protocol elements.
250      *   If {@code null} simple type cast will be used for byte to char conversion.
251      * @param charencoder encoder to be used for encoding HTTP protocol elements.
252      *   If {@code null} simple type cast will be used for char to byte conversion.
253      * @param incomingContentStrategy incoming content length strategy. If {@code null}
254      *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
255      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
256      *   {@link StrictContentLengthStrategy#INSTANCE} will be used.
257      *
258      * @since 4.3
259      */
260     protected NHttpConnectionBase(
261             final IOSession session,
262             final int buffersize,
263             final int fragmentSizeHint,
264             final ByteBufferAllocator allocator,
265             final CharsetDecoder chardecoder,
266             final CharsetEncoder charencoder,
267             final ContentLengthStrategy incomingContentStrategy,
268             final ContentLengthStrategy outgoingContentStrategy) {
269         this(session, buffersize, fragmentSizeHint, allocator, chardecoder, charencoder,
270                 null, incomingContentStrategy, outgoingContentStrategy);
271     }
272 
273     private void setSession(final IOSession session) {
274         this.session = session;
275         this.context = new SessionHttpContext(this.session);
276         this.session.setBufferStatus(this);
277         this.remote = this.session.getRemoteAddress();
278     }
279 
280     /**
281      * Binds the connection to a different {@link IOSession}. This may be necessary
282      * when the underlying I/O session gets upgraded with SSL/TLS encryption.
283      *
284      * @since 4.2
285      */
286     protected void bind(final IOSession session) {
287         Args.notNull(session, "I/O session");
288         this.session.setBufferStatus(null);
289         setSession(session);
290     }
291 
292     /**
293      * @since 4.2
294      *
295      * @deprecated (4.3) use constructor.
296      */
297     @Deprecated
298     protected ContentLengthStrategy createIncomingContentStrategy() {
299         return new LaxContentLengthStrategy();
300     }
301 
302     /**
303      * @since 4.2
304      *
305      * @deprecated (4.3) use constructor.
306      */
307     @Deprecated
308     protected ContentLengthStrategy createOutgoingContentStrategy() {
309         return new StrictContentLengthStrategy();
310     }
311 
312     /**
313      * @since 4.1
314      *
315      * @deprecated (4.3) no longer used.
316      */
317     @Deprecated
318     protected HttpTransportMetricsImpl createTransportMetrics() {
319         return new HttpTransportMetricsImpl();
320     }
321 
322     /**
323      * @since 4.1
324      *
325      * @deprecated (4.3) use decorator to add additional metrics.
326      */
327     @Deprecated
328     protected HttpConnectionMetricsImpl createConnectionMetrics(
329             final HttpTransportMetrics inTransportMetric,
330             final HttpTransportMetrics outTransportMetric) {
331         return new HttpConnectionMetricsImpl(inTransportMetric, outTransportMetric);
332     }
333 
334     @Override
335     public int getStatus() {
336         return this.status;
337     }
338 
339     @Override
340     public HttpContext getContext() {
341         return this.context;
342     }
343 
344     @Override
345     public HttpRequest getHttpRequest() {
346         return this.request;
347     }
348 
349     @Override
350     public HttpResponse getHttpResponse() {
351         return this.response;
352     }
353 
354     @Override
355     public void requestInput() {
356         this.session.setEvent(EventMask.READ);
357     }
358 
359     @Override
360     public void requestOutput() {
361         this.session.setEvent(EventMask.WRITE);
362     }
363 
364     @Override
365     public void suspendInput() {
366         this.session.clearEvent(EventMask.READ);
367     }
368 
369     @Override
370     public void suspendOutput() {
371         this.session.clearEvent(EventMask.WRITE);
372     }
373 
374     /**
375      * Initializes a specific {@link ContentDecoder} implementation based on the
376      * properties of the given {@link HttpMessage} and generates an instance of
377      * {@link HttpEntity} matching the properties of the content decoder.
378      *
379      * @param message the HTTP message.
380      * @return HTTP entity.
381      * @throws HttpException in case of an HTTP protocol violation.
382      */
383     protected HttpEntity prepareDecoder(final HttpMessage message) throws HttpException {
384         final BasicHttpEntity entity = new BasicHttpEntity();
385         final long len = this.incomingContentStrategy.determineLength(message);
386         this.contentDecoder = createContentDecoder(
387                 len,
388                 this.session.channel(),
389                 this.inbuf,
390                 this.inTransportMetrics);
391         if (len == ContentLengthStrategy.CHUNKED) {
392             entity.setChunked(true);
393             entity.setContentLength(-1);
394         } else if (len == ContentLengthStrategy.IDENTITY) {
395             entity.setChunked(false);
396             entity.setContentLength(-1);
397         } else {
398             entity.setChunked(false);
399             entity.setContentLength(len);
400         }
401 
402         final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE);
403         if (contentTypeHeader != null) {
404             entity.setContentType(contentTypeHeader);
405         }
406         final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING);
407         if (contentEncodingHeader != null) {
408             entity.setContentEncoding(contentEncodingHeader);
409         }
410         return entity;
411     }
412 
413     /**
414      * Factory method for {@link ContentDecoder} instances.
415      *
416      * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
417      *   {@link ContentLengthStrategy#IDENTITY}, if unknown.
418      * @param channel the session channel.
419      * @param buffer the session buffer.
420      * @param metrics transport metrics.
421      *
422      * @return content decoder.
423      *
424      * @since 4.1
425      */
426     protected ContentDecoder createContentDecoder(
427             final long len,
428             final ReadableByteChannel channel,
429             final SessionInputBuffer buffer,
430             final HttpTransportMetricsImpl metrics) {
431         if (len == ContentLengthStrategy.CHUNKED) {
432             return new ChunkDecoder(channel, buffer, this.constraints, metrics);
433         } else if (len == ContentLengthStrategy.IDENTITY) {
434             return new IdentityDecoder(channel, buffer, metrics);
435         } else {
436             return new LengthDelimitedDecoder(channel, buffer, metrics, len);
437         }
438     }
439 
440     /**
441      * Initializes a specific {@link ContentEncoder} implementation based on the
442      * properties of the given {@link HttpMessage}.
443      *
444      * @param message the HTTP message.
445      * @throws HttpException in case of an HTTP protocol violation.
446      */
447     protected void prepareEncoder(final HttpMessage message) throws HttpException {
448         final long len = this.outgoingContentStrategy.determineLength(message);
449         this.contentEncoder = createContentEncoder(
450                 len,
451                 this.session.channel(),
452                 this.outbuf,
453                 this.outTransportMetrics);
454     }
455 
456     /**
457      * Factory method for {@link ContentEncoder} instances.
458      *
459      * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
460      *   {@link ContentLengthStrategy#IDENTITY}, if unknown.
461      * @param channel the session channel.
462      * @param buffer the session buffer.
463      * @param metrics transport metrics.
464      *
465      * @return content encoder.
466      *
467      * @since 4.1
468      */
469     protected ContentEncoder createContentEncoder(
470             final long len,
471             final WritableByteChannel channel,
472             final SessionOutputBuffer buffer,
473             final HttpTransportMetricsImpl metrics) {
474         if (len == ContentLengthStrategy.CHUNKED) {
475             return new ChunkEncoder(channel, buffer, metrics, this.fragmentSizeHint);
476         } else if (len == ContentLengthStrategy.IDENTITY) {
477             return new IdentityEncoder(channel, buffer, metrics, this.fragmentSizeHint);
478         } else {
479             return new LengthDelimitedEncoder(channel, buffer, metrics, len, this.fragmentSizeHint);
480         }
481     }
482 
483     @Override
484     public boolean hasBufferedInput() {
485         return this.hasBufferedInput;
486     }
487 
488     @Override
489     public boolean hasBufferedOutput() {
490         return this.hasBufferedOutput;
491     }
492 
493     /**
494      * Assets if the connection is still open.
495      *
496      * @throws ConnectionClosedException in case the connection has already
497      *   been closed.
498      */
499     protected void assertNotClosed() throws ConnectionClosedException {
500         if (this.status != ACTIVE) {
501             throw new ConnectionClosedException("Connection is closed");
502         }
503     }
504 
505     @Override
506     public void close() throws IOException {
507         if (this.status != ACTIVE) {
508             return;
509         }
510         this.status = CLOSING;
511         if (this.outbuf.hasData()) {
512             this.session.setEvent(EventMask.WRITE);
513         } else {
514             this.session.close();
515             this.status = CLOSED;
516         }
517     }
518 
519     @Override
520     public boolean isOpen() {
521         return this.status == ACTIVE && !this.session.isClosed();
522     }
523 
524     @Override
525     public boolean isStale() {
526         return this.session.isClosed();
527     }
528 
529     @Override
530     public InetAddress getLocalAddress() {
531         final SocketAddress address = this.session.getLocalAddress();
532         if (address instanceof InetSocketAddress) {
533             return ((InetSocketAddress) address).getAddress();
534         } else {
535             return null;
536         }
537     }
538 
539     @Override
540     public int getLocalPort() {
541         final SocketAddress address = this.session.getLocalAddress();
542         if (address instanceof InetSocketAddress) {
543             return ((InetSocketAddress) address).getPort();
544         } else {
545             return -1;
546         }
547     }
548 
549     @Override
550     public InetAddress getRemoteAddress() {
551         final SocketAddress address = this.session.getRemoteAddress();
552         if (address instanceof InetSocketAddress) {
553             return ((InetSocketAddress) address).getAddress();
554         } else {
555             return null;
556         }
557     }
558 
559     @Override
560     public int getRemotePort() {
561         final SocketAddress address = this.session.getRemoteAddress();
562         if (address instanceof InetSocketAddress) {
563             return ((InetSocketAddress) address).getPort();
564         } else {
565             return -1;
566         }
567     }
568 
569     @Override
570     public void setSocketTimeout(final int timeout) {
571         this.session.setSocketTimeout(timeout);
572     }
573 
574     @Override
575     public int getSocketTimeout() {
576         return this.session.getSocketTimeout();
577     }
578 
579     @Override
580     public void shutdown() throws IOException {
581         this.status = CLOSED;
582         this.session.shutdown();
583     }
584 
585     @Override
586     public HttpConnectionMetrics getMetrics() {
587         return this.connMetrics;
588     }
589 
590     @Override
591     public String toString() {
592         final SocketAddress remoteAddress = this.session.getRemoteAddress();
593         final SocketAddress localAddress = this.session.getLocalAddress();
594         if (remoteAddress != null && localAddress != null) {
595             final StringBuilder buffer = new StringBuilder();
596             NetUtils.formatAddress(buffer, localAddress);
597             buffer.append("<->");
598             NetUtils.formatAddress(buffer, remoteAddress);
599             return buffer.toString();
600         } else {
601             return "[Not bound]";
602         }
603     }
604 
605     @Override
606     public Socket getSocket() {
607         if (this.session instanceof SocketAccessor) {
608             return ((SocketAccessor) this.session).getSocket();
609         } else {
610             return null;
611         }
612     }
613 
614 }