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

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

There is a newer version: 0.3.18
Show newest version
/*
 * Copyright 2014 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.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.ReferenceCountUtil;
import io.reactivex.netty.metrics.Clock;
import io.reactivex.netty.metrics.MetricEventsSubject;
import io.reactivex.netty.protocol.http.UnicastContentSubject;
import io.reactivex.netty.server.ServerMetricsEvent;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * A channel handler for {@link HttpServer} to convert netty's http request/response objects to {@link HttpServer}'s
 * request/response objects. It handles the following message types:
 *
 * 

Reading Objects

*
  • {@link HttpRequest}: Converts it to {@link HttpServerRequest }
  • {@link HttpContent}: Converts it to the content of the previously generated {@link HttpServerRequest }
  • {@link FullHttpRequest}: Converts it to a {@link HttpServerRequest } with pre-populated content observable.
  • Any other object: Assumes that it is a transformed HTTP content & pass it through to the content observable.
* *

Writing Objects

*
  • {@link HttpServerResponse}: Converts it to a {@link HttpResponse}
  • {@link ByteBuf} to an {@link HttpContent}
  • Pass through any other message type.
* * @author Nitesh Kant */ public class ServerRequestResponseConverter extends ChannelDuplexHandler { public static final IOException CONN_CLOSE_BEFORE_REQUEST_COMPLETE = new IOException("Connection closed by peer before sending the entire request."); private final MetricEventsSubject> eventsSubject; private final long requestContentSubscriptionTimeoutMs; private RequestState currentRequestState; public ServerRequestResponseConverter(MetricEventsSubject> eventsSubject, long requestContentSubscriptionTimeoutMs) { this.eventsSubject = eventsSubject; this.requestContentSubscriptionTimeoutMs = requestContentSubscriptionTimeoutMs; currentRequestState = new RequestState(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Class recievedMsgClass = msg.getClass(); final RequestState stateToUse = currentRequestState; boolean isHttpRequest = false; if (HttpRequest.class.isAssignableFrom(recievedMsgClass)) { eventsSubject.onEvent(HttpServerMetricsEvent.REQUEST_HEADERS_RECEIVED); stateToUse.createRxRequest(ctx, (HttpRequest) msg); // Update the state to use. stateToUse.onProcessingStart(Clock.newStartTimeMillis()); super.channelRead(ctx, stateToUse.rxRequest); isHttpRequest = true; } if (HttpContent.class.isAssignableFrom(recievedMsgClass)) {// This will be executed if the incoming message is a FullHttpRequest or only HttpContent. ByteBuf content = ((ByteBufHolder) msg).content(); eventsSubject.onEvent(HttpServerMetricsEvent.REQUEST_CONTENT_RECEIVED); invokeContentOnNext(content, stateToUse.contentSubject); if (LastHttpContent.class.isAssignableFrom(recievedMsgClass)) { stateToUse.onRequestComplete(); currentRequestState = new RequestState(); // Reset the current state for the next request to arrive on this connection } } else if(!isHttpRequest) { // If it is not HttpContent and not HttpRequest then it is a custom user object that is just sent as content. invokeContentOnNext(msg, stateToUse.contentSubject); } } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { Class recievedMsgClass = msg.getClass(); final long startTimeMillis = Clock.newStartTimeMillis(); if (HttpServerResponse.class.isAssignableFrom(recievedMsgClass)) { @SuppressWarnings("rawtypes") HttpServerResponse rxResponse = (HttpServerResponse) msg; eventsSubject.onEvent(HttpServerMetricsEvent.RESPONSE_HEADERS_WRITE_START); addWriteCompleteEvents(promise, startTimeMillis, HttpServerMetricsEvent.RESPONSE_HEADERS_WRITE_SUCCESS, HttpServerMetricsEvent.RESPONSE_HEADERS_WRITE_FAILED); super.write(ctx, rxResponse.getNettyResponse(), promise); } else if (ByteBuf.class.isAssignableFrom(recievedMsgClass)) { eventsSubject.onEvent(HttpServerMetricsEvent.RESPONSE_CONTENT_WRITE_START); addWriteCompleteEvents(promise, startTimeMillis, HttpServerMetricsEvent.RESPONSE_CONTENT_WRITE_SUCCESS, HttpServerMetricsEvent.RESPONSE_CONTENT_WRITE_FAILED); HttpContent content = new DefaultHttpContent((ByteBuf) msg); super.write(ctx, content, promise); } else { super.write(ctx, msg, promise); // pass through, since we do not understand this message. } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { super.channelReadComplete(ctx); ctx.pipeline().flush(); // If there is nothing to flush, this is a short-circuit in netty. } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { currentRequestState.onConnectionClose(); super.channelInactive(ctx); } private void addWriteCompleteEvents(ChannelPromise promise, final long startTimeMillis, final HttpServerMetricsEvent successEvent, final HttpServerMetricsEvent failureEvent) { promise.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { eventsSubject.onEvent(successEvent, Clock.onEndMillis(startTimeMillis)); } else { eventsSubject.onEvent(failureEvent, Clock.onEndMillis(startTimeMillis), future.cause()); } } }); } @SuppressWarnings({"unchecked", "rawtypes"}) private static void invokeContentOnNext(Object nextObject, UnicastContentSubject contentSubject) { try { contentSubject.onNext(nextObject); } catch (ClassCastException e) { contentSubject.onError(e); } finally { ReferenceCountUtil.release(nextObject); } } private final class RequestState { @SuppressWarnings("rawtypes") private HttpServerRequest rxRequest; @SuppressWarnings("rawtypes") private UnicastContentSubject contentSubject; private boolean isReadingRequest; @SuppressWarnings({"rawtypes", "unchecked"}) private void createRxRequest(ChannelHandlerContext ctx, HttpRequest httpRequest) { contentSubject = UnicastContentSubject.create(requestContentSubscriptionTimeoutMs, TimeUnit.MILLISECONDS); rxRequest = new HttpServerRequest(ctx.channel(), httpRequest, contentSubject); } private void onProcessingStart(long startTimeMillis) { rxRequest.onProcessingStart(startTimeMillis); isReadingRequest = true; } private void onRequestComplete() { isReadingRequest = false; long durationInMs = rxRequest.onProcessingEnd(); eventsSubject.onEvent(HttpServerMetricsEvent.REQUEST_RECEIVE_COMPLETE, durationInMs); contentSubject.onCompleted(); } private void onConnectionClose() { if (isReadingRequest) { contentSubject.onError(CONN_CLOSE_BEFORE_REQUEST_COMPLETE); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy