Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP/2 set initial settings #2341

Merged
merged 4 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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 io.servicetalk.http.api;

/**
* Utilities for <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">HTTP/2 Setting</a>.
*/
public final class Http2Settings {
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_HEADER_TABLE_SIZE</a>.
*/
public static final char HEADER_TABLE_SIZE = 0x1;

/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_ENABLE_PUSH</a>.
*/
public static final char ENABLE_PUSH = 0x2;

/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
* SETTINGS_MAX_CONCURRENT_STREAMS</a>.
*/
public static final char MAX_CONCURRENT_STREAMS = 0x3;

/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">
* SETTINGS_INITIAL_WINDOW_SIZE</a>.
*/
public static final char INITIAL_WINDOW_SIZE = 0x4;

/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_FRAME_SIZE</a>.
*/
public static final char MAX_FRAME_SIZE = 0x5;

/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">
* SETTINGS_MAX_HEADER_LIST_SIZE</a>.
*/
public static final char MAX_HEADER_LIST_SIZE = 0x6;

private Http2Settings() {
}
}
10 changes: 10 additions & 0 deletions servicetalk-http-netty/gradle/spotbugs/main-exclusions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,14 @@
<Method name="handlerAdded"/>
<Bug pattern="THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"/>
</Match>
<Match>
<Class name="io.servicetalk.http.netty.H2ProtocolConfigBuilder"/>
<Field name="h2Settings"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="io.servicetalk.http.netty.Http2SettingsBuilder"/>
<Method name="build"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
</FindBugsFilter>
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,31 @@
import io.netty.handler.codec.http2.DefaultHttp2GoAwayFrame;
import io.netty.handler.codec.http2.Http2FrameCodecBuilder;
import io.netty.handler.codec.http2.Http2MultiplexHandler;
import io.netty.handler.codec.http2.Http2Settings;

import java.util.function.BiPredicate;
import java.util.Map;

import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.servicetalk.http.netty.H2ServerParentChannelInitializer.initFrameLogger;
import static io.servicetalk.http.netty.H2ServerParentChannelInitializer.toNettySettings;

