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