001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.nio;
029
030import java.io.IOException;
031import java.net.InetAddress;
032import java.net.InetSocketAddress;
033import java.net.Socket;
034import java.net.SocketAddress;
035import java.nio.channels.ReadableByteChannel;
036import java.nio.channels.WritableByteChannel;
037import java.nio.charset.Charset;
038import java.nio.charset.CharsetDecoder;
039import java.nio.charset.CharsetEncoder;
040import java.nio.charset.CodingErrorAction;
041
042import org.apache.http.ConnectionClosedException;
043import org.apache.http.Consts;
044import org.apache.http.Header;
045import org.apache.http.HttpConnectionMetrics;
046import org.apache.http.HttpEntity;
047import org.apache.http.HttpException;
048import org.apache.http.HttpInetConnection;
049import org.apache.http.HttpMessage;
050import org.apache.http.HttpRequest;
051import org.apache.http.HttpResponse;
052import org.apache.http.config.MessageConstraints;
053import org.apache.http.entity.BasicHttpEntity;
054import org.apache.http.entity.ContentLengthStrategy;
055import org.apache.http.impl.HttpConnectionMetricsImpl;
056import org.apache.http.impl.entity.LaxContentLengthStrategy;
057import org.apache.http.impl.entity.StrictContentLengthStrategy;
058import org.apache.http.impl.io.HttpTransportMetricsImpl;
059import org.apache.http.impl.nio.codecs.ChunkDecoder;
060import org.apache.http.impl.nio.codecs.ChunkEncoder;
061import org.apache.http.impl.nio.codecs.IdentityDecoder;
062import org.apache.http.impl.nio.codecs.IdentityEncoder;
063import org.apache.http.impl.nio.codecs.LengthDelimitedDecoder;
064import org.apache.http.impl.nio.codecs.LengthDelimitedEncoder;
065import org.apache.http.impl.nio.reactor.SessionInputBufferImpl;
066import org.apache.http.impl.nio.reactor.SessionOutputBufferImpl;
067import org.apache.http.io.HttpTransportMetrics;
068import org.apache.http.nio.ContentDecoder;
069import org.apache.http.nio.ContentEncoder;
070import org.apache.http.nio.NHttpConnection;
071import org.apache.http.nio.reactor.EventMask;
072import org.apache.http.nio.reactor.IOSession;
073import org.apache.http.nio.reactor.SessionBufferStatus;
074import org.apache.http.nio.reactor.SessionInputBuffer;
075import org.apache.http.nio.reactor.SessionOutputBuffer;
076import org.apache.http.nio.reactor.SocketAccessor;
077import org.apache.http.nio.util.ByteBufferAllocator;
078import org.apache.http.params.CoreConnectionPNames;
079import org.apache.http.params.CoreProtocolPNames;
080import org.apache.http.params.HttpParams;
081import org.apache.http.protocol.HTTP;
082import org.apache.http.protocol.HttpContext;
083import org.apache.http.util.Args;
084import org.apache.http.util.CharsetUtils;
085import org.apache.http.util.NetUtils;
086
087/**
088 * This class serves as a base for all {@link NHttpConnection} implementations and provides
089 * functionality common to both client and server HTTP connections.
090 *
091 * @since 4.0
092 */
093@SuppressWarnings("deprecation")
094public class NHttpConnectionBase
095        implements NHttpConnection, HttpInetConnection, SessionBufferStatus, SocketAccessor {
096
097    protected final ContentLengthStrategy incomingContentStrategy;
098    protected final ContentLengthStrategy outgoingContentStrategy;
099
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}