final class H2ClientParentChannelInitializer implements ChannelInitializer {

private static final Http2Settings DEFAULT_NETTY_SETTINGS =
applyClientDefaultSettings(Http2Settings.defaultSettings());
private final H2ProtocolConfig config;
private final io.netty.handler.codec.http2.Http2Settings nettySettings;

H2ClientParentChannelInitializer(final H2ProtocolConfig config) {
this.config = config;
final Map<Character, Integer> h2Settings = config.initialSettings();
nettySettings = h2Settings.isEmpty() ?
DEFAULT_NETTY_SETTINGS : applyClientDefaultSettings(toNettySettings(h2Settings));
}

@Override
public void init(final Channel channel) {
final Http2FrameCodecBuilder multiplexCodecBuilder = new OptimizedHttp2FrameCodecBuilder(false)
final Http2FrameCodecBuilder multiplexCodecBuilder =
new OptimizedHttp2FrameCodecBuilder(false, config.flowControlQuantum())
// We do not want close to trigger graceful closure (go away), instead when user triggers a graceful
// close, we do the appropriate go away handling.
.decoupleCloseAndGoAway(true)
Expand All @@ -51,18 +59,13 @@ public void init(final Channel channel) {
.autoAckPingFrame(false)
// We don't want to rely upon Netty to manage the graceful close timeout, because we expect
// the user to apply their own timeout at the call site.
.gracefulShutdownTimeoutMillis(-1);

// Notify server that this client does not support server push and request it to be disabled.
multiplexCodecBuilder.initialSettings().pushEnabled(false).maxConcurrentStreams(0L);

final BiPredicate<CharSequence, CharSequence> headersSensitivityDetector =
config.headersSensitivityDetector();
multiplexCodecBuilder.headerSensitivityDetector(headersSensitivityDetector::test);
.gracefulShutdownTimeoutMillis(-1)
.initialSettings(nettySettings)
.headerSensitivityDetector(config.headersSensitivityDetector()::test);

initFrameLogger(multiplexCodecBuilder, config.frameLoggerConfig());

// TODO(scott): more configuration. header validation, settings stream, etc...
// TODO(scott): more configuration. header validation, etc...

channel.pipeline().addLast(multiplexCodecBuilder.build(),
new Http2MultiplexHandler(H2PushStreamHandler.INSTANCE));
Expand All @@ -84,4 +87,9 @@ public void channelRegistered(final ChannelHandlerContext ctx) {
// Http2ConnectionHandler.processGoAwayWriteResult will close the connection after GO_AWAY is flushed
}
}

private static Http2Settings applyClientDefaultSettings(Http2Settings settings) {
// Notify server that this client does not support server push and request it to be disabled.
return settings.pushEnabled(false).maxConcurrentStreams(0L);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.servicetalk.logging.api.UserDataLoggerConfig;

import java.time.Duration;
import java.util.Map;
import java.util.function.BiPredicate;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -60,6 +61,21 @@ default String alpnId() {
@Nullable
KeepAlivePolicy keepAlivePolicy();

/**
* Get a {@link Map} which provides a hint for the initial settings for any h2 connection. Note that some settings
* may be ignored if not supported (e.g. push promise).
* @return a {@link Map} which provides a hint for the initial settings for any h2 connection. Note that some
* settings may be ignored if not supported (e.g. push promise).
*/
Map<Character, Integer> initialSettings();

/**
* Provide a hint on the number of bytes that the flow controller will attempt to give to a stream for each
* allocation (assuming the stream has this much eligible data).
idelpivnitskiy marked this conversation as resolved.
Show resolved Hide resolved
* @return number of bytes.
*/
int flowControlQuantum();
idelpivnitskiy marked this conversation as resolved.
Show resolved Hide resolved

/**
* A policy for sending <a href="https://tools.ietf.org/html/rfc7540#section-6.7">PING frames</a> to the peer.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
import io.servicetalk.logging.api.UserDataLoggerConfig;
import io.servicetalk.logging.slf4j.internal.DefaultUserDataLoggerConfig;

import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.BooleanSupplier;
import javax.annotation.Nullable;

import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
import static io.servicetalk.http.netty.H2KeepAlivePolicies.DISABLE_KEEP_ALIVE;
import static java.util.Objects.requireNonNull;

Expand All @@ -35,15 +37,15 @@
* @see HttpProtocolConfigs#h2()
*/
public final class H2ProtocolConfigBuilder {

private static final BiPredicate<CharSequence, CharSequence> DEFAULT_SENSITIVITY_DETECTOR = (name, value) -> false;

private Map<Character, Integer> h2Settings = newDefaultSettingsBuilder().build();
private HttpHeadersFactory headersFactory = H2HeadersFactory.INSTANCE;
private BiPredicate<CharSequence, CharSequence> headersSensitivityDetector = DEFAULT_SENSITIVITY_DETECTOR;
@Nullable
private UserDataLoggerConfig frameLoggerConfig;
@Nullable
private KeepAlivePolicy keepAlivePolicy;
private int flowControlQuantum = defaultFlowControlQuantum();

H2ProtocolConfigBuilder() {
}
Expand Down Expand Up @@ -103,33 +105,79 @@ public H2ProtocolConfigBuilder keepAlivePolicy(final KeepAlivePolicy policy) {
return this;
}

/**
* Sets the initial <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">HTTP/2 Setting</a> to for
* each h2 connection.
* @param settings the initial settings to for each h2 connection.
* @return {@code this}
* @see Http2SettingsBuilder
*/
public H2ProtocolConfigBuilder initialSettings(Map<Character, Integer> settings) {
this.h2Settings = requireNonNull(settings);
return this;
}

/**
* Provide a hint on the number of bytes that the flow controller will attempt to give to a stream for each
* allocation (assuming the stream has this much eligible data).
* @param flowControlQuantum a hint on the number of bytes that the flow controller will attempt to give to a
* stream for each allocation (assuming the stream has this much eligible data).
* @return {@code this}
*/
public H2ProtocolConfigBuilder flowControlQuantum(int flowControlQuantum) {
if (flowControlQuantum <= 0) {
throw new IllegalArgumentException("flowControlQuantum " + flowControlQuantum + " (expected >0)");
}
this.flowControlQuantum = flowControlQuantum;
return this;
}

/**
* Builds {@link H2ProtocolConfig}.
*
* @return {@link H2ProtocolConfig}
*/
public H2ProtocolConfig build() {
return new DefaultH2ProtocolConfig(headersFactory, headersSensitivityDetector, frameLoggerConfig,
keepAlivePolicy);
return new DefaultH2ProtocolConfig(h2Settings, headersFactory, headersSensitivityDetector, frameLoggerConfig,
keepAlivePolicy, flowControlQuantum);
}

private static final class DefaultH2ProtocolConfig implements H2ProtocolConfig {
static Http2SettingsBuilder newDefaultSettingsBuilder() {
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
return new Http2SettingsBuilder()
.initialWindowSize(1048576) // 1mb default window size
.maxHeaderListSize((int) DEFAULT_HEADER_LIST_SIZE);
}

/**
* Default allocation quantum to use for the remote flow controller.
* @return Default allocation quantum to use for the remote flow controller.
*/
private static int defaultFlowControlQuantum() {
return 1024 * 16;
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}

private static final class DefaultH2ProtocolConfig implements H2ProtocolConfig {
private final Map<Character, Integer> h2Settings;
private final HttpHeadersFactory headersFactory;
private final BiPredicate<CharSequence, CharSequence> headersSensitivityDetector;
@Nullable
private final UserDataLoggerConfig frameLoggerConfig;
@Nullable
private final KeepAlivePolicy keepAlivePolicy;
private final int flowControlQuantum;

DefaultH2ProtocolConfig(final HttpHeadersFactory headersFactory,
DefaultH2ProtocolConfig(final Map<Character, Integer> h2Settings,
final HttpHeadersFactory headersFactory,
final BiPredicate<CharSequence, CharSequence> headersSensitivityDetector,
@Nullable final UserDataLoggerConfig frameLoggerConfig,
@Nullable final KeepAlivePolicy keepAlivePolicy) {
@Nullable final KeepAlivePolicy keepAlivePolicy,
final int flowControlQuantum) {
this.h2Settings = h2Settings;
this.headersFactory = headersFactory;
this.headersSensitivityDetector = headersSensitivityDetector;
this.frameLoggerConfig = frameLoggerConfig;
this.keepAlivePolicy = keepAlivePolicy;
this.flowControlQuantum = flowControlQuantum;
}

@Override
Expand All @@ -154,16 +202,27 @@ public KeepAlivePolicy keepAlivePolicy() {
return keepAlivePolicy;
}

@Override
public Map<Character, Integer> initialSettings() {
return h2Settings;
}

@Override
public int flowControlQuantum() {
return flowControlQuantum;
}

@Override
public String toString() {
return getClass().getSimpleName() +
"{alpnId=" + alpnId() +
", headersFactory=" + headersFactory +
", headersSensitivityDetector=" + (headersSensitivityDetector == DEFAULT_SENSITIVITY_DETECTOR ?
"DEFAULT_SENSITIVITY_DETECTOR" : headersSensitivityDetector.toString()) +
"DEFAULT_SENSITIVITY_DETECTOR" : headersSensitivityDetector.toString()) +
", frameLoggerConfig=" + frameLoggerConfig +
", keepAlivePolicy=" + keepAlivePolicy +
'}';
", flowControlQuantum=" + flowControlQuantum +
", h2Settings=" + h2Settings + '}';
}
}
}
Loading