Java tutorial
/* * Copyright 2015 the original author or authors. * * 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 com.heliosapm.tsdblite.handlers; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import javax.management.ObjectName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.heliosapm.tsdblite.Constants; import com.heliosapm.tsdblite.Server; import com.heliosapm.tsdblite.handlers.http.HttpStaticFileServerHandler; import com.heliosapm.tsdblite.handlers.http.HttpSwitch; import com.heliosapm.tsdblite.handlers.text.StringArrayTraceDecoder; import com.heliosapm.tsdblite.handlers.text.WordSplitter; import com.heliosapm.tsdblite.jmx.ManagedDefaultExecutorServiceFactory; import com.heliosapm.utils.config.ConfigurationHelper; import com.heliosapm.utils.jmx.JMXHelper; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.compression.ZlibWrapper; import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.util.concurrent.DefaultEventExecutorGroup; /** * <p>Title: ProtocolSwitch</p> * <p>Description: Channel handler to sniff out the first few received bytes of a new connection and * modify the pipeline to handle the discovered protocol accordingly. This is copied largely from the * <a href="http://netty.io/5.0/xref/io/netty/example/portunification/PortUnificationServerHandler.html">PortUnification</a> example from Netty.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>com.heliosapm.tsdblite.handlers.ProtocolSwitch</code></p> */ public class ProtocolSwitch extends ByteToMessageDecoder { /** Indicates if we should be checking for GZip */ private final boolean detectGzip; /** Instance logger */ protected final Logger log = LoggerFactory.getLogger(getClass()); /** The netty channel group thread pool */ protected static final ExecutorService eventPool; /** The event executor */ protected static final DefaultEventExecutorGroup eventExecutorGroup; private static final StringEncoder PLAINTEXT_ENCODER = new StringEncoder(); private static final WordSplitter PLAINTEXT_DECODER = new WordSplitter(); private static final StringArrayTraceDecoder TRACE_DECODER = new StringArrayTraceDecoder(); private final HttpSwitch HTTP_SWITCH = new HttpSwitch(eventExecutorGroup); /** The child channel logging handler */ @SuppressWarnings("unused") private static final LoggingHandler loggingHandler = new LoggingHandler(ProtocolSwitch.class, LogLevel.INFO); /** The event pool JMX ObjectName */ public static final ObjectName EVENT_POOL_ON = JMXHelper .objectName(Server.class.getPackage().getName() + ":service=ForkJoinPool,name=EventPool"); static { final int eventThreads = ConfigurationHelper.getIntSystemThenEnvProperty(Constants.CONF_NETTY_EVENT_THREADS, Constants.DEFAULT_NETTY_EVENT_THREADS); eventPool = new ManagedDefaultExecutorServiceFactory("eventPool").newExecutorService(eventThreads); eventExecutorGroup = new DefaultEventExecutorGroup(24, new ThreadFactory() { final AtomicInteger serial = new AtomicInteger(); @Override public Thread newThread(final Runnable r) { final Thread t = new Thread(r, "EventPoolThread#" + serial.incrementAndGet()); t.setDaemon(true); return t; } }); // eventThreads, eventPool HttpStaticFileServerHandler.getInstance(); // ManagedForkJoinPool.register(eventPool, EVENT_POOL_ON); } /** * Creates a new ProtocolSwitch * @param detectGzip true to enable gzip detection, false otherwise */ public ProtocolSwitch(final boolean detectGzip) { this.detectGzip = detectGzip; } /** * Creates a new ProtocolSwitch with gzip detection turned on */ public ProtocolSwitch() { this(true); } /** * {@inheritDoc} * @see io.netty.handler.codec.ByteToMessageDecoder#decode(io.netty.channel.ChannelHandlerContext, io.netty.buffer.ByteBuf, java.util.List) */ @Override protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) throws Exception { // Will use the first five bytes to detect a protocol. if (in.readableBytes() < 5) { log.info("No ProtocolSwitch. Bytes: {}", in.readableBytes()); return; } final int magic1 = in.getUnsignedByte(in.readerIndex()); final int magic2 = in.getUnsignedByte(in.readerIndex() + 1); if (detectGzip && isGzip(magic1, magic2)) { enableGzip(ctx); log.info("Enabled GZip on channel [{}]", ctx.channel().id().asShortText()); } else if (isHttp(magic1, magic2)) { switchToHttp(ctx); log.info("Switched to HTTP on channel [{}]", ctx.channel().id().asShortText()); } else if (isText(magic1, magic2)) { switchToPlainText(ctx); log.info("Switched to PlainText on channel [{}]", ctx.channel().id().asShortText()); } else { log.error("No protocol recognized on [{}]", ctx.channel().id().asLongText()); in.clear(); ctx.close(); } } private void enableGzip(ChannelHandlerContext ctx) { ChannelPipeline p = ctx.pipeline(); p.addLast("gzipdeflater", ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP)); p.addLast("gzipinflater", ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)); p.addLast("2ndPhaseSwitch", new ProtocolSwitch(false)); p.remove(this); log.info("enabled gzip: [{}]", ctx.channel().id()); } private void switchToHttp(ChannelHandlerContext ctx) { ChannelPipeline p = ctx.pipeline(); //p.addLast("logging", loggingHandler); // p.addLast(new HttpObjectAggregator(1048576)); final HttpServerCodec sourceCodec = new HttpServerCodec(); p.addLast("httpCodec", sourceCodec); // HttpServerUpgradeHandler.UpgradeCodec upgradeCodec = new Http2ServerUpgradeCodec(new HelloWorldHttp2Handler()); // HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(sourceCodec, Collections.singletonList(upgradeCodec), 65536); // p.addLast("http2Upgrader", upgradeHandler); // p.addLast("encoder", new HttpResponseEncoder()); // p.addLast("decoder", new HttpRequestDecoder()); // p.addLast("deflater", new HttpContentCompressor(1)); p.addLast("inflater", new HttpContentDecompressor()); //p.addLast("logging", loggingHandler); //p.addLast("encoder", new HttpResponseEncoder()); //p.addLast("logging", loggingHandler); // p.addLast("logging", loggingHandler); //WebSocketServerHandler //p.addLast(eventExecutorGroup, "requestManager", new WebSocketServerHandler()); p.addLast(new HttpObjectAggregator(1048576 * 2)); p.addLast("httpSwitch", HTTP_SWITCH); // p.addLast(eventExecutorGroup, "requestManager", HttpRequestManager.getInstance()); // p.addLast("requestManager", HttpRequestManager.getInstance()); p.remove(this); } private void switchToPlainText(ChannelHandlerContext ctx) { ChannelPipeline p = ctx.pipeline(); p.addLast("framer", new LineBasedFrameDecoder(1024)); p.addLast("encoder", PLAINTEXT_ENCODER); p.addLast("decoder", PLAINTEXT_DECODER); p.addLast("traceDecoder", TRACE_DECODER); p.remove(this); log.info("switched to plain text: [{}]", ctx.channel().id()); } /** * {@inheritDoc} * @see io.netty.channel.ChannelHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext, java.lang.Throwable) */ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable t) throws Exception { log.error("Uncaught exception", t); //super.exceptionCaught(ctx, t); } /** * Examines the passed unsigned bytes to see if they match the GZip signature * @param magic1 The first unsigned byte in the data to test * @param magic2 The second unsigned byte in the data to test * @return true if the signature matches gzip, false otherwise */ public boolean isGzip(final int magic1, final int magic2) { if (detectGzip) { return magic1 == 31 && magic2 == 139; } return false; } /** * Examines the passed unsigned bytes to see if they match a possible HTTP request * @param magic1 The first unsigned byte in the data to test * @param magic2 The second unsigned byte in the data to test * @return true if the signature matches an HTTP request, false otherwise */ public static boolean isHttp(final int magic1, final 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 } /** * Examines the passed unsigned bytes to see if they match a possible plain text request * @param magic1 The first unsigned byte in the data to test * @param magic2 The second unsigned byte in the data to test * @return true if the signature matches a plain text request, false otherwise */ public static boolean isText(final int magic1, final int magic2) { if (isHttp(magic1, magic2)) return false; return (magic1 >= 32 && magic1 <= 126 && magic2 >= 32 && magic2 <= 126); } }