reactor.ipc.netty.http.server.HttpServerHandler.java Source code

Java tutorial

Introduction

Here is the source code for reactor.ipc.netty.http.server.HttpServerHandler.java

Source

/*
 * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved.
 *
 * 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 reactor.ipc.netty.http.server;

import java.util.Queue;

import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpStatusClass;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.ReferenceCountUtil;
import reactor.core.Exceptions;
import reactor.ipc.netty.NettyPipeline;
import reactor.ipc.netty.channel.ContextHandler;
import reactor.util.concurrent.QueueSupplier;

import static io.netty.handler.codec.http.HttpUtil.*;

/**
 * Replace {@link io.netty.handler.codec.http.HttpServerKeepAliveHandler} with extra
 * handler management.
 */
final class HttpServerHandler extends ChannelDuplexHandler implements Runnable {

    static final String MULTIPART_PREFIX = "multipart";

    final ContextHandler<?> parentContext;

    boolean persistentConnection = true;
    // Track pending responses to support client pipelining: https://tools.ietf.org/html/rfc7230#section-6.3.2
    int pendingResponses;

    Queue<Object> pipelined;

    ChannelHandlerContext ctx;

    boolean overflow;
    boolean mustRecycleEncoder;

    HttpServerHandler(ContextHandler<?> parentContext) {
        this.parentContext = parentContext;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        this.ctx = ctx;
        if (HttpServerOperations.log.isDebugEnabled()) {
            HttpServerOperations.log.debug("New http connection, requesting read");
        }
        ctx.read();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // read message and track if it was keepAlive
        if (msg instanceof HttpRequest) {
            final HttpRequest request = (HttpRequest) msg;
            if (persistentConnection) {
                pendingResponses += 1;
                persistentConnection = isKeepAlive(request);
            } else {
                if (HttpServerOperations.log.isDebugEnabled()) {
                    HttpServerOperations.log.debug(
                            "dropping pipelined HTTP request, " + "previous response requested connection close");
                }
                ReferenceCountUtil.release(msg);
                return;
            }
            if (overflow || pendingResponses > 1) {
                if (HttpServerOperations.log.isDebugEnabled()) {
                    HttpServerOperations.log.debug(
                            "buffering pipelined HTTP request, " + "pending response count: {}, queue: {}",
                            pendingResponses, pipelined != null ? pipelined.size() : 0);
                }
                overflow = true;
                doPipeline(ctx, msg);
                return;
            } else {
                overflow = false;
                parentContext.createOperations(ctx.channel(), msg);

                if (!(msg instanceof FullHttpRequest)) {
                    return;
                }
            }
        } else if (overflow) {
            if (HttpServerOperations.log.isDebugEnabled()) {
                HttpServerOperations.log.debug(
                        "buffering pipelined HTTP content, " + "pending response count: {}, pending pipeline:{}",
                        pendingResponses, pipelined != null ? pipelined.size() : 0);
            }
            doPipeline(ctx, msg);
            return;
        }
        ctx.fireChannelRead(msg);
    }

    void doPipeline(ChannelHandlerContext ctx, Object msg) {
        if (pipelined == null) {
            pipelined = QueueSupplier.unbounded().get();
        }
        if (!pipelined.offer(msg)) {
            ctx.fireExceptionCaught(Exceptions.failWithOverflow());
        }
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        // modify message on way out to add headers if needed
        if (msg instanceof HttpResponse) {
            final HttpResponse response = (HttpResponse) msg;
            trackResponse(response);
            // Assume the response writer knows if they can persist or not and sets isKeepAlive on the response
            if (!isKeepAlive(response) || !isSelfDefinedMessageLength(response)) {
                // No longer keep alive as the client can't tell when the message is done unless we close connection
                pendingResponses = 0;
                persistentConnection = false;
            }
            // Server might think it can keep connection alive, but we should fix response header if we know better
            if (!shouldKeepAlive()) {
                setKeepAlive(response, false);
            }
        }
        if (msg instanceof LastHttpContent) {
            if (!shouldKeepAlive()) {
                if (HttpServerOperations.log.isDebugEnabled()) {
                    HttpServerOperations.log.debug(
                            "Detected non persistent http " + "connection," + " " + "preparing to close",
                            pendingResponses);
                }
                promise.addListener(ChannelFutureListener.CLOSE);
            } else if (mustRecycleEncoder) {
                mustRecycleEncoder = false;
                pendingResponses -= 1;
            }
        }
        ctx.write(msg, promise);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt == NettyPipeline.handlerTerminatedEvent()) {
            if (mustRecycleEncoder) {
                mustRecycleEncoder = false;
                pendingResponses -= 1;

                ctx.pipeline().replace(NettyPipeline.HttpEncoder, NettyPipeline.HttpEncoder,
                        new HttpResponseEncoder());
            }

            if (pipelined != null && !pipelined.isEmpty()) {
                if (HttpServerOperations.log.isDebugEnabled()) {
                    HttpServerOperations.log.debug("draining next pipelined " + "request,"
                            + " pending response count: {}, queued: " + "{}", pendingResponses, pipelined.size());
                }
                ctx.executor().execute(this);
            } else {
                ctx.read();
            }
        }

        ctx.fireUserEventTriggered(evt);
    }

    void trackResponse(HttpResponse response) {
        mustRecycleEncoder = !isInformational(response);
    }

    @Override
    public void run() {
        Object next;
        boolean nextRequest = false;
        while ((next = pipelined.peek()) != null) {
            if (next instanceof HttpRequest) {
                if (nextRequest || !persistentConnection) {
                    return;
                }
                nextRequest = true;
                parentContext.createOperations(ctx.channel(), next);

                if (!(next instanceof FullHttpRequest)) {
                    pipelined.poll();
                    continue;
                }
            }
            ctx.fireChannelRead(pipelined.poll());
        }
        overflow = false;
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        discard();
    }

    final void discard() {
        if (pipelined != null && !pipelined.isEmpty()) {
            Object o;
            while ((o = pipelined.poll()) != null) {
                ReferenceCountUtil.release(o);
            }

        }
    }

    boolean shouldKeepAlive() {
        return pendingResponses != 0 || persistentConnection;
    }

    /**
     * Keep-alive only works if the client can detect when the message has ended without
     * relying on the connection being closed.
     * <p>
     * <ul> <li>See <a href="https://tools.ietf.org/html/rfc7230#section-6.3"/></li>
     * <li>See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3"/></li> </ul>
     *
     * @param response The HttpResponse to check
     *
     * @return true if the response has a self defined message length.
     */
    static boolean isSelfDefinedMessageLength(HttpResponse response) {
        return isContentLengthSet(response) || isTransferEncodingChunked(response) || isMultipart(response)
                || isInformational(response);
    }

    static boolean isInformational(HttpResponse response) {
        return response.status().codeClass() == HttpStatusClass.INFORMATIONAL;
    }

    static boolean isMultipart(HttpResponse response) {
        String contentType = response.headers().get(HttpHeaderNames.CONTENT_TYPE);
        return contentType != null
                && contentType.regionMatches(true, 0, MULTIPART_PREFIX, 0, MULTIPART_PREFIX.length());
    }
}