/*
* 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<I, O> implements ConnectionHandler<HttpServerRequest<I>, Object> {
private static final Logger logger = LoggerFactory.getLogger(HttpConnectionHandler.class);
private final RequestHandler<I, O> requestHandler;
private final HttpServerEventPublisher eventPublisher;
private final boolean sendHttp10ResponseFor10Request;
public HttpConnectionHandler(RequestHandler<I, O> requestHandler, HttpServerEventPublisher eventPublisher,
boolean sendHttp10ResponseFor10Request) {
this.requestHandler = requestHandler;
this.eventPublisher = eventPublisher;
this.sendHttp10ResponseFor10Request = sendHttp10ResponseFor10Request;
}
@Override
public Observable<Void> handle(final Connection<HttpServerRequest<I>, Object> c) {
return c.getInput()
.nest()
.concatMap(new Func1<Observable<HttpServerRequest<I>>, Observable<Void>>() {
@Override
public Observable<Void> call(Observable<HttpServerRequest<I>> reqSource) {
return reqSource.take(1)
.flatMap(new Func1<HttpServerRequest<I>, Observable<Void>>() {
@Override
public Observable<Void> call(HttpServerRequest<I> req) {
final long startNanos = eventPublisher.publishingEnabled()
? Clock.newStartTimeNanos()
: -1;
if (eventPublisher.publishingEnabled()) {
eventPublisher.onNewRequestReceived();
}
final HttpServerResponse<O> response = newResponse(req, c);
return handleRequest(req, startNanos, response, c);
}
});
}
})
.repeat()
.ambWith(c.closeListener());
}
@SuppressWarnings("unchecked")
private Observable<Void> handleRequest(HttpServerRequest<I> request, final long startTimeNanos,
final HttpServerResponse<O> response,
final Connection<HttpServerRequest<I>, Object> c) {
Observable<Void> 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.<O>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.<O>empty());
}
if (eventPublisher.publishingEnabled()) {
requestHandlingResult = requestHandlingResult.lift(new Operator<Void, Void>() {
@Override
public Subscriber<? super Void> call(final Subscriber<? super Void> o) {
if (eventPublisher.publishingEnabled()) {
eventPublisher.onRequestHandlingStart(onEndNanos(startTimeNanos), NANOSECONDS);
}
return new Subscriber<Void>(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<Throwable, Observable<Void>>() {
@Override
public Observable<Void> call(Throwable throwable) {
logger.error("Unexpected error while processing request.", throwable);
return response.setStatus(INTERNAL_SERVER_ERROR)
.dispose()
.concatWith(c.close())
.onErrorResumeNext(Observable.<Void>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<O> newResponse(HttpServerRequest<I> request,
final Connection<HttpServerRequest<I>, 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<O> response = HttpServerResponseImpl.create(request, c, responseHeaders);
setConnectionHeader(request, response);
return response;
}
private void setConnectionHeader(HttpServerRequest<I> request, HttpServerResponse<O> 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);
}
}
}