/* * The MIT License * * Copyright 2013 Tim Boudreau. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mastfrog.acteur.server; import com.google.inject.Provider; import static com.mastfrog.acteur.server.ServerModule.HTTP_COMPRESSION; import static com.mastfrog.acteur.server.ServerModule.MAX_CONTENT_LENGTH; import com.mastfrog.acteur.spi.ApplicationControl; import com.mastfrog.settings.Settings; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseEncoder; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; @Singleton @Sharable class PipelineFactoryImpl extends ChannelInitializer<SocketChannel> { static final boolean DEFAULT_AGGREGATE_CHUNKS = true; private final Provider<ChannelHandler> handler; private final boolean aggregateChunks; private final int maxContentLength; private final boolean httpCompression; private final Provider<ApplicationControl> app; private final PipelineDecorator decorator; @Inject PipelineFactoryImpl(Provider<ChannelHandler> handler, Provider<ApplicationControl> app, Settings settings, PipelineDecorator decorator) { this.decorator = decorator; this.handler = handler; this.app = app; aggregateChunks = settings.getBoolean("aggregateChunks", DEFAULT_AGGREGATE_CHUNKS); httpCompression = settings.getBoolean(HTTP_COMPRESSION, true); maxContentLength = settings.getInt(MAX_CONTENT_LENGTH, 1048576); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { app.get().internalOnError(cause); } private final MessageBufEncoder messageBufEncoder = new MessageBufEncoder(); @Override public void initChannel(SocketChannel ch) throws Exception { // Create a default pipeline implementation. ChannelPipeline pipeline = ch.pipeline(); decorator.onCreatePipeline(pipeline); ChannelHandler decoder = new HackHttpRequestDecoder(); ChannelHandler encoder = new HttpResponseEncoder(); // SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); // engine.setUseClientMode(false); // pipeline.addLast("ssl", new SslHandler(engine)); pipeline.addLast(PipelineDecorator.DECODER, decoder); // Uncomment the following line if you don't want to handle HttpChunks. if (aggregateChunks) { ChannelHandler aggregator = new HttpObjectAggregator(maxContentLength); pipeline.addLast(PipelineDecorator.AGGREGATOR, aggregator); } pipeline.addLast(PipelineDecorator.BYTES, messageBufEncoder); pipeline.addLast(PipelineDecorator.ENCODER, encoder); // Remove the following line if you don't want automatic content compression. if (httpCompression) { ChannelHandler compressor = new SelectiveCompressor(); pipeline.addLast(PipelineDecorator.COMPRESSOR, compressor); } pipeline.addLast(PipelineDecorator.HANDLER, handler.get()); decorator.onPipelineInitialized(pipeline); } @Sharable private static class MessageBufEncoder extends MessageToByteEncoder<ByteBuf> { @Override protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { out.writeBytes(msg); } } private static class SelectiveCompressor extends HttpContentCompressor { @Override protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception { if (headers.headers().contains("X-Internal-Compress")) { headers.headers().remove("X-Internal-Compress"); return null; } return super.beginEncode(headers, acceptEncoding); } } static class HackHttpRequestDecoder extends HttpRequestDecoder { // See https://github.com/netty/netty/issues/3247 protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while (in.isReadable()) { int outSize = out.size(); int oldInputLength = in.readableBytes(); decode(ctx, in, out); // Check if this handler was removed before continuing the loop. // If it was removed, it is not safe to continue to operate on the buffer. // // See https://github.com/netty/netty/issues/1664 if (ctx.isRemoved()) { break; } if (outSize == out.size()) { if (oldInputLength == in.readableBytes()) { break; } else { continue; } } if (isSingleDecode()) { break; } } } catch (DecoderException e) { throw e; } catch (Throwable cause) { throw new DecoderException(cause); } } } }