Skip to content

Commit

Permalink
Refactor to use immutable approach to configuration and to use @Confi…
Browse files Browse the repository at this point in the history
…gBean
  • Loading branch information
tomas-langer committed Jan 17, 2023
1 parent ab8a972 commit adbb2b1
Show file tree
Hide file tree
Showing 46 changed files with 849 additions and 907 deletions.
55 changes: 55 additions & 0 deletions docs-internal/nima-connections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Connections
----

# Server Connection Providers

Níma server uses SPI to discover how to handle a specific incoming connection.
The main entry point is `ServerConnectionProvider` - a service loader provider interface to discover
which connections are supported.

We have the following implementations currently:

- `Http1ConnectionProvider` - support for the usual HTTP/1.1 connections
- `Http2ConnectionProvider` - support for "prior-knowledge" HTTP/2 connections

The connection provider creates a configured `ServerConnectionSelector`, which is then used at runtime.
The selector works based on initial bytes of the connection.


# HTTP/1.1 Upgrade Providers

HTTP/1.1 supports the concept of upgrading a connection. This is supported through
`Http1UpgradeProvider` - a service loader provider interface to discover which upgrades are supported.

We have the following implementations currently:

- `Http2UpgradeProvider` - upgrade to HTTP/2 using upgrade
- `WsUpgradeProvider` - upgrade to Níma WebSocket implementation
- `TyrusUpgradeProvider` - upgrade to MP Tyrus WebSocket implementation (higher weight than WsUpgradeProvider)

The upgrade provider creates a configured `Http1Upgrader`, which is then used at runtime.
Upgraders work based on protocol identificator (`h2c`, `websocket`). When more than one for the same protocol is configured,
the provider with higher weight will be used.

# Configurability

ALl of connection providers, HTTP/1.1 upgrade providers and HTTP/2 subprotocols are configured under `server.connection-providers`, to have a single configuration of a protocol regardless whether this is accessed directly or through upgrade mechanism.

The configuration key is the one provided by the Connection provider, HTTP/1.1 Upgrade provider, or HTTP/2 SubProtocol provider `configKey()` or `configKeys()` method.

As all providers are configured on the same leave, each provider should have a descriptive and unique configuration key
relevant to its purpose.

Example of such configuration (Tyrus and Níma WebSocket both use `websocket`, as only one of them can be active):
```yaml
server:
connection-providers:
http_1_1:
max-prologue-length: 4096
websocket:
origins: ["origin1"]
http_2:
max-frame-size: 128000
grpc:
something: "value"
```
6 changes: 5 additions & 1 deletion etc/checkstyle-suppressions.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<!--
Copyright (c) 2016, 2022 Oracle and/or its affiliates.
Copyright (c) 2016, 2023 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,6 +57,10 @@
<suppress checks="IllegalImport"
files="DerUtils\.java"/>

<!-- this is a record style, all parameters are always needed, no benefit of changing to builder -->
<suppress files="nima/http2/webserver/src/main/java/io/helidon/nima/http2/webserver/spi/Http2SubProtocolSelector.java"
checks="ParameterNumber"/>

<!-- this is a record style, all parameters are always needed, no benefit of changing to builder -->
<suppress files="nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/ConnectionContext.java"
checks="ParameterNumber"/>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,61 @@

package io.helidon.microprofile.tyrus;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;

import io.helidon.nima.webserver.http1.Http1Upgrader;
import io.helidon.config.Config;
import io.helidon.nima.webserver.http1.spi.Http1Upgrader;
import io.helidon.nima.websocket.webserver.WsUpgradeProvider;

