Skip to content

Feature/http2 #32

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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
7 changes: 6 additions & 1 deletion driver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<copyright>Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved.</copyright>
<java.apidoc>http://docs.oracle.com/javase/8/docs/api</java.apidoc>
<maven.deploy.skip>false</maven.deploy.skip>
<netty.version>4.1.77.Final</netty.version>
<netty.version>4.1.82.Final</netty.version>
<jackson.version>2.12.5</jackson.version>
<bouncy.version>1.70</bouncy.version>
<!-- by default, skip tests; tests require a profile -->
Expand Down Expand Up @@ -175,6 +175,11 @@
<artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http2</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
Expand Down
36 changes: 36 additions & 0 deletions driver/src/main/java/oracle/nosql/driver/NoSQLHandleConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import oracle.nosql.driver.Region.RegionProvider;
import oracle.nosql.driver.iam.SignatureProvider;
import oracle.nosql.driver.util.HttpConstants;
import io.netty.handler.ssl.SslContext;

/**
Expand Down Expand Up @@ -142,6 +143,13 @@ public class NoSQLHandleConfig implements Cloneable {
*/
private int maxChunkSize = 0;

/**
* Default http protocols
*
* Default: prefer H2 but fallback to Http1.1
*/
private List<String> httpProtocols = new ArrayList<>(Arrays.asList(HttpConstants.HTTP_2, HttpConstants.HTTP_1_1));

/**
* A RetryHandler, or null if not configured by the user.
*/
Expand Down Expand Up @@ -553,6 +561,18 @@ public int getDefaultRequestTimeout() {
return timeout == 0 ? DEFAULT_TIMEOUT : timeout;
}

/**
* Returns the list of Http Protocols. If there is no configured
* protocol, a "default" value of
* List({@link HttpConstants#HTTP_2}, {@link HttpConstants#HTTP_1_1})
* is used.
*
* @return Http protocol settings
Copy link
Member

@gmfeinberg gmfeinberg Oct 14, 2022

Choose a reason for hiding this comment

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

needs an actual explanation of what's being returned

*/
public List<String> getHttpProtocols() {
return httpProtocols;
}

/**
* Returns the configured table request timeout value, in milliseconds.
* The table request timeout default can be specified independently to allow
Expand Down Expand Up @@ -631,6 +651,22 @@ public NoSQLHandleConfig setRequestTimeout(int timeout) {
return this;
}

/**
* Sets the default http protocol(s). The default is {@link HttpConstants#HTTP_2}
* and fall back to {@link HttpConstants#HTTP_1_1}
*
* @param protocols Protocol list
*
* @return this
*/
public NoSQLHandleConfig setHttpProtocols(String ... protocols) {
Copy link
Member

Choose a reason for hiding this comment

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

This needs javadoc comment

this.httpProtocols = new ArrayList<>(2);
for (String p : protocols) {
this.httpProtocols.add(p);
}
return this;
}

/**
* Sets the default table request timeout.
* The table request timeout can be specified independently
Expand Down
1 change: 1 addition & 0 deletions driver/src/main/java/oracle/nosql/driver/http/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ public Client(Logger logger,
sslCtx,
config.getSSLHandshakeTimeout(),
"NoSQL Driver",
config.getHttpProtocols(),
logger);
if (httpConfig.getProxyHost() != null) {
httpClient.configureProxy(httpConfig);
Expand Down
13 changes: 13 additions & 0 deletions driver/src/main/java/oracle/nosql/driver/http/NoSQLHandleImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

import javax.net.ssl.SSLException;

import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import oracle.nosql.driver.AuthorizationProvider;
import oracle.nosql.driver.NoSQLHandle;
import oracle.nosql.driver.NoSQLHandleConfig;
Expand Down Expand Up @@ -46,6 +49,7 @@
import oracle.nosql.driver.ops.TableUsageResult;
import oracle.nosql.driver.ops.WriteMultipleRequest;
import oracle.nosql.driver.ops.WriteMultipleResult;
import oracle.nosql.driver.util.HttpConstants;
import oracle.nosql.driver.values.FieldValue;
import oracle.nosql.driver.values.JsonUtils;
import oracle.nosql.driver.values.MapValue;
Expand Down Expand Up @@ -124,6 +128,14 @@ private void configSslContext(NoSQLHandleConfig config) {
}
builder.sessionTimeout(config.getSSLSessionTimeout());
builder.sessionCacheSize(config.getSSLSessionCacheSize());
if (config.getHttpProtocols().contains(HttpConstants.HTTP_2)) {
builder.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE);
}
builder.applicationProtocolConfig(
new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
config.getHttpProtocols()));
config.setSslContext(builder.build());
} catch (SSLException se) {
throw new IllegalStateException(
Expand All @@ -137,6 +149,7 @@ private void configAuthProvider(Logger logger, NoSQLHandleConfig config) {
if (ap instanceof StoreAccessTokenProvider) {
final StoreAccessTokenProvider stProvider =
(StoreAccessTokenProvider) ap;
stProvider.setHttpProtocols(config.getHttpProtocols());
if (stProvider.getLogger() == null) {
stProvider.setLogger(logger);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import static oracle.nosql.driver.util.LogUtil.logWarning;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
Expand All @@ -39,6 +42,7 @@
* from this config needs to be be abstracted to a generic class.
*/
import oracle.nosql.driver.NoSQLHandleConfig;
import oracle.nosql.driver.util.HttpConstants;

