many test fixes, bytebuf leaks, add TLS protocol for client

This commit is contained in:
Jörg Prante 2019-08-25 23:26:13 +02:00
parent 712dd570e7
commit 47a1176048
72 changed files with 1036 additions and 609 deletions

View file

@ -1,7 +1,6 @@
plugins { plugins {
id "com.github.spotbugs" version "2.0.0" id "com.github.spotbugs" version "2.0.0"
id "org.sonarqube" version "2.6.1" id "io.codearte.nexus-staging" version "0.21.0"
id "io.codearte.nexus-staging" version "0.11.0"
id "org.xbib.gradle.plugin.asciidoctor" version "1.5.6.0.1" id "org.xbib.gradle.plugin.asciidoctor" version "1.5.6.0.1"
} }
@ -10,21 +9,14 @@ apply plugin: "io.codearte.nexus-staging"
subprojects { subprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: "com.github.spotbugs" apply plugin: "com.github.spotbugs"
configurations {
asciidoclet
}
dependencies { dependencies {
testCompile "org.junit.jupiter:junit-jupiter-api:${project.property('junit.version')}" testCompile "org.junit.jupiter:junit-jupiter-api:${project.property('junit.version')}"
testCompile "org.junit.jupiter:junit-jupiter-params:${project.property('junit.version')}" testCompile "org.junit.jupiter:junit-jupiter-params:${project.property('junit.version')}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}" testCompile "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}"
testCompile "org.junit.vintage:junit-vintage-engine:${project.property('junit.version')}" testCompile "org.junit.vintage:junit-vintage-engine:${project.property('junit.version')}"
testCompile "junit:junit:${project.property('junit4.version')}" testCompile "junit:junit:${project.property('junit4.version')}"
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
} }
compileJava { compileJava {
@ -52,10 +44,10 @@ subprojects {
test { test {
useJUnitPlatform() useJUnitPlatform()
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
failFast = false failFast = false
testLogging { testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
showStandardStreams = false
} }
afterSuite { desc, result -> afterSuite { desc, result ->
if (!desc.parent) { if (!desc.parent) {
@ -84,6 +76,21 @@ subprojects {
'source-highlighter': 'coderay' 'source-highlighter': 'coderay'
} }
spotbugs {
toolVersion = '3.1.12'
sourceSets = [sourceSets.main]
ignoreFailures = true
effort = "max"
reportLevel = "high"
// includeFilter = file("config/findbugs/findbugs-include.xml")
// excludeFilter = file("config/findbugs/findbugs-excludes.xml")
}
tasks.withType(com.github.spotbugs.SpotBugsTask) {
reports.xml.enabled = false
reports.html.enabled = true
}
/*javadoc { /*javadoc {
options.docletpath = configurations.asciidoclet.files.asType(List) options.docletpath = configurations.asciidoclet.files.asType(List)
options.doclet = "org.xbib.asciidoclet.Asciidoclet" options.doclet = "org.xbib.asciidoclet.Asciidoclet"
@ -115,12 +122,73 @@ subprojects {
ext { ext {
user = 'jprante' user = 'jprante'
name = 'netty-http' name = 'netty-http'
description = 'HTTP client and server for Netty' projectDescription = 'HTTP client and server for Netty'
scmUrl = 'https://github.com/' + user + '/' + name scmUrl = 'https://github.com/jprante/netty-http'
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' scmConnection = 'scm:git:git://github.com/jprante/netty-http.git'
scmDeveloperConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' scmDeveloperConnection = 'scm:git:git://github.com/jprante/netty-http.git'
inceptionDate = '2012'
organizationName = 'xbib'
organizationUrl = 'http://xbib.org'
licenseName = 'The Apache License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
} }
/*publishing {
publications {
mavenJava(MavenPublication) {
from components.java
groupId project.group
artifactId project.name
version project.version
artifact sourcesJar
artifact javadocJar
pom {
name = project.name
description = projectDescription
inceptionYear = inceptionDate
url = scmUrl
organization {
name = organizationName
url = organizationUrl
}
scm {
url = scmUrl
connection = scmConnection
developerConnection = scmDeveloperConnection
}
licenses {
license {
name = licenseName
url = licenseUrl
}
}
developers {
developer {
id = user
name = 'Jörg Prante'
email = 'joergprante@gmail.com'
url = 'https://github.com/jprante'
}
}
}
}
}
repositories {
maven {
url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
credentials {
username ossrhUsername
password ossrhPassword
}
}
}
}
signing {
sign publishing.publications.mavenJava
}*/
task sonaTypeUpload(type: Upload) { task sonaTypeUpload(type: Upload) {
group = 'publish' group = 'publish'
configuration = configurations.archives configuration = configurations.archives
@ -142,11 +210,11 @@ subprojects {
name project.name name project.name
description description description description
packaging 'jar' packaging 'jar'
inceptionYear '2012' inceptionYear inceptionDate
url scmUrl url scmUrl
organization { organization {
name 'xbib' name organizationName
url 'http://xbib.org' url organizationUrl
} }
developers { developers {
developer { developer {
@ -163,8 +231,8 @@ subprojects {
} }
licenses { licenses {
license { license {
name 'The Apache License, Version 2.0' name licenseName
url 'http://www.apache.org/licenses/LICENSE-2.0.txt' url licenseUrl
} }
} }
} }
@ -172,34 +240,7 @@ subprojects {
} }
} }
} }
spotbugs {
toolVersion = '3.1.12'
sourceSets = [sourceSets.main]
ignoreFailures = true
effort = "max"
reportLevel = "high"
// includeFilter = file("config/findbugs/findbugs-include.xml")
// excludeFilter = file("config/findbugs/findbugs-excludes.xml")
}
// To generate an HTML report instead of XML
tasks.withType(com.github.spotbugs.SpotBugsTask) {
reports.xml.enabled = false
reports.html.enabled = true
}
}
sonarqube {
properties {
property "sonar.projectName", "${project.group} ${project.name}"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.tests", "src/test/java"
property "sonar.scm.provider", "git"
property "sonar.junit.reportsPath", "build/test-results/test/"
}
}
nexusStaging { nexusStaging {
packageGroup = "org.xbib" packageGroup = "org.xbib"
} }
}

View file

@ -1,30 +1,28 @@
group = org.xbib group = org.xbib
name = netty-http name = netty-http
version = 4.1.38.3 version = 4.1.39.0
# main packages # netty
netty.version = 4.1.38.Final netty.version = 4.1.39.Final
tcnative.version = 2.0.25.Final tcnative.version = 2.0.25.Final
# common # for netty-http-common
xbib-net-url.version = 2.0.0 xbib-net-url.version = 2.0.0
# server # for netty-http-server
bouncycastle.version = 1.61 bouncycastle.version = 1.62
# reactive # for netty-http-server-reactive
reactivestreams.version = 1.0.2 reactivestreams.version = 1.0.2
# rest # for netty-http-server-rest
xbib-guice.version = 4.0.4 xbib-guice.version = 4.0.4
# test # test
junit.version = 5.5.1 junit.version = 5.5.1
junit4.version = 4.12 junit4.version = 4.12
conscrypt.version = 2.0.0 conscrypt.version = 2.2.1
jackson.version = 2.9.9 jackson.version = 2.9.9
# doc # doc
asciidoclet.version = 1.5.4 asciidoclet.version = 1.5.4
org.gradle.warning.mode = all

View file

@ -1,5 +1,5 @@
#Tue Aug 06 15:30:36 CEST 2019 #Sun Aug 18 22:06:23 CEST 2019
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

6
gradlew vendored
View file

@ -7,7 +7,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -125,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if $cygwin ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`

2
gradlew.bat vendored
View file

@ -5,7 +5,7 @@
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem http://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,

View file

@ -1,6 +1,7 @@
package org.xbib.netty.http.client.rest; package org.xbib.netty.http.client.rest;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
@ -11,6 +12,7 @@ import org.xbib.netty.http.common.HttpResponse;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class RestClient { public class RestClient {
@ -18,11 +20,14 @@ public class RestClient {
private HttpResponse response; private HttpResponse response;
private ByteBuf byteBuf;
private RestClient() { private RestClient() {
} }
public void setResponse(HttpResponse response) { public void setResponse(HttpResponse response) {
this.response = response; this.response = response;
this.byteBuf = response != null ? response.getBody().retain() : null;
} }
public HttpResponse getResponse() { public HttpResponse getResponse() {
@ -34,7 +39,6 @@ public class RestClient {
} }
public String asString(Charset charset) { public String asString(Charset charset) {
ByteBuf byteBuf = response != null ? response.getBody() : null;
return byteBuf != null && byteBuf.isReadable() ? byteBuf.toString(charset) : null; return byteBuf != null && byteBuf.isReadable() ? byteBuf.toString(charset) : null;
} }
@ -43,11 +47,11 @@ public class RestClient {
} }
public static RestClient get(String urlString) throws IOException { public static RestClient get(String urlString) throws IOException {
return method(urlString, null, null, HttpMethod.GET); return method(urlString, HttpMethod.GET);
} }
public static RestClient delete(String urlString) throws IOException { public static RestClient delete(String urlString) throws IOException {
return method(urlString, null, null, HttpMethod.DELETE); return method(urlString, HttpMethod.DELETE);
} }
public static RestClient post(String urlString, String body) throws IOException { public static RestClient post(String urlString, String body) throws IOException {
@ -66,28 +70,31 @@ public class RestClient {
return method(urlString, content, HttpMethod.PUT); return method(urlString, content, HttpMethod.PUT);
} }
public static RestClient method(String urlString,
HttpMethod httpMethod) throws IOException {
return method(urlString, Unpooled.buffer(), httpMethod);
}
public static RestClient method(String urlString, public static RestClient method(String urlString,
String body, Charset charset, String body, Charset charset,
HttpMethod httpMethod) throws IOException { HttpMethod httpMethod) throws IOException {
ByteBuf byteBuf = null; Objects.requireNonNull(body);
if (body != null && charset != null) { Objects.requireNonNull(charset);
byteBuf = client.getByteBufAllocator().buffer(); ByteBuf byteBuf = client.getByteBufAllocator().buffer();
byteBuf.writeCharSequence(body, charset); byteBuf.writeCharSequence(body, charset);
}
return method(urlString, byteBuf, httpMethod); return method(urlString, byteBuf, httpMethod);
} }
public static RestClient method(String urlString, public static RestClient method(String urlString,
ByteBuf byteBuf, ByteBuf byteBuf,
HttpMethod httpMethod) throws IOException { HttpMethod httpMethod) throws IOException {
Objects.requireNonNull(byteBuf);
URL url = URL.create(urlString); URL url = URL.create(urlString);
RestClient restClient = new RestClient(); RestClient restClient = new RestClient();
Request.Builder requestBuilder = Request.builder(httpMethod).url(url); Request.Builder requestBuilder = Request.builder(httpMethod).url(url);
if (byteBuf != null) {
requestBuilder.content(byteBuf); requestBuilder.content(byteBuf);
}
client.newTransport(HttpAddress.http1(url)) client.newTransport(HttpAddress.http1(url))
.execute(requestBuilder.build().setResponseListener(restClient::setResponse)).get(); .execute(requestBuilder.build().setResponseListener(restClient::setResponse)).close();
return restClient; return restClient;
} }
} }

View file

@ -0,0 +1,32 @@
package org.xbib.netty.http.client.rest;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class NettyHttpTestExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
System.setProperty("io.netty.noUnsafe", Boolean.toString(true));
System.setProperty("io.netty.leakDetection.level", "ADVANCED");
Level level = Level.INFO;
System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n");
LogManager.getLogManager().reset();
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
rootLogger.addHandler(handler);
rootLogger.setLevel(level);
for (Handler h : rootLogger.getHandlers()) {
handler.setFormatter(new SimpleFormatter());
h.setLevel(level);
}
}
}

View file

@ -1,10 +1,12 @@
package org.xbib.netty.http.client.rest; package org.xbib.netty.http.client.rest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpTestExtension.class)
class RestClientTest { class RestClientTest {
private static final Logger logger = Logger.getLogger(RestClientTest.class.getName()); private static final Logger logger = Logger.getLogger(RestClientTest.class.getName());

View file

@ -57,11 +57,12 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public final class Client { public final class Client implements AutoCloseable {
private static final Logger logger = Logger.getLogger(Client.class.getName()); private static final Logger logger = Logger.getLogger(Client.class.getName());
@ -79,6 +80,9 @@ public final class Client {
System.setProperty("io.netty.noKeySetOptimization", Boolean.toString(true)); System.setProperty("io.netty.noKeySetOptimization", Boolean.toString(true));
} }
} }
private static final AtomicLong requestCounter = new AtomicLong();
private static final AtomicLong responseCounter = new AtomicLong();
private final ClientConfig clientConfig; private final ClientConfig clientConfig;
@ -140,7 +144,7 @@ public final class Client {
ClientChannelPoolHandler clientChannelPoolHandler = new ClientChannelPoolHandler(); ClientChannelPoolHandler clientChannelPoolHandler = new ClientChannelPoolHandler();
this.pool = new BoundedChannelPool<>(semaphore, clientConfig.getPoolVersion(), this.pool = new BoundedChannelPool<>(semaphore, clientConfig.getPoolVersion(),
nodes, bootstrap, clientChannelPoolHandler, retries, nodes, bootstrap, clientChannelPoolHandler, retries,
BoundedChannelPool.PoolKeySelectorType.ROUNDROBIN); clientConfig.getPoolKeySelectorType());
Integer nodeConnectionLimit = clientConfig.getPoolNodeConnectionLimit(); Integer nodeConnectionLimit = clientConfig.getPoolNodeConnectionLimit();
if (nodeConnectionLimit == null || nodeConnectionLimit == 0) { if (nodeConnectionLimit == null || nodeConnectionLimit == 0) {
nodeConnectionLimit = nodes.size(); nodeConnectionLimit = nodes.size();
@ -150,6 +154,7 @@ public final class Client {
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);
} }
logger.log(Level.FINE, "client pool prepared: size = " + nodeConnectionLimit);
} }
} }
@ -182,6 +187,14 @@ public final class Client {
logger.log(level, NetworkUtils::displayNetworkInterfaces); logger.log(level, NetworkUtils::displayNetworkInterfaces);
} }
public AtomicLong getRequestCounter() {
return requestCounter;
}
public AtomicLong getResponseCounter() {
return responseCounter;
}
public Transport newTransport() { public Transport newTransport() {
return newTransport(null); return newTransport(null);
} }
@ -293,8 +306,21 @@ public final class Client {
close(transport); close(transport);
} }
@Override
public void close() {
try {
shutdownGracefully();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
public void shutdownGracefully() throws IOException { public void shutdownGracefully() throws IOException {
logger.log(Level.FINE, "shutting down gracefully"); shutdownGracefully(30L, TimeUnit.SECONDS);
}
public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException {
logger.log(Level.FINE, "shutting down");
for (Transport transport : transports) { for (Transport transport : transports) {
close(transport); close(transport);
} }
@ -302,12 +328,11 @@ public final class Client {
if (hasPooledConnections()) { if (hasPooledConnections()) {
pool.close(); pool.close();
} }
logger.log(Level.FINE, "shutting down"); eventLoopGroup.shutdownGracefully(1L, amount, timeUnit);
eventLoopGroup.shutdownGracefully();
try { try {
eventLoopGroup.awaitTermination(10L, TimeUnit.SECONDS); eventLoopGroup.awaitTermination(amount, timeUnit);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// ignore throw new IOException(e);
} }
} }
@ -359,14 +384,17 @@ public final class Client {
default: default:
break; break;
} }
engine.setEnabledProtocols(clientConfig.getProtocols());
return sslHandler; return sslHandler;
} }
private static SslContext newSslContext(ClientConfig clientConfig, HttpVersion httpVersion) throws SSLException { private static SslContext newSslContext(ClientConfig clientConfig, HttpVersion httpVersion) throws SSLException {
// Conscrypt?
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient() SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
.sslProvider(clientConfig.getSslProvider()) .sslProvider(clientConfig.getSslProvider())
.ciphers(clientConfig.getCiphers(), clientConfig.getCipherSuiteFilter()) .ciphers(clientConfig.getCiphers(), clientConfig.getCipherSuiteFilter())
.applicationProtocolConfig(newApplicationProtocolConfig(httpVersion)); .applicationProtocolConfig(newApplicationProtocolConfig(httpVersion));
if (clientConfig.getSslContextProvider() != null) { if (clientConfig.getSslContextProvider() != null) {
sslContextBuilder.sslContextProvider(clientConfig.getSslContextProvider()); sslContextBuilder.sslContextProvider(clientConfig.getSslContextProvider());
} }
@ -415,12 +443,14 @@ public final class Client {
HttpAddress httpAddress = channel.attr(pool.getAttributeKey()).get(); HttpAddress httpAddress = channel.attr(pool.getAttributeKey()).get();
HttpVersion httpVersion = httpAddress.getVersion(); HttpVersion httpVersion = httpAddress.getVersion();
SslContext sslContext = newSslContext(clientConfig, httpAddress.getVersion()); SslContext sslContext = newSslContext(clientConfig, httpAddress.getVersion());
SslHandlerFactory sslHandlerFactory = new SslHandlerFactory(sslContext, clientConfig, httpAddress, byteBufAllocator); SslHandlerFactory sslHandlerFactory = new SslHandlerFactory(sslContext,
clientConfig, httpAddress, byteBufAllocator);
Http2ChannelInitializer http2ChannelInitializer = Http2ChannelInitializer http2ChannelInitializer =
new Http2ChannelInitializer(clientConfig, httpAddress, sslHandlerFactory); new Http2ChannelInitializer(clientConfig, httpAddress, sslHandlerFactory);
if (httpVersion.majorVersion() == 1) { if (httpVersion.majorVersion() == 1) {
HttpChannelInitializer initializer = HttpChannelInitializer initializer =
new HttpChannelInitializer(clientConfig, httpAddress, sslHandlerFactory, http2ChannelInitializer); new HttpChannelInitializer(clientConfig, httpAddress,
sslHandlerFactory, http2ChannelInitializer);
initializer.initChannel(channel); initializer.initChannel(channel);
} else { } else {
http2ChannelInitializer.initChannel(channel); http2ChannelInitializer.initChannel(channel);
@ -428,7 +458,7 @@ public final class Client {
} }
} }
public class SslHandlerFactory { public static class SslHandlerFactory {
private final SslContext sslContext; private final SslContext sslContext;
@ -438,7 +468,8 @@ public final class Client {
private final ByteBufAllocator allocator; private final ByteBufAllocator allocator;
SslHandlerFactory(SslContext sslContext, ClientConfig clientConfig, HttpAddress httpAddress, ByteBufAllocator allocator) { SslHandlerFactory(SslContext sslContext, ClientConfig clientConfig,
HttpAddress httpAddress, ByteBufAllocator allocator) {
this.sslContext = sslContext; this.sslContext = sslContext;
this.clientConfig = clientConfig; this.clientConfig = clientConfig;
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
@ -559,7 +590,7 @@ public final class Client {
return this; return this;
} }
public Builder setEnableGzip(boolean enableGzip) { public Builder enableGzip(boolean enableGzip) {
clientConfig.setEnableGzip(enableGzip); clientConfig.setEnableGzip(enableGzip);
return this; return this;
} }
@ -586,6 +617,11 @@ public final class Client {
return this; return this;
} }
public Builder setTlsProtocols(String[] protocols) {
clientConfig.setProtocols(protocols);
return this;
}
public Builder setCiphers(Iterable<String> ciphers) { public Builder setCiphers(Iterable<String> ciphers) {
clientConfig.setCiphers(ciphers); clientConfig.setCiphers(ciphers);
return this; return this;

View file

@ -8,6 +8,8 @@ import io.netty.handler.logging.LogLevel;
import io.netty.handler.proxy.HttpProxyHandler; import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.CipherSuiteFilter;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import org.xbib.netty.http.client.pool.BoundedChannelPool;
import org.xbib.netty.http.client.pool.Pool;
import org.xbib.netty.http.client.retry.BackOff; import org.xbib.netty.http.client.retry.BackOff;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.common.security.SecurityUtil;
@ -18,6 +20,7 @@ import java.security.KeyStore;
import java.security.Provider; import java.security.Provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
public class ClientConfig { public class ClientConfig {
@ -118,6 +121,11 @@ public class ClientConfig {
*/ */
Provider SSL_CONTEXT_PROVIDER = null; Provider SSL_CONTEXT_PROVIDER = null;
/**
* Transport layer security protocol versions.
*/
String[] PROTOCOLS = new String[] { "TLSv1.3", "TLSv1.2" };
/** /**
* Default ciphers. We care about HTTP/2. * Default ciphers. We care about HTTP/2.
*/ */
@ -143,6 +151,8 @@ public class ClientConfig {
*/ */
HttpVersion POOL_VERSION = HttpVersion.HTTP_1_1; HttpVersion POOL_VERSION = HttpVersion.HTTP_1_1;
Pool.PoolKeySelectorType POOL_KEY_SELECTOR_TYPE = Pool.PoolKeySelectorType.ROUNDROBIN;
/** /**
* Default connection pool security. * Default connection pool security.
*/ */
@ -204,6 +214,8 @@ public class ClientConfig {
private Provider sslContextProvider = Defaults.SSL_CONTEXT_PROVIDER; private Provider sslContextProvider = Defaults.SSL_CONTEXT_PROVIDER;
private String[] protocols = Defaults.PROTOCOLS;
private Iterable<String> ciphers = Defaults.CIPHERS; private Iterable<String> ciphers = Defaults.CIPHERS;
private CipherSuiteFilter cipherSuiteFilter = Defaults.CIPHER_SUITE_FILTER; private CipherSuiteFilter cipherSuiteFilter = Defaults.CIPHER_SUITE_FILTER;
@ -224,6 +236,8 @@ public class ClientConfig {
private List<HttpAddress> poolNodes = new ArrayList<>(); private List<HttpAddress> poolNodes = new ArrayList<>();
private Pool.PoolKeySelectorType poolKeySelectorType = Defaults.POOL_KEY_SELECTOR_TYPE;
private Integer poolNodeConnectionLimit; private Integer poolNodeConnectionLimit;
private Integer retriesPerPoolNode = Defaults.RETRIES_PER_NODE; private Integer retriesPerPoolNode = Defaults.RETRIES_PER_NODE;
@ -465,6 +479,15 @@ public class ClientConfig {
return sslContextProvider; return sslContextProvider;
} }
public ClientConfig setProtocols(String[] protocols) {
this.protocols = protocols;
return this;
}
public String[] getProtocols() {
return protocols;
}
public ClientConfig setCiphers(Iterable<String> ciphers) { public ClientConfig setCiphers(Iterable<String> ciphers) {
this.ciphers = ciphers; this.ciphers = ciphers;
return this; return this;
@ -536,6 +559,15 @@ public class ClientConfig {
return poolNodes; return poolNodes;
} }
public ClientConfig setPoolKeySelectorType(Pool.PoolKeySelectorType poolKeySelectorType) {
this.poolKeySelectorType = poolKeySelectorType;
return this;
}
public Pool.PoolKeySelectorType getPoolKeySelectorType() {
return poolKeySelectorType;
}
public ClientConfig addPoolNode(HttpAddress poolNodeAddress) { public ClientConfig addPoolNode(HttpAddress poolNodeAddress) {
this.poolNodes.add(poolNodeAddress); this.poolNodes.add(poolNodeAddress);
return this; return this;

View file

@ -309,6 +309,8 @@ public class Request {
private URL url; private URL url;
private String uri;
private HttpParameters uriParameters; private HttpParameters uriParameters;
private HttpParameters formParameters; private HttpParameters formParameters;
@ -327,21 +329,21 @@ public class Request {
Builder(ByteBufAllocator allocator) { Builder(ByteBufAllocator allocator) {
this.allocator = allocator; this.allocator = allocator;
httpMethod = DEFAULT_METHOD; this.httpMethod = DEFAULT_METHOD;
httpVersion = DEFAULT_HTTP_VERSION; this.httpVersion = DEFAULT_HTTP_VERSION;
userAgent = DEFAULT_USER_AGENT; this.userAgent = DEFAULT_USER_AGENT;
gzip = DEFAULT_GZIP; this.gzip = DEFAULT_GZIP;
keepalive = DEFAULT_KEEPALIVE; this.keepalive = DEFAULT_KEEPALIVE;
url = DEFAULT_URL; this.url = DEFAULT_URL;
timeoutInMillis = DEFAULT_TIMEOUT_MILLIS; this.timeoutInMillis = DEFAULT_TIMEOUT_MILLIS;
followRedirect = DEFAULT_FOLLOW_REDIRECT; this.followRedirect = DEFAULT_FOLLOW_REDIRECT;
maxRedirects = DEFAULT_MAX_REDIRECT; this.maxRedirects = DEFAULT_MAX_REDIRECT;
headers = new DefaultHttpHeaders(); this.headers = new DefaultHttpHeaders();
removeHeaders = new ArrayList<>(); this.removeHeaders = new ArrayList<>();
cookies = new HashSet<>(); this.cookies = new HashSet<>();
encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8); this.encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
uriParameters = new HttpParameters(); this.uriParameters = new HttpParameters();
formParameters = new HttpParameters(DEFAULT_FORM_CONTENT_TYPE); this.formParameters = new HttpParameters(DEFAULT_FORM_CONTENT_TYPE);
} }
public Builder setMethod(HttpMethod httpMethod) { public Builder setMethod(HttpMethod httpMethod) {
@ -394,7 +396,7 @@ public class Request {
} }
public Builder uri(String uri) { public Builder uri(String uri) {
this.url = url.resolve(uri); this.uri = uri;
return this; return this;
} }
@ -482,12 +484,22 @@ public class Request {
} }
public Builder text(String text) { public Builder text(String text) {
content(ByteBufUtil.writeUtf8(allocator, text), HttpHeaderValues.TEXT_PLAIN); ByteBuf byteBuf = ByteBufUtil.writeUtf8(allocator, text);
try {
content(byteBuf, HttpHeaderValues.TEXT_PLAIN);
} finally {
byteBuf.release();
}
return this; return this;
} }
public Builder json(String json) { public Builder json(String json) {
content(ByteBufUtil.writeUtf8(allocator, json), HttpHeaderValues.APPLICATION_JSON); ByteBuf byteBuf = ByteBufUtil.writeUtf8(allocator, json);
try {
content(byteBuf, HttpHeaderValues.APPLICATION_JSON);
} finally {
byteBuf.release();
}
return this; return this;
} }
@ -518,23 +530,12 @@ public class Request {
} }
public Request build() { public Request build() {
if (url == null) { DefaultHttpHeaders validatedHeaders = new DefaultHttpHeaders(true);
throw new IllegalStateException("URL not set"); if (url != null) {
}
// form parameters
if (!formParameters.isEmpty()) {
try {
// formParameters is already percent encoded
content(formParameters.getAsQueryString(false), formParameters.getContentType());
} catch (MalformedInputException | UnmappableCharacterException e) {
throw new IllegalArgumentException();
}
}
// attach user query parameters to URL // attach user query parameters to URL
URL.Builder mutator = url.mutator(); URL.Builder mutator = url.mutator();
uriParameters.forEach((k, v) -> v.forEach(value -> mutator.queryParam(k, value))); uriParameters.forEach((k, v) -> v.forEach(value -> mutator.queryParam(k, value)));
url = mutator.build(); url = mutator.build();
Objects.requireNonNull(url.getHost());
// let Netty's query string decoder/encoder work over the URL to add parameters given implicitly in url() // let Netty's query string decoder/encoder work over the URL to add parameters given implicitly in url()
String path = url.getPath(); String path = url.getPath();
String query = url.getQuery(); String query = url.getQuery();
@ -555,14 +556,14 @@ public class Request {
if (fragment != null && !fragment.isEmpty()) { if (fragment != null && !fragment.isEmpty()) {
sb.append('#').append(fragment); sb.append('#').append(fragment);
} }
String uri = sb.toString(); // the encoded form of path/query/fragment this.uri = sb.toString(); // the encoded form of path/query/fragment
DefaultHttpHeaders validatedHeaders = new DefaultHttpHeaders(true);
validatedHeaders.set(headers); validatedHeaders.set(headers);
String scheme = url.getScheme(); String scheme = url.getScheme();
if (httpVersion.majorVersion() == 2) { if (httpVersion.majorVersion() == 2) {
validatedHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme); validatedHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme);
} }
validatedHeaders.set(HttpHeaderNames.HOST, url.getHostInfo()); validatedHeaders.set(HttpHeaderNames.HOST, url.getHostInfo());
}
validatedHeaders.set(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); validatedHeaders.set(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
if (userAgent != null) { if (userAgent != null) {
validatedHeaders.set(HttpHeaderNames.USER_AGENT, userAgent); validatedHeaders.set(HttpHeaderNames.USER_AGENT, userAgent);
@ -570,7 +571,16 @@ public class Request {
if (gzip) { if (gzip) {
validatedHeaders.set(HttpHeaderNames.ACCEPT_ENCODING, "gzip"); validatedHeaders.set(HttpHeaderNames.ACCEPT_ENCODING, "gzip");
} }
int length = content != null ? content.capacity() : 0; // form parameters
if (!formParameters.isEmpty()) {
try {
// formParameters is already percent encoded
content(formParameters.getAsQueryString(false), formParameters.getContentType());
} catch (MalformedInputException | UnmappableCharacterException e) {
throw new IllegalArgumentException();
}
}
int length = content != null ? content.readableBytes() : 0;
if (!validatedHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) && !validatedHeaders.contains(HttpHeaderNames.TRANSFER_ENCODING)) { if (!validatedHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) && !validatedHeaders.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
if (length < 0) { if (length < 0) {
validatedHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); validatedHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, "chunked");

View file

@ -5,16 +5,14 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import org.xbib.netty.http.client.transport.Transport; import org.xbib.netty.http.client.transport.Transport;
import org.xbib.netty.http.common.DefaultHttpResponse;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> { public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
@Override @Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse httpResponse) throws Exception { public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
transport.responseReceived(ctx.channel(),null, transport.responseReceived(ctx.channel(), null, fullHttpResponse);
new DefaultHttpResponse(transport.getHttpAddress(), httpResponse.retain()));
} }
@Override @Override

View file

@ -5,6 +5,9 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http2.DefaultHttp2SettingsFrame; import io.netty.handler.codec.http2.DefaultHttp2SettingsFrame;
import io.netty.handler.codec.http2.Http2ConnectionPrefaceAndSettingsFrameWrittenEvent; import io.netty.handler.codec.http2.Http2ConnectionPrefaceAndSettingsFrameWrittenEvent;
import io.netty.handler.codec.http2.Http2FrameLogger; import io.netty.handler.codec.http2.Http2FrameLogger;
@ -74,6 +77,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
Http2MultiplexCodec multiplexCodec = multiplexCodecBuilder.autoAckSettingsFrame(true) .build(); Http2MultiplexCodec multiplexCodec = multiplexCodecBuilder.autoAckSettingsFrame(true) .build();
ChannelPipeline pipeline = ch.pipeline(); ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("client-multiplex", multiplexCodec); pipeline.addLast("client-multiplex", multiplexCodec);
// does not work
//pipeline.addLast("client-decompressor", new HttpContentDecompressor());
pipeline.addLast("client-messages", new ClientMessages()); pipeline.addLast("client-messages", new ClientMessages());
} }

View file

@ -6,7 +6,6 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpConversionUtil;
import org.xbib.netty.http.client.transport.Transport; import org.xbib.netty.http.client.transport.Transport;
import org.xbib.netty.http.common.DefaultHttpResponse;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> { public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
@ -15,8 +14,7 @@ public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpRe
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse httpResponse) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse httpResponse) throws Exception {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
transport.responseReceived(ctx.channel(), streamId, transport.responseReceived(ctx.channel(), streamId, httpResponse);
new DefaultHttpResponse(transport.getHttpAddress(), httpResponse.retain()));
} }
@Override @Override

View file

@ -111,6 +111,7 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
counts.put(node, 0); counts.put(node, 0);
failedCounts.put(node, 0); failedCounts.put(node, 0);
} }
logger.log(Level.FINE, "pool is up");
} }
public HttpVersion getVersion() { public HttpVersion getVersion() {
@ -174,7 +175,7 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
channelQueue.add(channel); channelQueue.add(channel);
} }
} else if (channel.isOpen() && close) { } else if (channel.isOpen() && close) {
logger.log(Level.FINE, "trying to close channel " + channel); logger.log(Level.FINE, "closing channel " + channel);
channel.close(); channel.close();
} }
if (channelPoolhandler != null) { if (channelPoolhandler != null) {
@ -211,7 +212,7 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
channelPromise.get(); channelPromise.get();
logger.log(Level.FINE, "goaway frame sent to " + channel); logger.log(Level.FINE, "goaway frame sent to " + channel);
} catch (ExecutionException e) { } catch (ExecutionException e) {
// ignore error if goaway can not be sent logger.log(Level.FINE, e.getMessage(), e);
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new IOException(e); throw new IOException(e);
} }
@ -235,9 +236,8 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
K key = null; K key = null;
Integer min = Integer.MAX_VALUE; Integer min = Integer.MAX_VALUE;
Integer next; Integer next;
//int r = ThreadLocalRandom.current().nextInt(numberOfNodes);
for (int j = 0; j < numberOfNodes; j++) { for (int j = 0; j < numberOfNodes; j++) {
K nextKey = poolKeySelector.key(); //nodes.get(j % numberOfNodes); K nextKey = poolKeySelector.key();
next = counts.get(nextKey); next = counts.get(nextKey);
if (next == null || next == 0) { if (next == null || next == 0) {
key = nextKey; key = nextKey;
@ -303,9 +303,9 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
private Channel poll() { private Channel poll() {
Queue<Channel> channelQueue; Queue<Channel> channelQueue;
Channel channel; Channel channel;
//int r = ThreadLocalRandom.current().nextInt(numberOfNodes);
for (int j = 0; j < numberOfNodes; j++) { for (int j = 0; j < numberOfNodes; j++) {
K key = poolKeySelector.key(); //nodes.get(j % numberOfNodes); K key = poolKeySelector.key();
logger.log(Level.FINE, "poll: key = " + key);
channelQueue = availableChannels.get(key); channelQueue = availableChannels.get(key);
if (channelQueue != null) { if (channelQueue != null) {
channel = channelQueue.poll(); channel = channelQueue.poll();
@ -319,10 +319,6 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
return null; return null;
} }
public enum PoolKeySelectorType {
RANDOM, ROUNDROBIN
}
private interface PoolKeySelector<K extends PoolKey> { private interface PoolKeySelector<K extends PoolKey> {
K key(); K key();
} }

View file

@ -9,4 +9,8 @@ public interface Pool<T> extends Closeable {
T acquire() throws Exception; T acquire() throws Exception;
void release(T t, boolean close) throws Exception; void release(T t, boolean close) throws Exception;
enum PoolKeySelectorType {
RANDOM, ROUNDROBIN
}
} }

View file

@ -78,8 +78,8 @@ abstract class BaseTransport implements Transport {
* @return completable future * @return completable future
*/ */
@Override @Override
public <T> CompletableFuture<T> execute(Request request, public <T> CompletableFuture<T> execute(Request request, Function<HttpResponse, T> supplier)
Function<HttpResponse, T> supplier) throws IOException { throws IOException {
Objects.requireNonNull(supplier); Objects.requireNonNull(supplier);
final CompletableFuture<T> completableFuture = new CompletableFuture<>(); final CompletableFuture<T> completableFuture = new CompletableFuture<>();
request.setResponseListener(response -> { request.setResponseListener(response -> {
@ -94,9 +94,11 @@ abstract class BaseTransport implements Transport {
} }
@Override @Override
public synchronized void close() { public void close() {
if (!channels.isEmpty()) {
get(); get();
} }
}
@Override @Override
public boolean isFailed() { public boolean isFailed() {
@ -133,8 +135,12 @@ abstract class BaseTransport implements Transport {
@Override @Override
public Transport get(long value, TimeUnit timeUnit) { public Transport get(long value, TimeUnit timeUnit) {
if (channels.isEmpty()) {
return this;
}
for (Map.Entry<String, Flow> entry : channelFlowMap.entrySet()) { for (Map.Entry<String, Flow> entry : channelFlowMap.entrySet()) {
Flow flow = entry.getValue(); Flow flow = entry.getValue();
if (!flow.isClosed()) {
for (Integer key : flow.keys()) { for (Integer key : flow.keys()) {
try { try {
flow.get(key).get(value, timeUnit); flow.get(key).get(value, timeUnit);
@ -153,6 +159,7 @@ abstract class BaseTransport implements Transport {
} }
flow.close(); flow.close();
} }
}
channels.values().forEach(channel -> { channels.values().forEach(channel -> {
try { try {
client.releaseChannel(channel, true); client.releaseChannel(channel, true);
@ -160,14 +167,14 @@ abstract class BaseTransport implements Transport {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.WARNING, e.getMessage(), e);
} }
}); });
channelFlowMap.clear();
channels.clear();
requests.clear();
return this; return this;
} }
@Override @Override
public void cancel() { public void cancel() {
if (channels.isEmpty()) {
return;
}
for (Map.Entry<String, Flow> entry : channelFlowMap.entrySet()) { for (Map.Entry<String, Flow> entry : channelFlowMap.entrySet()) {
Flow flow = entry.getValue(); Flow flow = entry.getValue();
for (Integer key : flow.keys()) { for (Integer key : flow.keys()) {
@ -198,6 +205,7 @@ abstract class BaseTransport implements Transport {
requests.clear(); requests.clear();
} }
@Override
public SSLSession getSession() { public SSLSession getSession() {
return sslSession; return sslSession;
} }

View file

@ -44,7 +44,7 @@ class Flow {
} }
Integer nextStreamId() { Integer nextStreamId() {
Integer streamId = counter.getAndAdd(2); int streamId = counter.getAndAdd(2);
if (streamId == Integer.MIN_VALUE) { if (streamId == Integer.MIN_VALUE) {
// reset if overflow, Java wraps atomic integers to Integer.MIN_VALUE // reset if overflow, Java wraps atomic integers to Integer.MIN_VALUE
// should we send a GOAWAY? // should we send a GOAWAY?
@ -65,6 +65,10 @@ class Flow {
map.clear(); map.clear();
} }
public boolean isClosed() {
return map.isEmpty();
}
@Override @Override
public String toString() { public String toString() {
return "[next=" + counter + ", " + map + "]"; return "[next=" + counter + ", " + map + "]";

View file

@ -3,6 +3,7 @@ package org.xbib.netty.http.client.transport;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http2.DefaultHttp2DataFrame; import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
@ -22,6 +23,7 @@ import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler;
import org.xbib.netty.http.client.handler.http2.Http2StreamFrameToHttpObjectCodec; import org.xbib.netty.http.client.handler.http2.Http2StreamFrameToHttpObjectCodec;
import org.xbib.netty.http.client.listener.CookieListener; import org.xbib.netty.http.client.listener.CookieListener;
import org.xbib.netty.http.client.listener.StatusListener; import org.xbib.netty.http.client.listener.StatusListener;
import org.xbib.netty.http.common.DefaultHttpResponse;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.listener.ResponseListener; import org.xbib.netty.http.client.listener.ResponseListener;
@ -50,7 +52,7 @@ public class Http2Transport extends BaseTransport {
super(client, httpAddress); super(client, httpAddress);
this.settingsPromise = httpAddress != null ? new CompletableFuture<>() : null; this.settingsPromise = httpAddress != null ? new CompletableFuture<>() : null;
final Transport transport = this; final Transport transport = this;
this.initializer = new ChannelInitializer<Channel>() { this.initializer = new ChannelInitializer<>() {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) {
ch.attr(TRANSPORT_ATTRIBUTE_KEY).set(transport); ch.attr(TRANSPORT_ATTRIBUTE_KEY).set(transport);
@ -104,6 +106,7 @@ public class Http2Transport extends BaseTransport {
childChannel.write(dataFrame); childChannel.write(dataFrame);
} }
childChannel.flush(); childChannel.flush();
client.getRequestCounter().incrementAndGet();
if (client.hasPooledConnections()) { if (client.hasPooledConnections()) {
client.releaseChannel(channel, false); client.releaseChannel(channel, false);
} }
@ -134,29 +137,38 @@ public class Http2Transport extends BaseTransport {
} }
@Override @Override
public void responseReceived(Channel channel, Integer streamId, HttpResponse httpResponse) { public void responseReceived(Channel channel, Integer streamId, FullHttpResponse fullHttpResponse) {
if (throwable != null) { if (throwable != null) {
logger.log(Level.WARNING, "throwable not null for response " + httpResponse, throwable); logger.log(Level.WARNING, "throwable is not null?", throwable);
return; return;
} }
if (streamId == null) { if (streamId == null) {
logger.log(Level.WARNING, "stream ID is null for response " + httpResponse); logger.log(Level.WARNING, "stream ID is null?");
return; return;
} }
DefaultHttpResponse httpResponse = new DefaultHttpResponse(httpAddress, fullHttpResponse);
client.getResponseCounter().incrementAndGet();
try {
// format of childchan channel ID is <parent channel ID> "/" <substream ID> // format of childchan channel ID is <parent channel ID> "/" <substream ID>
String channelId = channel.id().toString(); String channelId = channel.id().toString();
int pos = channelId.indexOf('/'); int pos = channelId.indexOf('/');
channelId = pos > 0 ? channelId.substring(0, pos) : channelId; channelId = pos > 0 ? channelId.substring(0, pos) : channelId;
Flow flow = channelFlowMap.get(channelId); Flow flow = channelFlowMap.get(channelId);
if (flow == null) { if (flow == null) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "flow is null? channelId = " + channelId);
}
return; return;
} }
String requestKey = getRequestKey(channelId, streamId); Request request = requests.remove(getRequestKey(channelId, streamId));
if (request == null) {
CompletableFuture<Boolean> promise = flow.get(streamId); CompletableFuture<Boolean> promise = flow.get(streamId);
if (promise != null) { if (promise != null) {
Request request = requests.get(requestKey); if (logger.isLoggable(Level.WARNING)) {
if (request == null) { logger.log(Level.WARNING, "request is null? channelId = " + channelId + " streamId = " + streamId);
promise.completeExceptionally(new IllegalStateException()); }
promise.completeExceptionally(new IllegalStateException("no request"));
}
} else { } else {
StatusListener statusListener = request.getStatusListener(); StatusListener statusListener = request.getStatusListener();
if (statusListener != null) { if (statusListener != null) {
@ -170,11 +182,12 @@ public class Http2Transport extends BaseTransport {
cookieListener.onCookie(cookie); cookieListener.onCookie(cookie);
} }
} }
CompletableFuture<Boolean> promise = flow.get(streamId);
try {
ResponseListener<HttpResponse> responseListener = request.getResponseListener(); ResponseListener<HttpResponse> responseListener = request.getResponseListener();
if (responseListener != null) { if (responseListener != null) {
responseListener.onResponse(httpResponse); responseListener.onResponse(httpResponse);
} }
try {
Request retryRequest = retry(request, httpResponse); Request retryRequest = retry(request, httpResponse);
if (retryRequest != null) { if (retryRequest != null) {
// retry transport, wait for completion // retry transport, wait for completion
@ -186,14 +199,25 @@ public class Http2Transport extends BaseTransport {
client.continuation(this, continueRequest); client.continuation(this, continueRequest);
} }
} }
if (promise != null) {
promise.complete(true); promise.complete(true);
} else {
// when transport is closed, flow map will be emptied
logger.log(Level.FINE, "promise is null, flow lost");
}
} catch (URLSyntaxException | IOException e) { } catch (URLSyntaxException | IOException e) {
if (promise != null) {
promise.completeExceptionally(e); promise.completeExceptionally(e);
} else {
logger.log(Level.FINE, "promise is null, can't abort flow");
}
} finally {
flow.remove(streamId);
} }
} }
} finally {
httpResponse.release();
} }
channelFlowMap.get(channelId).remove(streamId);
requests.remove(requestKey);
} }
@Override @Override

View file

@ -3,6 +3,7 @@ package org.xbib.netty.http.client.transport;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
@ -13,6 +14,7 @@ import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
import org.xbib.netty.http.client.cookie.ClientCookieEncoder; import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
import org.xbib.netty.http.client.listener.CookieListener; import org.xbib.netty.http.client.listener.CookieListener;
import org.xbib.netty.http.client.listener.StatusListener; import org.xbib.netty.http.client.listener.StatusListener;
import org.xbib.netty.http.common.DefaultHttpResponse;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.listener.ResponseListener; import org.xbib.netty.http.client.listener.ResponseListener;
@ -70,20 +72,24 @@ public class HttpTransport extends BaseTransport {
// flush after putting request into requests map // flush after putting request into requests map
if (channel.isWritable()) { if (channel.isWritable()) {
channel.writeAndFlush(fullHttpRequest); channel.writeAndFlush(fullHttpRequest);
client.getRequestCounter().incrementAndGet();
} }
return this; return this;
} }
@Override @Override
public void responseReceived(Channel channel, Integer streamId, HttpResponse httpResponse) { public void responseReceived(Channel channel, Integer streamId, FullHttpResponse fullHttpResponse) {
if (throwable != null) { if (throwable != null) {
logger.log(Level.WARNING, "throwable not null for response " + httpResponse, throwable); logger.log(Level.WARNING, "throwable not null", throwable);
return; return;
} }
if (requests.isEmpty()) { if (requests.isEmpty()) {
logger.log(Level.WARNING, "no request present for responding"); logger.log(Level.WARNING, "no request present for responding");
return; return;
} }
HttpResponse httpResponse = new DefaultHttpResponse(httpAddress, fullHttpResponse);
client.getResponseCounter().incrementAndGet();
try {
// streamID is expected to be null, last request on memory is expected to be current, remove request from memory // streamID is expected to be null, last request on memory is expected to be current, remove request from memory
Request request = requests.remove(requests.lastKey()); Request request = requests.remove(requests.lastKey());
if (request != null) { if (request != null) {
@ -128,6 +134,9 @@ public class HttpTransport extends BaseTransport {
if (promise != null) { if (promise != null) {
promise.complete(true); promise.complete(true);
} }
} finally {
httpResponse.release();
}
} }
@Override @Override

View file

@ -1,6 +1,7 @@
package org.xbib.netty.http.client.transport; package org.xbib.netty.http.client.transport;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
@ -29,7 +30,7 @@ public interface Transport {
void settingsReceived(Http2Settings http2Settings) throws IOException; void settingsReceived(Http2Settings http2Settings) throws IOException;
void responseReceived(Channel channel, Integer streamId, HttpResponse fullHttpResponse) throws IOException; void responseReceived(Channel channel, Integer streamId, FullHttpResponse fullHttpResponse) throws IOException;
void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers); void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers);

View file

@ -8,19 +8,26 @@ import org.xbib.netty.http.client.Request;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Provider;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class ConscryptTest { class ConscryptTest {
private static final Logger logger = Logger.getLogger(ConscryptTest.class.getName()); private static final Logger logger = Logger.getLogger(ConscryptTest.class.getName());
@Test @Test
void testConscrypt() throws IOException { void testConscrypt() throws IOException {
Provider provider = Conscrypt.newProviderBuilder()
.provideTrustManager(true)
.build();
Client client = Client.builder() Client client = Client.builder()
.setJdkSslProvider() .setJdkSslProvider()
.setSslContextProvider(Conscrypt.newProvider()) .setSslContextProvider(provider)
.setTlsProtocols(new String[]{"TLSv1.2"}) // disable TLSv1.3 for Conscrypt
.build(); .build();
logger.log(Level.INFO, client.getClientConfig().toString()); logger.log(Level.INFO, client.getClientConfig().toString());
try { try {

View file

@ -10,7 +10,7 @@ import java.nio.charset.StandardCharsets;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class CookieSetterHttpBinTest { class CookieSetterHttpBinTest {
private static final Logger logger = Logger.getLogger(CookieSetterHttpBinTest.class.getName()); private static final Logger logger = Logger.getLogger(CookieSetterHttpBinTest.class.getName());

View file

@ -11,7 +11,7 @@ import java.nio.charset.StandardCharsets;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class Http1Test { class Http1Test {
private static final Logger logger = Logger.getLogger(Http1Test.class.getName()); private static final Logger logger = Logger.getLogger(Http1Test.class.getName());
@ -22,7 +22,7 @@ class Http1Test {
.build(); .build();
try { try {
Request request = Request.get().url("http://xbib.org").build() Request request = Request.get().url("http://xbib.org").build()
.setResponseListener(resp -> logger.log(Level.INFO, .setResponseListener(resp -> logger.log(Level.FINE,
"got response: " + resp.getHeaders() + "got response: " + resp.getHeaders() +
resp.getBodyAsString(StandardCharsets.UTF_8) + resp.getBodyAsString(StandardCharsets.UTF_8) +
" status=" + resp.getStatus())); " status=" + resp.getStatus()));
@ -38,12 +38,12 @@ class Http1Test {
.build(); .build();
try { try {
Request request1 = Request.get().url("http://xbib.org").build() Request request1 = Request.get().url("http://xbib.org").build()
.setResponseListener(resp -> logger.log(Level.INFO, "got response: " + .setResponseListener(resp -> logger.log(Level.FINE, "got response: " +
resp.getBodyAsString(StandardCharsets.UTF_8))); resp.getBodyAsString(StandardCharsets.UTF_8)));
client.execute(request1).get(); client.execute(request1).get();
Request request2 = Request.get().url("http://google.com").setVersion("HTTP/1.1").build() Request request2 = Request.get().url("http://google.com").setVersion("HTTP/1.1").build()
.setResponseListener(resp -> logger.log(Level.INFO, "got response: " + .setResponseListener(resp -> logger.log(Level.FINE, "got response: " +
resp.getBodyAsString(StandardCharsets.UTF_8))); resp.getBodyAsString(StandardCharsets.UTF_8)));
client.execute(request2).get(); client.execute(request2).get();
} finally { } finally {
@ -59,12 +59,12 @@ class Http1Test {
Request request1 = Request.builder(HttpMethod.GET) Request request1 = Request.builder(HttpMethod.GET)
.url("http://xbib.org").setVersion("HTTP/1.1") .url("http://xbib.org").setVersion("HTTP/1.1")
.build() .build()
.setResponseListener(resp -> logger.log(Level.INFO, "got response: " + .setResponseListener(resp -> logger.log(Level.FINE, "got response: " +
resp.getHeaders() + " status=" +resp.getStatus())); resp.getHeaders() + " status=" +resp.getStatus()));
Request request2 = Request.builder(HttpMethod.GET) Request request2 = Request.builder(HttpMethod.GET)
.url("http://xbib.org").setVersion("HTTP/1.1") .url("http://xbib.org").setVersion("HTTP/1.1")
.build() .build()
.setResponseListener(resp -> logger.log(Level.INFO, "got response: " + .setResponseListener(resp -> logger.log(Level.FINE, "got response: " +
resp.getHeaders() + " status=" +resp.getStatus())); resp.getHeaders() + " status=" +resp.getStatus()));
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {

View file

@ -12,7 +12,7 @@ import java.util.logging.LogManager;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.logging.SimpleFormatter; import java.util.logging.SimpleFormatter;
public class NettyHttpExtension implements BeforeAllCallback { public class NettyHttpTestExtension implements BeforeAllCallback {
@Override @Override
public void beforeAll(ExtensionContext context) { public void beforeAll(ExtensionContext context) {
@ -20,6 +20,7 @@ public class NettyHttpExtension implements BeforeAllCallback {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); System.setProperty("io.netty.noUnsafe", Boolean.toString(true));
// System.setProperty("io.netty.leakDetection.level", "paranoid");
Level level = Level.INFO; Level level = Level.INFO;
System.setProperty("java.util.logging.SimpleFormatter.format", System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n"); "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n");

View file

@ -18,15 +18,12 @@ class RequestBuilderTest {
URI uri = URI.create("http://localhost"); URI uri = URI.create("http://localhost");
URI uri2 = uri.resolve("/path"); URI uri2 = uri.resolve("/path");
assertEquals("http://localhost/path", uri2.toString()); assertEquals("http://localhost/path", uri2.toString());
uri = URI.create("http://localhost/path1?a=b"); uri = URI.create("http://localhost/path1?a=b");
uri2 = uri.resolve("path2?c=d"); uri2 = uri.resolve("path2?c=d");
assertEquals("http://localhost/path2?c=d", uri2.toString()); assertEquals("http://localhost/path2?c=d", uri2.toString());
URL url = URL.from("http://localhost"); URL url = URL.from("http://localhost");
URL url2 = url.resolve("/path"); URL url2 = url.resolve("/path");
assertEquals("http://localhost/path", url2.toString()); assertEquals("http://localhost/path", url2.toString());
url = URL.from("http://localhost/path1?a=b"); url = URL.from("http://localhost/path1?a=b");
url2 = url.resolve("path2?c=d"); url2 = url.resolve("path2?c=d");
assertEquals("http://localhost/path2?c=d", url2.toString()); assertEquals("http://localhost/path2?c=d", url2.toString());
@ -35,13 +32,13 @@ class RequestBuilderTest {
@Test @Test
void testRelativeUri() { void testRelativeUri() {
Request.Builder httpRequestBuilder = Request.get(); Request.Builder httpRequestBuilder = Request.get();
httpRequestBuilder.url("https://localhost").uri("/path"); httpRequestBuilder.url("https://localhost/path");
assertEquals("/path", httpRequestBuilder.build().relative()); assertEquals("/path", httpRequestBuilder.build().relative());
httpRequestBuilder.uri("/foobar"); httpRequestBuilder.url("https://localhost/foobar");
assertEquals("/foobar", httpRequestBuilder.build().relative()); assertEquals("/foobar", httpRequestBuilder.build().relative());
httpRequestBuilder.uri("/path1?a=b"); httpRequestBuilder.url("/path1?a=b");
assertEquals("/path1?a=b", httpRequestBuilder.build().relative()); assertEquals("/path1?a=b", httpRequestBuilder.build().relative());
httpRequestBuilder.uri("/path2?c=d"); httpRequestBuilder.url("/path2?c=d");
assertEquals("/path2?c=d", httpRequestBuilder.build().relative()); assertEquals("/path2?c=d", httpRequestBuilder.build().relative());
} }
@ -98,7 +95,6 @@ class RequestBuilderTest {
assertEquals("?%20a%20=%20b", request.relative()); assertEquals("?%20a%20=%20b", request.relative());
assertEquals("https://google.com? a = b", request.url().toString()); assertEquals("https://google.com? a = b", request.url().toString());
assertEquals("https://google.com?%20a%20=%20b", request.url().toExternalForm()); assertEquals("https://google.com?%20a%20=%20b", request.url().toExternalForm());
request = Request.get() request = Request.get()
.url("https://google.com?%20a%20=%20b") .url("https://google.com?%20a%20=%20b")
.build(); .build();

View file

@ -11,14 +11,15 @@ import java.nio.charset.StandardCharsets;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class SecureHttpTest { class SecureHttpTest {
private static final Logger logger = Logger.getLogger(SecureHttpTest.class.getName()); private static final Logger logger = Logger.getLogger(SecureHttpTest.class.getName());
@Test @Test
void testHttp1() throws Exception { void testHttp1WithTlsV13() throws Exception {
Client client = Client.builder() Client client = Client.builder()
.setTlsProtocols(new String[] { "TLSv1.3" })
.build(); .build();
try { try {
Request request = Request.get().url("https://www.google.com/").build() Request request = Request.get().url("https://www.google.com/").build()
@ -37,13 +38,14 @@ class SecureHttpTest {
.build(); .build();
try { try {
Request request1 = Request.get().url("https://google.com").build() Request request1 = Request.get().url("https://google.com").build()
.setResponseListener(resp -> logger.log(Level.INFO, "got response: " + .setResponseListener(resp -> logger.log(Level.INFO, "got HTTP 1.1 response: " +
resp.getBodyAsString(StandardCharsets.UTF_8))); resp.getBodyAsString(StandardCharsets.UTF_8)));
client.execute(request1).get(); client.execute(request1).get();
// TODO decompression of frames
Request request2 = Request.get().url("https://google.com").setVersion("HTTP/2.0").build() Request request2 = Request.get().url("https://google.com").setVersion("HTTP/2.0").build()
.setResponseListener(resp -> logger.log(Level.INFO, "got response: " + .setResponseListener(resp -> logger.log(Level.INFO, "got HTTP/2 response: " +
resp.getBodyAsString(StandardCharsets.UTF_8))); resp.getHeaders() + resp.getBodyAsString(StandardCharsets.UTF_8)));
client.execute(request2).get(); client.execute(request2).get();
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();

View file

@ -11,7 +11,7 @@ import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ThreadLeakTest { class ThreadLeakTest {

View file

@ -4,14 +4,14 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.test.NettyHttpExtension; import org.xbib.netty.http.client.test.NettyHttpTestExtension;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
public class AkamaiTest { public class AkamaiTest {
private static Logger logger = Logger.getLogger(AkamaiTest.class.getName()); private static Logger logger = Logger.getLogger(AkamaiTest.class.getName());

View file

@ -22,6 +22,8 @@ import io.netty.util.AttributeKey;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.test.NettyHttpTestExtension;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -39,30 +41,14 @@ import java.util.logging.LogManager;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.logging.SimpleFormatter; import java.util.logging.SimpleFormatter;
@ExtendWith(NettyHttpTestExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class SimpleHttp1Test { class SimpleHttp1Test {
private static final Logger logger = Logger.getLogger(SimpleHttp1Test.class.getName()); private static final Logger logger = Logger.getLogger(SimpleHttp1Test.class.getName());
static {
System.setProperty("io.netty.leakDetection.level", "paranoid");
System.setProperty("io.netty.noKeySetOptimization", Boolean.toString(true));
System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %2$s %5$s %6$s%n");
LogManager.getLogManager().reset();
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
rootLogger.addHandler(handler);
rootLogger.setLevel(Level.ALL);
for (Handler h : rootLogger.getHandlers()) {
handler.setFormatter(new SimpleFormatter());
h.setLevel(Level.ALL);
}
}
@AfterAll @AfterAll
public void checkThreads() { void checkThreads() {
Set<Thread> threadSet = Thread.getAllStackTraces().keySet(); Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
logger.log(Level.INFO, "threads = " + threadSet.size() ); logger.log(Level.INFO, "threads = " + threadSet.size() );
threadSet.forEach( thread -> { threadSet.forEach( thread -> {
@ -117,16 +103,12 @@ public class SimpleHttp1Test {
private final Bootstrap bootstrap; private final Bootstrap bootstrap;
private final HttpResponseHandler httpResponseHandler;
private final Initializer initializer;
private final List<HttpTransport> transports; private final List<HttpTransport> transports;
Client() { Client() {
eventLoopGroup = new NioEventLoopGroup(); eventLoopGroup = new NioEventLoopGroup();
httpResponseHandler = new HttpResponseHandler(); HttpResponseHandler httpResponseHandler = new HttpResponseHandler();
initializer = new Initializer(httpResponseHandler); Initializer initializer = new Initializer(httpResponseHandler);
bootstrap = new Bootstrap() bootstrap = new Bootstrap()
.group(eventLoopGroup) .group(eventLoopGroup)
.channel(NioSocketChannel.class) .channel(NioSocketChannel.class)
@ -154,7 +136,7 @@ public class SimpleHttp1Test {
return transport; return transport;
} }
synchronized void close() { void close() {
for (HttpTransport transport : transports) { for (HttpTransport transport : transports) {
transport.close(); transport.close();
} }

View file

@ -5,13 +5,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.test.NettyHttpExtension; import org.xbib.netty.http.client.test.NettyHttpTestExtension;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class Http2PushTest { class Http2PushTest {
private static final Logger logger = Logger.getLogger(Http2PushTest.class.getName()); private static final Logger logger = Logger.getLogger(Http2PushTest.class.getName());

View file

@ -6,9 +6,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.listener.ResponseListener; import org.xbib.netty.http.client.listener.ResponseListener;
import org.xbib.netty.http.client.test.NettyHttpExtension; import org.xbib.netty.http.client.test.NettyHttpTestExtension;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.common.HttpResponse;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -19,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class PooledClientTest { class PooledClientTest {
private static final Logger logger = Logger.getLogger(PooledClientTest.class.getName()); private static final Logger logger = Logger.getLogger(PooledClientTest.class.getName());
@ -28,14 +29,14 @@ class PooledClientTest {
void testPooledClientWithSingleNode() throws IOException { void testPooledClientWithSingleNode() throws IOException {
int loop = 10; int loop = 10;
int threads = Runtime.getRuntime().availableProcessors(); int threads = Runtime.getRuntime().availableProcessors();
URL url = URL.from("https://fl-test.hbz-nrw.de/app/fl"); URL url = URL.from("https://fl-test.hbz-nrw.de/");
HttpAddress httpAddress = HttpAddress.of(url, HttpVersion.valueOf("HTTP/2.0")); HttpAddress httpAddress = HttpAddress.of(url, HttpVersion.valueOf("HTTP/2.0"));
Client client = Client.builder() Client client = Client.builder()
.addPoolNode(httpAddress) .addPoolNode(httpAddress)
.setPoolNodeConnectionLimit(threads) .setPoolNodeConnectionLimit(threads)
.build(); .build();
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
ResponseListener responseListener = resp -> { ResponseListener<HttpResponse> responseListener = resp -> {
String response = resp.getBodyAsString(StandardCharsets.UTF_8); String response = resp.getBodyAsString(StandardCharsets.UTF_8);
count.getAndIncrement(); count.getAndIncrement();
}; };

View file

@ -5,13 +5,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.test.NettyHttpExtension; import org.xbib.netty.http.client.test.NettyHttpTestExtension;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class WebtideTest { class WebtideTest {
private static final Logger logger = Logger.getLogger(WebtideTest.class.getName()); private static final Logger logger = Logger.getLogger(WebtideTest.class.getName());

View file

@ -1,5 +0,0 @@
handlers = java.util.logging.ConsoleHandler
.level = FINE
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = %1$tFT%1$tT.%1$tL%1$tz [%4$-11s] [%3$s] %5$s %6$s%n

View file

@ -19,9 +19,9 @@ public class DefaultHttpResponse implements HttpResponse {
public DefaultHttpResponse(HttpAddress httpAddress, FullHttpResponse fullHttpResponse) { public DefaultHttpResponse(HttpAddress httpAddress, FullHttpResponse fullHttpResponse) {
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
this.fullHttpResponse = fullHttpResponse; this.fullHttpResponse = fullHttpResponse.retain();
this.httpStatus = new HttpStatus(fullHttpResponse.status()); this.httpStatus = new HttpStatus(this.fullHttpResponse.status());
this.httpHeaders = new DefaultHttpHeaders(fullHttpResponse.headers()); this.httpHeaders = new DefaultHttpHeaders(this.fullHttpResponse.headers());
} }
@Override @Override
@ -41,7 +41,7 @@ public class DefaultHttpResponse implements HttpResponse {
@Override @Override
public ByteBuf getBody() { public ByteBuf getBody() {
return fullHttpResponse.content().asReadOnly(); return fullHttpResponse.content();
} }
@Override @Override
@ -53,4 +53,9 @@ public class DefaultHttpResponse implements HttpResponse {
public String getBodyAsString(Charset charset) { public String getBodyAsString(Charset charset) {
return getBody().toString(charset); return getBody().toString(charset);
} }
@Override
public void release() {
this.fullHttpResponse.release();
}
} }

View file

@ -18,4 +18,6 @@ public interface HttpResponse {
InputStream getBodyAsStream(); InputStream getBodyAsStream();
String getBodyAsString(Charset charset); String getBodyAsString(Charset charset);
void release();
} }

View file

@ -1,5 +1,5 @@
package org.xbib.netty.http.common.cookie; package org.xbib.netty.http.common.cookie;
public enum SameSite { public enum SameSite {
STRICT, LAX STRICT, LAX, NONE
} }

View file

@ -30,13 +30,15 @@ import org.xbib.netty.http.server.transport.Transport;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* HTTP server. * HTTP server.
*/ */
public final class Server { public final class Server implements AutoCloseable {
private static final Logger logger = Logger.getLogger(Server.class.getName()); private static final Logger logger = Logger.getLogger(Server.class.getName());
@ -54,6 +56,10 @@ public final class Server {
} }
} }
private static final AtomicLong requestCounter = new AtomicLong();
private static final AtomicLong responseCounter = new AtomicLong();
private final ServerConfig serverConfig; private final ServerConfig serverConfig;
private final ByteBufAllocator byteBufAllocator; private final ByteBufAllocator byteBufAllocator;
@ -136,16 +142,16 @@ public final class Server {
/** /**
* Returns the named server with the given name. * Returns the named server with the given name.
* *
* @param name the name of the virtual host to return, or null for * @param name the name of the virtual host to return or null for the
* the default virtual host * default domain
* @return the virtual host with the given name, or null if it doesn't exist * @return the virtual host with the given name or the default domain
*/ */
public Domain getNamedServer(String name) { public Domain getNamedServer(String name) {
return serverConfig.getDomain(name); Domain domain = serverConfig.getDomain(name);
if (domain == null) {
domain = serverConfig.getDefaultDomain();
} }
return domain;
public Domain getDefaultNamedServer() {
return serverConfig.getDefaultDomain();
} }
/** /**
@ -170,7 +176,7 @@ public final class Server {
logger.log(level, () -> "OpenSSL available: " + OpenSsl.isAvailable()); logger.log(level, () -> "OpenSSL available: " + OpenSsl.isAvailable());
logger.log(level, () -> "OpenSSL ALPN support: " + OpenSsl.isAlpnSupported()); logger.log(level, () -> "OpenSSL ALPN support: " + OpenSsl.isAlpnSupported());
logger.log(level, () -> "Installed ciphers on default server: " + logger.log(level, () -> "Installed ciphers on default server: " +
(serverConfig.getAddress().isSecure() ? getDefaultNamedServer().getSslContext().cipherSuites() : "")); (serverConfig.getAddress().isSecure() ? serverConfig.getDefaultDomain().getSslContext().cipherSuites() : ""));
logger.log(level, () -> "Local host name: " + NetworkUtils.getLocalHostName("localhost")); logger.log(level, () -> "Local host name: " + NetworkUtils.getLocalHostName("localhost"));
logger.log(level, () -> "Parent event loop group: " + parentEventLoopGroup + " threads=" + serverConfig.getParentThreadCount()); logger.log(level, () -> "Parent event loop group: " + parentEventLoopGroup + " threads=" + serverConfig.getParentThreadCount());
logger.log(level, () -> "Child event loop group: " + childEventLoopGroup + " threads=" +serverConfig.getChildThreadCount()); logger.log(level, () -> "Child event loop group: " + childEventLoopGroup + " threads=" +serverConfig.getChildThreadCount());
@ -179,18 +185,49 @@ public final class Server {
logger.log(level, NetworkUtils::displayNetworkInterfaces); logger.log(level, NetworkUtils::displayNetworkInterfaces);
} }
public AtomicLong getRequestCounter() {
return requestCounter;
}
public AtomicLong getResponseCounter() {
return responseCounter;
}
public Transport newTransport(HttpVersion httpVersion) { public Transport newTransport(HttpVersion httpVersion) {
return httpVersion.majorVersion() == 1 ? new HttpTransport(this) : new Http2Transport(this); return httpVersion.majorVersion() == 1 ? new HttpTransport(this) : new Http2Transport(this);
} }
public synchronized void shutdownGracefully() throws IOException { @Override
logger.log(Level.FINE, "shutting down gracefully"); public void close() {
try {
shutdownGracefully();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
public void shutdownGracefully() throws IOException {
shutdownGracefully(30L, TimeUnit.SECONDS);
}
public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException {
logger.log(Level.FINE, "shutting down");
// first, shut down threads, then server socket // first, shut down threads, then server socket
childEventLoopGroup.shutdownGracefully(); childEventLoopGroup.shutdownGracefully(1L, amount, timeUnit);
parentEventLoopGroup.shutdownGracefully(); try {
childEventLoopGroup.awaitTermination(amount, timeUnit);
} catch (InterruptedException e) {
throw new IOException(e);
}
parentEventLoopGroup.shutdownGracefully(1L, amount, timeUnit);
try {
childEventLoopGroup.awaitTermination(amount, timeUnit);
} catch (InterruptedException e) {
throw new IOException(e);
}
try { try {
if (channelFuture != null) { if (channelFuture != null) {
// close channel and wait // close channel and wait for unbind
channelFuture.channel().closeFuture().sync(); channelFuture.channel().closeFuture().sync();
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -441,5 +478,4 @@ public final class Server {
return new Server(serverConfig, byteBufAllocator, parentEventLoopGroup, childEventLoopGroup, socketChannelClass); return new Server(serverConfig, byteBufAllocator, parentEventLoopGroup, childEventLoopGroup, socketChannelClass);
} }
} }
} }

View file

@ -4,11 +4,17 @@ import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.Epoll;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.ssl.CipherSuiteFilter;
import io.netty.handler.ssl.SslProvider;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.security.SecurityUtil;
import java.security.KeyStore;
import java.security.Provider;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import javax.net.ssl.TrustManagerFactory;
public class ServerConfig { public class ServerConfig {
@ -138,6 +144,31 @@ public class ServerConfig {
*/ */
boolean INSTALL_HTTP_UPGRADE2 = false; boolean INSTALL_HTTP_UPGRADE2 = false;
/**
* Default SSL provider.
*/
SslProvider SSL_PROVIDER = SecurityUtil.Defaults.DEFAULT_SSL_PROVIDER;
/**
* Default SSL context provider (for JDK SSL only).
*/
Provider SSL_CONTEXT_PROVIDER = null;
/**
* Transport layer security protocol versions.
*/
String[] PROTOCOLS = new String[] { "TLSv1.3", "TLSv1.2" };
/**
* Default ciphers. We care about HTTP/2.
*/
Iterable<String> CIPHERS = SecurityUtil.Defaults.DEFAULT_CIPHERS;
/**
* Default cipher suite filter.
*/
CipherSuiteFilter CIPHER_SUITE_FILTER = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
} }
private HttpAddress httpAddress = Defaults.ADDRESS; private HttpAddress httpAddress = Defaults.ADDRESS;
@ -190,6 +221,20 @@ public class ServerConfig {
private final Map<String, Domain> domains; private final Map<String, Domain> domains;
private SslProvider sslProvider = Defaults.SSL_PROVIDER;
private Provider sslContextProvider = Defaults.SSL_CONTEXT_PROVIDER;
private String[] protocols = Defaults.PROTOCOLS;
private Iterable<String> ciphers = Defaults.CIPHERS;
private CipherSuiteFilter cipherSuiteFilter = Defaults.CIPHER_SUITE_FILTER;
private TrustManagerFactory trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
private KeyStore trustManagerKeyStore = null;
public ServerConfig() { public ServerConfig() {
this.domains = new LinkedHashMap<>(); this.domains = new LinkedHashMap<>();
} }
@ -425,13 +470,84 @@ public class ServerConfig {
return http2Settings; return http2Settings;
} }
public ServerConfig setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
this.trustManagerFactory = trustManagerFactory;
return this;
}
public TrustManagerFactory getTrustManagerFactory() {
return trustManagerFactory;
}
public ServerConfig setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
this.trustManagerKeyStore = trustManagerKeyStore;
return this;
}
public KeyStore getTrustManagerKeyStore() {
return trustManagerKeyStore;
}
public ServerConfig setSslProvider(SslProvider sslProvider) {
this.sslProvider = sslProvider;
return this;
}
public SslProvider getSslProvider() {
return sslProvider;
}
public ServerConfig setJdkSslProvider() {
this.sslProvider = SslProvider.JDK;
return this;
}
public ServerConfig setOpenSSLSslProvider() {
this.sslProvider = SslProvider.OPENSSL;
return this;
}
public ServerConfig setSslContextProvider(Provider sslContextProvider) {
this.sslContextProvider = sslContextProvider;
return this;
}
public Provider getSslContextProvider() {
return sslContextProvider;
}
public ServerConfig setProtocols(String[] protocols) {
this.protocols = protocols;
return this;
}
public String[] getProtocols() {
return protocols;
}
public ServerConfig setCiphers(Iterable<String> ciphers) {
this.ciphers = ciphers;
return this;
}
public Iterable<String> getCiphers() {
return ciphers;
}
public ServerConfig setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
this.cipherSuiteFilter = cipherSuiteFilter;
return this;
}
public CipherSuiteFilter getCipherSuiteFilter() {
return cipherSuiteFilter;
}
public ServerConfig putDomain(Domain domain) { public ServerConfig putDomain(Domain domain) {
synchronized (domains) {
domains.put(domain.getName(), domain); domains.put(domain.getName(), domain);
for (String alias : domain.getAliases()) { for (String alias : domain.getAliases()) {
domains.put(alias, domain); domains.put(alias, domain);
} }
}
return this; return this;
} }
@ -445,12 +561,10 @@ public class ServerConfig {
} }
public ServerConfig removeDomain(Domain domain) { public ServerConfig removeDomain(Domain domain) {
synchronized (domains) {
domains.remove(domain.getName()); domains.remove(domain.getName());
for (String alias : domain.getAliases()) { for (String alias : domain.getAliases()) {
domains.remove(alias, domain); domains.remove(alias, domain);
} }
}
return this; return this;
} }

View file

@ -18,8 +18,6 @@ public interface ServerRequest {
URL getURL(); URL getURL();
Channel getChannel();
HttpEndpointDescriptor getEndpointDescriptor(); HttpEndpointDescriptor getEndpointDescriptor();
void setContext(List<String> context); void setContext(List<String> context);
@ -30,6 +28,8 @@ public interface ServerRequest {
Map<String, String> getPathParameters(); Map<String, String> getPathParameters();
String getRequestURI();
HttpMethod getMethod(); HttpMethod getMethod();
HttpHeaders getHeaders(); HttpHeaders getHeaders();
@ -44,7 +44,7 @@ public interface ServerRequest {
Integer getStreamId(); Integer getStreamId();
Integer getRequestId(); Long getRequestId();
SSLSession getSession(); SSLSession getSession();

View file

@ -66,10 +66,18 @@ public interface ServerResponse {
} }
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, String text) { static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, String text) {
ByteBuf byteBuf = ByteBufUtil.writeUtf8(serverResponse.getChannelHandlerContext().alloc(), text);
serverResponse.withStatus(status) serverResponse.withStatus(status)
.withContentType(contentType) .withContentType(contentType)
.withCharset(StandardCharsets.UTF_8) .withCharset(StandardCharsets.UTF_8)
.write(ByteBufUtil.writeUtf8(serverResponse.getChannelHandlerContext().alloc(), text)); .write(byteBuf);
}
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, ByteBuf byteBuf) {
serverResponse.withStatus(status)
.withContentType(contentType)
.withCharset(StandardCharsets.UTF_8)
.write(byteBuf);
} }
static void write(ServerResponse serverResponse, static void write(ServerResponse serverResponse,
@ -79,10 +87,11 @@ public interface ServerResponse {
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType,
CharBuffer charBuffer, Charset charset) { CharBuffer charBuffer, Charset charset) {
ByteBuf byteBuf = ByteBufUtil.encodeString(serverResponse.getChannelHandlerContext().alloc(), charBuffer, charset);
serverResponse.withStatus(status) serverResponse.withStatus(status)
.withContentType(contentType) .withContentType(contentType)
.withCharset(charset) .withCharset(charset)
.write(ByteBufUtil.encodeString(serverResponse.getChannelHandlerContext().alloc(), charBuffer, charset)); .write(byteBuf);
} }
String EMPTY_STRING = ""; String EMPTY_STRING = "";

View file

@ -13,9 +13,9 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
private final String contentType; private final String contentType;
public HttpEndpointDescriptor(HttpServerRequest serverRequest) { public HttpEndpointDescriptor(HttpServerRequest serverRequest) {
this.path = extractPath(serverRequest.getRequest().uri()); this.path = extractPath(serverRequest.getRequestURI());
this.method = serverRequest.getRequest().method().name(); this.method = serverRequest.getMethod().name();
this.contentType = serverRequest.getRequest().headers().get(CONTENT_TYPE); this.contentType = serverRequest.getHeaders().get(CONTENT_TYPE);
} }
public String getPath() { public String getPath() {

View file

@ -0,0 +1,45 @@
package org.xbib.netty.http.server.handler;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.SniHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.Mapping;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.ServerConfig;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
public class ExtendedSNIHandler extends SniHandler {
private final ServerConfig serverConfig;
private final HttpAddress httpAddress;
public ExtendedSNIHandler(Mapping<? super String, ? extends SslContext> mapping,
ServerConfig serverConfig, HttpAddress httpAddress) {
super(mapping);
this.serverConfig = serverConfig;
this.httpAddress = httpAddress;
}
@Override
protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocator) {
return newSslHandler(context, serverConfig, allocator, httpAddress);
}
private static SslHandler newSslHandler(SslContext sslContext,
ServerConfig serverConfig,
ByteBufAllocator allocator,
HttpAddress httpAddress) {
InetSocketAddress peer = httpAddress.getInetSocketAddress();
SslHandler sslHandler = sslContext.newHandler(allocator, peer.getHostName(), peer.getPort());
SSLEngine engine = sslHandler.engine();
SSLParameters params = engine.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(params);
engine.setEnabledProtocols(serverConfig.getProtocols());
return sslHandler;
}
}

View file

@ -15,13 +15,13 @@ import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.ssl.SniHandler;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.DomainNameMapping; import io.netty.util.DomainNameMapping;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerConfig; import org.xbib.netty.http.server.ServerConfig;
import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
import org.xbib.netty.http.server.handler.TrafficLoggingHandler; import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
import org.xbib.netty.http.server.transport.Transport; import org.xbib.netty.http.server.transport.Transport;
@ -39,7 +39,6 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
private final HttpAddress httpAddress; private final HttpAddress httpAddress;
private final HttpHandler httpHandler;
private final DomainNameMapping<SslContext> domainNameMapping; private final DomainNameMapping<SslContext> domainNameMapping;
@ -49,7 +48,6 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
this.server = server; this.server = server;
this.serverConfig = server.getServerConfig(); this.serverConfig = server.getServerConfig();
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
this.httpHandler = new HttpHandler(server);
this.domainNameMapping = domainNameMapping; this.domainNameMapping = domainNameMapping;
} }
@ -71,7 +69,8 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
} }
private void configureEncrypted(SocketChannel channel) { private void configureEncrypted(SocketChannel channel) {
channel.pipeline().addLast("sni-handker", new SniHandler(domainNameMapping)); channel.pipeline().addLast("sni-handler",
new ExtendedSNIHandler(domainNameMapping, serverConfig, httpAddress));
configureCleartext(channel); configureCleartext(channel);
} }
@ -92,7 +91,7 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
pipeline.addLast("http-server-aggregator", httpObjectAggregator); pipeline.addLast("http-server-aggregator", httpObjectAggregator);
pipeline.addLast("http-server-pipelining", new HttpPipeliningHandler(1024)); pipeline.addLast("http-server-pipelining", new HttpPipeliningHandler(1024));
pipeline.addLast("http-server-chunked-write", new ChunkedWriteHandler()); pipeline.addLast("http-server-chunked-write", new ChunkedWriteHandler());
pipeline.addLast(httpHandler); pipeline.addLast("http-server-handler", new HttpHandler(server));
} }
@Sharable @Sharable
@ -108,12 +107,14 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.log(Level.FINE, "channelRead: " + msg.getClass().getName());
if (msg instanceof HttpPipelinedRequest) { if (msg instanceof HttpPipelinedRequest) {
HttpPipelinedRequest httpPipelinedRequest = (HttpPipelinedRequest) msg; HttpPipelinedRequest httpPipelinedRequest = (HttpPipelinedRequest) msg;
if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest) { if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) httpPipelinedRequest.getRequest(); FullHttpRequest fullHttpRequest = (FullHttpRequest) httpPipelinedRequest.getRequest();
Transport transport = server.newTransport(fullHttpRequest.protocolVersion()); Transport transport = server.newTransport(fullHttpRequest.protocolVersion());
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId()); transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
fullHttpRequest.release();
} }
} else { } else {
super.channelRead(ctx, msg); super.channelRead(ctx, msg);

View file

@ -8,6 +8,8 @@ import io.netty.handler.codec.http.LastHttpContent;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* Implements HTTP pipelining ordering, ensuring that responses are completely served in the same order as their * Implements HTTP pipelining ordering, ensuring that responses are completely served in the same order as their
@ -19,6 +21,8 @@ public class HttpPipeliningHandler extends ChannelDuplexHandler {
private final int pipelineCapacity; private final int pipelineCapacity;
private final Lock lock;
private final Queue<HttpPipelinedResponse> httpPipelinedResponses; private final Queue<HttpPipelinedResponse> httpPipelinedResponses;
private final AtomicInteger requestCounter; private final AtomicInteger requestCounter;
@ -32,6 +36,7 @@ public class HttpPipeliningHandler extends ChannelDuplexHandler {
*/ */
public HttpPipeliningHandler(int pipelineCapacity) { public HttpPipeliningHandler(int pipelineCapacity) {
this.pipelineCapacity = pipelineCapacity; this.pipelineCapacity = pipelineCapacity;
this.lock = new ReentrantLock();
this.httpPipelinedResponses = new PriorityQueue<>(3); this.httpPipelinedResponses = new PriorityQueue<>(3);
this.requestCounter = new AtomicInteger(); this.requestCounter = new AtomicInteger();
this.writtenRequests = new AtomicInteger(); this.writtenRequests = new AtomicInteger();
@ -48,7 +53,8 @@ public class HttpPipeliningHandler extends ChannelDuplexHandler {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof HttpPipelinedResponse) { if (msg instanceof HttpPipelinedResponse) {
boolean channelShouldClose = false; boolean channelShouldClose = false;
synchronized (httpPipelinedResponses) { lock.lock();
try {
if (httpPipelinedResponses.size() < pipelineCapacity) { if (httpPipelinedResponses.size() < pipelineCapacity) {
HttpPipelinedResponse currentEvent = (HttpPipelinedResponse) msg; HttpPipelinedResponse currentEvent = (HttpPipelinedResponse) msg;
httpPipelinedResponses.add(currentEvent); httpPipelinedResponses.add(currentEvent);
@ -64,6 +70,8 @@ public class HttpPipeliningHandler extends ChannelDuplexHandler {
} else { } else {
channelShouldClose = true; channelShouldClose = true;
} }
} finally {
lock.unlock();
} }
if (channelShouldClose) { if (channelShouldClose) {
ctx.close(); ctx.close();

View file

@ -22,7 +22,6 @@ import io.netty.handler.codec.http2.Http2MultiplexCodecBuilder;
import io.netty.handler.codec.http2.Http2ServerUpgradeCodec; import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.ssl.SniHandler;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
@ -30,6 +29,7 @@ import io.netty.util.DomainNameMapping;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerConfig; import org.xbib.netty.http.server.ServerConfig;
import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
import org.xbib.netty.http.server.handler.TrafficLoggingHandler; import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
import org.xbib.netty.http.server.transport.Transport; import org.xbib.netty.http.server.transport.Transport;
@ -76,7 +76,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
} }
private void configureEncrypted(Channel channel) { private void configureEncrypted(Channel channel) {
channel.pipeline().addLast("sni-handler", new SniHandler(domainNameMapping)); channel.pipeline().addLast("sni-handler",
new ExtendedSNIHandler(domainNameMapping, serverConfig, httpAddress));
configureCleartext(channel); configureCleartext(channel);
} }
@ -123,19 +124,16 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
pipeline.addLast("server-messages", new ServerMessages()); pipeline.addLast("server-messages", new ServerMessages());
} }
class ServerRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> { static class ServerRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
if (server.getServerConfig().isDebug() && logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "HTTP/2 server pipeline: " + ctx.channel().pipeline().names());
}
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
transport.requestReceived(ctx, fullHttpRequest); transport.requestReceived(ctx, fullHttpRequest, null);
} }
} }
class ServerMessages extends ChannelInboundHandlerAdapter { static class ServerMessages extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

View file

@ -10,8 +10,6 @@ import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -19,8 +17,6 @@ abstract class BaseTransport implements Transport {
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName()); private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
static final AtomicInteger requestCounter = new AtomicInteger();
protected final Server server; protected final Server server;
BaseTransport(Server server) { BaseTransport(Server server) {
@ -55,10 +51,7 @@ abstract class BaseTransport implements Transport {
// return a continue response before reading body // return a continue response before reading body
String expect = reqHeaders.get(HttpHeaderNames.EXPECT); String expect = reqHeaders.get(HttpHeaderNames.EXPECT);
if (expect != null) { if (expect != null) {
if ("100-continue".equalsIgnoreCase(expect)) { if (!"100-continue".equalsIgnoreCase(expect)) {
//ServerResponse tempResp = new ServerResponse(serverResponse);
//tempResp.sendHeaders(100);
} else {
// RFC2616#14.20: if unknown expect, send 417 // RFC2616#14.20: if unknown expect, send 417
ServerResponse.write(serverResponse, HttpResponseStatus.EXPECTATION_FAILED); ServerResponse.write(serverResponse, HttpResponseStatus.EXPECTATION_FAILED);
return false; return false;
@ -71,17 +64,4 @@ abstract class BaseTransport implements Transport {
} }
return true; return true;
} }
/**
* Handles a request according to the request method.
* @param domain the named server
* @param serverRequest the request
* @param serverResponse the response (into which the response is written)
* @throws IOException if and error occurs
*/
static void handle(Domain domain, HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
// create server URL and parse parameters from query string, path, and parse body, if exists
serverRequest.handleParameters();
domain.handle(serverRequest, serverResponse);
}
} }

View file

@ -18,6 +18,7 @@ import io.netty.handler.codec.http2.Http2HeadersFrame;
import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.handler.stream.ChunkedInput; import io.netty.handler.stream.ChunkedInput;
import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.Cookie;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerName; import org.xbib.netty.http.server.ServerName;
import org.xbib.netty.http.server.ServerRequest; import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
@ -35,6 +36,8 @@ public class Http2ServerResponse implements ServerResponse {
private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName()); private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName());
private final Server server;
private final ServerRequest serverRequest; private final ServerRequest serverRequest;
private final ChannelHandlerContext ctx; private final ChannelHandlerContext ctx;
@ -43,13 +46,10 @@ public class Http2ServerResponse implements ServerResponse {
private HttpResponseStatus httpResponseStatus; private HttpResponseStatus httpResponseStatus;
private ByteBufOutputStream byteBufOutputStream; Http2ServerResponse(Server server, HttpServerRequest serverRequest, ChannelHandlerContext ctx) {
this.server = server;
public Http2ServerResponse(HttpServerRequest serverRequest) {
Objects.requireNonNull(serverRequest);
Objects.requireNonNull(serverRequest.getChannelHandlerContext());
this.serverRequest = serverRequest; this.serverRequest = serverRequest;
this.ctx = serverRequest.getChannelHandlerContext(); this.ctx = ctx;
this.headers = new DefaultHttp2Headers(); this.headers = new DefaultHttp2Headers();
} }
@ -101,8 +101,7 @@ public class Http2ServerResponse implements ServerResponse {
@Override @Override
public ByteBufOutputStream getOutputStream() { public ByteBufOutputStream getOutputStream() {
this.byteBufOutputStream = new ByteBufOutputStream(ctx.alloc().buffer()); return new ByteBufOutputStream(ctx.alloc().buffer());
return byteBufOutputStream;
} }
@Override @Override
@ -159,6 +158,9 @@ public class Http2ServerResponse implements ServerResponse {
ctx.channel().write(http2DataFrame); ctx.channel().write(http2DataFrame);
} }
ctx.channel().flush(); ctx.channel().flush();
server.getResponseCounter().incrementAndGet();
} else {
logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel());
} }
} }
@ -185,15 +187,15 @@ public class Http2ServerResponse implements ServerResponse {
if (ctx.channel().isWritable()) { if (ctx.channel().isWritable()) {
Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers); Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers);
Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,false); Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,false);
logger.log(Level.FINEST, http2HeadersFrame::toString);
ctx.channel().write(http2HeadersFrame); ctx.channel().write(http2HeadersFrame);
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput)); ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) && if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) { !headers.contains(HttpHeaderNames.CONNECTION)) {
channelFuture.addListener(ChannelFutureListener.CLOSE); channelFuture.addListener(ChannelFutureListener.CLOSE);
} }
server.getResponseCounter().incrementAndGet();
} else { } else {
logger.log(Level.WARNING, "channel not writeable"); logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel());
} }
} }
} }

View file

@ -11,41 +11,39 @@ import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Http2Transport extends BaseTransport { public class Http2Transport extends BaseTransport {
private static final Logger logger = Logger.getLogger(Http2Transport.class.getName());
public Http2Transport(Server server) { public Http2Transport(Server server) {
super(server); super(server);
} }
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
requestReceived(ctx, fullHttpRequest, null);
}
@Override @Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
int requestId = requestCounter.incrementAndGet();
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST)); Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
if (domain == null) { HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest);
domain = server.getDefaultNamedServer(); try {
}
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
HttpServerRequest serverRequest = new HttpServerRequest();
serverRequest.setChannelHandlerContext(ctx);
serverRequest.setRequest(fullHttpRequest);
serverRequest.setSequenceId(sequenceId); serverRequest.setSequenceId(sequenceId);
serverRequest.setRequestId(requestId); serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
serverRequest.setStreamId(streamId); serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()));
ServerResponse serverResponse = new Http2ServerResponse(serverRequest); ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx);
if (acceptRequest(domain, serverRequest, serverResponse)) { if (acceptRequest(domain, serverRequest, serverResponse)) {
handle(domain, serverRequest, serverResponse); serverRequest.handleParameters();
domain.handle(serverRequest, serverResponse);
} else { } else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
} }
} finally {
serverRequest.release();
}
} }
@Override @Override
public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) { public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) {
logger.log(Level.FINER, "settings received, ignoring");
} }
} }

View file

@ -2,7 +2,6 @@ package org.xbib.netty.http.server.transport;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
@ -12,12 +11,12 @@ import org.xbib.net.Pair;
import org.xbib.net.QueryParameters; import org.xbib.net.QueryParameters;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerRequest; import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.endpoint.HttpEndpointDescriptor; import org.xbib.netty.http.server.endpoint.HttpEndpointDescriptor;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.MalformedInputException; import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.charset.UnmappableCharacterException; import java.nio.charset.UnmappableCharacterException;
@ -34,7 +33,7 @@ public class HttpServerRequest implements ServerRequest {
private static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded"; private static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
private ChannelHandlerContext ctx; private final FullHttpRequest httpRequest;
private List<String> context; private List<String> context;
@ -42,9 +41,7 @@ public class HttpServerRequest implements ServerRequest {
private Map<String, String> pathParameters = new LinkedHashMap<>(); private Map<String, String> pathParameters = new LinkedHashMap<>();
private FullHttpRequest httpRequest; private HttpEndpointDescriptor httpEndpointDescriptor;
private HttpEndpointDescriptor info;
private HttpParameters parameters; private HttpParameters parameters;
@ -54,15 +51,19 @@ public class HttpServerRequest implements ServerRequest {
private Integer streamId; private Integer streamId;
private Integer requestId; private Long requestId;
private SSLSession sslSession; private SSLSession sslSession;
public void handleParameters() throws IOException { HttpServerRequest(Server server, FullHttpRequest fullHttpRequest) {
this.httpRequest = fullHttpRequest.retainedDuplicate();
this.httpEndpointDescriptor = new HttpEndpointDescriptor(this);
}
void handleParameters() throws IOException {
try { try {
HttpParameters httpParameters = new HttpParameters(); HttpParameters httpParameters = new HttpParameters();
URL.Builder builder = URL.builder().path(getRequest().uri()); this.url = URL.builder().path(httpRequest.uri()).build();
this.url = builder.build();
QueryParameters queryParameters = url.getQueryParams(); QueryParameters queryParameters = url.getQueryParams();
ByteBuf byteBuf = httpRequest.content(); ByteBuf byteBuf = httpRequest.content();
if (APPLICATION_FORM_URL_ENCODED.equals(HttpUtil.getMimeType(httpRequest)) && byteBuf != null) { if (APPLICATION_FORM_URL_ENCODED.equals(HttpUtil.getMimeType(httpRequest)) && byteBuf != null) {
@ -78,36 +79,14 @@ public class HttpServerRequest implements ServerRequest {
} }
} }
public void setChannelHandlerContext(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public ChannelHandlerContext getChannelHandlerContext() {
return ctx;
}
public void setRequest(FullHttpRequest fullHttpRequest) {
this.httpRequest = fullHttpRequest;
this.info = new HttpEndpointDescriptor(this);
}
public FullHttpRequest getRequest() {
return httpRequest;
}
@Override @Override
public URL getURL() { public URL getURL() {
return url; return url;
} }
@Override
public Channel getChannel() {
return ctx.channel();
}
@Override @Override
public HttpEndpointDescriptor getEndpointDescriptor() { public HttpEndpointDescriptor getEndpointDescriptor() {
return info; return httpEndpointDescriptor;
} }
@Override @Override
@ -160,6 +139,11 @@ public class HttpServerRequest implements ServerRequest {
return parameters; return parameters;
} }
@Override
public String getRequestURI() {
return httpRequest.uri();
}
public void setSequenceId(Integer sequenceId) { public void setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId; this.sequenceId = sequenceId;
} }
@ -178,12 +162,12 @@ public class HttpServerRequest implements ServerRequest {
return streamId; return streamId;
} }
public void setRequestId(Integer requestId) { public void setRequestId(Long requestId) {
this.requestId = requestId; this.requestId = requestId;
} }
@Override @Override
public Integer getRequestId() { public Long getRequestId() {
return requestId; return requestId;
} }
@ -206,6 +190,10 @@ public class HttpServerRequest implements ServerRequest {
return new ByteBufInputStream(getContent(), true); return new ByteBufInputStream(getContent(), true);
} }
public void release() {
httpRequest.release();
}
public String toString() { public String toString() {
return "ServerRequest[request=" + httpRequest + "]"; return "ServerRequest[request=" + httpRequest + "]";
} }

View file

@ -19,13 +19,13 @@ import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.stream.ChunkedInput; import io.netty.handler.stream.ChunkedInput;
import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.Cookie;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerName; import org.xbib.netty.http.server.ServerName;
import org.xbib.netty.http.server.ServerRequest; import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.cookie.ServerCookieEncoder; import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse; import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@ -38,6 +38,10 @@ public class HttpServerResponse implements ServerResponse {
private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName()); private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName());
private static final ByteBuf EMPTY = Unpooled.buffer(0);
private final Server server;
private final ServerRequest serverRequest; private final ServerRequest serverRequest;
private final ChannelHandlerContext ctx; private final ChannelHandlerContext ctx;
@ -48,13 +52,10 @@ public class HttpServerResponse implements ServerResponse {
private HttpResponseStatus httpResponseStatus; private HttpResponseStatus httpResponseStatus;
private ByteBufOutputStream byteBufOutputStream; HttpServerResponse(Server server, HttpServerRequest serverRequest, ChannelHandlerContext ctx) {
this.server = server;
public HttpServerResponse(HttpServerRequest serverRequest) {
Objects.requireNonNull(serverRequest, "serverRequest");
Objects.requireNonNull(serverRequest.getChannelHandlerContext(), "serverRequest channelHandlerContext");
this.serverRequest = serverRequest; this.serverRequest = serverRequest;
this.ctx = serverRequest.getChannelHandlerContext(); this.ctx = ctx;
this.headers = new DefaultHttpHeaders(); this.headers = new DefaultHttpHeaders();
this.trailingHeaders = new DefaultHttpHeaders(); this.trailingHeaders = new DefaultHttpHeaders();
} }
@ -107,8 +108,7 @@ public class HttpServerResponse implements ServerResponse {
@Override @Override
public ByteBufOutputStream getOutputStream() { public ByteBufOutputStream getOutputStream() {
this.byteBufOutputStream = new ByteBufOutputStream(ctx.alloc().buffer()); return new ByteBufOutputStream(ctx.alloc().buffer());
return byteBufOutputStream;
} }
@Override @Override
@ -153,18 +153,23 @@ public class HttpServerResponse implements ServerResponse {
} }
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
if (ctx.channel().isWritable()) { if (ctx.channel().isWritable()) {
FullHttpResponse fullHttpResponse = byteBuf != null ? FullHttpResponse fullHttpResponse;
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders) : if (byteBuf != null) {
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, Unpooled.buffer(0), headers, trailingHeaders); fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders);
} else {
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, EMPTY, headers, trailingHeaders);
}
if (serverRequest != null && serverRequest.getSequenceId() != null) { if (serverRequest != null && serverRequest.getSequenceId() != null) {
HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse,
ctx.channel().newPromise(), serverRequest.getSequenceId()); ctx.channel().newPromise(), serverRequest.getSequenceId());
ctx.channel().writeAndFlush(httpPipelinedResponse); ctx.channel().writeAndFlush(httpPipelinedResponse);
server.getResponseCounter().incrementAndGet();
} else { } else {
ctx.channel().writeAndFlush(fullHttpResponse); ctx.channel().writeAndFlush(fullHttpResponse);
server.getResponseCounter().incrementAndGet();
} }
} else { } else {
logger.log(Level.WARNING, "channel not writeable"); logger.log(Level.WARNING, "channel not writeable: " + ctx.channel());
} }
} }

View file

@ -18,34 +18,28 @@ public class HttpTransport extends BaseTransport {
super(server); super(server);
} }
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
requestReceived(ctx, fullHttpRequest, 0);
}
@Override @Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId)
throws IOException { throws IOException {
int requestId = requestCounter.incrementAndGet();
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST)); Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
if (domain == null) { HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest);
domain = server.getDefaultNamedServer(); try {
}
HttpServerRequest serverRequest = new HttpServerRequest();
serverRequest.setChannelHandlerContext(ctx);
serverRequest.setRequest(fullHttpRequest);
serverRequest.setSequenceId(sequenceId); serverRequest.setSequenceId(sequenceId);
serverRequest.setRequestId(requestId); serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class); SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class);
if (sslHandler != null) { if (sslHandler != null) {
serverRequest.setSession(sslHandler.engine().getSession()); serverRequest.setSession(sslHandler.engine().getSession());
} }
HttpServerResponse serverResponse = new HttpServerResponse(serverRequest); HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx);
if (acceptRequest(domain, serverRequest, serverResponse)) { if (acceptRequest(domain, serverRequest, serverResponse)) {
handle(domain, serverRequest, serverResponse); serverRequest.handleParameters();
domain.handle(serverRequest, serverResponse);
} else { } else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
} }
} finally {
serverRequest.release();
}
} }
@Override @Override

View file

@ -11,8 +11,6 @@ public interface Transport {
AttributeKey<Transport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); AttributeKey<Transport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException;
void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException; void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException;
void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) throws Exception; void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) throws Exception;

View file

@ -18,7 +18,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class ClassloaderServiceTest { class ClassloaderServiceTest {
private static final Logger logger = Logger.getLogger(ClassloaderServiceTest.class.getName()); private static final Logger logger = Logger.getLogger(ClassloaderServiceTest.class.getName());

View file

@ -12,7 +12,9 @@ import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.ServerResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -22,7 +24,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class CleartextHttp1Test { class CleartextHttp1Test {
private static final Logger logger = Logger.getLogger(CleartextHttp1Test.class.getName()); private static final Logger logger = Logger.getLogger(CleartextHttp1Test.class.getName());
@ -32,9 +34,8 @@ class CleartextHttp1Test {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/**", (request, response) -> .singleEndpoint("/**", (request, response) ->
response.withStatus(HttpResponseStatus.OK) ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
.withContentType("text/plain") request.getContent().retain()))
.write(request.getContent().retain()))
.build(); .build();
Server server = Server.builder(domain).build(); Server server = Server.builder(domain).build();
server.accept(); server.accept();
@ -43,6 +44,7 @@ class CleartextHttp1Test {
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
final ResponseListener<HttpResponse> responseListener = resp -> { final ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
logger.log(Level.INFO, resp.getBodyAsString(StandardCharsets.UTF_8));
counter.incrementAndGet(); counter.incrementAndGet();
} }
}; };
@ -62,13 +64,12 @@ class CleartextHttp1Test {
@Test @Test
void testPooledClearTextHttp1() throws Exception { void testPooledClearTextHttp1() throws Exception {
int loop = 4096; int loop = 1000;
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/**", (request, response) -> .singleEndpoint("/**", (request, response) ->
response.withStatus(HttpResponseStatus.OK) ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
.withContentType("text/plain") request.getContent().retain()))
.write(request.getContent().retain()))
.build(); .build();
Server server = Server.builder(domain).build(); Server server = Server.builder(domain).build();
server.accept(); server.accept();
@ -79,6 +80,7 @@ class CleartextHttp1Test {
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
final ResponseListener<HttpResponse> responseListener = resp -> { final ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
logger.log(Level.INFO, resp.getBodyAsString(StandardCharsets.UTF_8));
counter.incrementAndGet(); counter.incrementAndGet();
} }
}; };
@ -106,14 +108,13 @@ class CleartextHttp1Test {
@Test @Test
void testMultithreadedPooledClearTextHttp1() throws Exception { void testMultithreadedPooledClearTextHttp1() throws Exception {
int threads = 4; int threads = 8;
int loop = 4 * 1024; int loop = 1000;
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/**", (request, response) -> .singleEndpoint("/**", (request, response) ->
response.withStatus(HttpResponseStatus.OK) ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
.withContentType("text/plain") request.getContent().retain()))
.write(request.getContent().retain()))
.build(); .build();
Server server = Server.builder(domain).build(); Server server = Server.builder(domain).build();
server.accept(); server.accept();
@ -124,6 +125,7 @@ class CleartextHttp1Test {
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
final ResponseListener<HttpResponse> responseListener = resp -> { final ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
logger.log(Level.FINE, resp.getBodyAsString(StandardCharsets.UTF_8));
counter.incrementAndGet(); counter.incrementAndGet();
} }
}; };
@ -134,7 +136,7 @@ class CleartextHttp1Test {
executorService.submit(() -> { executorService.submit(() -> {
try { try {
for (int i = 0; i < loop; i++) { for (int i = 0; i < loop; i++) {
String payload = Integer.toString(t) + "/" + Integer.toString(i); String payload = t + "/" + i;
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base()) .url(server.getServerConfig().getAddress().base())
.content(payload, "text/plain") .content(payload, "text/plain")

View file

@ -13,7 +13,6 @@ import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -24,7 +23,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class CleartextHttp2Test { class CleartextHttp2Test {
private static final Logger logger = Logger.getLogger(CleartextHttp2Test.class.getName()); private static final Logger logger = Logger.getLogger(CleartextHttp2Test.class.getName());
@ -34,20 +33,23 @@ class CleartextHttp2Test {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/", (request, response) -> .singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK) ServerResponse.write(response, HttpResponseStatus.OK, "text.plain",
.withContentType("text/plain") request.getContent().toString(StandardCharsets.UTF_8)))
.write(request.getContent().retain())) .build();
Server server = Server.builder(domain)
.build(); .build();
Server server = Server.builder(domain).build();
server.accept(); server.accept();
Client client = Client.builder() Client client = Client.builder()
.build(); .build();
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
// a single instance of HTTP/2 response listener, always receives responses out-of-order // a single instance of HTTP/2 response listener, always receives responses out-of-order
ResponseListener<HttpResponse> responseListener = resp -> { ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
counter.incrementAndGet();
} else {
logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() + logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() +
" response body = " + resp.getBodyAsString(StandardCharsets.UTF_8)); " response body = " + resp.getBodyAsString(StandardCharsets.UTF_8));
counter.incrementAndGet(); }
}; };
try { try {
String payload = 0 + "/" + 0; String payload = 0 + "/" + 0;
@ -63,15 +65,16 @@ class CleartextHttp2Test {
} }
transport.get(); transport.get();
} finally { } finally {
client.shutdownGracefully();
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully();
} }
logger.log(Level.INFO, "expecting=" + 1 + " counter=" + counter.get());
assertEquals(1, counter.get()); assertEquals(1, counter.get());
} }
@Test @Test
void testPooledClearTextHttp2() throws Exception { void testPooledClearTextHttp2() throws Exception {
int loop = 4096; int loop = 1000;
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/", (request, response) -> .singleEndpoint("/", (request, response) ->
@ -79,7 +82,8 @@ class CleartextHttp2Test {
.withContentType("text/plain") .withContentType("text/plain")
.write(request.getContent().retain())) .write(request.getContent().retain()))
.build(); .build();
Server server = Server.builder(domain).build(); Server server = Server.builder(domain)
.build();
server.accept(); server.accept();
Client client = Client.builder() Client client = Client.builder()
.addPoolNode(httpAddress) .addPoolNode(httpAddress)
@ -87,7 +91,14 @@ class CleartextHttp2Test {
.build(); .build();
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
// a single instance of HTTP/2 response listener, always receives responses out-of-order // a single instance of HTTP/2 response listener, always receives responses out-of-order
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet(); final ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
counter.incrementAndGet();
} else {
logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() +
" response body = " + resp.getBodyAsString(StandardCharsets.UTF_8));
}
};
try { try {
// single transport, single thread // single transport, single thread
Transport transport = client.newTransport(); Transport transport = client.newTransport();
@ -106,8 +117,8 @@ class CleartextHttp2Test {
} }
transport.get(); transport.get();
} finally { } finally {
client.shutdownGracefully();
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully();
} }
logger.log(Level.INFO, "expecting=" + loop + " counter=" + counter.get()); logger.log(Level.INFO, "expecting=" + loop + " counter=" + counter.get());
assertEquals(loop, counter.get()); assertEquals(loop, counter.get());
@ -116,14 +127,15 @@ class CleartextHttp2Test {
@Test @Test
void testMultithreadPooledClearTextHttp2() throws Exception { void testMultithreadPooledClearTextHttp2() throws Exception {
int threads = 2; int threads = 2;
int loop = 2 * 1024; int loop = 2000;
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/", (request, response) -> .singleEndpoint("/", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getContent().toString(StandardCharsets.UTF_8))) request.getContent().retain()))
.build();
Server server = Server.builder(domain)
.build(); .build();
Server server = Server.builder(domain).build();
server.accept(); server.accept();
Client client = Client.builder() Client client = Client.builder()
.addPoolNode(httpAddress) .addPoolNode(httpAddress)
@ -131,9 +143,16 @@ class CleartextHttp2Test {
.build(); .build();
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
// a HTTP/2 listener always receives responses out-of-order // a HTTP/2 listener always receives responses out-of-order
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet(); final ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
counter.incrementAndGet();
} else {
logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() +
" response body = " + resp.getBodyAsString(StandardCharsets.UTF_8));
}
};
try { try {
// note: for HTTP/2 only, we can use a single shared transport // note: for HTTP/2 only, we use a single shared transport
final Transport transport = client.newTransport(); final Transport transport = client.newTransport();
ExecutorService executorService = Executors.newFixedThreadPool(threads); ExecutorService executorService = Executors.newFixedThreadPool(threads);
for (int n = 0; n < threads; n++) { for (int n = 0; n < threads; n++) {
@ -153,19 +172,24 @@ class CleartextHttp2Test {
break; break;
} }
} }
} catch (IOException e) { } catch (Throwable e) {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.WARNING, e.getMessage(), e);
} }
}); });
} }
executorService.shutdown(); executorService.shutdown();
boolean terminated = executorService.awaitTermination(60, TimeUnit.SECONDS); boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS);
executorService.shutdownNow();
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
transport.get(60, TimeUnit.SECONDS); transport.get(10L, TimeUnit.SECONDS);
} finally { } finally {
client.shutdownGracefully(); server.shutdownGracefully(10L, TimeUnit.SECONDS);
server.shutdownGracefully(); client.shutdownGracefully(10L, TimeUnit.SECONDS);
} }
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
" server responses = " + server.getResponseCounter());
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
" client responses = " + client.getResponseCounter());
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get()); logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
assertEquals(threads * loop , counter.get()); assertEquals(threads * loop , counter.get());
} }
@ -173,18 +197,18 @@ class CleartextHttp2Test {
@Test @Test
void testTwoPooledClearTextHttp2() throws Exception { void testTwoPooledClearTextHttp2() throws Exception {
int threads = 2; int threads = 2;
int loop = 4 * 1024; int loop = 4000;
HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008); HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008);
AtomicInteger counter1 = new AtomicInteger(); AtomicInteger counter1 = new AtomicInteger();
Domain domain1 = Domain.builder(httpAddress1) Domain domain1 = Domain.builder(httpAddress1)
.singleEndpoint("/", (request, response) -> { .singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", ServerResponse.write(response, HttpResponseStatus.OK, "text.plain",
request.getContent().toString(StandardCharsets.UTF_8)); request.getContent().toString(StandardCharsets.UTF_8));
counter1.incrementAndGet(); counter1.incrementAndGet();
}) })
.build(); .build();
Server server1 = Server.builder(domain1).build(); Server server1 = Server.builder(domain1)
.build();
server1.accept(); server1.accept();
HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009); HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009);
AtomicInteger counter2 = new AtomicInteger(); AtomicInteger counter2 = new AtomicInteger();
@ -195,7 +219,8 @@ class CleartextHttp2Test {
counter2.incrementAndGet(); counter2.incrementAndGet();
}) })
.build(); .build();
Server server2 = Server.builder(domain2).build(); Server server2 = Server.builder(domain2)
.build();
server2.accept(); server2.accept();
Client client = Client.builder() Client client = Client.builder()
.addPoolNode(httpAddress1) .addPoolNode(httpAddress1)
@ -204,7 +229,14 @@ class CleartextHttp2Test {
.build(); .build();
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
// a single instance of HTTP/2 response listener, always receives responses out-of-order // a single instance of HTTP/2 response listener, always receives responses out-of-order
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet(); final ResponseListener<HttpResponse> responseListener = resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
counter.incrementAndGet();
} else {
logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() +
" response body = " + resp.getBodyAsString(StandardCharsets.UTF_8));
}
};
try { try {
// note: for HTTP/2 only, we can use a single shared transport // note: for HTTP/2 only, we can use a single shared transport
final Transport transport = client.newTransport(); final Transport transport = client.newTransport();
@ -215,7 +247,9 @@ class CleartextHttp2Test {
try { try {
for (int i = 0; i < loop; i++) { for (int i = 0; i < loop; i++) {
String payload = t + "/" + i; String payload = t + "/" + i;
Request request = Request.get().setVersion("HTTP/2.0") Request request = Request.get()
.setVersion("HTTP/2.0")
//.url(server1.getServerConfig().getAddress().base())
.uri("/") .uri("/")
.content(payload, "text/plain") .content(payload, "text/plain")
.build() .build()
@ -226,20 +260,27 @@ class CleartextHttp2Test {
break; break;
} }
} }
} catch (IOException e) { } catch (Throwable e) {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.WARNING, e.getMessage(), e);
} }
}); });
} }
executorService.shutdown(); executorService.shutdown();
boolean terminated = executorService.awaitTermination(60, TimeUnit.SECONDS); boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS);
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
transport.get(60, TimeUnit.SECONDS); transport.get(10L, TimeUnit.SECONDS);
logger.log(Level.INFO, "transport complete");
} finally { } finally {
client.shutdownGracefully(); server1.shutdownGracefully(10L, TimeUnit.SECONDS);
server1.shutdownGracefully(); server2.shutdownGracefully(10L, TimeUnit.SECONDS);
server2.shutdownGracefully(); client.shutdownGracefully(10L, TimeUnit.SECONDS);
} }
logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() +
" server1 responses = " + server1.getResponseCounter());
logger.log(Level.INFO, "server2 requests = " + server1.getRequestCounter() +
" server2 responses = " + server1.getResponseCounter());
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
" client responses = " + client.getResponseCounter());
logger.log(Level.INFO, "counter1=" + counter1.get() + " counter2=" + counter2.get()); logger.log(Level.INFO, "counter1=" + counter1.get() + " counter2=" + counter2.get());
logger.log(Level.INFO, "expecting=" + threads * loop + " counter=" + counter.get()); logger.log(Level.INFO, "expecting=" + threads * loop + " counter=" + counter.get());
assertEquals(threads * loop, counter.get()); assertEquals(threads * loop, counter.get());

View file

@ -28,7 +28,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class EndpointTest { class EndpointTest {
private static final Logger logger = Logger.getLogger(EndpointTest.class.getName()); private static final Logger logger = Logger.getLogger(EndpointTest.class.getName());

View file

@ -21,7 +21,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class FileServiceTest { class FileServiceTest {
private static final Logger logger = Logger.getLogger(FileServiceTest.class.getName()); private static final Logger logger = Logger.getLogger(FileServiceTest.class.getName());

View file

@ -12,7 +12,7 @@ import java.util.logging.LogManager;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.logging.SimpleFormatter; import java.util.logging.SimpleFormatter;
public class NettyHttpExtension implements BeforeAllCallback { public class NettyHttpTestExtension implements BeforeAllCallback {
@Override @Override
public void beforeAll(ExtensionContext context) { public void beforeAll(ExtensionContext context) {
@ -20,6 +20,7 @@ public class NettyHttpExtension implements BeforeAllCallback {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); System.setProperty("io.netty.noUnsafe", Boolean.toString(true));
//System.setProperty("io.netty.leakDetection.level", "paranoid");
Level level = Level.INFO; Level level = Level.INFO;
System.setProperty("java.util.logging.SimpleFormatter.format", System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n"); "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n");

View file

@ -6,8 +6,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.listener.ResponseListener;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
@ -18,7 +20,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class PostTest { class PostTest {
private static final Logger logger = Logger.getLogger(PostTest.class.getName()); private static final Logger logger = Logger.getLogger(PostTest.class.getName());
@ -29,9 +31,9 @@ class PostTest {
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> { .singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters(); HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got post " + parameters.toString()); logger.log(Level.INFO, "got request " + parameters.toString() + " , sending, OK");
ServerResponse.write(resp, HttpResponseStatus.OK); ServerResponse.write(resp, HttpResponseStatus.OK);
}, "POST") }, "GET", "POST")
.build(); .build();
Server server = Server.builder(domain) Server server = Server.builder(domain)
.build(); .build();
@ -40,18 +42,23 @@ class PostTest {
final AtomicBoolean success = new AtomicBoolean(false); final AtomicBoolean success = new AtomicBoolean(false);
try { try {
server.accept(); server.accept();
Request request = Request.post().setVersion(HttpVersion.HTTP_1_1)
ResponseListener<HttpResponse> responseListener = (resp) -> {
logger.log(Level.INFO, "got response = " + resp);
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
success.set(true);
}
};
Request postRequest = Request.post().setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base().resolve("/post/test.txt")) .url(server.getServerConfig().getAddress().base().resolve("/post/test.txt"))
.addParameter("a", "b") .addParameter("a", "b")
.addFormParameter("name", "Jörg") .addFormParameter("name", "Jörg")
.build() .build()
.setResponseListener(resp -> { .setResponseListener(responseListener);
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { client.execute(postRequest).get();
success.set(true);
} logger.log(Level.INFO, "complete");
});
client.execute(request).get();
logger.log(Level.INFO, "request complete");
} finally { } finally {
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully(); client.shutdownGracefully();
@ -60,14 +67,13 @@ class PostTest {
assertTrue(success.get()); assertTrue(success.get());
} }
@Test @Test
void testPostHttp2() throws Exception { void testPostHttp2() throws Exception {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> { .singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters(); HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got post " + parameters.toString()); logger.log(Level.INFO, "got request " + parameters.toString(), ", sending OK");
ServerResponse.write(resp, HttpResponseStatus.OK); ServerResponse.write(resp, HttpResponseStatus.OK);
}, "POST") }, "POST")
.build(); .build();
@ -78,18 +84,23 @@ class PostTest {
final AtomicBoolean success = new AtomicBoolean(false); final AtomicBoolean success = new AtomicBoolean(false);
try { try {
server.accept(); server.accept();
Request request = Request.post().setVersion("HTTP/2.0")
ResponseListener<HttpResponse> responseListener = (resp) -> {
logger.log(Level.INFO, "got response = " + resp);
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
success.set(true);
}
};
Request postRequest = Request.post().setVersion("HTTP/2.0")
.url(server.getServerConfig().getAddress().base().resolve("/post/test.txt")) .url(server.getServerConfig().getAddress().base().resolve("/post/test.txt"))
.addParameter("a", "b") .addParameter("a", "b")
.addFormParameter("name", "Jörg") .addFormParameter("name", "Jörg")
.build() .build()
.setResponseListener(resp -> { .setResponseListener(responseListener);
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { client.execute(postRequest).get();
success.set(true);
} logger.log(Level.INFO, "complete");
});
client.execute(request).get();
logger.log(Level.INFO, "request complete");
} finally { } finally {
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully(); client.shutdownGracefully();

View file

@ -22,7 +22,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class SecureFileServiceTest { class SecureFileServiceTest {
private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName()); private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName());

View file

@ -23,7 +23,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class SecureHttp1Test { class SecureHttp1Test {
private static final Logger logger = Logger.getLogger(SecureHttp1Test.class.getName()); private static final Logger logger = Logger.getLogger(SecureHttp1Test.class.getName());
@ -38,6 +38,7 @@ class SecureHttp1Test {
.withContentType("text/plain") .withContentType("text/plain")
.write(request.getContent().retain())) .write(request.getContent().retain()))
.build()) .build())
.enableDebug()
.build(); .build();
Client client = Client.builder() Client client = Client.builder()
.trustInsecure() .trustInsecure()

View file

@ -23,7 +23,7 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class SecureHttp2Test { class SecureHttp2Test {
private static final Logger logger = Logger.getLogger(SecureHttp2Test.class.getName()); private static final Logger logger = Logger.getLogger(SecureHttp2Test.class.getName());

View file

@ -17,7 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class StreamTest { class StreamTest {
@Test @Test

View file

@ -15,7 +15,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class ThreadLeakTest { class ThreadLeakTest {
private static final Logger logger = Logger.getLogger(ThreadLeakTest.class.getName()); private static final Logger logger = Logger.getLogger(ThreadLeakTest.class.getName());

View file

@ -31,7 +31,7 @@ import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; import io.netty.handler.logging.LoggingHandler;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.test.NettyHttpExtension; import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class CleartextHttp2Test { class CleartextHttp2Test {
private static final Logger clientLogger = Logger.getLogger("client"); private static final Logger clientLogger = Logger.getLogger("client");

View file

@ -26,7 +26,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.handler.http.HttpPipelinedRequest; import org.xbib.netty.http.server.handler.http.HttpPipelinedRequest;
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse; import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
import org.xbib.netty.http.server.handler.http.HttpPipeliningHandler; import org.xbib.netty.http.server.handler.http.HttpPipeliningHandler;
import org.xbib.netty.http.server.test.NettyHttpExtension; import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -52,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/** flaky */ /** flaky */
@Disabled @Disabled
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class HttpPipeliningHandlerTest { class HttpPipeliningHandlerTest {
private static final Logger logger = Logger.getLogger(HttpPipeliningHandlerTest.class.getName()); private static final Logger logger = Logger.getLogger(HttpPipeliningHandlerTest.class.getName());

View file

@ -42,7 +42,7 @@ import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.test.NettyHttpExtension; import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -68,7 +68,7 @@ import java.util.logging.Logger;
* *
* *
*/ */
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class MultiplexCodecCleartextHttp2Test { class MultiplexCodecCleartextHttp2Test {
private static final Logger clientLogger = Logger.getLogger("client"); private static final Logger clientLogger = Logger.getLogger("client");
@ -82,8 +82,8 @@ class MultiplexCodecCleartextHttp2Test {
@Test @Test
void testMultiplexHttp2() throws Exception { void testMultiplexHttp2() throws Exception {
Http2FrameLogger serverFrameLogger = new Http2FrameLogger(LogLevel.INFO, "server"); Http2FrameLogger serverFrameLogger = new Http2FrameLogger(LogLevel.DEBUG, "server");
Http2FrameLogger clientFrameLogger = new Http2FrameLogger(LogLevel.INFO, "client"); Http2FrameLogger clientFrameLogger = new Http2FrameLogger(LogLevel.DEBUG, "client");
EventLoopGroup serverEventLoopGroup = new NioEventLoopGroup(); EventLoopGroup serverEventLoopGroup = new NioEventLoopGroup();
EventLoopGroup clientEventLoopGroup = new NioEventLoopGroup(); EventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();
try { try {
@ -99,7 +99,8 @@ class MultiplexCodecCleartextHttp2Test {
@Override @Override
protected void initChannel(Channel channel) { protected void initChannel(Channel channel) {
ChannelPipeline p = channel.pipeline(); ChannelPipeline p = channel.pipeline();
p.addLast("multiplex-server-traffic", new TrafficLoggingHandler("multiplex-server-traffic", LogLevel.INFO)); p.addLast("multiplex-server-traffic",
new TrafficLoggingHandler("multiplex-server-traffic", LogLevel.DEBUG));
p.addLast("multiplex-server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true)); p.addLast("multiplex-server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true));
p.addLast("multiplex-server-chunk-aggregator", new HttpObjectAggregator(1048576)); p.addLast("multiplex-server-chunk-aggregator", new HttpObjectAggregator(1048576));
p.addLast("multiplex-server-request-handler", new ServerRequestHandler()); p.addLast("multiplex-server-request-handler", new ServerRequestHandler());
@ -119,7 +120,8 @@ class MultiplexCodecCleartextHttp2Test {
HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory); HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory);
CleartextHttp2ServerUpgradeHandler cleartextHttp2ServerUpgradeHandler = CleartextHttp2ServerUpgradeHandler cleartextHttp2ServerUpgradeHandler =
new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, serverMultiplexCodec); new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, serverMultiplexCodec);
p.addLast("server-traffic", new TrafficLoggingHandler("server-traffic", LogLevel.INFO)); p.addLast("server-traffic",
new TrafficLoggingHandler("server-traffic", LogLevel.DEBUG));
p.addLast("server-upgrade", cleartextHttp2ServerUpgradeHandler); p.addLast("server-upgrade", cleartextHttp2ServerUpgradeHandler);
p.addLast("server-messages", new ServerMessages()); p.addLast("server-messages", new ServerMessages());
} }
@ -164,7 +166,8 @@ class MultiplexCodecCleartextHttp2Test {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast("child-client-traffic", new TrafficLoggingHandler("child-client-traffic", LogLevel.INFO)); p.addLast("child-client-traffic",
new TrafficLoggingHandler("child-client-traffic", LogLevel.DEBUG));
p.addLast("child-client-frame-converter", new Http2StreamFrameToHttpObjectCodec(false)); p.addLast("child-client-frame-converter", new Http2StreamFrameToHttpObjectCodec(false));
p.addLast("child-client-chunk-aggregator", new HttpObjectAggregator(1048576)); p.addLast("child-client-chunk-aggregator", new HttpObjectAggregator(1048576));
p.addLast("child-client-response-handler", new ClientResponseHandler()); p.addLast("child-client-response-handler", new ClientResponseHandler());
@ -195,7 +198,7 @@ class MultiplexCodecCleartextHttp2Test {
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) { protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) {
clientLogger.log(Level.INFO, "response received on client: " + msg); clientLogger.log(Level.FINE, "response received on client: " + msg);
responseFuture.complete(true); responseFuture.complete(true);
} }
} }
@ -226,11 +229,11 @@ class MultiplexCodecCleartextHttp2Test {
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) {
serverLogger.log(Level.INFO, "request received on server: " + msg + serverLogger.log(Level.FINE, "request received on server: " + msg +
" path = " + msg); " path = " + msg);
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK); HttpResponseStatus.OK);
serverLogger.log(Level.INFO, "writing server response: " + response); serverLogger.log(Level.FINE, "writing server response: " + response);
ctx.writeAndFlush(response); ctx.writeAndFlush(response);
} }
} }

View file

@ -29,7 +29,7 @@ import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder;
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.test.NettyHttpExtension; import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -40,7 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class MultithreadedCleartextHttp2Test { class MultithreadedCleartextHttp2Test {
private static final Logger clientLogger = Logger.getLogger("client"); private static final Logger clientLogger = Logger.getLogger("client");

View file

@ -37,7 +37,7 @@ import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.test.NettyHttpExtension; import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -53,7 +53,7 @@ import java.util.logging.Logger;
* Multithreaded Http2MultiplexCodec demo for cleartext HTTP/2 between a server and a client. * Multithreaded Http2MultiplexCodec demo for cleartext HTTP/2 between a server and a client.
* *
*/ */
@ExtendWith(NettyHttpExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class MultithreadedMultiplexCodecCleartextHttp2Test { class MultithreadedMultiplexCodecCleartextHttp2Test {
private static final Logger clientLogger = Logger.getLogger("client"); private static final Logger clientLogger = Logger.getLogger("client");

View file

@ -1,5 +0,0 @@
handlers = java.util.logging.ConsoleHandler
.level = FINE
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = %1$tFT%1$tT.%1$tL%1$tz [%4$-11s] [%3$s] %5$s %6$s%n

View file

@ -1,3 +1,4 @@
include 'netty-http-common' include 'netty-http-common'
include 'netty-http-client' include 'netty-http-client'
include 'netty-http-client-rest' include 'netty-http-client-rest'