reactor.io.net.impl.netty.http.NettyHttpChannel.java Source code

Java tutorial

Introduction

Here is the source code for reactor.io.net.impl.netty.http.NettyHttpChannel.java

Source

/*
 * Copyright (c) 2011-2015 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.io.net.impl.netty.http;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.*;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import reactor.core.support.Assert;
import reactor.io.net.http.HttpChannel;
import reactor.io.net.http.model.*;
import reactor.io.net.impl.netty.NettyChannelStream;

import java.net.InetSocketAddress;

import static io.netty.handler.codec.http.HttpHeaders.is100ContinueExpected;

/**
 * @author Sebastien Deleuze
 * @author Stephane Maldini
 */
public class NettyHttpChannel<IN, OUT> extends HttpChannel<IN, OUT> {

    private static final FullHttpResponse CONTINUE = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
            HttpResponseStatus.CONTINUE, Unpooled.EMPTY_BUFFER);

    private final NettyChannelStream<IN, OUT> tcpStream;
    private final HttpRequest nettyRequest;
    private final NettyHttpHeaders headers;
    private HttpResponse nettyResponse;
    private NettyHttpResponseHeaders responseHeaders;

    public NettyHttpChannel(NettyChannelStream<IN, OUT> tcpStream, HttpRequest request) {
        super(tcpStream.getEnvironment(), tcpStream.getCapacity(), tcpStream.getDispatcher());
        this.tcpStream = tcpStream;
        this.nettyRequest = request;
        this.nettyResponse = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
        this.headers = new NettyHttpHeaders(request);
        this.responseHeaders = new NettyHttpResponseHeaders(this.nettyResponse);

        responseHeader(ResponseHeaders.TRANSFER_ENCODING, "chunked");
    }

    @Override
    protected void doSubscribeWriter(Publisher<? extends OUT> writer, Subscriber<? super Void> postWriter) {
        tcpStream.doSubscribeWriter(writer, postWriter);
    }

    @Override
    protected void doDecoded(IN in) {
        tcpStream.doDecoded(in);
    }

    @Override
    public void subscribe(final Subscriber<? super IN> subscriber) {
        // Handle the 'Expect: 100-continue' header if necessary.
        // TODO: Respond with 413 Request Entity Too Large
        //   and discard the traffic or close the connection.
        //       No need to notify the upstream handlers - just log.
        //       If decoding a response, just throw an exception.
        if (is100ContinueExpected(nettyRequest)) {
            tcpStream.delegate().writeAndFlush(CONTINUE).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (!future.isSuccess()) {
                        subscriber.onError(future.cause());
                    } else {
                        tcpStream.subscribe(subscriber);
                    }
                }
            });
        } else {
            tcpStream.subscribe(subscriber);
        }
    }

    // REQUEST contract

    @Override
    public Protocol protocol() {
        HttpVersion version = this.nettyRequest.getProtocolVersion();
        if (version.equals(HttpVersion.HTTP_1_0)) {
            return Protocol.HTTP_1_0;
        } else if (version.equals(HttpVersion.HTTP_1_1)) {
            return Protocol.HTTP_1_1;
        }
        throw new IllegalStateException(version.protocolName() + " not supported");
    }

    @Override
    protected void doHeader(String name, String value) {
        this.headers.set(name, value);
    }

    @Override
    protected void doAddHeader(String name, String value) {
        this.headers.add(name, value);
    }

    @Override
    public String uri() {
        return this.nettyRequest.getUri();
    }

    @Override
    public Method method() {
        return new Method(this.nettyRequest.getMethod().name());
    }

    @Override
    public NettyHttpHeaders headers() {
        return this.headers;
    }

    public HttpRequest getNettyRequest() {
        return nettyRequest;
    }

    // RESPONSE contract

    @Override
    public Status responseStatus() {
        return Status.valueOf(this.nettyResponse.getStatus().code());
    }

    @Override
    public void doResponseStatus(Status status) {
        this.nettyResponse.setStatus(HttpResponseStatus.valueOf(status.getCode()));
    }

    @Override
    public Transfer transfer() {
        if ("chunked".equals(this.headers.get(ResponseHeaders.TRANSFER_ENCODING))) {
            Assert.isTrue(Protocol.HTTP_1_1.equals(protocol()));
            return Transfer.CHUNKED;
        } else if (this.headers.get(ResponseHeaders.TRANSFER_ENCODING) == null) {
            return Transfer.NON_CHUNKED;
        }
        throw new IllegalStateException("Can't determine a valide transfer based on headers and protocol");
    }

    @Override
    public HttpChannel<IN, OUT> transfer(Transfer transfer) {
        switch (transfer) {
        case EVENT_STREAM:
            this.responseHeader(ResponseHeaders.CONTENT_TYPE, "text/event-stream");
        case CHUNKED:
            Assert.isTrue(Protocol.HTTP_1_1.equals(protocol()));
            this.responseHeader(ResponseHeaders.TRANSFER_ENCODING, "chunked");
            break;
        case NON_CHUNKED:
            this.responseHeaders().remove(ResponseHeaders.TRANSFER_ENCODING);
        }
        return this;
    }

    @Override
    public NettyHttpResponseHeaders responseHeaders() {
        return this.responseHeaders;
    }

    @Override
    protected void doResponseHeader(String name, String value) {
        this.responseHeaders.set(name, value);
    }

    @Override
    protected void doAddResponseHeader(String name, String value) {
        this.responseHeaders.add(name, value);
    }

    public HttpResponse getNettyResponse() {
        return nettyResponse;
    }

    @Override
    @SuppressWarnings("unchecked")
    public SocketChannel delegate() {
        return (SocketChannel) tcpStream.delegate();
    }

    @Override
    public InetSocketAddress remoteAddress() {
        return tcpStream.remoteAddress();
    }

    @Override
    public ConsumerSpec on() {
        return tcpStream.on();
    }

    void setNettyResponse(HttpResponse nettyResponse) {
        this.nettyResponse = nettyResponse;
    }

    boolean checkHeader() {
        return HEADERS_SENT.compareAndSet(this, 0, 1);
    }

    @Override
    public boolean isKeepAlive() {
        return headers.isKeepAlive();
    }

    @Override
    public HttpChannel<IN, OUT> keepAlive(boolean keepAlive) {
        responseHeaders.keepAlive(keepAlive);
        return this;
    }
}