/**
* Netty HTTP client. Initialization process:
Expand Down Expand Up @@ -96,6 +100,8 @@ public class HttpClient {
private final String host;
private final int port;
private final String name;
private final List<String> httpProtocols;
private final String httpFallbackProtocol;

/*
* Amount of time to wait for acquiring a channel before timing
Expand Down Expand Up @@ -134,12 +140,14 @@ public class HttpClient {
* @param handshakeTimeoutMs if not zero, timeout to use for SSL handshake
* @param name A name to use in logging messages for this client.
* @param logger A logger to use for logging messages.
* @param httpProtocols A list of preferred http protocols (H2 and Http1.1)
*/
public static HttpClient createMinimalClient(String host,
int port,
SslContext sslCtx,
int handshakeTimeoutMs,
String name,
List<String> httpProtocols,
Logger logger) {
return new HttpClient(host,
port,
Expand All @@ -149,7 +157,7 @@ public static HttpClient createMinimalClient(String host,
true, /* minimal client */
DEFAULT_MAX_CONTENT_LENGTH,
DEFAULT_MAX_CHUNK_SIZE,
sslCtx, handshakeTimeoutMs, name, logger);
sslCtx, handshakeTimeoutMs, name, httpProtocols, logger);
}

/**
Expand Down Expand Up @@ -178,6 +186,7 @@ public static HttpClient createMinimalClient(String host,
* @param handshakeTimeoutMs if not zero, timeout to use for SSL handshake
* @param name A name to use in logging messages for this client.
* @param logger A logger to use for logging messages.
* @param httpProtocols A list of preferred http protocols (H2 and Http1.1)
*/
public HttpClient(String host,
int port,
Expand All @@ -189,11 +198,12 @@ public HttpClient(String host,
SslContext sslCtx,
int handshakeTimeoutMs,
String name,
List<String> httpProtocols,
Logger logger) {

this(host, port, numThreads, connectionPoolMinSize,
inactivityPeriodSeconds, false /* not minimal */,
maxContentLength, maxChunkSize, sslCtx, handshakeTimeoutMs, name, logger);
maxContentLength, maxChunkSize, sslCtx, handshakeTimeoutMs, name, httpProtocols, logger);
}

/*
Expand All @@ -210,6 +220,7 @@ private HttpClient(String host,
SslContext sslCtx,
int handshakeTimeoutMs,
String name,
List<String> httpProtocols,
Logger logger) {

this.logger = logger;
Expand All @@ -218,6 +229,15 @@ private HttpClient(String host,
this.port = port;
this.name = name;

this.httpProtocols = httpProtocols.size() > 0 ?
httpProtocols :
new ArrayList<>(Arrays.asList(HttpConstants.HTTP_2, HttpConstants.HTTP_1_1));

// If Http1.1 is in the httpProtocols list, we prefer use it as the fallback
// Else we use the last protocol in the httpProtocols list.
this.httpFallbackProtocol = this.httpProtocols.contains(HttpConstants.HTTP_1_1) ?
HttpConstants.HTTP_1_1 : this.httpProtocols.get(this.httpProtocols.size() - 1);

this.maxContentLength = (maxContentLength == 0 ?
DEFAULT_MAX_CONTENT_LENGTH : maxContentLength);
this.maxChunkSize = (maxChunkSize == 0 ?
Expand Down Expand Up @@ -292,6 +312,14 @@ String getName() {
return name;
}

List<String> getHttpProtocols() {
return httpProtocols;
}

public String getHttpFallbackProtocol() {
return httpFallbackProtocol;
}

Logger getLogger() {
return logger;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static oracle.nosql.driver.util.LogUtil.logFine;

import java.net.InetSocketAddress;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;

Expand All @@ -21,12 +22,11 @@
import io.netty.channel.EventLoop;
import io.netty.channel.pool.ChannelHealthChecker;
import io.netty.channel.pool.ChannelPoolHandler;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.util.concurrent.Future;
import oracle.nosql.driver.util.HttpConstants;

/**
* This is an instance of Netty's ChannelPoolHandler used to initialize
Expand All @@ -37,10 +37,6 @@
public class HttpClientChannelPoolHandler implements ChannelPoolHandler,
ChannelHealthChecker {

private static final String CODEC_HANDLER_NAME = "http-codec";
private static final String AGG_HANDLER_NAME = "http-aggregator";
private static final String HTTP_HANDLER_NAME = "http-response-handler";

private final HttpClient client;

/**
Expand All @@ -53,6 +49,63 @@ public class HttpClientChannelPoolHandler implements ChannelPoolHandler,
this.client = client;
}

private void configureSSL(Channel ch) {
ChannelPipeline p = ch.pipeline();
/* Enable hostname verification */
final SslHandler sslHandler = client.getSslContext().newHandler(
ch.alloc(), client.getHost(), client.getPort());
final SSLEngine sslEngine = sslHandler.engine();
final SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
sslHandler.setHandshakeTimeoutMillis(client.getHandshakeTimeoutMs());

