many test fixes, bytebuf leaks, add TLS protocol for client
This commit is contained in:
parent
712dd570e7
commit
47a1176048
72 changed files with 1036 additions and 609 deletions
137
build.gradle
137
build.gradle
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
nexusStaging {
|
||||||
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 {
|
|
||||||
packageGroup = "org.xbib"
|
packageGroup = "org.xbib"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
6
gradlew
vendored
|
@ -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
2
gradlew.bat
vendored
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 + "]";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
|
@ -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");
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,4 +18,6 @@ public interface HttpResponse {
|
||||||
InputStream getBodyAsStream();
|
InputStream getBodyAsStream();
|
||||||
|
|
||||||
String getBodyAsString(Charset charset);
|
String getBodyAsString(Charset charset);
|
||||||
|
|
||||||
|
void release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 = "";
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 + "]";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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");
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in a new issue