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.nio.channels.SelectionKey;
032import java.nio.charset.CharsetDecoder;
033import java.nio.charset.CharsetEncoder;
034
035import org.apache.http.HttpEntity;
036import org.apache.http.HttpEntityEnclosingRequest;
037import org.apache.http.HttpException;
038import org.apache.http.HttpRequest;
039import org.apache.http.HttpResponse;
040import org.apache.http.HttpResponseFactory;
041import org.apache.http.config.MessageConstraints;
042import org.apache.http.entity.ContentLengthStrategy;
043import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriter;
044import org.apache.http.impl.nio.codecs.DefaultHttpRequestWriterFactory;
045import org.apache.http.impl.nio.codecs.DefaultHttpResponseParser;
046import org.apache.http.impl.nio.codecs.DefaultHttpResponseParserFactory;
047import org.apache.http.nio.NHttpClientEventHandler;
048import org.apache.http.nio.NHttpClientHandler;
049import org.apache.http.nio.NHttpClientIOTarget;
050import org.apache.http.nio.NHttpMessageParser;
051import org.apache.http.nio.NHttpMessageParserFactory;
052import org.apache.http.nio.NHttpMessageWriter;
053import org.apache.http.nio.NHttpMessageWriterFactory;
054import org.apache.http.nio.reactor.EventMask;
055import org.apache.http.nio.reactor.IOSession;
056import org.apache.http.nio.reactor.SessionInputBuffer;
057import org.apache.http.nio.reactor.SessionOutputBuffer;
058import org.apache.http.nio.util.ByteBufferAllocator;
059import org.apache.http.params.HttpParamConfig;
060import org.apache.http.params.HttpParams;
061import org.apache.http.util.Args;
062
063/**
064 * Default implementation of the {@link org.apache.http.nio.NHttpClientConnection}
065 * interface.
066 *
067 * @since 4.0
068 */
069@SuppressWarnings("deprecation")
070public class DefaultNHttpClientConnection
071    extends NHttpConnectionBase implements NHttpClientIOTarget {
072
073    protected final NHttpMessageParser<HttpResponse> responseParser;
074    protected final NHttpMessageWriter<HttpRequest> requestWriter;
075
076    /**
077     * Creates a new instance of this class given the underlying I/O session.
078     *
079     * @param session the underlying I/O session.
080     * @param responseFactory HTTP response factory.
081     * @param allocator byte buffer allocator.
082     * @param params HTTP parameters.
083     *
084     * @deprecated (4.3) use {@link DefaultNHttpClientConnection#DefaultNHttpClientConnection(
085     *   IOSession, int, int, ByteBufferAllocator, CharsetDecoder, CharsetEncoder,
086     *   MessageConstraints, ContentLengthStrategy, ContentLengthStrategy,
087     *   NHttpMessageWriterFactory, NHttpMessageParserFactory)}
088     */
089    @Deprecated
090    public DefaultNHttpClientConnection(
091            final IOSession session,
092            final HttpResponseFactory responseFactory,
093            final ByteBufferAllocator allocator,
094            final HttpParams params) {
095        super(session, allocator, params);
096        Args.notNull(responseFactory, "Response factory");
097        this.responseParser = createResponseParser(this.inbuf, responseFactory, params);
098        this.requestWriter = createRequestWriter(this.outbuf, params);
099        this.hasBufferedInput = false;
100        this.hasBufferedOutput = false;
101        this.session.setBufferStatus(this);
102    }
103
104    /**
105     * Creates new instance DefaultNHttpClientConnection given the underlying I/O session.
106     *
107     * @param session the underlying I/O session.
108     * @param buffersize buffer size. Must be a positive number.
109     * @param fragmentSizeHint fragment size hint.
110     * @param allocator memory allocator.
111     *   If {@code null} {@link org.apache.http.nio.util.HeapByteBufferAllocator#INSTANCE}
112     *   will be used.
113     * @param chardecoder decoder to be used for decoding HTTP protocol elements.
114     *   If {@code null} simple type cast will be used for byte to char conversion.
115     * @param charencoder encoder to be used for encoding HTTP protocol elements.
116     *   If {@code null} simple type cast will be used for char to byte conversion.
117     * @param constraints Message constraints. If {@code null}
118     *   {@link MessageConstraints#DEFAULT} will be used.
119     * @param incomingContentStrategy incoming content length strategy. If {@code null}
120     *   {@link org.apache.http.impl.entity.LaxContentLengthStrategy#INSTANCE} will be used.
121     * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
122     *   {@link org.apache.http.impl.entity.StrictContentLengthStrategy#INSTANCE} will be used.
123     *
124     * @since 4.3
125     */
126    public DefaultNHttpClientConnection(
127            final IOSession session,
128            final int buffersize,
129            final int fragmentSizeHint,
130            final ByteBufferAllocator allocator,
131            final CharsetDecoder chardecoder,
132            final CharsetEncoder charencoder,
133            final MessageConstraints constraints,
134            final ContentLengthStrategy incomingContentStrategy,
135            final ContentLengthStrategy outgoingContentStrategy,
136            final NHttpMessageWriterFactory<HttpRequest> requestWriterFactory,
137            final NHttpMessageParserFactory<HttpResponse> responseParserFactory) {
138        super(session, buffersize, fragmentSizeHint, allocator, chardecoder, charencoder,
139                constraints, incomingContentStrategy, outgoingContentStrategy);
140        this.requestWriter = (requestWriterFactory != null ? requestWriterFactory :
141            DefaultHttpRequestWriterFactory.INSTANCE).create(this.outbuf);
142        this.responseParser = (responseParserFactory != null ? responseParserFactory :
143            DefaultHttpResponseParserFactory.INSTANCE).create(this.inbuf, constraints);
144    }
145
146    /**
147     * @since 4.3
148     */
149    public DefaultNHttpClientConnection(
150            final IOSession session,
151            final int buffersize,
152            final CharsetDecoder chardecoder,
153            final CharsetEncoder charencoder,
154            final MessageConstraints constraints) {
155        this(session, buffersize, buffersize, null, chardecoder, charencoder, constraints,
156                null, null, null, null);
157    }
158
159    /**
160     * @since 4.3
161     */
162    public DefaultNHttpClientConnection(final IOSession session, final int buffersize) {
163        this(session, buffersize, buffersize, null, null, null, null, null, null, null, null);
164    }
165
166    /**
167     * Creates an instance of {@link NHttpMessageParser} to be used
168     * by this connection for parsing incoming {@link HttpResponse} messages.
169     * <p>
170     * This method can be overridden in a super class in order to provide
171     * a different implementation of the {@link NHttpMessageParser} interface.
172     *
173     * @return HTTP response parser.
174     *
175     * @deprecated (4.3) use constructor.
176     */
177    @Deprecated
178    protected NHttpMessageParser<HttpResponse> createResponseParser(
179            final SessionInputBuffer buffer,
180            final HttpResponseFactory responseFactory,
181            final HttpParams params) {
182        // override in derived class to specify a line parser
183        final MessageConstraints constraints = HttpParamConfig.getMessageConstraints(params);
184        return new DefaultHttpResponseParser(buffer, null, responseFactory, constraints);
185    }
186
187    /**
188     * Creates an instance of {@link NHttpMessageWriter} to be used
189     * by this connection for writing out outgoing {@link HttpRequest} messages.
190     * <p>
191     * This method can be overridden by a super class in order to provide
192     * a different implementation of the {@link NHttpMessageWriter} interface.
193     *
194     * @return HTTP response parser.
195     *
196     * @deprecated (4.3) use constructor.
197     */
198    @Deprecated
199    protected NHttpMessageWriter<HttpRequest> createRequestWriter(
200            final SessionOutputBuffer buffer,
201            final HttpParams params) {
202        // override in derived class to specify a line formatter
203        return new DefaultHttpRequestWriter(buffer, null);
204    }
205
206    /**
207     * @since 4.2
208     */
209    protected void onResponseReceived(final HttpResponse response) {
210    }
211
212    /**
213     * @since 4.2
214     */
215    protected void onRequestSubmitted(final HttpRequest request) {
216    }
217
218    @Override
219    public void resetInput() {
220        this.response = null;
221        this.contentDecoder = null;
222        this.responseParser.reset();
223    }
224
225    @Override
226    public void resetOutput() {
227        this.request = null;
228        this.contentEncoder = null;
229        this.requestWriter.reset();
230    }
231
232    public void consumeInput(final NHttpClientEventHandler handler) {
233        if (this.status != ACTIVE) {
234            this.session.clearEvent(EventMask.READ);
235            return;
236        }
237        try {
238            if (this.response == null) {
239                int bytesRead;
240                do {
241                    bytesRead = this.responseParser.fillBuffer(this.session.channel());
242                    if (bytesRead > 0) {
243                        this.inTransportMetrics.incrementBytesTransferred(bytesRead);
244                    }
245                    this.response = this.responseParser.parse();
246                } while (bytesRead > 0 && this.response == null);
247                if (this.response != null) {
248                    if (this.response.getStatusLine().getStatusCode() >= 200) {
249                        final HttpEntity entity = prepareDecoder(this.response);
250                        this.response.setEntity(entity);
251                        this.connMetrics.incrementResponseCount();
252                    }
253                    this.hasBufferedInput = this.inbuf.hasData();
254                    onResponseReceived(this.response);
255                    handler.responseReceived(this);
256                    if (this.contentDecoder == null) {
257                        resetInput();
258                    }
259                }
260                if (bytesRead == -1 && !this.inbuf.hasData()) {
261                    handler.endOfInput(this);
262                }
263            }
264            if (this.contentDecoder != null && (this.session.getEventMask() & SelectionKey.OP_READ) > 0) {
265                handler.inputReady(this, this.contentDecoder);
266                if (this.contentDecoder.isCompleted()) {
267                    // Response entity received
268                    // Ready to receive a new response
269                    resetInput();
270                }
271            }
272        } catch (final HttpException ex) {
273            resetInput();
274            handler.exception(this, ex);
275        } catch (final Exception ex) {
276            handler.exception(this, ex);
277        } finally {
278            // Finally set buffered input flag
279            this.hasBufferedInput = this.inbuf.hasData();
280        }
281    }
282
283    public void produceOutput(final NHttpClientEventHandler handler) {
284        try {
285            if (this.status == ACTIVE) {
286                if (this.contentEncoder == null && !this.outbuf.hasData()) {
287                    handler.requestReady(this);
288                }
289                if (this.contentEncoder != null) {
290                    handler.outputReady(this, this.contentEncoder);
291                    if (this.contentEncoder.isCompleted()) {
292                        resetOutput();
293                    }
294                }
295            }
296            if (this.outbuf.hasData()) {
297                final int bytesWritten = this.outbuf.flush(this.session.channel());
298                if (bytesWritten > 0) {
299                    this.outTransportMetrics.incrementBytesTransferred(bytesWritten);
300                }
301            }
302            if (!this.outbuf.hasData()) {
303                if (this.status == CLOSING) {
304                    this.session.close();
305                    this.status = CLOSED;
306                    resetOutput();
307                }
308            }
309        } catch (final Exception ex) {
310            handler.exception(this, ex);
311        } finally {
312            // Finally set the buffered output flag
313            this.hasBufferedOutput = this.outbuf.hasData();
314        }
315    }
316
317    @Override
318    public void submitRequest(final HttpRequest request) throws IOException, HttpException {
319        Args.notNull(request, "HTTP request");
320        assertNotClosed();
321        if (this.request != null) {
322            throw new HttpException("Request already submitted");
323        }
324        onRequestSubmitted(request);
325        this.requestWriter.write(request);
326        this.hasBufferedOutput = this.outbuf.hasData();
327
328        if (request instanceof HttpEntityEnclosingRequest
329                && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
330            prepareEncoder(request);
331            this.request = request;
332        }
333        this.connMetrics.incrementRequestCount();
334        this.session.setEvent(EventMask.WRITE);
335    }
336
337    @Override
338    public boolean isRequestSubmitted() {
339        return this.request != null;
340    }
341
342    @Override
343    public void consumeInput(final NHttpClientHandler handler) {
344        consumeInput(new NHttpClientEventHandlerAdaptor(handler));
345    }
346
347    @Override
348    public void produceOutput(final NHttpClientHandler handler) {
349        produceOutput(new NHttpClientEventHandlerAdaptor(handler));
350    }
351
352}