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