All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactivex.netty.protocol.http.server.HttpConnectionHandler Maven / Gradle / Ivy

There is a newer version: 0.5.3-rc.2
Show newest version
/*
 * Copyright 2015 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.reactivex.netty.protocol.http.server;

import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpVersion;
import io.reactivex.netty.channel.Connection;
import io.reactivex.netty.events.Clock;
import io.reactivex.netty.protocol.http.server.events.HttpServerEventPublisher;
import io.reactivex.netty.protocol.tcp.server.ConnectionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observable.Operator;
import rx.Subscriber;
import rx.functions.Func1;

import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.reactivex.netty.events.Clock.*;
import static java.util.concurrent.TimeUnit.*;

public class HttpConnectionHandler implements ConnectionHandler, Object> {

    private static final Logger logger = LoggerFactory.getLogger(HttpConnectionHandler.class);

    private final RequestHandler requestHandler;
    private final HttpServerEventPublisher eventPublisher;
    private final boolean sendHttp10ResponseFor10Request;

    public HttpConnectionHandler(RequestHandler requestHandler, HttpServerEventPublisher eventPublisher,
                                 boolean sendHttp10ResponseFor10Request) {
        this.requestHandler = requestHandler;
        this.eventPublisher = eventPublisher;
        this.sendHttp10ResponseFor10Request = sendHttp10ResponseFor10Request;
    }

    @Override
    public Observable handle(final Connection, Object> c) {
        return c.getInput()
                .nest()
                .concatMap(new Func1>, Observable>() {
                    @Override
                    public Observable call(Observable> reqSource) {
                        return reqSource.take(1)
                                        .flatMap(new Func1, Observable>() {
                                            @Override
                                            public Observable call(HttpServerRequest req) {
                                                final long startNanos = eventPublisher.publishingEnabled()
                                                                                ? Clock.newStartTimeNanos()
                                                                                : -1;

                                                if (eventPublisher.publishingEnabled()) {
                                                    eventPublisher.onNewRequestReceived();
                                                }

                                                final HttpServerResponse response = newResponse(req, c);
                                                return handleRequest(req, startNanos, response, c);
                                            }
                                        });
                    }
                })
                .repeat()
                .ambWith(c.closeListener());
    }

    @SuppressWarnings("unchecked")
    private Observable handleRequest(HttpServerRequest request, final long startTimeNanos,
                                           final HttpServerResponse response,
                                           final Connection, Object> c) {
        Observable requestHandlingResult = null;
        try {

            if (request.decoderResult().isSuccess()) {
                requestHandlingResult = requestHandler.handle(request, response);
            }

            if(null == requestHandlingResult) {
                        /*If decoding failed an appropriate response status would have been set.
                          Otherwise, overwrite the status to 500*/
                if (response.getStatus().equals(OK)) {
                    response.setStatus(INTERNAL_SERVER_ERROR);
                }
                requestHandlingResult = response.write(Observable.empty());
            }

        } catch (Throwable throwable) {
            logger.error("Unexpected error while invoking HTTP user handler.", throwable);
                    /*If the headers are already written, then this will produce an error Observable.*/
            requestHandlingResult = response.setStatus(INTERNAL_SERVER_ERROR)
                                            .write(Observable.empty());
        }

        if (eventPublisher.publishingEnabled()) {
            requestHandlingResult = requestHandlingResult.lift(new Operator() {
                @Override
                public Subscriber call(final Subscriber o) {

                    if (eventPublisher.publishingEnabled()) {
                        eventPublisher.onRequestHandlingStart(onEndNanos(startTimeNanos), NANOSECONDS);
                    }

                    return new Subscriber(o) {
                        @Override
                        public void onCompleted() {
                            if (eventPublisher.publishingEnabled()) {
                                eventPublisher.onRequestHandlingSuccess(onEndNanos(startTimeNanos),
                                                                        NANOSECONDS);
                            }
                            o.onCompleted();
                        }

                        @Override
                        public void onError(Throwable e) {
                            if (eventPublisher.publishingEnabled()) {
                                eventPublisher.onRequestHandlingFailed(onEndNanos(startTimeNanos),
                                                                       NANOSECONDS, e);
                            }
                            logger.error("Unexpected error processing a request.", e);
                            o.onError(e);
                        }

                        @Override
                        public void onNext(Void aVoid) {
                            // No Op, its a void
                        }
                    };
                }
            });
        }

        return requestHandlingResult.onErrorResumeNext(new Func1>() {
            @Override
            public Observable call(Throwable throwable) {
                logger.error("Unexpected error while processing request.", throwable);
                return response.setStatus(INTERNAL_SERVER_ERROR)
                               .dispose()
                               .concatWith(c.close())
                               .onErrorResumeNext(Observable.empty());// Ignore errors on cleanup
            }
        }).concatWith(request.dispose()/*Dispose request at the end of processing to discard content if not read*/
        ).concatWith(response.dispose()/*Dispose response at the end of processing to cleanup*/);

    }

    private HttpServerResponse newResponse(HttpServerRequest request,
                                              final Connection, Object> c) {

                /*
                 * Server should send the highest version it is compatible with.
                 * http://tools.ietf.org/html/rfc2145#section-2.3
                 *
                 * unless overriden explicitly.
                 */
        final HttpVersion version = sendHttp10ResponseFor10Request ? request.getHttpVersion()
                : HttpVersion.HTTP_1_1;

        HttpResponse responseHeaders;
        if (request.decoderResult().isFailure()) {
            // As per the spec, we should send 414/431 for URI too long and headers too long, but we do not have
            // enough info to decide which kind of failure has caused this error here.
            responseHeaders = new DefaultHttpResponse(version, REQUEST_HEADER_FIELDS_TOO_LARGE);
            responseHeaders.headers()
                           .set(CONNECTION, HttpHeaderValues.CLOSE)
                           .set(CONTENT_LENGTH, 0);
        } else {
            responseHeaders = new DefaultHttpResponse(version, OK);
        }
        HttpServerResponse response = HttpServerResponseImpl.create(request, c, responseHeaders);
        setConnectionHeader(request, response);
        return response;
    }

    private void setConnectionHeader(HttpServerRequest request, HttpServerResponse response) {
        if (request.isKeepAlive()) {
            if (!request.getHttpVersion().isKeepAliveDefault()) {
                // Avoid sending keep-alive header if keep alive is default.
                // Issue: https://github.com/Netflix/RxNetty/issues/167
                // This optimizes data transferred on the wire.

                // Add keep alive header as per:
                // - http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
                response.setHeader(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            }
        } else {
            response.setHeader(CONNECTION, HttpHeaderValues.CLOSE);
        }
    }

}