Let Http2ServerUpgradeCodec support Http2FrameCodec
Motivation: Http2ServerUpgradeCodec should support Http2FrameCodec. Modifications: - Add support for Http2FrameCodec - Add example that uses Http2FrameCodec Result: More flexible use of Http2ServerUpgradeCodec
This commit is contained in:
parent
0afe4e0964
commit
dbd82e07b1
@ -363,7 +363,7 @@ public class Http2FrameCodec extends ChannelDuplexHandler {
|
||||
dataFrame.streamId(streamId);
|
||||
ctx.fireChannelRead(dataFrame);
|
||||
|
||||
// We return the bytes in bytesConsumed() once the stream channel consumed the bytes.
|
||||
// We return the bytes in consumeBytes() once the stream channel consumed the bytes.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http2;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
@ -95,6 +96,25 @@ public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.Upgrade
|
||||
this(handlerName, http2Codec.frameCodec().connectionHandler(), http2Codec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the codec using a default name for the connection handler when adding to the
|
||||
* pipeline.
|
||||
*
|
||||
* @param http2Codec the HTTP/2 frame handler.
|
||||
* @param handlers the handlers that will handle the {@link Http2Frame}s.
|
||||
*/
|
||||
public Http2ServerUpgradeCodec(final Http2FrameCodec http2Codec, final ChannelHandler... handlers) {
|
||||
this(null, http2Codec.connectionHandler(), new ChannelHandlerAdapter() {
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.pipeline().addLast(http2Codec);
|
||||
ctx.pipeline().addLast(handlers);
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Http2ServerUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler,
|
||||
ChannelHandler upgradeToHandler) {
|
||||
this.handlerName = handlerName;
|
||||
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2016 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.example.http2.helloworld.frame.server;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2WindowUpdateFrame;
|
||||
import io.netty.handler.codec.http2.Http2DataFrame;
|
||||
import io.netty.handler.codec.http2.Http2Headers;
|
||||
import io.netty.handler.codec.http2.Http2HeadersFrame;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import static io.netty.buffer.Unpooled.copiedBuffer;
|
||||
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
|
||||
/**
|
||||
* A simple handler that responds with the message "Hello World!".
|
||||
*
|
||||
* <p>This example is making use of the "frame codec" http2 API. This API is very experimental and incomplete.
|
||||
*/
|
||||
@Sharable
|
||||
public class HelloWorldHttp2Handler extends ChannelDuplexHandler {
|
||||
|
||||
static final ByteBuf RESPONSE_BYTES = unreleasableBuffer(copiedBuffer("Hello World", CharsetUtil.UTF_8));
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
super.exceptionCaught(ctx, cause);
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof Http2HeadersFrame) {
|
||||
onHeadersRead(ctx, (Http2HeadersFrame) msg);
|
||||
} else if (msg instanceof Http2DataFrame) {
|
||||
onDataRead(ctx, (Http2DataFrame) msg);
|
||||
} else {
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If receive a frame with end-of-stream set, send a pre-canned response.
|
||||
*/
|
||||
public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data) throws Exception {
|
||||
int consumed = data.padding() + data.content().readableBytes();
|
||||
int streamId = data.streamId();
|
||||
|
||||
if (data.isEndStream()) {
|
||||
sendResponse(ctx, streamId, data.content());
|
||||
} else {
|
||||
// We do not send back the response to the remote-peer, so we need to release it.
|
||||
data.release();
|
||||
}
|
||||
|
||||
// Update the flowcontroller
|
||||
ctx.write(new DefaultHttp2WindowUpdateFrame(consumed).streamId(streamId));
|
||||
}
|
||||
|
||||
/**
|
||||
* If receive a frame with end-of-stream set, send a pre-canned response.
|
||||
*/
|
||||
public void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headers)
|
||||
throws Exception {
|
||||
if (headers.isEndStream()) {
|
||||
ByteBuf content = ctx.alloc().buffer();
|
||||
content.writeBytes(RESPONSE_BYTES.duplicate());
|
||||
ByteBufUtil.writeAscii(content, " - via HTTP/2");
|
||||
sendResponse(ctx, headers.streamId(), content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a "Hello World" DATA frame to the client.
|
||||
*/
|
||||
private static void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
|
||||
// Send a frame for the response status
|
||||
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
|
||||
ctx.write(new DefaultHttp2HeadersFrame(headers).streamId(streamId));
|
||||
ctx.writeAndFlush(new DefaultHttp2DataFrame(payload, true).streamId(streamId));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2016 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.example.http2.helloworld.frame.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.example.http2.helloworld.server.HelloWorldHttp1Handler;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http2.Http2FrameCodec;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
|
||||
|
||||
/**
|
||||
* Negotiates with the browser if HTTP2 or HTTP is going to be used. Once decided, the Netty
|
||||
* pipeline is setup with the correct handlers for the selected protocol.
|
||||
*/
|
||||
public class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler {
|
||||
|
||||
private static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
protected Http2OrHttpHandler() {
|
||||
super(ApplicationProtocolNames.HTTP_1_1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
|
||||
if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
|
||||
ctx.pipeline().addLast(new Http2FrameCodec(true), new HelloWorldHttp2Handler());
|
||||
return;
|
||||
}
|
||||
|
||||
if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
|
||||
ctx.pipeline().addLast(new HttpServerCodec(),
|
||||
new HttpObjectAggregator(MAX_CONTENT_LENGTH),
|
||||
new HelloWorldHttp1Handler("ALPN Negotiation"));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("unknown protocol: " + protocol);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2016 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.example.http2.helloworld.frame.server;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
|
||||
/**
|
||||
* A HTTP/2 Server that responds to requests with a Hello World. Once started, you can test the
|
||||
* server with the example client.
|
||||
*
|
||||
* <p>This example is making use of the "multiplexing" http2 API, where streams are mapped to child
|
||||
* Channels. This API is very experimental and incomplete.
|
||||
*/
|
||||
public final class Http2Server {
|
||||
|
||||
static final boolean SSL = System.getProperty("ssl") != null;
|
||||
|
||||
static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080"));
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Configure SSL.
|
||||
final SslContext sslCtx;
|
||||
if (SSL) {
|
||||
SslProvider provider = OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK;
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(provider)
|
||||
/* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification.
|
||||
* Please refer to the HTTP/2 specification for cipher requirements. */
|
||||
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
|
||||
.applicationProtocolConfig(new ApplicationProtocolConfig(
|
||||
Protocol.ALPN,
|
||||
// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectorFailureBehavior.NO_ADVERTISE,
|
||||
// ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectedListenerFailureBehavior.ACCEPT,
|
||||
ApplicationProtocolNames.HTTP_2,
|
||||
ApplicationProtocolNames.HTTP_1_1))
|
||||
.build();
|
||||
} else {
|
||||
sslCtx = null;
|
||||
}
|
||||
// Configure the server.
|
||||
EventLoopGroup group = new NioEventLoopGroup();
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
b.group(group)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new Http2ServerInitializer(sslCtx));
|
||||
|
||||
Channel ch = b.bind(PORT).sync().channel();
|
||||
|
||||
System.err.println("Open your HTTP/2-enabled web browser and navigate to " +
|
||||
(SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');
|
||||
|
||||
ch.closeFuture().sync();
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2016 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.example.http2.helloworld.frame.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.example.http2.helloworld.server.HelloWorldHttp1Handler;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.HttpServerUpgradeHandler;
|
||||
import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeCodec;
|
||||
import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeCodecFactory;
|
||||
import io.netty.handler.codec.http2.Http2CodecUtil;
|
||||
import io.netty.handler.codec.http2.Http2FrameCodec;
|
||||
import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
/**
|
||||
* Sets up the Netty pipeline for the example server. Depending on the endpoint config, sets up the
|
||||
* pipeline for NPN or cleartext HTTP upgrade to HTTP/2.
|
||||
*/
|
||||
public class Http2ServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private static final UpgradeCodecFactory upgradeCodecFactory = new UpgradeCodecFactory() {
|
||||
@Override
|
||||
public UpgradeCodec newUpgradeCodec(CharSequence protocol) {
|
||||
if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
|
||||
return new Http2ServerUpgradeCodec(new Http2FrameCodec(true), new HelloWorldHttp2Handler());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final SslContext sslCtx;
|
||||
private final int maxHttpContentLength;
|
||||
|
||||
public Http2ServerInitializer(SslContext sslCtx) {
|
||||
this(sslCtx, 16 * 1024);
|
||||
}
|
||||
|
||||
public Http2ServerInitializer(SslContext sslCtx, int maxHttpContentLength) {
|
||||
if (maxHttpContentLength < 0) {
|
||||
throw new IllegalArgumentException("maxHttpContentLength (expected >= 0): " + maxHttpContentLength);
|
||||
}
|
||||
this.sslCtx = sslCtx;
|
||||
this.maxHttpContentLength = maxHttpContentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) {
|
||||
if (sslCtx != null) {
|
||||
configureSsl(ch);
|
||||
} else {
|
||||
configureClearText(ch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the pipeline for TLS NPN negotiation to HTTP/2.
|
||||
*/
|
||||
private void configureSsl(SocketChannel ch) {
|
||||
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new Http2OrHttpHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the pipeline for a cleartext upgrade from HTTP to HTTP/2.0
|
||||
*/
|
||||
private void configureClearText(SocketChannel ch) {
|
||||
final ChannelPipeline p = ch.pipeline();
|
||||
final HttpServerCodec sourceCodec = new HttpServerCodec();
|
||||
|
||||
p.addLast(sourceCodec);
|
||||
p.addLast(new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory));
|
||||
p.addLast(new SimpleChannelInboundHandler<HttpMessage>() {
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, HttpMessage msg) throws Exception {
|
||||
// If this handler is hit then no upgrade has been attempted and the client is just talking HTTP.
|
||||
System.err.println("Directly talking: " + msg.protocolVersion() + " (no upgrade was attempted)");
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
ChannelHandlerContext thisCtx = pipeline.context(this);
|
||||
pipeline.addAfter(thisCtx.name(), null, new HelloWorldHttp1Handler("Direct. No Upgrade Attempted."));
|
||||
pipeline.replace(this, null, new HttpObjectAggregator(maxHttpContentLength));
|
||||
ctx.fireChannelRead(ReferenceCountUtil.retain(msg));
|
||||
}
|
||||
});
|
||||
|
||||
p.addLast(new UserEventLogger());
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that logs any User Events triggered on this channel.
|
||||
*/
|
||||
private static class UserEventLogger extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
System.out.println("User Event Triggered: " + evt);
|
||||
ctx.fireUserEventTriggered(evt);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user