p.addLast(sslHandler);
p.addLast(new ChannelLoggingHandler(client));
// Handle ALPN protocol negotiation result, and configure the pipeline accordingly
p.addLast(new HttpProtocolNegotiationHandler(
client.getHttpFallbackProtocol(), new HttpClientHandler(client.getLogger()),
client.getMaxChunkSize(), client.getMaxContentLength(), client.getLogger()));
}

private void configureClearText(Channel ch) {
ChannelPipeline p = ch.pipeline();
HttpClientHandler handler = new HttpClientHandler(client.getLogger());
boolean useHttp2 = client.getHttpProtocols().contains(HttpConstants.HTTP_2);

// Only true when HTTP_2 is the only protocol, as if user set:
// config.setHttpProtocols(HttpConstants.HTTP_2);
if (useHttp2 &&
HttpConstants.HTTP_2.equals(client.getHttpFallbackProtocol())) {
// If choose to use H2 and fallback is also H2
// Then there is no need to upgrade from Http1.1 to H2C
// Directly connects with H2 protocol, so called Http2-prior-knowledge
HttpUtil.configureHttp2(ch.pipeline(), client.getMaxContentLength());
p.addLast(handler);
return;
}

// Only true when HTTP_1_1 is the only protocol, as if user set:
// config.setHttpProtocols(HttpConstants.HTTP_1_1);
if (!useHttp2 &&
HttpConstants.HTTP_1_1.equals(client.getHttpFallbackProtocol())) {
HttpUtil.configureHttp1(ch.pipeline(), client.getMaxChunkSize(), client.getMaxContentLength());
p.addLast(handler);
return;
}

// Only true when both HTTP_2 and HTTP_1_1 are available, the default option:
// config.setHttpProtocols(HttpConstants.HTTP_2,
// HttpConstants.HTTP_1_1)
if (useHttp2 &&
HttpConstants.HTTP_1_1.equals(client.getHttpFallbackProtocol())) {
HttpUtil.configureH2C(ch.pipeline(), client.getMaxChunkSize(), client.getMaxContentLength());
p.addLast(handler);
return;
}
throw new IllegalStateException("unknown protocol: " + client.getHttpProtocols());
}

/**
* Initialize a channel with handlers that:
* 1 -- handle and HTTP
Expand All @@ -67,28 +120,11 @@ public void channelCreated(Channel ch) {
logFine(client.getLogger(),
"HttpClient " + client.getName() + ", channel created: " + ch
+ ", acquired channel cnt " + client.getAcquiredChannelCount());
ChannelPipeline p = ch.pipeline();
if (client.getSslContext() != null) {
/* Enable hostname verification */
final SslHandler sslHandler = client.getSslContext().newHandler(
ch.alloc(), client.getHost(), client.getPort());
final SSLEngine sslEngine = sslHandler.engine();
final SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
sslHandler.setHandshakeTimeoutMillis(client.getHandshakeTimeoutMs());

p.addLast(sslHandler);
p.addLast(new ChannelLoggingHandler(client));
configureSSL(ch);
} else {
configureClearText(ch);
}
p.addLast(CODEC_HANDLER_NAME, new HttpClientCodec
(4096, // initial line
8192, // header size
client.getMaxChunkSize()));
p.addLast(AGG_HANDLER_NAME, new HttpObjectAggregator(
client.getMaxContentLength()));
p.addLast(HTTP_HANDLER_NAME,
new HttpClientHandler(client.getLogger()));

if (client.getProxyHost() != null) {
InetSocketAddress sockAddr =
Expand All @@ -101,7 +137,7 @@ public void channelCreated(Channel ch) {
client.getProxyUsername(),
client.getProxyPassword());

p.addFirst("proxyServer", proxyHandler);
ch.pipeline().addFirst("proxyServer", proxyHandler);
}
}

Expand Down
Loading