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         setSession(session);
289     }
290 
291     /**
292      * @since 4.2
293      *
294      * @deprecated (4.3) use constructor.
295      */
296     @Deprecated
297     protected ContentLengthStrategy createIncomingContentStrategy() {
298         return new LaxContentLengthStrategy();
299     }
300 
301     /**
302      * @since 4.2
303      *
304      * @deprecated (4.3) use constructor.
305      */
306     @Deprecated
307     protected ContentLengthStrategy createOutgoingContentStrategy() {
308         return new StrictContentLengthStrategy();
309     }
310 
311     /**
312      * @since 4.1
313      *
314      * @deprecated (4.3) no longer used.
315      */
316     @Deprecated
317     protected HttpTransportMetricsImpl createTransportMetrics() {
318         return new HttpTransportMetricsImpl();
319     }
320 
321     /**
322      * @since 4.1
323      *
324      * @deprecated (4.3) use decorator to add additional metrics.
325      */
326     @Deprecated
327     protected HttpConnectionMetricsImpl createConnectionMetrics(
328             final HttpTransportMetrics inTransportMetric,
329             final HttpTransportMetrics outTransportMetric) {
330         return new HttpConnectionMetricsImpl(inTransportMetric, outTransportMetric);
331     }
332 
333     @Override
334     public int getStatus() {
335         return this.status;
336     }
337 
338     @Override
339     public HttpContext getContext() {
340         return this.context;
341     }
342 
343     @Override
344     public HttpRequest getHttpRequest() {
345         return this.request;
346     }
347 
348     @Override
349     public HttpResponse getHttpResponse() {
350         return this.response;
351     }
352 
353     @Override
354     public void requestInput() {
355         this.session.setEvent(EventMask.READ);
356     }
357 
358     @Override
359     public void requestOutput() {
360         this.session.setEvent(EventMask.WRITE);
361     }
362 
363     @Override
364     public void suspendInput() {
365         this.session.clearEvent(EventMask.READ);
366     }
367 
368     @Override
369     public void suspendOutput() {
370         this.session.clearEvent(EventMask.WRITE);
371     }
372 
373     /**
374      * Initializes a specific {@link ContentDecoder} implementation based on the
375      * properties of the given {@link HttpMessage} and generates an instance of
376      * {@link HttpEntity} matching the properties of the content decoder.
377      *
378      * @param message the HTTP message.
379      * @return HTTP entity.
380      * @throws HttpException in case of an HTTP protocol violation.
381      */
382     protected HttpEntity prepareDecoder(final HttpMessage message) throws HttpException {
383         final BasicHttpEntity entity = new BasicHttpEntity();
384         final long len = this.incomingContentStrategy.determineLength(message);
385         this.contentDecoder = createContentDecoder(
386                 len,
387                 this.session.channel(),
388                 this.inbuf,
389                 this.inTransportMetrics);
390         if (len == ContentLengthStrategy.CHUNKED) {
391             entity.setChunked(true);
392             entity.setContentLength(-1);
393         } else if (len == ContentLengthStrategy.IDENTITY) {
394             entity.setChunked(false);
395             entity.setContentLength(-1);
396         } else {
397             entity.setChunked(false);
398             entity.setContentLength(len);
399         }
400 
401         final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE);
402         if (contentTypeHeader != null) {
403             entity.setContentType(contentTypeHeader);
404         }
405         final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING);
406         if (contentEncodingHeader != null) {
407             entity.setContentEncoding(contentEncodingHeader);
408         }
409         return entity;
410     }
411 
412     /**
413      * Factory method for {@link ContentDecoder} instances.
414      *
415      * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
416      *   {@link ContentLengthStrategy#IDENTITY}, if unknown.
417      * @param channel the session channel.
418      * @param buffer the session buffer.
419      * @param metrics transport metrics.
420      *
421      * @return content decoder.
422      *
423      * @since 4.1
424      */
425     protected ContentDecoder createContentDecoder(
426             final long len,
427             final ReadableByteChannel channel,
428             final SessionInputBuffer buffer,
429             final HttpTransportMetricsImpl metrics) {
430         if (len == ContentLengthStrategy.CHUNKED) {
431             return new ChunkDecoder(channel, buffer, this.constraints, metrics);
432         } else if (len == ContentLengthStrategy.IDENTITY) {
433             return new IdentityDecoder(channel, buffer, metrics);
434         } else {
435             return new LengthDelimitedDecoder(channel, buffer, metrics, len);
436         }
437     }
438 
439     /**
440      * Initializes a specific {@link ContentEncoder} implementation based on the
441      * properties of the given {@link HttpMessage}.
442      *
443      * @param message the HTTP message.
444      * @throws HttpException in case of an HTTP protocol violation.
445      */
446     protected void prepareEncoder(final HttpMessage message) throws HttpException {
447         final long len = this.outgoingContentStrategy.determineLength(message);
448         this.contentEncoder = createContentEncoder(
449                 len,
450                 this.session.channel(),
451                 this.outbuf,
452                 this.outTransportMetrics);
453     }
454 
455     /**
456      * Factory method for {@link ContentEncoder} instances.
457      *
458      * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
459      *   {@link ContentLengthStrategy#IDENTITY}, if unknown.
460      * @param channel the session channel.
461      * @param buffer the session buffer.
462      * @param metrics transport metrics.
463      *
464      * @return content encoder.
465      *
466      * @since 4.1
467      */
468     protected ContentEncoder createContentEncoder(
469             final long len,
470             final WritableByteChannel channel,
471             final SessionOutputBuffer buffer,
472             final HttpTransportMetricsImpl metrics) {
473         if (len == ContentLengthStrategy.CHUNKED) {
474             return new ChunkEncoder(channel, buffer, metrics, this.fragmentSizeHint);
475         } else if (len == ContentLengthStrategy.IDENTITY) {
476             return new IdentityEncoder(channel, buffer, metrics, this.fragmentSizeHint);
477         } else {
478             return new LengthDelimitedEncoder(channel, buffer, metrics, len, this.fragmentSizeHint);
479         }
480     }
481 
482     @Override
483     public boolean hasBufferedInput() {
484         return this.hasBufferedInput;
485     }
486 
487     @Override
488     public boolean hasBufferedOutput() {
489         return this.hasBufferedOutput;
490     }
491 
492     /**
493      * Assets if the connection is still open.
494      *
495      * @throws ConnectionClosedException in case the connection has already
496      *   been closed.
497      */
498     protected void assertNotClosed() throws ConnectionClosedException {
499         if (this.status != ACTIVE) {
500             throw new ConnectionClosedException("Connection is closed");
501         }
502     }
503 
504     @Override
505     public void close() throws IOException {
506         if (this.status != ACTIVE) {
507             return;
508         }
509         this.status = CLOSING;
510         if (this.outbuf.hasData()) {
511             this.session.setEvent(EventMask.WRITE);
512         } else {
513             this.session.close();
514             this.status = CLOSED;
515         }
516     }
517 
518     @Override
519     public boolean isOpen() {
520         return this.status == ACTIVE && !this.session.isClosed();
521     }
522 
523     @Override
524     public boolean isStale() {
525         return this.session.isClosed();
526     }
527 
528     @Override
529     public InetAddress getLocalAddress() {
530         final SocketAddress address = this.session.getLocalAddress();
531         if (address instanceof InetSocketAddress) {
532             return ((InetSocketAddress) address).getAddress();
533         } else {
534             return null;
535         }
536     }
537 
538     @Override
539     public int getLocalPort() {
540         final SocketAddress address = this.session.getLocalAddress();
541         if (address instanceof InetSocketAddress) {
542             return ((InetSocketAddress) address).getPort();
543         } else {
544             return -1;
545         }
546     }
547 
548     @Override
549     public InetAddress getRemoteAddress() {
550         final SocketAddress address = this.session.getRemoteAddress();
551         if (address instanceof InetSocketAddress) {
552             return ((InetSocketAddress) address).getAddress();
553         } else {
554             return null;
555         }
556     }
557 
558     @Override
559     public int getRemotePort() {
560         final SocketAddress address = this.session.getRemoteAddress();
561         if (address instanceof InetSocketAddress) {
562             return ((InetSocketAddress) address).getPort();
563         } else {
564             return -1;
565         }
566     }
567 
568     @Override
569     public void setSocketTimeout(final int timeout) {
570         this.session.setSocketTimeout(timeout);
571     }
572 
573     @Override
574     public int getSocketTimeout() {
575         return this.session.getSocketTimeout();
576     }
577 
578     @Override
579     public void shutdown() throws IOException {
580         this.status = CLOSED;
581         this.session.shutdown();
582     }
583 
584     @Override
585     public HttpConnectionMetrics getMetrics() {
586         return this.connMetrics;
587     }
588 
589     @Override
590     public String toString() {
591         final SocketAddress remoteAddress = this.session.getRemoteAddress();
592         final SocketAddress localAddress = this.session.getLocalAddress();
593         if (remoteAddress != null && localAddress != null) {
594             final StringBuilder buffer = new StringBuilder();
595             NetUtils.formatAddress(buffer, localAddress);
596             buffer.append("<->");
597             NetUtils.formatAddress(buffer, remoteAddress);
598             return buffer.toString();
599         } else {
600             return "[Not bound]";
601         }
602     }
603 
604     @Override
605     public Socket getSocket() {
606         if (this.session instanceof SocketAccessor) {
607             return ((SocketAccessor) this.session).getSocket();
608         } else {
609             return null;
610         }
611     }
612 
613 }