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 all commits
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,84 @@
/*
* 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;

import java.util.function.BiConsumer;
import javax.annotation.Nullable;

/**
* Object representing a <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">HTTP/2 Setting</a> frame.
*/
public interface Http2Settings {
/**
* Get the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_HEADER_TABLE_SIZE</a>.
* @return the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_HEADER_TABLE_SIZE</a>.
*/
@Nullable
Long headerTableSize();

/**
* Get the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_CONCURRENT_STREAMS</a>.
* @return the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_CONCURRENT_STREAMS</a>.
*/
@Nullable
Long maxConcurrentStreams();

/**
* Get the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_INITIAL_WINDOW_SIZE</a>.
* @return the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_INITIAL_WINDOW_SIZE</a>.
*/
@Nullable
Integer initialWindowSize();

/**
* Get the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_FRAME_SIZE</a>.
* @return the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_FRAME_SIZE</a>.
*/
@Nullable
Integer maxFrameSize();

/**
* Get the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
* @return the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
*/
@Nullable
Long maxHeaderListSize();

/**
* Get the setting value associated with an
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">identifier</a>.
* @param identifier the <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">identifier</a>.
* @return {@code null} if no setting value corresponding {@code identifier} exists, otherwise the value.
*/
@Nullable
Long settingValue(char identifier);

/**
* Iterate over all the &lt;identifier, value&gt; tuple in this settings object.
* @param action Invoked on each &lt;identifier, value&gt; tuple.
*/
void forEach(BiConsumer<? super Character, ? super Long> action);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* 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;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;

/**
* Builder to help create a {@link Map} for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">HTTP/2 Setting</a>.
*/
public final class Http2SettingsBuilder {
private static final long MAX_UNSIGNED_INT = 0xffffffffL;
private static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
private static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xffffff;
/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_HEADER_TABLE_SIZE</a>.
*/
private 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>.
*/
private static final char ENABLE_PUSH = 0x2;
/**
* Identifier <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">
* SETTINGS_MAX_CONCURRENT_STREAMS</a>.
*/
private 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>.
*/
private 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>.
*/
private 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>.
*/
private static final char MAX_HEADER_LIST_SIZE = 0x6;
private final Map<Character, Long> settings;

/**
* Create a new instance.
*/
public Http2SettingsBuilder() {
settings = new HashMap<>(6);
}

/**
* Set the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_HEADER_TABLE_SIZE</a>.
*
* @param value The value.
* @return {@code this}.
*/
public Http2SettingsBuilder headerTableSize(long value) {
validate32Unsigned(value);
settings.put(HEADER_TABLE_SIZE, value);
return this;
}

/**
* Set the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_CONCURRENT_STREAMS</a>.
*
* @param value The value.
* @return {@code this}.
*/
public Http2SettingsBuilder maxConcurrentStreams(long value) {
validate32Unsigned(value);
settings.put(MAX_CONCURRENT_STREAMS, value);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
return this;
}

/**
* Set the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_INITIAL_WINDOW_SIZE</a>.
*
* @param value The value.
* @return {@code this}.
*/
public Http2SettingsBuilder initialWindowSize(long value) {
validate31Unsigned(value);
settings.put(INITIAL_WINDOW_SIZE, value);
return this;
}

/**
* Set the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_FRAME_SIZE</a>.
*
* @param value The value.
* @return {@code this}.
*/
public Http2SettingsBuilder maxFrameSize(int value) {
if (value < MAX_FRAME_SIZE_LOWER_BOUND || value > MAX_FRAME_SIZE_UPPER_BOUND) {
throw new IllegalArgumentException("value: " + value + "(expected [" + MAX_FRAME_SIZE_LOWER_BOUND + ", " +
MAX_FRAME_SIZE_UPPER_BOUND + "]");
}
settings.put(MAX_FRAME_SIZE, (long) value);
return this;
}

/**
* Set the value for
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
*
* @param value The value.
* @return {@code this}.
*/
public Http2SettingsBuilder maxHeaderListSize(long value) {
validate32Unsigned(value);
settings.put(MAX_HEADER_LIST_SIZE, value);
return this;
}

/**
* Build the {@link Map} that represents
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">HTTP/2 Setting</a>.
* @return the {@link Map} that represents
* <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.1">HTTP/2 Setting</a>.
*/
public Http2Settings build() {
return new DefaultHttp2Settings(new HashMap<>(settings));
}

private static void validate32Unsigned(long value) {
if (value < 0 || value >= MAX_UNSIGNED_INT) {
throw new IllegalArgumentException("value: " + value + "(expected [0, " + MAX_UNSIGNED_INT + "]");
}
}

private static void validate31Unsigned(long value) {
if (value < 0 || value >= Integer.MAX_VALUE) {
throw new IllegalArgumentException("value: " + value + "(expected [0, " + Integer.MAX_VALUE + "]");
}
}

private static final class DefaultHttp2Settings implements Http2Settings {
private final Map<Character, Long> settings;

private DefaultHttp2Settings(final Map<Character, Long> settings) {
this.settings = settings;
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}

@Nullable
@Override
public Long headerTableSize() {
return settings.get(HEADER_TABLE_SIZE);
}

@Nullable
@Override
public Long maxConcurrentStreams() {
return settings.get(MAX_CONCURRENT_STREAMS);
}

@Nullable
@Override
public Integer initialWindowSize() {
return toInteger(settings.get(INITIAL_WINDOW_SIZE));
}

@Nullable
@Override
public Integer maxFrameSize() {
return toInteger(settings.get(MAX_FRAME_SIZE));
}

@Nullable
@Override
public Long maxHeaderListSize() {
return settings.get(MAX_HEADER_LIST_SIZE);
}

@Nullable
@Override
public Long settingValue(final char identifier) {
return settings.get(identifier);
}

@Override
public void forEach(final BiConsumer<? super Character, ? super Long> action) {
settings.forEach(action);
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder(settings.size() * 10);
final String separator = ", ";
sb.append('{');
settings.forEach((identity, value) ->
sb.append(identityToString(identity)).append('=').append(value).append(separator));
if (sb.length() > separator.length()) {
sb.setLength(sb.length() - separator.length());
}
return sb.append('}').toString();
}

@Override
public int hashCode() {
return settings.hashCode();
}

@Override
public boolean equals(Object o) {
return o instanceof DefaultHttp2Settings && settings.equals(((DefaultHttp2Settings) o).settings);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}

private static String identityToString(Character identity) {
switch (identity) {
case HEADER_TABLE_SIZE:
return "HEADER_TABLE_SIZE";
case ENABLE_PUSH:
return "ENABLE_PUSH";
case MAX_CONCURRENT_STREAMS:
return "MAX_CONCURRENT_STREAMS";
case INITIAL_WINDOW_SIZE:
return "INITIAL_WINDOW_SIZE";
case MAX_FRAME_SIZE:
return "MAX_FRAME_SIZE";
case MAX_HEADER_LIST_SIZE:
return "MAX_HEADER_LIST_SIZE";
default:
return identity.toString();
}
}

@Nullable
private static Integer toInteger(@Nullable Long value) {
return value == null ? null : value.intValue();
}
}
}
5 changes: 5 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,9 @@
<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>
</FindBugsFilter>
Loading