From 59cd054fe3c0df5f8931aa15b5cf55eda0418939 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 30 Sep 2012 21:18:54 +0200 Subject: [PATCH] Merge SpdyOrHttpChooser and SpdyHttpResponseStreamIdHandler into master. See #626 --- .../spdy/SpdyHttpResponseStreamIdHandler.java | 60 +++++++ .../handler/codec/spdy/SpdyOrHttpChooser.java | 156 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java create mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java new file mode 100644 index 0000000000..9a072a25d4 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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.netty.handler.codec.spdy; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageCodec; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + +/** + * {@link MessageToMessageCodec} that takes care of adding the right {@link SpdyHttpHeaders#Names#STREAM_ID} to the + * {@link HttpResponse} if one is not present. This makes it possible to just re-use plan handlers current used + * for HTTP. + */ +public class SpdyHttpResponseStreamIdHandler extends + MessageToMessageCodec { + private static final Integer NO_ID = -1; + private final Queue ids = new ConcurrentLinkedQueue(); + + public SpdyHttpResponseStreamIdHandler() { + super(new Class[] { HttpRequest.class }, new Class[] { HttpResponse.class }); + } + + @Override + public HttpResponse encode(ChannelHandlerContext ctx, HttpResponse msg) throws Exception { + boolean contains = msg.containsHeader(SpdyHttpHeaders.Names.STREAM_ID); + if (!contains) { + ids.add(NO_ID); + } else { + ids.add(SpdyHttpHeaders.getStreamId(msg)); + } + return msg; + } + + @Override + public HttpRequest decode(ChannelHandlerContext ctx, HttpRequest msg) throws Exception { + Integer id = ids.poll(); + if (id != null && id != NO_ID && !msg.containsHeader(SpdyHttpHeaders.Names.STREAM_ID)) { + SpdyHttpHeaders.setStreamId(msg, id); + } + return msg; + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java new file mode 100644 index 0000000000..a26af09a64 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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.netty.handler.codec.spdy; + +import javax.net.ssl.SSLEngine; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundByteHandler; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http.HttpChunkAggregator; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.ssl.SslHandler; + +/** + * {@link ChannelInboundByteHandler} which is responsible to setup the {@link ChannelPipeline} either for + * HTTP or SPDY. This offers an easy way for users to support both at the same time while not care to + * much about the low-level details. + * + */ +public abstract class SpdyOrHttpChooser extends ChannelHandlerAdapter implements ChannelInboundByteHandler { + + public static enum SelectedProtocol { + SpdyVersion2, + SpdyVersion3, + HttpVersion1_1, + HttpVersion1_0, + None + } + + private final int maxSpdyContentLength; + private final int maxHttpContentLength; + + public SpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) { + this.maxSpdyContentLength = maxSpdyContentLength; + this.maxHttpContentLength = maxHttpContentLength; + } + + /** + * Return the {@link SelectedProtocol} for the {@link SSLEngine}. If its not known yet implementations + * MUST return {@link SelectedProtocol#None}. + * + */ + protected abstract SelectedProtocol getProtocol(SSLEngine engine); + + @Override + public ByteBuf newInboundBuffer(ChannelHandlerContext ctx) throws Exception { + return ctx.nextInboundByteBuffer(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + initPipeline(ctx); + ctx.fireChannelActive(); + } + + @Override + public void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception { + initPipeline(ctx); + ctx.fireInboundBufferUpdated(); + } + + private void initPipeline(ChannelHandlerContext ctx) { + // Get the SslHandler from the ChannelPipeline so we can obtain the SslEngine from it. + SslHandler handler = ctx.pipeline().get(SslHandler.class); + if (handler == null) { + // SslHandler is needed by SPDY by design. + throw new IllegalStateException("SslHandler is needed for SPDY"); + } + + ChannelPipeline pipeline = ctx.pipeline(); + SelectedProtocol protocol = getProtocol(handler.getEngine()); + switch (protocol) { + case None: + // Not done with choosing the protocol, so just return here for now, + return; + case SpdyVersion2: + addSpdyHandlers(ctx, 2); + break; + case SpdyVersion3: + addSpdyHandlers(ctx, 3); + break; + case HttpVersion1_0: + case HttpVersion1_1: + addHttpHandlers(ctx); + break; + default: + throw new IllegalStateException("Unknown SelectedProtocol"); + } + // When we reached here we can remove this handler as its now clear what protocol we want to use + // from this point on. + pipeline.remove(this); + } + + /** + * Add all {@link ChannelHandler}'s that are needed for SPDY with the given version. + */ + protected void addSpdyHandlers(ChannelHandlerContext ctx, int version) { + ChannelPipeline pipeline = ctx.pipeline(); + pipeline.addLast("spdyDecoder", new SpdyFrameDecoder(version)); + pipeline.addLast("spdyEncoder", new SpdyFrameEncoder(version)); + pipeline.addLast("spdySessionHandler", new SpdySessionHandler(version, true)); + pipeline.addLast("spdyHttpEncoder", new SpdyHttpEncoder(version)); + pipeline.addLast("spdyHttpDecoder", new SpdyHttpDecoder(version, maxSpdyContentLength)); + pipeline.addLast("spdyStreamIdHandler", new SpdyHttpResponseStreamIdHandler()); + pipeline.addLast("httpRquestHandler", createHttpRequestHandlerForSpdy()); + } + + /** + * Add all {@link ChannelHandler}'s that are needed for HTTP. + */ + protected void addHttpHandlers(ChannelHandlerContext ctx) { + ChannelPipeline pipeline = ctx.pipeline(); + pipeline.addLast("httpRquestDecoder", new HttpRequestDecoder()); + pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpChunkAggregator", new HttpChunkAggregator(maxHttpContentLength)); + pipeline.addLast("httpRquestHandler", createHttpRequestHandlerForHttp()); + } + + /** + * Create the {@link ChannelInboundHandler} that is responsible for handling the {@link HttpRequest}'s + * when the {@link SelectedProtocol} was {@link SelectedProtocol#HttpVersion1_0} or + * {@link SelectedProtocol#HttpVersion1_1} + */ + protected abstract ChannelInboundHandler createHttpRequestHandlerForHttp(); + + /** + * Create the {@link ChannelInboundHandler} that is responsible for handling the {@link HttpRequest}'s + * when the {@link SelectedProtocol} was {@link SelectedProtocol#SpdyVersion2} or + * {@link SelectedProtocol#SpdyVersion3}. + * + * Bye default this method will just delecate to {@link #createHttpRequestHandlerForHttp()}, but + * sub-classes may override this to change the behaviour. + */ + protected ChannelInboundHandler createHttpRequestHandlerForSpdy() { + return createHttpRequestHandlerForHttp(); + } +}