org.dcache.http.KeepAliveHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.dcache.http.KeepAliveHandler.java

Source

/* dCache - http://www.dcache.org/
 *
 * Copyright (C) 2015 Deutsches Elektronen-Synchrotron
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.dcache.http;

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.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * Add the HTTP KeepAlive related response header when appropriate and
 * ensure the connection is terminated once advertised.
 * <p>
 * See Section 8 of RFC-2616 for more details.
 */
public class KeepAliveHandler extends ChannelDuplexHandler {
    private static final Logger LOG = LoggerFactory.getLogger(KeepAliveHandler.class);

    private boolean _hasPreviousRequest;
    private boolean _isLastRequestKeepAlive;
    private final Deque<Boolean> _inflightKeepAlive = new ArrayDeque();

    @Override
    public void channelRead(ChannelHandlerContext context, Object message) throws Exception {
        if (message instanceof HttpRequest) {
            if (_hasPreviousRequest && !_isLastRequestKeepAlive) {
                /*
                 * The client has attempted to send a further request after
                 * the previous request signaled the connection should be closed.
                 *
                 * For HTTP/1.1, this is plain broken: RFC 2616 (8.1.2)
                 *     "Once a close has been signaled, the client MUST NOT
                 *     send any more requests on that connection."
                 *
                 * For HTTP/1.0, this is undefined.
                 *
                 * For HTTP/1.1, dCache is required not to generate any further
                 * responses: RFC 2616 (8.1.2.1)
                 *     "If either the client or the server sends the close
                 *     token in the Connection header, that request becomes
                 *     the last one for the connection."
                 *
                 * Therefore, the request is simply dropped.  As soon as the
                 * server side of the TCP connection is closed, the OS will
                 * reply to any further traffic with a RST, tearing down the
                 * client side of the TCP connection.
                 */
                LOG.debug("Broken client sent request after previously asking " + "the connection be closed.");
                return;
            }

            _isLastRequestKeepAlive = HttpHeaders.isKeepAlive((HttpRequest) message);
            _inflightKeepAlive.offerLast(_isLastRequestKeepAlive);
            _hasPreviousRequest = true;
        }

        super.channelRead(context, message);
    }

    @Override
    public void write(ChannelHandlerContext context, Object message, ChannelPromise promise) throws Exception {
        boolean is100Continue = false;

        if (message instanceof HttpResponse) {
            HttpResponse response = (HttpResponse) message;
            is100Continue = response.getStatus().equals(HttpResponseStatus.CONTINUE);

            boolean keepAlive = _inflightKeepAlive.getFirst();

            /*
             * An upstream handler can request the connection be closed even if
             * the client make no such request.
             */
            if (keepAlive && !HttpHeaders.isKeepAlive(response)) {
                _inflightKeepAlive.removeFirst();
                _inflightKeepAlive.addFirst(Boolean.FALSE);
                keepAlive = false;
            }

            /* REVISIT: It is not clear from RFC-2616 whether, when the client
             * issues a request with both the "Expect: 100-continue" and the
             * "Connection: close" headers, the initial CONTINUE response and
             * the final response should both include the server's
             * "Connection: close" header, or just the initial CONTINUE, or
             * just the final response.
             *
             * We choose (somewhat arbitrarily) not to send the
             * "Connection: close" header with CONTINUE responses.
             */
            if (!is100Continue) {
                HttpHeaders.setKeepAlive(response, keepAlive);
            }
        }

        ChannelFuture writePromise = context.write(message, promise);

        /*
         * Netty (currently) requires that the message(s) for the 100-continue
         * partial response contain a LastHttpContent message; therefore, an
         * HTTP PUT request with the "Expect: 100-continue" header will
         * generate two LastHttpContent messages.
         */
        if (message instanceof LastHttpContent && !is100Continue) {
            boolean keepAlive = _inflightKeepAlive.remove();

            if (!keepAlive) {
                writePromise.addListener(ChannelFutureListener.CLOSE);
            }
        }
    }
}