Java tutorial
/* * 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; } }