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 - Enable http2 H2C #483

Merged
merged 11 commits into from
Aug 7, 2024
2 changes: 1 addition & 1 deletion .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
- name: 'Build and test with Maven'
run: ./mvnw -B package --file pom.xml
- name: 'Submit Dependency Snapshot'
uses: advanced-security/maven-dependency-submission-action@v3
uses: advanced-security/maven-dependency-submission-action@v4
- uses: actions/upload-artifact@v4
if: always()
with:
Expand Down
30 changes: 18 additions & 12 deletions carapace-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@

<name>Carapace :: Server</name>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>${libs.projectreactor}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.projectreactor.netty</groupId>
Expand Down Expand Up @@ -314,6 +303,12 @@
<artifactId>junit</artifactId>
<version>${libs.junit}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
Expand All @@ -327,12 +322,24 @@
<artifactId>wiremock-jre8</artifactId>
<version>${libs.wiremock}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${libs.powermock}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
Expand Down Expand Up @@ -366,7 +373,6 @@
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>${libs.hamcrest}</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ public Map<String, BackendBean> getAll() {
Map<String, BackendBean> res = new HashMap<>();

server.getMapper().getBackends().values().forEach(backendConf -> {
String id = backendConf.getId();
String id = backendConf.id();
String hostPort = backendConf.getHostPort();
BackendBean bean = new BackendBean(id, backendConf.getHost(), backendConf.getPort());
bean.lastProbePath = backendConf.getProbePath();
BackendBean bean = new BackendBean(id, backendConf.host(), backendConf.port());
bean.lastProbePath = backendConf.probePath();
EndpointKey key = EndpointKey.make(hostPort);
Map<String, ConnectionPoolStats> poolsStats = server.getConnectionPoolsStats().get(key);
if (poolsStats != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import org.carapaceproxy.core.RuntimeServerConfiguration;
import org.carapaceproxy.server.config.ConfigurationNotValidException;

/**
* Stores configuration
* Abstraction over a configuration storage.
* <br>
* The {@link RuntimeServerConfiguration Carapace configuration} is mostly a collection of key-value pairs.
* Aside from providing access to these properties, it also stores the keys for certificates management.
*
* @author enrico.olivelli
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import herddb.client.ClientConfiguration;
import herddb.jdbc.BasicHerdDBDataSource;
import herddb.jdbc.HerdDBEmbeddedDataSource;
import herddb.security.SimpleSingleUserManager;
import herddb.server.ServerConfiguration;
Expand Down Expand Up @@ -57,7 +58,10 @@
import org.shredzone.acme4j.toolbox.JSON;

/**
* Reads/Write the configuration to a JDBC database. This configuration store is able to track versions of configuration properties
* Configuration storage implementation tha reads the configuration from a JDBC database,
* i.e., and {@link BasicHerdDBDataSource HerdDB instance}.
* <br>
* This configuration store is able to commit edits to the database and track versions of configuration properties.
*
* @author enrico.olivelli
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import org.carapaceproxy.server.config.ConnectionPoolConfiguration;

/**
* Reads configuration from a Java properties file
* Configuration storage implementation tha reads the configuration from a Java {@link Properties} file.
* It resides in memory,
* and it does <b>not</b> support {@link #commitConfiguration(ConfigurationStore) commiting} changes.
*
* @author enrico.olivelli
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.carapaceproxy.core;

import org.carapaceproxy.client.EndpointKey;
import reactor.netty.http.HttpProtocol;

/**
* The class models the key used to identify a connection.
*
* @param host the string to get hostname and port from
* @param protocolVersion the HTTP protocol version; this is important to avoid mismatches
*/
public record ConnectionKey(String host, HttpProtocol protocolVersion) {
public ConnectionKey(final EndpointKey key, final String id, final HttpProtocol protocolVersion) {
this(key.getHostPort() + "_" + id, protocolVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@
import org.eclipse.jetty.webapp.WebAppContext;
import org.glassfish.jersey.servlet.ServletContainer;

/**
* The main server implementation of Carapace proxy.
*
* @see Listeners The logic handling incomping requests
* @see RuntimeServerConfiguration The server confingurations, e.g., the route-to-listener mapping
*/
public class HttpProxyServer implements AutoCloseable {

private static final Logger LOG = Logger.getLogger(HttpProxyServer.class.getName());
Expand Down
58 changes: 33 additions & 25 deletions carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@
import static org.carapaceproxy.utils.CertificatesUtils.loadKeyStoreData;
import static org.carapaceproxy.utils.CertificatesUtils.loadKeyStoreFromFile;
import static org.carapaceproxy.utils.CertificatesUtils.readChainFromKeystore;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static reactor.netty.ConnectionObserver.State.CONNECTED;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOption;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.OpenSslCachingX509KeyManagerFactory;
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
Expand Down Expand Up @@ -60,7 +57,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.logging.Level;
Expand All @@ -69,23 +65,25 @@
import jdk.net.ExtendedSocketOptions;
import lombok.Data;
import org.carapaceproxy.server.config.ConfigurationNotValidException;
import org.carapaceproxy.server.config.HostPort;
import org.carapaceproxy.server.config.NetworkListenerConfiguration;
import org.carapaceproxy.server.config.NetworkListenerConfiguration.HostPort;
import org.carapaceproxy.server.config.SSLCertificateConfiguration;
import org.carapaceproxy.utils.CarapaceLogger;
import org.carapaceproxy.utils.CertificatesUtils;
import org.carapaceproxy.utils.PrometheusUtils;
import reactor.netty.DisposableServer;
import reactor.netty.FutureMono;
import reactor.netty.NettyPipeline;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.HttpServer;

/**
* Listeners waiting for incoming clients requests
* Collection of listeners waiting for incoming clients requests on the configured HTTP ports.
* <br>
* While the {@link RuntimeServerConfiguration} is actually <i>mutable</i>, this class won't watch it for updates;
* the caller should request a {@link #reloadCurrentConfiguration() reload of the configuration} manually instead.
*
* @author enrico.olivelli
*/
@SuppressFBWarnings(value = "OBL_UNSATISFIED_OBLIGATION", justification = "https://github.com/spotbugs/spotbugs/issues/432")
public class Listeners {

public static final String OCSP_CERTIFICATE_CHAIN = "ocsp-certificate";
Expand Down Expand Up @@ -150,10 +148,22 @@ private void stopListener(HostPort hostport) throws InterruptedException {
}
}

/**
* Re-apply the current configuration; it should be invoked after editing it.
*
* @throws InterruptedException if it is interrupted while starting or stopping a listener
*/
public void reloadCurrentConfiguration() throws InterruptedException {
reloadConfiguration(this.currentConfiguration);
}

/**
* Apply a new configuration and refresh the listeners according to it.
*
* @param newConfiguration the configuration
* @throws InterruptedException if it is interrupted while starting or stopping a listener
* @see #reloadCurrentConfiguration()
*/
void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws InterruptedException {
if (!started) {
this.currentConfiguration = newConfiguration;
Expand All @@ -164,9 +174,8 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int

// stop dropped listeners, start new one
List<HostPort> listenersToStop = new ArrayList<>();
List<HostPort> listenersToStart = new ArrayList<>();
List<HostPort> listenersToRestart = new ArrayList<>();
for (Entry<HostPort, ListeningChannel> channel : listeningChannels.entrySet()) {
for (Map.Entry<HostPort, ListeningChannel> channel : listeningChannels.entrySet()) {
HostPort key = channel.getKey();
NetworkListenerConfiguration actualListenerConfig = currentConfiguration.getListener(key);
NetworkListenerConfiguration newConfigurationForListener = newConfiguration.getListener(key);
Expand All @@ -181,6 +190,7 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int
}
channel.getValue().clear();
}
List<HostPort> listenersToStart = new ArrayList<>();
for (NetworkListenerConfiguration config : newConfiguration.getListeners()) {
HostPort key = config.getKey();
if (!listeningChannels.containsKey(key)) {
Expand Down Expand Up @@ -219,13 +229,18 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int
private void bootListener(NetworkListenerConfiguration config) throws InterruptedException {
HostPort hostPort = new HostPort(config.getHost(), config.getPort() + parent.getListenersOffsetPort());
ListeningChannel listeningChannel = new ListeningChannel(hostPort, config);
LOG.log(Level.INFO, "Starting listener at {0}:{1} ssl:{2}", new Object[]{hostPort.host(), hostPort.port() + "", config.isSsl()});
LOG.log(Level.INFO, "Starting listener at {0}:{1} ssl:{2}", new Object[]{hostPort.host(), String.valueOf(hostPort.port()), config.isSsl()});

// Listener setup
HttpServer httpServer = HttpServer.create()
.host(hostPort.host())
.port(hostPort.port())
//.protocol(HttpProtocol.H2) // HTTP/2.0 setup
.protocol(config.getProtocols().toArray(HttpProtocol[]::new))
/*
// .secure()
todo: to enable H2, see config.isSsl() & snimappings
see https://projectreactor.io/docs/netty/release/reference/index.html#_server_name_indication_3
*/
.metrics(true, Function.identity())
.forwarded(ForwardedStrategy.of(config.getForwardedStrategy(), config.getTrustedIps()))
.option(ChannelOption.SO_BACKLOG, config.getSoBacklog())
Expand Down Expand Up @@ -266,24 +281,17 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato
};
channel.pipeline().addFirst(sni);
}
channel.pipeline().addAfter(NettyPipeline.HttpCodec, "uriEncoder", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof final HttpRequest request) {
request.setUri(request.uri()
.replaceAll("\\[", "%5B")
.replaceAll("]", "%5D")
);
}
ctx.fireChannelRead(msg);
}
});
})
.doOnConnection(conn -> {
CURRENT_CONNECTED_CLIENTS_GAUGE.inc();
conn.channel().closeFuture().addListener(e -> CURRENT_CONNECTED_CLIENTS_GAUGE.dec());
config.getGroup().add(conn.channel());
})
.childObserve((connection, state) -> {
if (state == CONNECTED) {
UriCleanerHandler.INSTANCE.addToPipeline(connection.channel());
}
})
.httpRequestDecoder(option -> option.maxHeaderSize(currentConfiguration.getMaxHeaderSize()))
.handle((request, response) -> { // Custom request-response handling
if (CarapaceLogger.isLoggingDebugEnabled()) {
Expand Down
Loading
Loading