/* * Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and * contributors. All rights reserved. * * 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.noctarius.tengi.server.impl.transport.http; import com.noctarius.tengi.core.exception.ConnectionFailedException; import com.noctarius.tengi.server.ServerTransports; import com.noctarius.tengi.server.impl.ConnectionManager; import com.noctarius.tengi.spi.serialization.Serializer; 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.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.ssl.SslHandler; import javax.net.ssl.SSLEngine; import java.util.List; import static io.netty.handler.codec.http2.Http2CodecUtil.TLS_UPGRADE_PROTOCOL_NAME; class Http2Negotiator extends ByteToMessageDecoder { private final int port; private final int maxHttpContentLength; private final ConnectionManager connectionManager; private final Serializer serializer; Http2Negotiator(int port, int maxHttpContentLength, ConnectionManager connectionManager, Serializer serializer) { this.port = port; this.maxHttpContentLength = maxHttpContentLength; this.connectionManager = connectionManager; this.serializer = serializer; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (initPipeline(ctx)) { // When we reached here we can remove this handler as its now clear // what protocol we want to use // from this point on. This will also take care of forward all // messages. ctx.pipeline().remove(this); } } private boolean initPipeline(ChannelHandlerContext ctx) { // Get the SslHandler from the ChannelPipeline so we can obtain the // SslEngine from it. SslHandler handler = ctx.pipeline().get(SslHandler.class); if (handler == null) { // SSL is necessary to negotiate HTTP2, therefore it can only be HTTP switchToHttp(ctx); return true; } SelectedProtocol protocol = getProtocol(handler.engine()); switch (protocol) { case UNKNOWN: // Not done with choosing the protocol, so just return here for now, //return false; switchToHttp(ctx); break; case HTTP_2: if (!connectionManager.acceptTransport(ServerTransports.HTTP2_TRANSPORT, port)) { ctx.close(); throw new ConnectionFailedException("Transport not enabled"); } switchToHttp2(ctx); break; case HTTP_1_0: case HTTP_1_1: switchToHttp(ctx); break; default: throw new IllegalStateException("Unknown SelectedProtocol"); } return true; } private void switchToHttp2(ChannelHandlerContext ctx) { ChannelPipeline pipeline = ctx.pipeline(); pipeline.addLast("http2-connection-processor", new Http2ConnectionProcessor(connectionManager, serializer)); pipeline.remove(this); } private void switchToHttp(ChannelHandlerContext ctx) { ChannelPipeline pipeline = ctx.pipeline(); pipeline.addLast("httpCodec", new HttpServerCodec()); pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(maxHttpContentLength)); pipeline.addLast("websocketNegotiator", new WebsocketNegotiator(port, connectionManager, serializer)); } private SelectedProtocol getProtocol(SSLEngine engine) { String[] protocol = engine.getSession().getProtocol().split(":"); if (protocol != null && protocol.length > 1) { SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]); System.err.println("Selected Protocol is " + selectedProtocol); return selectedProtocol; } return SelectedProtocol.UNKNOWN; } private enum SelectedProtocol { /** * Must be updated to match the HTTP/2 draft number. */ HTTP_2(TLS_UPGRADE_PROTOCOL_NAME), HTTP_1_1("http/1.1"), HTTP_1_0("http/1.0"), UNKNOWN("Unknown"); private final String name; SelectedProtocol(String defaultName) { name = defaultName; } public String protocolName() { return name; } /** * Get an instance of this enum based on the protocol name returned by the NPN server provider * * @param name the protocol name * @return the SelectedProtocol instance */ public static SelectedProtocol protocol(String name) { for (SelectedProtocol protocol : SelectedProtocol.values()) { if (protocol.protocolName().equals(name)) { return protocol; } } return UNKNOWN; } } }