/**
* {@link java.util.ServiceLoader} provider implementation for upgrade from HTTP/1.1 to Tyrus connection.
*/
public class TyrusUpgradeProvider extends WsUpgradeProvider {

TyrusUpgradeProvider(Builder builder) {
super(builder);
}

/**
* @deprecated This constructor is only to be used by {@link java.util.ServiceLoader}, use {@link #builder()}
*/
@Deprecated()
public TyrusUpgradeProvider() {
this(new HashSet<>());
this(tyrusBuilder());
}

TyrusUpgradeProvider(Set<String> origins) {
super(origins);
/**
* New builder.
*
* @return builder
*/
public static Builder tyrusBuilder() {
return new Builder();
}

@Override
public Http1Upgrader create() {
return new TyrusUpgrader(Set.copyOf(origins()));
public Http1Upgrader create(Function<String, Config> config) {
Set<String> usedOrigins;

if (origins().isEmpty()) {
usedOrigins = config.apply(CONFIG_NAME)
.get("origins")
.asList(String.class)
.map(Set::copyOf)
.orElseGet(Set::of);
} else {
usedOrigins = origins();
}

return new TyrusUpgrader(usedOrigins);
}

// jUnit test accessor for origins set (package private only)
protected Set<String> origins() {
return super.origins();
}

/**
* New builder.
*
* @return builder
*/
public static Builder tyrusBuilder() {
return new Builder();
}

/**
* Fluent API builder for {@link TyrusUpgradeProvider}.
*/
Expand All @@ -69,7 +82,7 @@ private Builder() {

@Override
public TyrusUpgradeProvider build() {
return new TyrusUpgradeProvider(origins());
return new TyrusUpgradeProvider(this);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,15 +37,15 @@
import io.helidon.nima.http2.Http2StreamState;
import io.helidon.nima.http2.Http2StreamWriter;
import io.helidon.nima.http2.Http2WindowUpdate;
import io.helidon.nima.http2.webserver.spi.Http2SubProtocolProvider;
import io.helidon.nima.http2.webserver.spi.Http2SubProtocolSelector;

import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.Status;

class GrpcProtocolHandler<REQ, RES> implements Http2SubProtocolProvider.SubProtocolHandler {
class GrpcProtocolHandler<REQ, RES> implements Http2SubProtocolSelector.SubProtocolHandler {
private static final System.Logger LOGGER = System.getLogger(GrpcProtocolHandler.class.getName());
private static final HeaderValue GRPC_CONTENT_TYPE = Header.createCached(Header.CONTENT_TYPE, "application/grpc");
private static final HeaderValue GRPC_ENCODING_IDENTITY = Header.createCached("grpc-encoding", "identity");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,9 +26,9 @@
import io.helidon.nima.http2.Http2StreamState;
import io.helidon.nima.http2.Http2StreamWriter;
import io.helidon.nima.http2.Http2WindowUpdate;
import io.helidon.nima.http2.webserver.spi.Http2SubProtocolProvider;
import io.helidon.nima.http2.webserver.spi.Http2SubProtocolSelector;

class GrpcProtocolHandlerNotFound implements Http2SubProtocolProvider.SubProtocolHandler {
class GrpcProtocolHandlerNotFound implements Http2SubProtocolSelector.SubProtocolHandler {
private final Http2StreamWriter streamWriter;
private final int streamId;
private Http2StreamState currentStreamState;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,11 +20,13 @@
import io.helidon.common.http.Http;
import io.helidon.common.http.Http.Header;
import io.helidon.common.http.HttpPrologue;
import io.helidon.config.Config;
import io.helidon.nima.http2.Http2Headers;
import io.helidon.nima.http2.Http2Settings;
import io.helidon.nima.http2.Http2StreamState;
import io.helidon.nima.http2.Http2StreamWriter;
import io.helidon.nima.http2.webserver.spi.Http2SubProtocolProvider;
import io.helidon.nima.http2.webserver.spi.Http2SubProtocolSelector;
import io.helidon.nima.http2.webserver.spi.SubProtocolResult;
import io.helidon.nima.webserver.ConnectionContext;
import io.helidon.nima.webserver.Router;
Expand All @@ -43,45 +45,57 @@ public GrpcProtocolProvider() {
}

@Override
public SubProtocolResult subProtocol(ConnectionContext ctx,
HttpPrologue prologue,
Http2Headers headers,
Http2StreamWriter streamWriter,
int streamId,
Http2Settings serverSettings,
Http2Settings clientSettings,
Http2StreamState currentStreamState,
Router router) {
if (prologue.method() != Http.Method.POST) {
return NOT_SUPPORTED;
}
public String configKey() {
return "grpc";
}

// we know this is HTTP/2, so no need to check protocol and version
Headers httpHeaders = headers.httpHeaders();
@Override
public Http2SubProtocolSelector create(Config config) {
return new GrpcProtocolSelector();
}

if (httpHeaders.contains(Header.CONTENT_TYPE)) {
String contentType = httpHeaders.get(Header.CONTENT_TYPE).value();
private static class GrpcProtocolSelector implements Http2SubProtocolSelector {
@Override
public SubProtocolResult subProtocol(ConnectionContext ctx,
HttpPrologue prologue,
Http2Headers headers,
Http2StreamWriter streamWriter,
int streamId,
Http2Settings serverSettings,
Http2Settings clientSettings,
Http2StreamState currentStreamState,
Router router) {
if (prologue.method() != Http.Method.POST) {
return NOT_SUPPORTED;
}

// we know this is HTTP/2, so no need to check protocol and version
Headers httpHeaders = headers.httpHeaders();

if (contentType.startsWith("application/grpc")) {
GrpcRouting routing = router.routing(GrpcRouting.class, GrpcRouting.empty());
if (httpHeaders.contains(Header.CONTENT_TYPE)) {
String contentType = httpHeaders.get(Header.CONTENT_TYPE).value();

Grpc<?, ?> route = routing.findRoute(prologue);
if (contentType.startsWith("application/grpc")) {
GrpcRouting routing = router.routing(GrpcRouting.class, GrpcRouting.empty());

if (route == null) {
Grpc<?, ?> route = routing.findRoute(prologue);

if (route == null) {
return new SubProtocolResult(true,
new GrpcProtocolHandlerNotFound(streamWriter, streamId, currentStreamState));
}
return new SubProtocolResult(true,
new GrpcProtocolHandlerNotFound(streamWriter, streamId, currentStreamState));
new GrpcProtocolHandler(prologue,
headers,
streamWriter,
streamId,
serverSettings,
clientSettings,
currentStreamState,
route));
}
return new SubProtocolResult(true,
new GrpcProtocolHandler(prologue,
headers,
streamWriter,
streamId,
serverSettings,
clientSettings,
currentStreamState,
route));
}
return NOT_SUPPORTED;
}
return NOT_SUPPORTED;
}
}
Loading

0 comments on commit adbb2b1

Please sign in to comment.