Skip to content
Open
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ Also, The `listener` can be configured to authenticate and authorize the inbound
built-in support for basic authentication, JWT authentication, and OAuth2 authentication.

In addition to that, supports both the HTTP/1.1 and HTTP2 protocols and connection keep-alive, content
chunking, HTTP caching, data compression/decompression, payload binding, and authorization can be highlighted as the features of a `Service`.
chunking, HTTP caching, data compression/decompression, payload binding, HTTP/2 stream concurrency limiting, and
authorization can be highlighted as the features of a `Service`.

## Issues and projects

Expand Down
3 changes: 2 additions & 1 deletion ballerina/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,5 @@ Also, The `listener` can be configured to authenticate and authorize the inbound
built-in support for basic authentication, JWT authentication, and OAuth2 authentication.

In addition to that, supports both the HTTP/1.1 and HTTP2 protocols and connection keep-alive, content
chunking, HTTP caching, data compression/decompression, payload binding, and authorization can be highlighted as the features of a `Service`.
chunking, HTTP caching, data compression/decompression, payload binding, HTTP/2 stream concurrency limiting, and
authorization can be highlighted as the features of a `Service`.
3 changes: 3 additions & 0 deletions ballerina/http_service_endpoint.bal
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ public type Local record {|
# + gracefulStopTimeout - Grace period of time in seconds for listener gracefulStop
# + socketConfig - Provides settings related to server socket configuration
# + http2InitialWindowSize - Configuration to change the initial window size in HTTP/2
# + http2MaxActiveStreams - Maximum concurrent HTTP/2 streams per connection advertised to clients.
# Defaults to 100
# + minIdleTimeInStaleState - Minimum time in seconds for a connection to be kept open which has received a GOAWAY.
# This only applies for HTTP/2. Default value is 5 minutes. If the value is set to -1,
# the connection will be closed after all in-flight streams are completed
Expand All @@ -164,6 +166,7 @@ public type ListenerConfiguration record {|
decimal gracefulStopTimeout = DEFAULT_GRACEFULSTOP_TIMEOUT;
ServerSocketConfig socketConfig = {};
int http2InitialWindowSize = 65535;
int http2MaxActiveStreams = 100;
decimal minIdleTimeInStaleState = 300;
decimal timeBetweenStaleEviction = 30;
|};
Expand Down
17 changes: 17 additions & 0 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The conforming implementation of the specification is released and included in t
* 2.1.1. [Automatically starting the service](#211-automatically-starting-the-service)
* 2.1.2. [Programmatically starting the service](#212-programmatically-starting-the-service)
* 2.1.3. [Default listener](#213-default-listener)
* 2.1.4. [HTTP2 stream concurrency](#214-http2-stream-concurrency)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we keep the terms consistent, with HTTP/2?

* 2.2. [Service](#22-service)
* 2.2.1. [Service type](#221-service-type)
* 2.2.2. [Service-base-path](#222-service-base-path)
Expand Down Expand Up @@ -177,6 +178,7 @@ public type ListenerConfiguration record {|
string? server = ();
RequestLimitConfigs requestLimits = {};
int http2InitialWindowSize = 65535;
int http2MaxActiveStreams = 100;
decimal minIdleTimeInStaleState = 300;
decimal timeBetweenStaleEviction = 30;
|};
Expand Down Expand Up @@ -254,6 +256,21 @@ path = "resources/certs/ballerinaKeystore.p12"
password = "ballerina"
```

#### 2.1.4. HTTP2 stream concurrency

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here also, should it be HTTP/2?


Each HTTP/2 connection can carry multiple requests concurrently over independent streams. The listener limits the
number of concurrent streams a single connection can open, advertised to clients via the `SETTINGS_MAX_CONCURRENT_STREAMS`
parameter, using the `http2MaxActiveStreams` field of the `ListenerConfiguration`. This defaults to `100`, the value
recommended by [RFC 7540 Section 6.5.2](https://www.rfc-editor.org/rfc/rfc7540#section-6.5.2).

```ballerina
listener http:Listener h2Listener = new (9090, {
http2MaxActiveStreams: 500
});
```

A client that reaches this limit on a connection opens an additional connection rather than stalling.

### 2.2. Service
Service is a collection of resources functions, which are the network entry points of a ballerina program.
In addition to that a service can contain public and private functions which can be accessed by calling with `self`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ public final class HttpConstants {
public static final BString ENDPOINT_CONFIG_GRACEFUL_STOP_TIMEOUT = StringUtils.fromString("gracefulStopTimeout");
public static final BString ENDPOINT_CONFIG_HTTP2_INITIAL_WINDOW_SIZE = StringUtils
.fromString("http2InitialWindowSize");
public static final BString ENDPOINT_CONFIG_HTTP2_MAX_ACTIVE_STREAMS = StringUtils
.fromString("http2MaxActiveStreams");
public static final BString ENDPOINT_CONFIG_IDLE_TIME_STALE_STATE = StringUtils.fromString(
"minIdleTimeInStaleState");
public static final BString ENDPOINT_CONFIG_TIME_BETWEEN_STALE_CHECK_RUNS = StringUtils.fromString(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
import static io.ballerina.stdlib.http.api.HttpConstants.ANN_CONFIG_ATTR_SSL_ENABLED_PROTOCOLS;
import static io.ballerina.stdlib.http.api.HttpConstants.CREATE_INTERCEPTORS_FUNCTION_NAME;
import static io.ballerina.stdlib.http.api.HttpConstants.ENDPOINT_CONFIG_HTTP2_INITIAL_WINDOW_SIZE;
import static io.ballerina.stdlib.http.api.HttpConstants.ENDPOINT_CONFIG_HTTP2_MAX_ACTIVE_STREAMS;
import static io.ballerina.stdlib.http.api.HttpConstants.HTTP_HEADERS;
import static io.ballerina.stdlib.http.api.HttpConstants.RESOLVED_REQUESTED_URI;
import static io.ballerina.stdlib.http.api.HttpConstants.RESPONSE_CACHE_CONTROL;
Expand Down Expand Up @@ -1583,6 +1584,8 @@ public static ListenerConfiguration getListenerConfig(long port, BMap endpointCo
listenerConfiguration.setPipeliningEnabled(true); //Pipelining is enabled all the time
listenerConfiguration.setHttp2InitialWindowSize(endpointConfig
.getIntValue(ENDPOINT_CONFIG_HTTP2_INITIAL_WINDOW_SIZE).intValue());
listenerConfiguration.setHttp2MaxConcurrentStreams(

@YasanPunch YasanPunch Jul 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the pr description, it says: "Set to -1 for unlimited streams (not recommended for public-facing services)"

Where is this logic handled?

endpointConfig.getIntValue(ENDPOINT_CONFIG_HTTP2_MAX_ACTIVE_STREAMS).intValue());

double minIdleTimeInStaleState =
((BDecimal) endpointConfig.get(HttpConstants.ENDPOINT_CONFIG_IDLE_TIME_STALE_STATE)).floatValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static ListenerConfiguration getDefault() {
private boolean socketReuse;
private boolean socketKeepAlive;
private int http2InitialWindowSize = 65535;
private int http2MaxActiveStreams = 100;
private long minIdleTimeInStaleState = 3000000;
private long timeBetweenStaleEviction = 30000;

Expand Down Expand Up @@ -285,6 +286,14 @@ public void setHttp2InitialWindowSize(int http2InitialWindowSize) {
this.http2InitialWindowSize = http2InitialWindowSize;
}

public int getHttp2MaxConcurrentStreams() {
return http2MaxActiveStreams;
}

public void setHttp2MaxConcurrentStreams(int http2MaxActiveStreams) {
this.http2MaxActiveStreams = http2MaxActiveStreams;
}

public void setTimeBetweenStaleEviction(long timeBetweenStaleEviction) {
this.timeBetweenStaleEviction = timeBetweenStaleEviction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public ServerConnector createServerConnector(ServerBootstrapConfiguration server
if (Constants.HTTP_2_0.equals(listenerConfig.getVersion())) {
serverConnectorBootstrap.setHttp2Enabled(true);
serverConnectorBootstrap.setHttp2InitialWindowSize(listenerConfig.getHttp2InitialWindowSize());
serverConnectorBootstrap.setHttp2MaxConcurrentStreams(listenerConfig.getHttp2MaxConcurrentStreams());
serverConnectorBootstrap.setMinIdleTimeInStaleState(listenerConfig.getMinIdleTimeInStaleState());
serverConnectorBootstrap.setTimeBetweenStaleEviction(listenerConfig.getTimeBetweenStaleEviction());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public class HttpServerChannelInitializer extends ChannelInitializer<SocketChann
private EventExecutorGroup pipeliningGroup;
private boolean webSocketCompressionEnabled;
private int http2InitialWindowSize;
private int http2MaxActiveStreams = 100;
private long minIdleTimeInStaleState;
private long timeBetweenStaleEviction;
private final BlockingQueue<Http2SourceHandler> http2StaleSourceHandlers = new LinkedBlockingQueue<>();
Expand Down Expand Up @@ -261,7 +262,7 @@ private void configureH2cPipeline(ChannelPipeline pipeline) {
// Add handler to handle http2 requests without an upgrade
pipeline.addLast(new Http2WithPriorKnowledgeHandler(
interfaceId, serverName, serverConnectorFuture, this, allChannels, listenerChannels,
reqSizeValidationConfig.getMaxHeaderSize(), http2InitialWindowSize));
reqSizeValidationConfig.getMaxHeaderSize(), http2InitialWindowSize, http2MaxActiveStreams));
// Add http2 upgrade decoder and upgrade handler
final HttpServerCodec sourceCodec = new HttpServerCodec(reqSizeValidationConfig.getMaxInitialLineLength(),
reqSizeValidationConfig.getMaxHeaderSize(),
Expand All @@ -281,7 +282,7 @@ private void configureH2cPipeline(ChannelPipeline pipeline) {
new Http2SourceConnectionHandlerBuilder(
interfaceId, serverConnectorFuture, serverName, this,
this.allChannels, this.listenerChannels, reqSizeValidationConfig.getMaxHeaderSize(),
this.http2InitialWindowSize).build());
this.http2InitialWindowSize, this.http2MaxActiveStreams).build());
} else {
return null;
}
Expand Down Expand Up @@ -355,6 +356,10 @@ void setHttp2InitialWindowSize(int http2InitialWindowSize) {
this.http2InitialWindowSize = http2InitialWindowSize;
}

void setHttp2MaxConcurrentStreams(int http2MaxActiveStreams) {
this.http2MaxActiveStreams = http2MaxActiveStreams;
}

void setTimeBetweenStaleEviction(long timeBetweenStaleEviction) {
this.timeBetweenStaleEviction = timeBetweenStaleEviction;
}
Expand Down Expand Up @@ -447,7 +452,7 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
new Http2SourceConnectionHandlerBuilder(
interfaceId, serverConnectorFuture, serverName, channelInitializer,
allChannels, listenerChannels, reqSizeValidationConfig.getMaxHeaderSize(),
http2InitialWindowSize).build());
http2InitialWindowSize, http2MaxActiveStreams).build());
} else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
// handles pipeline for HTTP/1.x requests after SSL handshake
configureHttpPipeline(ctx.pipeline(), Constants.HTTP_SCHEME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ public void setHttp2InitialWindowSize(int http2InitialWindowSize) {
httpServerChannelInitializer.setHttp2InitialWindowSize(http2InitialWindowSize);
}

public void setHttp2MaxConcurrentStreams(int http2MaxActiveStreams) {
httpServerChannelInitializer.setHttp2MaxConcurrentStreams(http2MaxActiveStreams);
}

public void setTimeBetweenStaleEviction(long timeBetweenStaleEviction) {
httpServerChannelInitializer.setTimeBetweenStaleEviction(timeBetweenStaleEviction);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public Http2SourceConnectionHandlerBuilder(String interfaceId, ServerConnectorFu
ChannelGroup allChannels,
ChannelGroup listenerChannels,
long maxHeaderListSize,
int initialWindowSize) {
int initialWindowSize,
int maxActiveStreams) {
this.interfaceId = interfaceId;
this.serverConnectorFuture = serverConnectorFuture;
this.serverName = serverName;
Expand All @@ -63,6 +64,7 @@ public Http2SourceConnectionHandlerBuilder(String interfaceId, ServerConnectorFu
this.listenerChannels = listenerChannels;
this.initialSettings().maxHeaderListSize(maxHeaderListSize);
this.initialSettings().initialWindowSize(initialWindowSize);
this.initialSettings().maxConcurrentStreams(maxActiveStreams);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,16 @@ public class Http2WithPriorKnowledgeHandler extends ChannelInboundHandlerAdapter
private ChannelGroup listenerChannels;
private long maxHeaderListSize;
private int initialWindowSize;
private int maxActiveStreams;

public Http2WithPriorKnowledgeHandler(String interfaceId, String serverName,
ServerConnectorFuture serverConnectorFuture,
HttpServerChannelInitializer serverChannelInitializer,
ChannelGroup allChannels,
ChannelGroup listenerChannels,
long maxHeaderListSize,
int initialWindowSize) {
int initialWindowSize,
int maxActiveStreams) {
this.interfaceId = interfaceId;
this.serverName = serverName;
this.serverConnectorFuture = serverConnectorFuture;
Expand All @@ -65,6 +67,7 @@ public Http2WithPriorKnowledgeHandler(String interfaceId, String serverName,
this.listenerChannels = listenerChannels;
this.maxHeaderListSize = maxHeaderListSize;
this.initialWindowSize = initialWindowSize;
this.maxActiveStreams = maxActiveStreams;
}

@Override
Expand All @@ -83,7 +86,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
Constants.HTTP2_SOURCE_CONNECTION_HANDLER,
new Http2SourceConnectionHandlerBuilder(
interfaceId, serverConnectorFuture, serverName, serverChannelInitializer,
allChannels, listenerChannels, maxHeaderListSize, initialWindowSize).build());
allChannels, listenerChannels, maxHeaderListSize, initialWindowSize,
maxActiveStreams).build());
safelyRemoveHandlers(pipeline, Constants.HTTP2_UPGRADE_HANDLER,
Constants.HTTP_COMPRESSOR, Constants.HTTP_TRACE_LOG_HANDLER);
}
Expand Down
Loading
Loading