All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.inverno.mod.http.server.internal.http2.H2cUpgradeHandler Maven / Gradle / Ivy

There is a newer version: 1.11.0
Show newest version
/*
 * Copyright 2021 Jeremy KUHN
 *
 * 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 io.inverno.mod.http.server.internal.http2;

import io.inverno.mod.http.base.header.Headers;
import io.inverno.mod.http.base.internal.netty.FlatFullHttpResponse;
import io.inverno.mod.http.base.internal.netty.LinkedHttpHeaders;
import io.inverno.mod.http.server.internal.HttpServerChannelConfigurer;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.EmptyHttpHeaders;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.Http2CodecUtil;
import io.netty.handler.codec.http2.Http2DataFrame;
import io.netty.handler.codec.http2.Http2HeadersFrame;
import io.netty.handler.codec.http2.Http2Settings;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.List;

/**
 * 

* HTTP/2 over cleartext upgrade handler. *

* *

* Implements HTTP/2 over cleartext upgrade protocol as defined by RFC 7540 Section 3.2. *

* * @author Jeremy Kuhn * @since 1.0 */ public class H2cUpgradeHandler extends ChannelInboundHandlerAdapter { private final HttpServerChannelConfigurer configurer; private Http2Connection http2Connection; private boolean upgrading; /** *

* Creates a H2C upgrade handler. *

* * @param configurer the HTTP channel configurer */ public H2cUpgradeHandler(HttpServerChannelConfigurer configurer) { this.configurer = configurer; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { this.upgrading |= msg instanceof HttpRequest && ((HttpRequest) msg).headers().contains(Headers.NAME_UPGRADE, Headers.VALUE_UPGRADE_H2C, true); if(!this.upgrading) { ctx.fireChannelRead(msg); return; } if(msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; HttpHeaders requestHeaders = request.headers(); // look for connection, settings... String connection = requestHeaders.get(Headers.NAME_CONNECTION); if(connection != null && connection.length() > 0) { int connectionIndex = 0; int length = connection.length(); String currentHeader = null; int currentHeaderIndex = 0; boolean skip = false; int headersFound = 0; while(connectionIndex < length) { char nextChar = Character.toLowerCase(connection.charAt(connectionIndex++)); if(nextChar == ',' || connectionIndex == length) { if(!skip) { headersFound++; } currentHeader = null; currentHeaderIndex = 0; skip = false; } else if(!skip && nextChar != ' ') { if(currentHeader == null) { if(nextChar == Headers.NAME_UPGRADE.charAt(currentHeaderIndex)) { currentHeader = Headers.NAME_UPGRADE; } else if(nextChar == Headers.NAME_HTTP2_SETTINGS.charAt(currentHeaderIndex)) { currentHeader = Headers.NAME_HTTP2_SETTINGS; } else { skip = true; } currentHeaderIndex++; } else { skip = nextChar != currentHeader.charAt(currentHeaderIndex++); } } } if(headersFound != 2) { // We must have: Connection: upgrade, http2-settings this.sendBadRequest(request.protocolVersion(), ctx); } // Connection: upgrade, http2-settings List http2SettingsHeader = requestHeaders.getAll(Headers.NAME_HTTP2_SETTINGS); if(http2SettingsHeader.isEmpty() || http2SettingsHeader.size() > 1) { // request MUST include exactly one HTTP2-Settings (Section 3.2.1) header field. this.sendBadRequest(request.protocolVersion(), ctx); } // parse the settings try { Http2Settings requestHttp2Settings = this.decodeSettingsHeader(http2SettingsHeader.get(0)); this.sendAcceptUpgrade(request.protocolVersion(), ctx); this.http2Connection = this.configurer.startHttp2Upgrade(ctx.pipeline()); this.http2Connection.onHttpServerUpgrade(requestHttp2Settings); this.http2Connection.onSettingsRead(ctx, requestHttp2Settings); // Convert to Http2 Headers and propagate DefaultHttp2Headers headers = new DefaultHttp2Headers(); headers.method(request.method().name()); headers.path(request.uri()); headers.authority(request.headers().get("host")); headers.scheme("http"); request.headers().remove("http2-settings"); request.headers().remove("host"); request.headers().forEach(header -> headers.set(header.getKey().toLowerCase(), header.getValue())); Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, false); this.http2Connection.onHeadersRead(ctx, 1, headersFrame.headers(), headersFrame.padding(), headersFrame.isEndStream()); } catch (IOException e) { this.sendBadRequest(request.protocolVersion(), ctx); } } } else if(this.http2Connection != null && msg instanceof HttpContent) { // transform content in http2 frame // just ignore content if connection is null since error should have been reported HttpContent content = (HttpContent)msg; boolean endStream = content instanceof LastHttpContent; Http2DataFrame dataFrame = new DefaultHttp2DataFrame(content.content(), endStream, 0); this.http2Connection.onDataRead(ctx, 1, dataFrame.content(), dataFrame.padding(), dataFrame.isEndStream()); if(endStream) { this.configurer.completeHttp2Upgrade(ctx.pipeline()); } } } private ChannelFuture sendBadRequest(HttpVersion version, ChannelHandlerContext ctx) { HttpHeaders responseHeaders = new LinkedHttpHeaders(); responseHeaders.add(Headers.NAME_CONNECTION, Headers.VALUE_CLOSE); FullHttpResponse response = new FlatFullHttpResponse(version, HttpResponseStatus.BAD_REQUEST, responseHeaders, Unpooled.EMPTY_BUFFER, EmptyHttpHeaders.INSTANCE); return ctx.writeAndFlush(response); } private ChannelFuture sendAcceptUpgrade(HttpVersion version, ChannelHandlerContext ctx) { HttpHeaders responseHeaders = new LinkedHttpHeaders(); responseHeaders.add(Headers.NAME_CONNECTION, Headers.NAME_UPGRADE); responseHeaders.add(Headers.NAME_UPGRADE, Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME); FullHttpResponse response = new FlatFullHttpResponse(version, HttpResponseStatus.SWITCHING_PROTOCOLS, responseHeaders, Unpooled.EMPTY_BUFFER, EmptyHttpHeaders.INSTANCE); return ctx.writeAndFlush(response); } private Http2Settings decodeSettingsHeader(CharSequence settingsHeader) throws IOException { Http2Settings http2Settings = new Http2Settings(); byte[] settingsHeaderBytes = Base64.getUrlDecoder().decode(settingsHeader.toString()); try(DataInputStream settingsDataStream = new DataInputStream(new ByteArrayInputStream(settingsHeaderBytes))) { int readCount = 0; while(readCount < settingsHeaderBytes.length) { int identifier = settingsDataStream.readUnsignedShort(); long value = Integer.toUnsignedLong(settingsDataStream.readInt()); http2Settings.put((char)identifier, (Long)value); readCount += 6; } return http2Settings; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy