From deb1c1b03860a868821705f295d1462885130e4d Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 2 Feb 2010 04:27:33 +0000 Subject: [PATCH] Resolved issue: NETTY-261 Write a port unification example --- .../PortUnificationServer.java | 58 +++++++ .../PortUnificationServerHandler.java | 161 ++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java create mode 100644 src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java diff --git a/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java new file mode 100644 index 0000000000..0c29ec0f47 --- /dev/null +++ b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.portunification; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + +/** + * Serves two protocols (HTTP and Factorial) using only one port, enabling + * either SSL or GZIP dynamically on demand. + *

+ * Because SSL and GZIP are enabled on demand, 5 combinations per protocol + * are possible: none, SSL only, GZIP only, SSL + GZIP, and GZIP + SSL. + * + * @author The Netty Project + * @author Trustin Lee + * + * @version $Rev$, $Date$ + */ +public class PortUnificationServer { + + public static void main(String[] args) throws Exception { + // Configure the server. + ServerBootstrap bootstrap = new ServerBootstrap( + new NioServerSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); + + // Set up the event pipeline factory. + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(new PortUnificationServerHandler()); + } + }); + + // Bind and start to accept incoming connections. + bootstrap.bind(new InetSocketAddress(8080)); + } +} diff --git a/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java new file mode 100644 index 0000000000..e0610cd21b --- /dev/null +++ b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java @@ -0,0 +1,161 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 org.jboss.netty.example.portunification; + +import javax.net.ssl.SSLEngine; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.example.factorial.BigIntegerDecoder; +import org.jboss.netty.example.factorial.FactorialServerHandler; +import org.jboss.netty.example.factorial.NumberEncoder; +import org.jboss.netty.example.http.snoop.HttpRequestHandler; +import org.jboss.netty.example.securechat.SecureChatSslContextFactory; +import org.jboss.netty.handler.codec.compression.ZlibDecoder; +import org.jboss.netty.handler.codec.compression.ZlibEncoder; +import org.jboss.netty.handler.codec.compression.ZlibWrapper; +import org.jboss.netty.handler.codec.frame.FrameDecoder; +import org.jboss.netty.handler.codec.http.HttpContentCompressor; +import org.jboss.netty.handler.codec.http.HttpRequestDecoder; +import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.jboss.netty.handler.ssl.SslHandler; + +/** + * + * + * @author The Netty Project + * @author Trustin Lee + * + * @version $Rev$, $Date$ + */ +public class PortUnificationServerHandler extends FrameDecoder { + + private final boolean detectSsl; + private final boolean detectGzip; + + public PortUnificationServerHandler() { + this(true, true); + } + + private PortUnificationServerHandler(boolean detectSsl, boolean detectGzip) { + this.detectSsl = detectSsl; + this.detectGzip = detectGzip; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + + // Will use the first two bytes to detect a protocol. + if (buffer.readableBytes() < 2) { + return null; + } + + final int magic1 = buffer.getUnsignedByte(buffer.readerIndex()); + final int magic2 = buffer.getUnsignedByte(buffer.readerIndex() + 1); + + if (isSsl(magic1)) { + enableSsl(ctx); + } else if (isGzip(magic1, magic2)) { + enableGzip(ctx); + } else if (isHttp(magic1, magic2)) { + switchToHttp(ctx); + } else if (isFactorial(magic1)) { + switchToFactorial(ctx); + } else { + // Unknown protocol + ctx.getChannel().close(); + return null; + } + + // Forward the current read buffer as is to the new handlers. + return buffer.readBytes(buffer.readableBytes()); + } + + private boolean isSsl(int magic1) { + if (detectSsl) { + switch (magic1) { + case 20: case 21: case 22: case 23: case 255: + return true; + default: + return magic1 >= 128; + } + } + return false; + } + + private boolean isGzip(int magic1, int magic2) { + if (detectGzip) { + return magic1 == 31 && magic2 == 139; + } + return false; + } + + private boolean isHttp(int magic1, int magic2) { + return + magic1 == 'G' && magic2 == 'E' || // GET + magic1 == 'P' && magic2 == 'O' || // POST + magic1 == 'P' && magic2 == 'U' || // PUT + magic1 == 'H' && magic2 == 'E' || // HEAD + magic1 == 'O' && magic2 == 'P' || // OPTIONS + magic1 == 'P' && magic2 == 'A' || // PATCH + magic1 == 'D' && magic2 == 'E' || // DELETE + magic1 == 'T' && magic2 == 'R' || // TRACE + magic1 == 'C' && magic2 == 'O'; // CONNECT + } + + private boolean isFactorial(int magic1) { + return magic1 == 'F'; + } + + private void enableSsl(ChannelHandlerContext ctx) { + ChannelPipeline p = ctx.getPipeline(); + + SSLEngine engine = + SecureChatSslContextFactory.getServerContext().createSSLEngine(); + engine.setUseClientMode(false); + + p.addLast("ssl", new SslHandler(engine)); + p.addLast("unificationA", new PortUnificationServerHandler(false, detectGzip)); + p.remove(this); + } + + private void enableGzip(ChannelHandlerContext ctx) { + ChannelPipeline p = ctx.getPipeline(); + p.addLast("gzipdeflater", new ZlibEncoder(ZlibWrapper.GZIP)); + p.addLast("gzipinflater", new ZlibDecoder(ZlibWrapper.GZIP)); + p.addLast("unificationB", new PortUnificationServerHandler(detectSsl, false)); + p.remove(this); + } + + private void switchToHttp(ChannelHandlerContext ctx) { + ChannelPipeline p = ctx.getPipeline(); + p.addLast("decoder", new HttpRequestDecoder()); + p.addLast("encoder", new HttpResponseEncoder()); + p.addLast("deflater", new HttpContentCompressor()); + p.addLast("handler", new HttpRequestHandler()); + p.remove(this); + } + + private void switchToFactorial(ChannelHandlerContext ctx) { + ChannelPipeline p = ctx.getPipeline(); + p.addLast("decoder", new BigIntegerDecoder()); + p.addLast("encoder", new NumberEncoder()); + p.addLast("handler", new FactorialServerHandler()); + p.remove(this); + } +}