/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.jooby.internal.netty; import java.util.List; import java.util.concurrent.TimeUnit; import org.jooby.spi.HttpHandler; import com.typesafe.config.Config; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; 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.ByteToMessageDecoder; 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.http2.DefaultHttp2Connection; import io.netty.handler.codec.http2.Http2CodecUtil; import io.netty.handler.codec.http2.Http2ConnectionHandler; import io.netty.handler.codec.http2.Http2FrameLogger; import io.netty.handler.codec.http2.Http2ServerUpgradeCodec; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder; import io.netty.handler.logging.LogLevel; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.AsciiString; import io.netty.util.concurrent.EventExecutorGroup; public class NettyPipeline extends ChannelInitializer<SocketChannel> { private EventExecutorGroup executor; private HttpHandler handler; private Config config; private int maxInitialLineLength; private int maxHeaderSize; private int maxChunkSize; int maxContentLength; private long idleTimeOut; private SslContext sslCtx; private boolean supportH2; public NettyPipeline(final EventExecutorGroup executor, final HttpHandler handler, final Config conf, final SslContext sslCtx) { this.executor = executor; this.handler = handler; this.config = conf; maxInitialLineLength = conf.getBytes("netty.http.MaxInitialLineLength").intValue(); maxHeaderSize = conf.getBytes("netty.http.MaxHeaderSize").intValue(); maxChunkSize = conf.getBytes("netty.http.MaxChunkSize").intValue(); maxContentLength = conf.getBytes("netty.http.MaxContentLength").intValue(); idleTimeOut = conf.getDuration("netty.http.IdleTimeout", TimeUnit.MILLISECONDS); supportH2 = conf.getBoolean("server.http2.enabled"); this.sslCtx = sslCtx; } @Override protected void initChannel(final SocketChannel ch) throws Exception { final ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast("ssl", sslCtx.newHandler(ch.alloc())); p.addLast("h1.1/h2", new Http2OrHttpHandler()); } else { if (supportH2) { p.addLast("h2c", new Http2PrefaceOrHttpHandler()); idle(p); aggregator(p); jooby(p); } else { http1(p); } } } private void idle(final ChannelPipeline p) { if (idleTimeOut > 0) { p.addLast("timeout", new IdleStateHandler(0, 0, idleTimeOut, TimeUnit.MILLISECONDS)); } } private HttpServerCodec http1Codec() { return new HttpServerCodec(maxInitialLineLength, maxHeaderSize, maxChunkSize, false); } private void http2(final ChannelPipeline p) { p.addLast("h2", newHttp2ConnectionHandler(p)); idle(p); jooby(p); } private void http1(final ChannelPipeline p) { p.addLast("codec", http1Codec()); idle(p); aggregator(p); jooby(p); } private void aggregator(final ChannelPipeline p) { p.addLast("aggregator", new HttpObjectAggregator(maxContentLength)); } private void jooby(final ChannelPipeline p) { p.addLast(executor, "jooby", new NettyHandler(handler, config)); } private Http2ConnectionHandler newHttp2ConnectionHandler(final ChannelPipeline p) { DefaultHttp2Connection connection = new DefaultHttp2Connection(true); InboundHttp2ToHttpAdapter listener = new InboundHttp2ToHttpAdapterBuilder(connection) .propagateSettings(false) .validateHttpHeaders(false) .maxContentLength(maxContentLength) .build(); HttpToHttp2ConnectionHandler http2handler = new HttpToHttp2ConnectionHandlerBuilder() .frameListener(listener) .frameLogger(new Http2FrameLogger(LogLevel.DEBUG)) .connection(connection) .build(); return http2handler; } class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler { public Http2OrHttpHandler() { super(ApplicationProtocolNames.HTTP_1_1); } @Override public void configurePipeline(final ChannelHandlerContext ctx, final String protocol) throws Exception { if (supportH2 && ApplicationProtocolNames.HTTP_2.equals(protocol)) { http2(ctx.pipeline()); } else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { http1(ctx.pipeline()); } else { throw new IllegalStateException("Unknown protocol: " + protocol); } } } class Http2PrefaceOrHttpHandler extends ByteToMessageDecoder { private static final int PRI = 0x50524920; private String name; @Override public void handlerAdded(final ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); name = ctx.name(); } @Override protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) throws Exception { if (in.readableBytes() < 4) { return; } if (in.getInt(in.readerIndex()) == PRI) { h2c(ctx); } else { h2cOrHttp1(ctx); } ctx.pipeline().remove(this); } private void h2cOrHttp1(final ChannelHandlerContext ctx) { ChannelPipeline p = ctx.pipeline(); HttpServerCodec http1codec = http1Codec(); String baseName = name; baseName = addAfter(p, baseName, "codec", http1codec); baseName = addAfter(p, baseName, "h2upgrade", new HttpServerUpgradeHandler(http1codec, protocol -> { if (!AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) { return null; } return new Http2ServerUpgradeCodec(newHttp2ConnectionHandler(p)); }, maxContentLength)); } private void h2c(final ChannelHandlerContext ctx) { final ChannelPipeline p = ctx.pipeline(); addAfter(p, name, "h2", newHttp2ConnectionHandler(p)); } private String addAfter(final ChannelPipeline p, final String baseName, final String name, final ChannelHandler h) { p.addAfter(baseName, name, h); return p.context(h).name(); } } }