smarter domains, certificate classes, API cleanup
This commit is contained in:
parent
aa82f681e3
commit
2c25413323
70 changed files with 1376 additions and 715 deletions
|
@ -1,6 +1,6 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.50.1
|
version = 4.1.50.2
|
||||||
|
|
||||||
gradle.wrapper.version = 6.4.1
|
gradle.wrapper.version = 6.4.1
|
||||||
netty.version = 4.1.50.Final
|
netty.version = 4.1.50.Final
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
apply plugin: "de.marcphilipp.nexus-publish"
|
apply plugin: "de.marcphilipp.nexus-publish"
|
||||||
|
|
||||||
|
@ -61,4 +62,5 @@ nexusPublishing {
|
||||||
packageGroup = "org.xbib"
|
packageGroup = "org.xbib"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientTimeout = Duration.ofSeconds(600)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":netty-http-common")
|
api project(":netty-http-server-api")
|
||||||
api "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}"
|
api "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}"
|
||||||
}
|
}
|
||||||
|
|
8
netty-http-bouncycastle/src/main/java/module-info.java
Normal file
8
netty-http-bouncycastle/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module org.xbib.netty.http.bouncycastle {
|
||||||
|
exports org.xbib.netty.http.bouncycastle;
|
||||||
|
requires org.xbib.netty.http.server.api;
|
||||||
|
requires org.bouncycastle.pkix;
|
||||||
|
requires org.bouncycastle.provider;
|
||||||
|
provides org.xbib.netty.http.server.api.security.ServerCertificateProvider with
|
||||||
|
org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package org.xbib.netty.http.bouncycastle;
|
package org.xbib.netty.http.bouncycastle;
|
||||||
|
|
||||||
import org.bouncycastle.operator.OperatorCreationException;
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
import org.xbib.netty.http.common.ServerCertificateProvider;
|
import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
|
|
@ -28,8 +28,6 @@ import java.security.NoSuchProviderException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a temporary self-signed certificate for testing purposes.
|
* Generates a temporary self-signed certificate for testing purposes.
|
||||||
|
@ -133,11 +131,6 @@ public final class SelfSignedCertificate {
|
||||||
outputStream.write(certBytes);
|
outputStream.write(certBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void exportPEM(Logger logger) {
|
|
||||||
logger.log(Level.INFO, new String(keyBytes, StandardCharsets.US_ASCII) +
|
|
||||||
new String(certBytes, StandardCharsets.US_ASCII));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeEncoded(byte[] bytes, OutputStream outputStream) throws IOException {
|
private void writeEncoded(byte[] bytes, OutputStream outputStream) throws IOException {
|
||||||
byte[] buf = new byte[64];
|
byte[] buf = new byte[64];
|
||||||
byte[] base64 = Base64.encode(bytes);
|
byte[] base64 = Base64.encode(bytes);
|
||||||
|
|
|
@ -3,17 +3,23 @@ package org.xbib.netty.http.bouncycastle;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
class SelfSignedCertificateTest {
|
class SelfSignedCertificateTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger("test");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSelfSignedCertificate() throws Exception {
|
void testSelfSignedCertificate() throws Exception {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
|
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
|
||||||
selfSignedCertificate.generate("localhost", new SecureRandom(), 2048);
|
selfSignedCertificate.generate("localhost", new SecureRandom(), 2048);
|
||||||
selfSignedCertificate.exportPEM(Logger.getLogger("test"));
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
selfSignedCertificate.exportPEM(outputStream);
|
||||||
|
logger.info(new String(outputStream.toByteArray(), StandardCharsets.US_ASCII));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
module org.xbib.netty.http.client.api {
|
module org.xbib.netty.http.client.api {
|
||||||
exports org.xbib.netty.http.client.api;
|
exports org.xbib.netty.http.client.api;
|
||||||
requires transitive org.xbib.netty.http.common;
|
requires transitive org.xbib.netty.http.common;
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.common;
|
|
||||||
requires io.netty.codec.http;
|
|
||||||
requires io.netty.codec.http2;
|
|
||||||
requires io.netty.transport;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.xbib.netty.http.client.api;
|
package org.xbib.netty.http.client.api;
|
||||||
|
|
||||||
public interface ProtocolProvider<C extends HttpChannelInitializer, T extends Transport> {
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
|
|
||||||
|
public interface ClientProtocolProvider<C extends HttpChannelInitializer, T extends ClientTransport> {
|
||||||
|
|
||||||
boolean supportsMajorVersion(int majorVersion);
|
boolean supportsMajorVersion(int majorVersion);
|
||||||
|
|
|
@ -7,21 +7,21 @@ import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.common.Transport;
|
||||||
import org.xbib.netty.http.common.cookie.CookieBox;
|
import org.xbib.netty.http.common.cookie.CookieBox;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public interface Transport extends AutoCloseable {
|
public interface ClientTransport extends Transport {
|
||||||
|
|
||||||
AttributeKey<Transport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
|
AttributeKey<ClientTransport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
|
||||||
|
|
||||||
HttpAddress getHttpAddress();
|
HttpAddress getHttpAddress();
|
||||||
|
|
||||||
Transport execute(Request request) throws IOException;
|
ClientTransport execute(Request request) throws IOException;
|
||||||
|
|
||||||
<T> CompletableFuture<T> execute(Request request, Function<HttpResponse, T> supplier) throws IOException;
|
<T> CompletableFuture<T> execute(Request request, Function<HttpResponse, T> supplier) throws IOException;
|
||||||
|
|
||||||
|
@ -33,18 +33,20 @@ public interface Transport extends AutoCloseable {
|
||||||
|
|
||||||
void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers);
|
void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers);
|
||||||
|
|
||||||
|
void fail(Channel channel, Throwable throwable);
|
||||||
|
|
||||||
|
void inactive(Channel channel);
|
||||||
|
|
||||||
void setCookieBox(CookieBox cookieBox);
|
void setCookieBox(CookieBox cookieBox);
|
||||||
|
|
||||||
CookieBox getCookieBox();
|
CookieBox getCookieBox();
|
||||||
|
|
||||||
Transport get();
|
ClientTransport get();
|
||||||
|
|
||||||
Transport get(long value, TimeUnit timeUnit);
|
ClientTransport get(long value, TimeUnit timeUnit);
|
||||||
|
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
void fail(Throwable throwable);
|
|
||||||
|
|
||||||
boolean isFailed();
|
boolean isFailed();
|
||||||
|
|
||||||
Throwable getFailure();
|
Throwable getFailure();
|
|
@ -1,7 +1,4 @@
|
||||||
module org.xbib.netty.http.client.rest {
|
module org.xbib.netty.http.client.rest {
|
||||||
exports org.xbib.netty.http.client.rest;
|
exports org.xbib.netty.http.client.rest;
|
||||||
requires transitive org.xbib.netty.http.client;
|
requires transitive org.xbib.netty.http.client;
|
||||||
requires org.xbib.net.url;
|
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.codec.http;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
import org.xbib.netty.http.client.Http1;
|
||||||
|
import org.xbib.netty.http.client.Http2;
|
||||||
|
|
||||||
module org.xbib.netty.http.client {
|
module org.xbib.netty.http.client {
|
||||||
|
uses org.xbib.netty.http.client.api.ClientProtocolProvider;
|
||||||
|
uses org.xbib.netty.http.common.TransportProvider;
|
||||||
exports org.xbib.netty.http.client;
|
exports org.xbib.netty.http.client;
|
||||||
exports org.xbib.netty.http.client.cookie;
|
exports org.xbib.netty.http.client.cookie;
|
||||||
exports org.xbib.netty.http.client.handler.http;
|
exports org.xbib.netty.http.client.handler.http;
|
||||||
|
@ -7,15 +12,7 @@ module org.xbib.netty.http.client {
|
||||||
exports org.xbib.netty.http.client.retry;
|
exports org.xbib.netty.http.client.retry;
|
||||||
exports org.xbib.netty.http.client.transport;
|
exports org.xbib.netty.http.client.transport;
|
||||||
requires transitive org.xbib.netty.http.client.api;
|
requires transitive org.xbib.netty.http.client.api;
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.common;
|
|
||||||
requires io.netty.codec.http;
|
|
||||||
requires io.netty.codec.http2;
|
|
||||||
requires io.netty.handler;
|
|
||||||
requires io.netty.handler.proxy;
|
requires io.netty.handler.proxy;
|
||||||
requires io.netty.transport;
|
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
provides org.xbib.netty.http.client.api.ProtocolProvider with
|
provides org.xbib.netty.http.client.api.ClientProtocolProvider with Http1, Http2;
|
||||||
org.xbib.netty.http.client.Http1Provider,
|
|
||||||
org.xbib.netty.http.client.Http2Provider;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,17 +22,16 @@ import io.netty.handler.ssl.SslHandler;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
import io.netty.handler.ssl.SslProvider;
|
||||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||||
import io.netty.util.concurrent.Future;
|
import io.netty.util.concurrent.Future;
|
||||||
import org.xbib.netty.http.client.api.HttpChannelInitializer;
|
import org.xbib.netty.http.client.api.ClientProtocolProvider;
|
||||||
import org.xbib.netty.http.client.api.ProtocolProvider;
|
|
||||||
import org.xbib.netty.http.client.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.client.pool.BoundedChannelPool;
|
import org.xbib.netty.http.client.pool.BoundedChannelPool;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.common.NetworkUtils;
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
import org.xbib.netty.http.common.TransportProvider;
|
import org.xbib.netty.http.common.TransportProvider;
|
||||||
import org.xbib.netty.http.common.security.SecurityUtil;
|
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||||
|
|
||||||
import javax.net.ssl.SNIHostName;
|
import javax.net.ssl.SNIHostName;
|
||||||
import javax.net.ssl.SNIServerName;
|
import javax.net.ssl.SNIServerName;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
|
@ -89,9 +88,9 @@ public final class Client implements AutoCloseable {
|
||||||
|
|
||||||
private final Bootstrap bootstrap;
|
private final Bootstrap bootstrap;
|
||||||
|
|
||||||
private final Queue<Transport> transports;
|
private final Queue<ClientTransport> transports;
|
||||||
|
|
||||||
private final List<ProtocolProvider<HttpChannelInitializer, Transport>> protocolProviders;
|
private final List<ClientProtocolProvider<HttpChannelInitializer, ClientTransport>> protocolProviders;
|
||||||
|
|
||||||
private final AtomicBoolean closed;
|
private final AtomicBoolean closed;
|
||||||
|
|
||||||
|
@ -118,7 +117,7 @@ public final class Client implements AutoCloseable {
|
||||||
this.closed = new AtomicBoolean(false);
|
this.closed = new AtomicBoolean(false);
|
||||||
this.clientConfig = clientConfig;
|
this.clientConfig = clientConfig;
|
||||||
this.protocolProviders = new ArrayList<>();
|
this.protocolProviders = new ArrayList<>();
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> provider : ServiceLoader.load(ProtocolProvider.class)) {
|
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> provider : ServiceLoader.load(ClientProtocolProvider.class)) {
|
||||||
protocolProviders.add(provider);
|
protocolProviders.add(provider);
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINEST)) {
|
||||||
logger.log(Level.FINEST, "protocol provider: " + provider.transportClass());
|
logger.log(Level.FINEST, "protocol provider: " + provider.transportClass());
|
||||||
|
@ -199,7 +198,7 @@ public final class Client implements AutoCloseable {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ProtocolProvider<HttpChannelInitializer, Transport>> getProtocolProviders() {
|
public List<ClientProtocolProvider<HttpChannelInitializer, ClientTransport>> getProtocolProviders() {
|
||||||
return protocolProviders;
|
return protocolProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,14 +222,14 @@ public final class Client implements AutoCloseable {
|
||||||
return responseCounter;
|
return responseCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transport newTransport() {
|
public ClientTransport newTransport() {
|
||||||
return newTransport(null);
|
return newTransport(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transport newTransport(HttpAddress httpAddress) {
|
public ClientTransport newTransport(HttpAddress httpAddress) {
|
||||||
Transport transport = null;
|
ClientTransport transport = null;
|
||||||
if (httpAddress != null) {
|
if (httpAddress != null) {
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
|
||||||
if (protocolProvider.supportsMajorVersion(httpAddress.getVersion().majorVersion())) {
|
if (protocolProvider.supportsMajorVersion(httpAddress.getVersion().majorVersion())) {
|
||||||
try {
|
try {
|
||||||
transport = protocolProvider.transportClass()
|
transport = protocolProvider.transportClass()
|
||||||
|
@ -245,7 +244,7 @@ public final class Client implements AutoCloseable {
|
||||||
throw new UnsupportedOperationException("no protocol support for " + httpAddress);
|
throw new UnsupportedOperationException("no protocol support for " + httpAddress);
|
||||||
}
|
}
|
||||||
} else if (hasPooledConnections()) {
|
} else if (hasPooledConnections()) {
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
|
||||||
if (protocolProvider.supportsMajorVersion(pool.getVersion().majorVersion())) {
|
if (protocolProvider.supportsMajorVersion(pool.getVersion().majorVersion())) {
|
||||||
try {
|
try {
|
||||||
transport = protocolProvider.transportClass()
|
transport = protocolProvider.transportClass()
|
||||||
|
@ -311,7 +310,7 @@ public final class Client implements AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transport execute(Request request) throws IOException {
|
public ClientTransport execute(Request request) throws IOException {
|
||||||
return newTransport(HttpAddress.of(request.url(), request.httpVersion()))
|
return newTransport(HttpAddress.of(request.url(), request.httpVersion()))
|
||||||
.execute(request);
|
.execute(request);
|
||||||
}
|
}
|
||||||
|
@ -337,8 +336,8 @@ public final class Client implements AutoCloseable {
|
||||||
* @param request the new request for continuing the request.
|
* @param request the new request for continuing the request.
|
||||||
* @throws IOException if continuation fails
|
* @throws IOException if continuation fails
|
||||||
*/
|
*/
|
||||||
public void continuation(Transport transport, Request request) throws IOException {
|
public void continuation(ClientTransport transport, Request request) throws IOException {
|
||||||
Transport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion()));
|
ClientTransport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion()));
|
||||||
nextTransport.setCookieBox(transport.getCookieBox());
|
nextTransport.setCookieBox(transport.getCookieBox());
|
||||||
nextTransport.execute(request);
|
nextTransport.execute(request);
|
||||||
nextTransport.get();
|
nextTransport.get();
|
||||||
|
@ -352,7 +351,7 @@ public final class Client implements AutoCloseable {
|
||||||
* @param request the request to retry
|
* @param request the request to retry
|
||||||
* @throws IOException if retry failed
|
* @throws IOException if retry failed
|
||||||
*/
|
*/
|
||||||
public void retry(Transport transport, Request request) throws IOException {
|
public void retry(ClientTransport transport, Request request) throws IOException {
|
||||||
transport.execute(request);
|
transport.execute(request);
|
||||||
transport.get();
|
transport.get();
|
||||||
closeAndRemove(transport);
|
closeAndRemove(transport);
|
||||||
|
@ -370,7 +369,7 @@ public final class Client implements AutoCloseable {
|
||||||
public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException {
|
public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException {
|
||||||
if (closed.compareAndSet(false, true)) {
|
if (closed.compareAndSet(false, true)) {
|
||||||
try {
|
try {
|
||||||
for (Transport transport : transports) {
|
for (ClientTransport transport : transports) {
|
||||||
transport.close();
|
transport.close();
|
||||||
}
|
}
|
||||||
transports.clear();
|
transports.clear();
|
||||||
|
@ -386,7 +385,7 @@ public final class Client implements AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeAndRemove(Transport transport) throws IOException {
|
private void closeAndRemove(ClientTransport transport) throws IOException {
|
||||||
try {
|
try {
|
||||||
transport.close();
|
transport.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -400,7 +399,7 @@ public final class Client implements AutoCloseable {
|
||||||
HttpAddress httpAddress,
|
HttpAddress httpAddress,
|
||||||
SslHandlerFactory sslHandlerFactory,
|
SslHandlerFactory sslHandlerFactory,
|
||||||
HttpChannelInitializer helper) {
|
HttpChannelInitializer helper) {
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
|
||||||
if (protocolProvider.supportsMajorVersion(majorVersion)) {
|
if (protocolProvider.supportsMajorVersion(majorVersion)) {
|
||||||
try {
|
try {
|
||||||
return protocolProvider.initializerClass()
|
return protocolProvider.initializerClass()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.client;
|
package org.xbib.netty.http.client;
|
||||||
|
|
||||||
import org.xbib.netty.http.client.api.ProtocolProvider;
|
import org.xbib.netty.http.client.api.ClientProtocolProvider;
|
||||||
import org.xbib.netty.http.client.handler.http.Http1ChannelInitializer;
|
import org.xbib.netty.http.client.handler.http.Http1ChannelInitializer;
|
||||||
import org.xbib.netty.http.client.transport.Http1Transport;
|
import org.xbib.netty.http.client.transport.Http1Transport;
|
||||||
|
|
||||||
public class Http1Provider implements ProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
public class Http1 implements ClientProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsMajorVersion(int majorVersion) {
|
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.client;
|
package org.xbib.netty.http.client;
|
||||||
|
|
||||||
import org.xbib.netty.http.client.api.ProtocolProvider;
|
import org.xbib.netty.http.client.api.ClientProtocolProvider;
|
||||||
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
||||||
import org.xbib.netty.http.client.transport.Http2Transport;
|
import org.xbib.netty.http.client.transport.Http2Transport;
|
||||||
|
|
||||||
public class Http2Provider implements ProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
public class Http2 implements ClientProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsMajorVersion(int majorVersion) {
|
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -14,10 +14,9 @@ import io.netty.handler.ssl.SslHandler;
|
||||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.ClientConfig;
|
import org.xbib.netty.http.client.ClientConfig;
|
||||||
import org.xbib.netty.http.client.api.HttpChannelInitializer;
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
|
@ -4,22 +4,25 @@ import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
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.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
|
|
||||||
@ChannelHandler.Sharable
|
@ChannelHandler.Sharable
|
||||||
public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception {
|
public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
|
if (transport != null) {
|
||||||
transport.responseReceived(ctx.channel(), null, fullHttpResponse);
|
transport.responseReceived(ctx.channel(), null, fullHttpResponse);
|
||||||
// do not close ctx here
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ctx.fireExceptionCaught(cause);
|
||||||
transport.fail(cause);
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
// do not close ctx here
|
if (transport != null) {
|
||||||
|
transport.fail(ctx.channel(), cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,10 @@ import io.netty.handler.codec.http2.Http2MultiplexCodecBuilder;
|
||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.ClientConfig;
|
import org.xbib.netty.http.client.ClientConfig;
|
||||||
import org.xbib.netty.http.client.api.HttpChannelInitializer;
|
|
||||||
import org.xbib.netty.http.client.handler.http.TrafficLoggingHandler;
|
import org.xbib.netty.http.client.handler.http.TrafficLoggingHandler;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
if (msg instanceof DefaultHttp2SettingsFrame) {
|
if (msg instanceof DefaultHttp2SettingsFrame) {
|
||||||
DefaultHttp2SettingsFrame settingsFrame = (DefaultHttp2SettingsFrame) msg;
|
DefaultHttp2SettingsFrame settingsFrame = (DefaultHttp2SettingsFrame) msg;
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
if (transport != null) {
|
if (transport != null) {
|
||||||
transport.settingsReceived(settingsFrame.settings());
|
transport.settingsReceived(settingsFrame.settings());
|
||||||
}
|
}
|
||||||
|
@ -100,7 +99,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
|
||||||
if (evt instanceof Http2ConnectionPrefaceAndSettingsFrameWrittenEvent) {
|
if (evt instanceof Http2ConnectionPrefaceAndSettingsFrameWrittenEvent) {
|
||||||
Http2ConnectionPrefaceAndSettingsFrameWrittenEvent event =
|
Http2ConnectionPrefaceAndSettingsFrameWrittenEvent event =
|
||||||
(Http2ConnectionPrefaceAndSettingsFrameWrittenEvent)evt;
|
(Http2ConnectionPrefaceAndSettingsFrameWrittenEvent)evt;
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
if (transport != null) {
|
if (transport != null) {
|
||||||
transport.settingsReceived(null);
|
transport.settingsReceived(null);
|
||||||
}
|
}
|
||||||
|
@ -110,9 +109,9 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
if (transport != null) {
|
if (transport != null) {
|
||||||
transport.fail(cause);
|
transport.fail(ctx.channel(), cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +125,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
|
||||||
public void logPushPromise(Direction direction, ChannelHandlerContext ctx, int streamId, int promisedStreamId,
|
public void logPushPromise(Direction direction, ChannelHandlerContext ctx, int streamId, int promisedStreamId,
|
||||||
Http2Headers headers, int padding) {
|
Http2Headers headers, int padding) {
|
||||||
super.logPushPromise(direction, ctx, streamId, promisedStreamId, headers, padding);
|
super.logPushPromise(direction, ctx, streamId, promisedStreamId, headers, padding);
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
if (transport != null) {
|
if (transport != null) {
|
||||||
transport.pushPromiseReceived(ctx.channel(), streamId, promisedStreamId, headers);
|
transport.pushPromiseReceived(ctx.channel(), streamId, promisedStreamId, headers);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,23 +5,37 @@ 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 io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
|
|
||||||
@ChannelHandler.Sharable
|
@ChannelHandler.Sharable
|
||||||
public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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();
|
|
||||||
Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
|
Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
|
||||||
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
|
if (transport != null) {
|
||||||
transport.responseReceived(ctx.channel(), streamId, httpResponse);
|
transport.responseReceived(ctx.channel(), streamId, httpResponse);
|
||||||
|
}
|
||||||
// do not close ctx here
|
// do not close ctx here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
ctx.fireChannelInactive();
|
||||||
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
|
if (transport != null) {
|
||||||
|
transport.inactive(ctx.channel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ctx.fireExceptionCaught(cause);
|
||||||
transport.fail(cause);
|
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
|
if (transport != null) {
|
||||||
|
transport.fail(ctx.channel(), cause);
|
||||||
|
}
|
||||||
// do not close ctx here
|
// do not close ctx here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,13 @@ import org.xbib.net.PercentDecoder;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.net.URLSyntaxException;
|
import org.xbib.net.URLSyntaxException;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.client.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.BackOff;
|
import org.xbib.netty.http.client.api.BackOff;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
import org.xbib.netty.http.common.cookie.CookieBox;
|
import org.xbib.netty.http.common.cookie.CookieBox;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
|
@ -38,7 +37,7 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public abstract class BaseTransport implements Transport {
|
public abstract class BaseTransport implements ClientTransport {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
|
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
|
||||||
|
|
||||||
|
@ -54,15 +53,15 @@ public abstract class BaseTransport implements Transport {
|
||||||
|
|
||||||
private SSLSession sslSession;
|
private SSLSession sslSession;
|
||||||
|
|
||||||
final Map<String, Flow> flowMap;
|
public final Map<String, Flow> flowMap;
|
||||||
|
|
||||||
final SortedMap<String, Request> requests;
|
protected final SortedMap<String, Request> requests;
|
||||||
|
|
||||||
private CookieBox cookieBox;
|
private CookieBox cookieBox;
|
||||||
|
|
||||||
protected HttpDataFactory httpDataFactory;
|
protected HttpDataFactory httpDataFactory;
|
||||||
|
|
||||||
BaseTransport(Client client, HttpAddress httpAddress) {
|
public BaseTransport(Client client, HttpAddress httpAddress) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.httpAddress = httpAddress;
|
this.httpAddress = httpAddress;
|
||||||
this.channels = new ConcurrentHashMap<>();
|
this.channels = new ConcurrentHashMap<>();
|
||||||
|
@ -103,9 +102,7 @@ public abstract class BaseTransport implements Transport {
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
// channels are present, maybe forgot a get() to receive responses?
|
// channels are present, maybe forgot a get() to receive responses?
|
||||||
if (!channels.isEmpty()) {
|
|
||||||
get();
|
get();
|
||||||
}
|
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,33 +117,36 @@ public abstract class BaseTransport implements Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The underlying network layer failed, not possible to know the request.
|
* The underlying network layer failed.
|
||||||
* So we fail all (open) promises.
|
* So we fail all (open) promises.
|
||||||
* @param throwable the exception
|
* @param throwable the exception
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void fail(Throwable throwable) {
|
public void fail(Channel channel, Throwable throwable) {
|
||||||
// do not fail more than once
|
// do not fail more than once
|
||||||
if (this.throwable != null) {
|
if (this.throwable != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.log(Level.SEVERE, "failing: " + throwable.getMessage(), throwable);
|
|
||||||
this.throwable = throwable;
|
this.throwable = throwable;
|
||||||
|
logger.log(Level.SEVERE, "channel " + channel + " failing: " + throwable.getMessage(), throwable);
|
||||||
for (Flow flow : flowMap.values()) {
|
for (Flow flow : flowMap.values()) {
|
||||||
flow.fail(throwable);
|
flow.fail(throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transport get() {
|
public void inactive(Channel channel) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientTransport get() {
|
||||||
return get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS);
|
return get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transport get(long value, TimeUnit timeUnit) {
|
public ClientTransport get(long value, TimeUnit timeUnit) {
|
||||||
if (channels.isEmpty()) {
|
if (!flowMap.isEmpty()) {
|
||||||
return this;
|
|
||||||
}
|
|
||||||
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
|
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
|
||||||
Flow flow = entry.getValue();
|
Flow flow = entry.getValue();
|
||||||
if (!flow.isClosed()) {
|
if (!flow.isClosed()) {
|
||||||
|
@ -165,6 +165,8 @@ public abstract class BaseTransport implements Transport {
|
||||||
flow.close();
|
flow.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
flowMap.clear();
|
||||||
|
}
|
||||||
channels.values().forEach(channel -> {
|
channels.values().forEach(channel -> {
|
||||||
try {
|
try {
|
||||||
client.releaseChannel(channel, true);
|
client.releaseChannel(channel, true);
|
||||||
|
@ -177,20 +179,14 @@ public abstract class BaseTransport implements Transport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
if (channels.isEmpty()) {
|
if (!flowMap.isEmpty()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
|
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
|
||||||
Flow flow = entry.getValue();
|
Flow flow = entry.getValue();
|
||||||
for (Integer key : flow.keys()) {
|
for (Integer key : flow.keys()) {
|
||||||
try {
|
try {
|
||||||
flow.get(key).cancel(true);
|
flow.get(key).cancel(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String requestKey = getRequestKey(entry.getKey(), key);
|
completeRequestExceptionally(getRequestKey(entry.getKey(), key), e);
|
||||||
Request request = requests.get(requestKey);
|
|
||||||
if (request != null && request.getCompletableFuture() != null) {
|
|
||||||
request.getCompletableFuture().completeExceptionally(e);
|
|
||||||
}
|
|
||||||
flow.fail(e);
|
flow.fail(e);
|
||||||
} finally {
|
} finally {
|
||||||
flow.remove(key);
|
flow.remove(key);
|
||||||
|
@ -198,6 +194,7 @@ public 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);
|
||||||
|
|
|
@ -6,13 +6,13 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
class Flow {
|
public class Flow {
|
||||||
|
|
||||||
private final AtomicInteger counter;
|
private final AtomicInteger counter;
|
||||||
|
|
||||||
private final SortedMap<Integer, CompletableFuture<Boolean>> map;
|
private final SortedMap<Integer, CompletableFuture<Boolean>> map;
|
||||||
|
|
||||||
Flow() {
|
public Flow() {
|
||||||
this.counter = new AtomicInteger(3);
|
this.counter = new AtomicInteger(3);
|
||||||
this.map = new ConcurrentSkipListMap<>();
|
this.map = new ConcurrentSkipListMap<>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
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.channel.ChannelFuture;
|
|
||||||
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.FullHttpResponse;
|
||||||
|
@ -12,17 +11,17 @@ import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import org.xbib.net.URLSyntaxException;
|
import org.xbib.net.URLSyntaxException;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
|
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.common.DefaultHttpResponse;
|
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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -36,7 +35,7 @@ public class Http1Transport extends BaseTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transport execute(Request request) throws IOException {
|
public ClientTransport execute(Request request) throws IOException {
|
||||||
Channel channel = mapChannel(request);
|
Channel channel = mapChannel(request);
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -175,6 +174,11 @@ public class Http1Transport extends BaseTransport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRequestKey(String channelId, Integer streamId) {
|
protected String getRequestKey(String channelId, Integer streamId) {
|
||||||
|
try {
|
||||||
return requests.isEmpty() ? null : requests.lastKey();
|
return requests.isEmpty() ? null : requests.lastKey();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
// ConcurrentSkipListMap is not thread-safe, can be emptied before lastKey() is called
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.net.URLSyntaxException;
|
import org.xbib.net.URLSyntaxException;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
|
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.handler.http2.Http2ResponseHandler;
|
import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler;
|
||||||
|
@ -49,7 +49,7 @@ public class Http2Transport extends BaseTransport {
|
||||||
public Http2Transport(Client client, HttpAddress httpAddress) {
|
public Http2Transport(Client client, HttpAddress httpAddress) {
|
||||||
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 ClientTransport transport = this;
|
||||||
this.initializer = new ChannelInitializer<>() {
|
this.initializer = new ChannelInitializer<>() {
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel ch) {
|
protected void initChannel(Channel ch) {
|
||||||
|
@ -68,7 +68,7 @@ public class Http2Transport extends BaseTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transport execute(Request request) throws IOException {
|
public ClientTransport execute(Request request) throws IOException {
|
||||||
Channel channel = mapChannel(request);
|
Channel channel = mapChannel(request);
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.xbib.netty.http.client.Http1
|
||||||
|
org.xbib.netty.http.client.Http2
|
|
@ -1,2 +0,0 @@
|
||||||
org.xbib.netty.http.client.Http1Provider
|
|
||||||
org.xbib.netty.http.client.Http2Provider
|
|
|
@ -1,15 +1,16 @@
|
||||||
module org.xbib.netty.http.common {
|
module org.xbib.netty.http.common {
|
||||||
exports org.xbib.netty.http.common;
|
exports org.xbib.netty.http.common;
|
||||||
exports org.xbib.netty.http.common.cookie;
|
exports org.xbib.netty.http.common.cookie;
|
||||||
exports org.xbib.netty.http.common.net;
|
|
||||||
exports org.xbib.netty.http.common.mime;
|
exports org.xbib.netty.http.common.mime;
|
||||||
exports org.xbib.netty.http.common.security;
|
exports org.xbib.netty.http.common.security;
|
||||||
exports org.xbib.netty.http.common.util;
|
exports org.xbib.netty.http.common.util;
|
||||||
requires transitive org.xbib.net.url;
|
requires transitive org.xbib.net.url;
|
||||||
requires io.netty.buffer;
|
requires transitive io.netty.buffer;
|
||||||
requires io.netty.common;
|
requires transitive io.netty.common;
|
||||||
requires io.netty.transport;
|
requires transitive io.netty.transport;
|
||||||
requires io.netty.handler;
|
requires transitive io.netty.handler;
|
||||||
requires io.netty.codec.http;
|
requires transitive io.netty.codec;
|
||||||
|
requires transitive io.netty.codec.http;
|
||||||
|
requires transitive io.netty.codec.http2;
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.client.api;
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
|
@ -6,5 +6,4 @@ import io.netty.channel.ChannelHandler;
|
||||||
public interface HttpChannelInitializer extends ChannelHandler {
|
public interface HttpChannelInitializer extends ChannelHandler {
|
||||||
|
|
||||||
void initChannel(Channel channel);
|
void initChannel(Channel channel);
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
|
public interface Transport {
|
||||||
|
}
|
|
@ -1,229 +0,0 @@
|
||||||
package org.xbib.netty.http.common.mime;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A MIME multi part message parser (RFC 2046).
|
|
||||||
*/
|
|
||||||
public class MalvaMimeMultipartParser implements MimeMultipartParser {
|
|
||||||
|
|
||||||
private String contentType;
|
|
||||||
|
|
||||||
private byte[] boundary;
|
|
||||||
|
|
||||||
private ByteBuf payload;
|
|
||||||
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
private String subType;
|
|
||||||
|
|
||||||
public MalvaMimeMultipartParser(String contentType, ByteBuf payload) {
|
|
||||||
this.contentType = contentType;
|
|
||||||
this.payload = payload;
|
|
||||||
if (contentType != null) {
|
|
||||||
int pos = contentType.indexOf(';');
|
|
||||||
this.type = pos >= 0 ? contentType.substring(0, pos) : contentType;
|
|
||||||
this.type = type.trim().toLowerCase();
|
|
||||||
this.subType = type.startsWith("multipart") ? type.substring(10).trim() : null;
|
|
||||||
Map<String, String> m = parseHeaderLine(contentType);
|
|
||||||
this.boundary = m.containsKey("boundary") ? m.get("boundary").toString().getBytes(StandardCharsets.US_ASCII) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String type() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String subType() {
|
|
||||||
return subType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void parse(MimeMultipartListener listener) throws IOException {
|
|
||||||
if (boundary == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Assumption: header is in 8 bytes (ISO-8859-1). Convert to Unicode.
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
boolean inHeader = true;
|
|
||||||
boolean inBody = false;
|
|
||||||
Integer start = null;
|
|
||||||
Map<String, String> headers = new LinkedHashMap<>();
|
|
||||||
int eol = 0;
|
|
||||||
byte[] payloadBytes = payload.array();
|
|
||||||
for (int i = 0; i < payloadBytes.length; i++) {
|
|
||||||
byte b = payloadBytes[i];
|
|
||||||
if (inHeader) {
|
|
||||||
switch (b) {
|
|
||||||
case '\r':
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
String[] s = sb.toString().split(":");
|
|
||||||
String k = s[0];
|
|
||||||
String v = s[1];
|
|
||||||
if (!k.startsWith("--")) {
|
|
||||||
headers.put(k.toLowerCase(Locale.ROOT), v.trim());
|
|
||||||
}
|
|
||||||
eol = 0;
|
|
||||||
sb.setLength(0);
|
|
||||||
} else {
|
|
||||||
eol++;
|
|
||||||
if (eol >= 1) {
|
|
||||||
eol = 0;
|
|
||||||
sb.setLength(0);
|
|
||||||
inHeader = false;
|
|
||||||
inBody = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
eol = 0;
|
|
||||||
sb.append(b);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inBody) {
|
|
||||||
int len = headers.containsKey("content-length") ?
|
|
||||||
Integer.parseInt(headers.get("content-length")) : -1;
|
|
||||||
if (len > 0) {
|
|
||||||
inBody = false;
|
|
||||||
inHeader = true;
|
|
||||||
} else {
|
|
||||||
if (start == null) {
|
|
||||||
if (b != '\r' && b != '\n') {
|
|
||||||
start = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (start != null) {
|
|
||||||
i = indexOf(payloadBytes, boundary, start, payloadBytes.length);
|
|
||||||
if (i == -1) {
|
|
||||||
throw new IOException("boundary not found");
|
|
||||||
}
|
|
||||||
int l = i - start;
|
|
||||||
if (l > 4) {
|
|
||||||
l = l - 4;
|
|
||||||
}
|
|
||||||
//BytesReference body = new BytesArray(payloadBytes, start, l)
|
|
||||||
ByteBuf body = payload.retainedSlice(start, l);
|
|
||||||
Map<String, String> m = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
|
||||||
m.putAll(parseHeaderLine(entry.getValue()));
|
|
||||||
}
|
|
||||||
headers.putAll(m);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.handle(type, subType, new MimePart(headers, body));
|
|
||||||
}
|
|
||||||
inBody = false;
|
|
||||||
inHeader = true;
|
|
||||||
headers = new LinkedHashMap<>();
|
|
||||||
start = null;
|
|
||||||
eol = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> parseHeaderLine(String line) {
|
|
||||||
Map<String, String> params = new LinkedHashMap<>();
|
|
||||||
int pos = line.indexOf(";");
|
|
||||||
String spec = line.substring(pos + 1);
|
|
||||||
if (pos < 0) {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
String key = "";
|
|
||||||
String value;
|
|
||||||
boolean inKey = true;
|
|
||||||
boolean inString = false;
|
|
||||||
int start = 0;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < spec.length(); i++) {
|
|
||||||
switch (spec.charAt(i)) {
|
|
||||||
case '=':
|
|
||||||
if (inKey) {
|
|
||||||
key = spec.substring(start, i).trim().toLowerCase();
|
|
||||||
start = i + 1;
|
|
||||||
inKey = false;
|
|
||||||
} else if (!inString) {
|
|
||||||
throw new IllegalArgumentException(contentType + " value has illegal character '=' at " + i + ": " + spec);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ';':
|
|
||||||
if (inKey) {
|
|
||||||
if (spec.substring(start, i).trim().length() > 0) {
|
|
||||||
throw new IllegalArgumentException(contentType + " parameter missing value at " + i + ": " + spec);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(contentType + " parameter key has illegal character ';' at " + i + ": " + spec);
|
|
||||||
}
|
|
||||||
} else if (!inString) {
|
|
||||||
value = spec.substring(start, i).trim();
|
|
||||||
params.put(key, value);
|
|
||||||
key = null;
|
|
||||||
start = i + 1;
|
|
||||||
inKey = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '"':
|
|
||||||
if (inKey) {
|
|
||||||
throw new IllegalArgumentException(contentType + " key has illegal character '\"' at " + i + ": " + spec);
|
|
||||||
} else if (inString) {
|
|
||||||
value = spec.substring(start, i).trim();
|
|
||||||
params.put(key, value);
|
|
||||||
key = null;
|
|
||||||
for (i++; i < spec.length() && spec.charAt(i) != ';'; i++) {
|
|
||||||
if (!Character.isWhitespace(spec.charAt(i))) {
|
|
||||||
throw new IllegalArgumentException(contentType + " value has garbage after quoted string at " + i + ": " + spec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
start = i + 1;
|
|
||||||
inString = false;
|
|
||||||
inKey = true;
|
|
||||||
} else {
|
|
||||||
if (spec.substring(start, i).trim().length() > 0) {
|
|
||||||
throw new IllegalArgumentException(contentType + " value has garbage before quoted string at " + i + ": " + spec);
|
|
||||||
}
|
|
||||||
start = i + 1;
|
|
||||||
inString = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inKey) {
|
|
||||||
if (pos > start && spec.substring(start, i).trim().length() > 0) {
|
|
||||||
throw new IllegalArgumentException(contentType + " missing value at " + i + ": " + spec);
|
|
||||||
}
|
|
||||||
} else if (!inString) {
|
|
||||||
value = spec.substring(start, i).trim();
|
|
||||||
params.put(key, value);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(contentType + " has an unterminated quoted string: " + spec);
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int indexOf(byte[] array, byte[] target, int start, int end) {
|
|
||||||
if (target.length == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
outer:
|
|
||||||
for (int i = start; i < end - target.length + 1; i++) {
|
|
||||||
for (int j = 0; j < target.length; j++) {
|
|
||||||
if (array[i + j] != target[j]) {
|
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,223 @@
|
||||||
package org.xbib.netty.http.common.mime;
|
package org.xbib.netty.http.common.mime;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface MimeMultipartParser {
|
/**
|
||||||
|
* A MIME multi part message parser (RFC 2046).
|
||||||
|
*/
|
||||||
|
public class MimeMultipartParser {
|
||||||
|
|
||||||
String type();
|
private final String contentType;
|
||||||
|
|
||||||
String subType();
|
private final ByteBuf payload;
|
||||||
|
|
||||||
void parse(MimeMultipartListener listener) throws IOException;
|
private byte[] boundary;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private String subType;
|
||||||
|
|
||||||
|
public MimeMultipartParser(String contentType, ByteBuf payload) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.payload = payload;
|
||||||
|
if (contentType != null) {
|
||||||
|
int pos = contentType.indexOf(';');
|
||||||
|
this.type = pos >= 0 ? contentType.substring(0, pos) : contentType;
|
||||||
|
this.type = type.trim().toLowerCase();
|
||||||
|
this.subType = type.startsWith("multipart") ? type.substring(10).trim() : null;
|
||||||
|
Map<String, String> m = parseHeaderLine(contentType);
|
||||||
|
this.boundary = m.containsKey("boundary") ?
|
||||||
|
m.get("boundary").getBytes(StandardCharsets.US_ASCII) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String subType() {
|
||||||
|
return subType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse(MimeMultipartListener listener) throws IOException {
|
||||||
|
if (boundary == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Assumption: header is in 8 bytes (ISO-8859-1). Convert to Unicode.
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean inHeader = true;
|
||||||
|
boolean inBody = false;
|
||||||
|
Integer start = null;
|
||||||
|
Map<String, String> headers = new LinkedHashMap<>();
|
||||||
|
int eol = 0;
|
||||||
|
byte[] payloadBytes = payload.array();
|
||||||
|
for (int i = 0; i < payloadBytes.length; i++) {
|
||||||
|
byte b = payloadBytes[i];
|
||||||
|
if (inHeader) {
|
||||||
|
switch (b) {
|
||||||
|
case '\r':
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
String[] s = sb.toString().split(":");
|
||||||
|
String k = s[0];
|
||||||
|
String v = s[1];
|
||||||
|
if (!k.startsWith("--")) {
|
||||||
|
headers.put(k.toLowerCase(Locale.ROOT), v.trim());
|
||||||
|
}
|
||||||
|
eol = 0;
|
||||||
|
sb.setLength(0);
|
||||||
|
} else {
|
||||||
|
eol++;
|
||||||
|
if (eol >= 1) {
|
||||||
|
eol = 0;
|
||||||
|
sb.setLength(0);
|
||||||
|
inHeader = false;
|
||||||
|
inBody = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
eol = 0;
|
||||||
|
sb.append(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inBody) {
|
||||||
|
int len = headers.containsKey("content-length") ?
|
||||||
|
Integer.parseInt(headers.get("content-length")) : -1;
|
||||||
|
if (len > 0) {
|
||||||
|
inBody = false;
|
||||||
|
inHeader = true;
|
||||||
|
} else {
|
||||||
|
if (b != '\r' && b != '\n') {
|
||||||
|
start = i;
|
||||||
|
}
|
||||||
|
if (start != null) {
|
||||||
|
i = indexOf(payloadBytes, boundary, start, payloadBytes.length);
|
||||||
|
if (i == -1) {
|
||||||
|
throw new IOException("boundary not found");
|
||||||
|
}
|
||||||
|
int l = i - start;
|
||||||
|
if (l > 4) {
|
||||||
|
l = l - 4;
|
||||||
|
}
|
||||||
|
//BytesReference body = new BytesArray(payloadBytes, start, l)
|
||||||
|
ByteBuf body = payload.retainedSlice(start, l);
|
||||||
|
Map<String, String> m = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||||
|
m.putAll(parseHeaderLine(entry.getValue()));
|
||||||
|
}
|
||||||
|
headers.putAll(m);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.handle(type, subType, new MimePart(headers, body));
|
||||||
|
}
|
||||||
|
inBody = false;
|
||||||
|
inHeader = true;
|
||||||
|
headers = new LinkedHashMap<>();
|
||||||
|
start = null;
|
||||||
|
eol = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> parseHeaderLine(String line) {
|
||||||
|
Map<String, String> params = new LinkedHashMap<>();
|
||||||
|
int pos = line.indexOf(";");
|
||||||
|
String spec = line.substring(pos + 1);
|
||||||
|
if (pos < 0) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
String key = "";
|
||||||
|
String value;
|
||||||
|
boolean inKey = true;
|
||||||
|
boolean inString = false;
|
||||||
|
int start = 0;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < spec.length(); i++) {
|
||||||
|
switch (spec.charAt(i)) {
|
||||||
|
case '=':
|
||||||
|
if (inKey) {
|
||||||
|
key = spec.substring(start, i).trim().toLowerCase();
|
||||||
|
start = i + 1;
|
||||||
|
inKey = false;
|
||||||
|
} else if (!inString) {
|
||||||
|
throw new IllegalArgumentException(contentType + " value has illegal character '=' at " + i + ": " + spec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ';':
|
||||||
|
if (inKey) {
|
||||||
|
if (spec.substring(start, i).trim().length() > 0) {
|
||||||
|
throw new IllegalArgumentException(contentType + " parameter missing value at " + i + ": " + spec);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(contentType + " parameter key has illegal character ';' at " + i + ": " + spec);
|
||||||
|
}
|
||||||
|
} else if (!inString) {
|
||||||
|
value = spec.substring(start, i).trim();
|
||||||
|
params.put(key, value);
|
||||||
|
key = null;
|
||||||
|
start = i + 1;
|
||||||
|
inKey = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
if (inKey) {
|
||||||
|
throw new IllegalArgumentException(contentType + " key has illegal character '\"' at " + i + ": " + spec);
|
||||||
|
} else if (inString) {
|
||||||
|
value = spec.substring(start, i).trim();
|
||||||
|
params.put(key, value);
|
||||||
|
key = null;
|
||||||
|
for (i++; i < spec.length() && spec.charAt(i) != ';'; i++) {
|
||||||
|
if (!Character.isWhitespace(spec.charAt(i))) {
|
||||||
|
throw new IllegalArgumentException(contentType + " value has garbage after quoted string at " + i + ": " + spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = i + 1;
|
||||||
|
inString = false;
|
||||||
|
inKey = true;
|
||||||
|
} else {
|
||||||
|
if (spec.substring(start, i).trim().length() > 0) {
|
||||||
|
throw new IllegalArgumentException(contentType + " value has garbage before quoted string at " + i + ": " + spec);
|
||||||
|
}
|
||||||
|
start = i + 1;
|
||||||
|
inString = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inKey) {
|
||||||
|
if (pos > start && spec.substring(start, i).trim().length() > 0) {
|
||||||
|
throw new IllegalArgumentException(contentType + " missing value at " + i + ": " + spec);
|
||||||
|
}
|
||||||
|
} else if (!inString) {
|
||||||
|
value = spec.substring(start, i).trim();
|
||||||
|
params.put(key, value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(contentType + " has an unterminated quoted string: " + spec);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int indexOf(byte[] array, byte[] target, int start, int end) {
|
||||||
|
if (target.length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
outer:
|
||||||
|
for (int i = start; i < end - target.length + 1; i++) {
|
||||||
|
for (int j = 0; j < target.length; j++) {
|
||||||
|
if (array[i + j] != target[j]) {
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package org.xbib.netty.http.common.net;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The network classes.
|
|
||||||
*/
|
|
||||||
public enum NetworkClass {
|
|
||||||
|
|
||||||
ANY, LOOPBACK, LOCAL, PUBLIC
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package org.xbib.netty.http.common.net;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The TCP/IP network protocol versions.
|
|
||||||
*/
|
|
||||||
public enum NetworkProtocolVersion {
|
|
||||||
|
|
||||||
IPV4, IPV6, IPV46, NONE
|
|
||||||
}
|
|
|
@ -51,5 +51,4 @@ public class SecurityUtil {
|
||||||
CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
|
CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
module org.xbib.netty.http.server.api {
|
module org.xbib.netty.http.server.api {
|
||||||
exports org.xbib.netty.http.server.api;
|
exports org.xbib.netty.http.server.api;
|
||||||
exports org.xbib.netty.http.server.api.annotation;
|
exports org.xbib.netty.http.server.api.annotation;
|
||||||
|
exports org.xbib.netty.http.server.api.security;
|
||||||
requires transitive org.xbib.netty.http.common;
|
requires transitive org.xbib.netty.http.common;
|
||||||
requires org.xbib.net.url;
|
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.common;
|
|
||||||
requires io.netty.handler;
|
|
||||||
requires io.netty.transport;
|
|
||||||
requires io.netty.codec.http;
|
|
||||||
requires io.netty.codec.http2;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package org.xbib.netty.http.server.api;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
|
|
||||||
public interface HttpChannelInitializer extends ChannelHandler {
|
|
||||||
|
|
||||||
void initChannel(Channel channel);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.xbib.netty.http.server.api;
|
package org.xbib.netty.http.server.api;
|
||||||
|
|
||||||
public interface ProtocolProvider<C extends HttpChannelInitializer, T extends Transport> {
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
|
|
||||||
|
public interface ServerProtocolProvider<C extends HttpChannelInitializer, T extends ServerTransport> {
|
||||||
|
|
||||||
boolean supportsMajorVersion(int majorVersion);
|
boolean supportsMajorVersion(int majorVersion);
|
||||||
|
|
|
@ -4,12 +4,12 @@ import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
|
import org.xbib.netty.http.common.Transport;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public interface Transport {
|
public interface ServerTransport extends Transport {
|
||||||
|
|
||||||
AttributeKey<Transport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
|
AttributeKey<ServerTransport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
|
||||||
|
|
||||||
void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException;
|
void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.common;
|
package org.xbib.netty.http.server.api.security;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@ -28,5 +28,4 @@ public interface ServerCertificateProvider {
|
||||||
* @return key password
|
* @return key password
|
||||||
*/
|
*/
|
||||||
String getKeyPassword();
|
String getKeyPassword();
|
||||||
|
|
||||||
}
|
}
|
|
@ -320,6 +320,7 @@ public class HandlerPublisher<T> extends ChannelDuplexHandler implements Publish
|
||||||
case DEMANDING:
|
case DEMANDING:
|
||||||
case IDLE:
|
case IDLE:
|
||||||
cancelled();
|
cancelled();
|
||||||
|
// fall through
|
||||||
case DRAINING:
|
case DRAINING:
|
||||||
state = DONE;
|
state = DONE;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module org.xbib.netty.http.server.rest {
|
module org.xbib.netty.http.server.rest {
|
||||||
exports org.xbib.netty.http.server.rest;
|
exports org.xbib.netty.http.server.rest;
|
||||||
exports org.xbib.netty.http.server.rest.util;
|
exports org.xbib.netty.http.server.rest.util;
|
||||||
requires org.xbib.netty.http.server;
|
requires transitive org.xbib.netty.http.server;
|
||||||
requires io.netty.transport;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
|
import org.xbib.netty.http.server.Http1;
|
||||||
|
import org.xbib.netty.http.server.Http2;
|
||||||
|
|
||||||
module org.xbib.netty.http.server {
|
module org.xbib.netty.http.server {
|
||||||
|
uses org.xbib.netty.http.server.api.security.ServerCertificateProvider;
|
||||||
|
uses org.xbib.netty.http.server.api.ServerProtocolProvider;
|
||||||
|
uses org.xbib.netty.http.common.TransportProvider;
|
||||||
exports org.xbib.netty.http.server;
|
exports org.xbib.netty.http.server;
|
||||||
exports org.xbib.netty.http.server.cookie;
|
exports org.xbib.netty.http.server.cookie;
|
||||||
exports org.xbib.netty.http.server.endpoint;
|
exports org.xbib.netty.http.server.endpoint;
|
||||||
|
@ -10,15 +16,6 @@ module org.xbib.netty.http.server {
|
||||||
exports org.xbib.netty.http.server.transport;
|
exports org.xbib.netty.http.server.transport;
|
||||||
exports org.xbib.netty.http.server.util;
|
exports org.xbib.netty.http.server.util;
|
||||||
requires transitive org.xbib.netty.http.server.api;
|
requires transitive org.xbib.netty.http.server.api;
|
||||||
requires org.xbib.net.url;
|
|
||||||
requires io.netty.buffer;
|
|
||||||
requires io.netty.common;
|
|
||||||
requires io.netty.handler;
|
|
||||||
requires io.netty.transport;
|
|
||||||
requires io.netty.codec.http;
|
|
||||||
requires io.netty.codec.http2;
|
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
provides org.xbib.netty.http.server.api.ProtocolProvider with
|
provides org.xbib.netty.http.server.api.ServerProtocolProvider with Http1, Http2;
|
||||||
org.xbib.netty.http.server.Http1Provider,
|
|
||||||
org.xbib.netty.http.server.Http2Provider;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,31 @@ import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
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.ServerCertificateProvider;
|
import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
|
||||||
import org.xbib.netty.http.common.security.SecurityUtil;
|
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||||
import org.xbib.netty.http.server.api.ServerRequest;
|
import org.xbib.netty.http.server.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
||||||
import org.xbib.netty.http.server.api.Filter;
|
import org.xbib.netty.http.server.api.Filter;
|
||||||
|
import org.xbib.netty.http.server.security.CertificateUtils;
|
||||||
|
import org.xbib.netty.http.server.security.PrivateKeyUtils;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -49,6 +58,8 @@ public class Domain {
|
||||||
|
|
||||||
private final List<HttpEndpointResolver> httpEndpointResolvers;
|
private final List<HttpEndpointResolver> httpEndpointResolvers;
|
||||||
|
|
||||||
|
private final Collection<? extends X509Certificate> certificates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code NamedServer} with the given name.
|
* Constructs a {@code NamedServer} with the given name.
|
||||||
*
|
*
|
||||||
|
@ -58,29 +69,41 @@ public class Domain {
|
||||||
* @param httpEndpointResolvers the endpoint resolvers
|
* @param httpEndpointResolvers the endpoint resolvers
|
||||||
* @param sslContext SSL context or null
|
* @param sslContext SSL context or null
|
||||||
*/
|
*/
|
||||||
protected Domain(String name, Set<String> aliases,
|
private Domain(String name,
|
||||||
|
Set<String> aliases,
|
||||||
HttpAddress httpAddress,
|
HttpAddress httpAddress,
|
||||||
List<HttpEndpointResolver> httpEndpointResolvers,
|
List<HttpEndpointResolver> httpEndpointResolvers,
|
||||||
SslContext sslContext) {
|
SslContext sslContext,
|
||||||
|
Collection<? extends X509Certificate> certificates) {
|
||||||
this.httpAddress = httpAddress;
|
this.httpAddress = httpAddress;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.sslContext = sslContext;
|
this.aliases = aliases;
|
||||||
this.aliases = Collections.unmodifiableSet(aliases);
|
|
||||||
this.httpEndpointResolvers = httpEndpointResolvers;
|
this.httpEndpointResolvers = httpEndpointResolvers;
|
||||||
|
this.sslContext = sslContext;
|
||||||
|
this.certificates = certificates;
|
||||||
|
Objects.requireNonNull(httpEndpointResolvers);
|
||||||
|
if (httpEndpointResolvers.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("domain must have at least one endpoint resolver");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return builder(HttpAddress.http1("localhost", 8008));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(HttpAddress httpAddress) {
|
public static Builder builder(HttpAddress httpAddress) {
|
||||||
return builder(httpAddress, "*");
|
return builder(httpAddress, httpAddress.getInetSocketAddress().getHostString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(HttpAddress httpAddress, String serverName) {
|
public static Builder builder(HttpAddress httpAddress, String serverName) {
|
||||||
return new Builder(httpAddress, serverName);
|
return new Builder(httpAddress).setServerName(serverName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Builder builder(Domain domain) {
|
||||||
|
return new Builder(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The address this domain binds to.
|
||||||
|
*
|
||||||
|
* @return the HTTP address
|
||||||
|
*/
|
||||||
public HttpAddress getHttpAddress() {
|
public HttpAddress getHttpAddress() {
|
||||||
return httpAddress;
|
return httpAddress;
|
||||||
}
|
}
|
||||||
|
@ -94,10 +117,6 @@ public class Domain {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SslContext getSslContext() {
|
|
||||||
return sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the aliases.
|
* Returns the aliases.
|
||||||
*
|
*
|
||||||
|
@ -107,6 +126,22 @@ public class Domain {
|
||||||
return aliases;
|
return aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns SSL context.
|
||||||
|
* @return the SSL context
|
||||||
|
*/
|
||||||
|
public SslContext getSslContext() {
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get certificate chain.
|
||||||
|
* @return the certificate chain or null if not secure
|
||||||
|
*/
|
||||||
|
public Collection<? extends X509Certificate> getCertificateChain() {
|
||||||
|
return certificates;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle server requests.
|
* Handle server requests.
|
||||||
* @param serverRequest the server request
|
* @param serverRequest the server request
|
||||||
|
@ -114,7 +149,6 @@ public class Domain {
|
||||||
* @throws IOException if handling server request fails
|
* @throws IOException if handling server request fails
|
||||||
*/
|
*/
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
if (httpEndpointResolvers != null) {
|
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
|
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
|
||||||
List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest);
|
List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest);
|
||||||
|
@ -125,27 +159,28 @@ public class Domain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED,
|
||||||
}
|
"text/plain", "No endpoint match for request " + serverRequest +
|
||||||
} else {
|
" endpoints = " + httpEndpointResolvers);
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name + " (" + httpAddress + ") " + aliases;
|
return name + " (" + httpAddress + ") aliases=" + aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private HttpAddress httpAddress;
|
private final HttpAddress httpAddress;
|
||||||
|
|
||||||
private String serverName;
|
private String serverName;
|
||||||
|
|
||||||
private Set<String> aliases;
|
private final Set<String> aliases;
|
||||||
|
|
||||||
private List<HttpEndpointResolver> httpEndpointResolvers;
|
private final List<HttpEndpointResolver> httpEndpointResolvers;
|
||||||
|
|
||||||
|
private SslContext sslContext;
|
||||||
|
|
||||||
private TrustManagerFactory trustManagerFactory;
|
private TrustManagerFactory trustManagerFactory;
|
||||||
|
|
||||||
|
@ -159,17 +194,13 @@ public class Domain {
|
||||||
|
|
||||||
private CipherSuiteFilter cipherSuiteFilter;
|
private CipherSuiteFilter cipherSuiteFilter;
|
||||||
|
|
||||||
private InputStream keyCertChainInputStream;
|
private Collection<? extends X509Certificate> keyCertChain;
|
||||||
|
|
||||||
private InputStream keyInputStream;
|
private PrivateKey privateKey;
|
||||||
|
|
||||||
private String keyPassword;
|
private Builder(HttpAddress httpAddress) {
|
||||||
|
|
||||||
Builder(HttpAddress httpAddress, String serverName) {
|
|
||||||
Objects.requireNonNull(httpAddress);
|
Objects.requireNonNull(httpAddress);
|
||||||
Objects.requireNonNull(serverName);
|
|
||||||
this.httpAddress = httpAddress;
|
this.httpAddress = httpAddress;
|
||||||
this.serverName = serverName;
|
|
||||||
this.aliases = new LinkedHashSet<>();
|
this.aliases = new LinkedHashSet<>();
|
||||||
this.httpEndpointResolvers = new ArrayList<>();
|
this.httpEndpointResolvers = new ArrayList<>();
|
||||||
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
|
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
|
||||||
|
@ -178,6 +209,26 @@ public class Domain {
|
||||||
this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Builder(Domain domain) {
|
||||||
|
this.httpAddress = domain.httpAddress;
|
||||||
|
this.aliases = new LinkedHashSet<>();
|
||||||
|
this.httpEndpointResolvers = new ArrayList<>(domain.httpEndpointResolvers);
|
||||||
|
this.sslContext = domain.sslContext;
|
||||||
|
this.keyCertChain = domain.certificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setServerName(String serverName) {
|
||||||
|
if (this.serverName == null) {
|
||||||
|
this.serverName = serverName;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setSslContext(SslContext sslContext) {
|
||||||
|
this.sslContext = sslContext;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
||||||
Objects.requireNonNull(trustManagerFactory);
|
Objects.requireNonNull(trustManagerFactory);
|
||||||
this.trustManagerFactory = trustManagerFactory;
|
this.trustManagerFactory = trustManagerFactory;
|
||||||
|
@ -226,56 +277,36 @@ public class Domain {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) {
|
public Builder setKeyCertChain(InputStream keyCertChainInputStream)
|
||||||
|
throws CertificateException {
|
||||||
Objects.requireNonNull(keyCertChainInputStream);
|
Objects.requireNonNull(keyCertChainInputStream);
|
||||||
this.keyCertChainInputStream = keyCertChainInputStream;
|
this.keyCertChain = CertificateUtils.toCertificate(keyCertChainInputStream);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setKeyInputStream(InputStream keyInputStream) {
|
public Builder setKey(InputStream keyInputStream, String keyPassword)
|
||||||
|
throws NoSuchPaddingException, NoSuchAlgorithmException, IOException,
|
||||||
|
KeyException, InvalidAlgorithmParameterException, InvalidKeySpecException {
|
||||||
Objects.requireNonNull(keyInputStream);
|
Objects.requireNonNull(keyInputStream);
|
||||||
this.keyInputStream = keyInputStream;
|
this.privateKey = PrivateKeyUtils.toPrivateKey(keyInputStream, keyPassword);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setKeyPassword(String keyPassword) {
|
public Builder setSelfCert() throws CertificateException, NoSuchPaddingException,
|
||||||
// null in keyPassword allowed, it means no password
|
NoSuchAlgorithmException, IOException, KeyException, InvalidAlgorithmParameterException,
|
||||||
this.keyPassword = keyPassword;
|
InvalidKeySpecException {
|
||||||
return this;
|
ServiceLoader<ServerCertificateProvider> serverCertificateProviders =
|
||||||
}
|
ServiceLoader.load(ServerCertificateProvider.class);
|
||||||
|
|
||||||
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
|
||||||
Objects.requireNonNull(keyCertChainInputStream);
|
|
||||||
Objects.requireNonNull(keyInputStream);
|
|
||||||
setKeyCertChainInputStream(keyCertChainInputStream);
|
|
||||||
setKeyInputStream(keyInputStream);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
|
||||||
String keyPassword) {
|
|
||||||
Objects.requireNonNull(keyCertChainInputStream);
|
|
||||||
Objects.requireNonNull(keyInputStream);
|
|
||||||
Objects.requireNonNull(keyPassword);
|
|
||||||
setKeyCertChainInputStream(keyCertChainInputStream);
|
|
||||||
setKeyInputStream(keyInputStream);
|
|
||||||
setKeyPassword(keyPassword);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setSelfCert() {
|
|
||||||
ServiceLoader<ServerCertificateProvider> serverCertificateProviders = ServiceLoader.load(ServerCertificateProvider.class);
|
|
||||||
for (ServerCertificateProvider serverCertificateProvider : serverCertificateProviders) {
|
for (ServerCertificateProvider serverCertificateProvider : serverCertificateProviders) {
|
||||||
if ("org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider".equals(serverCertificateProvider.getClass().getName())) {
|
if ("org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider".equals(serverCertificateProvider.getClass().getName())) {
|
||||||
serverCertificateProvider.prepare(serverName);
|
serverCertificateProvider.prepare(serverName);
|
||||||
setKeyCertChainInputStream(serverCertificateProvider.getCertificateChain());
|
setKeyCertChain(serverCertificateProvider.getCertificateChain());
|
||||||
setKeyInputStream(serverCertificateProvider.getPrivateKey());
|
setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword());
|
||||||
setKeyPassword(serverCertificateProvider.getKeyPassword());
|
|
||||||
logger.log(Level.INFO, "self signed certificate installed");
|
logger.log(Level.INFO, "self signed certificate installed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keyCertChainInputStream == null) {
|
if (keyCertChain == null) {
|
||||||
logger.log(Level.WARNING, "unable to install self signed certificate. Is netty-http-bouncycastle present?");
|
throw new CertificateException("unable to set self certificate");
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -301,7 +332,8 @@ public class Domain {
|
||||||
public Builder singleEndpoint(String path, Filter filter) {
|
public Builder singleEndpoint(String path, Filter filter) {
|
||||||
Objects.requireNonNull(path);
|
Objects.requireNonNull(path);
|
||||||
Objects.requireNonNull(filter);
|
Objects.requireNonNull(filter);
|
||||||
addEndpointResolver(HttpEndpointResolver.builder()
|
this.httpEndpointResolvers.clear();
|
||||||
|
this.httpEndpointResolvers.add(HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder()
|
.addEndpoint(HttpEndpoint.builder()
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.build())
|
.build())
|
||||||
|
@ -343,9 +375,10 @@ public class Domain {
|
||||||
public Domain build() {
|
public Domain build() {
|
||||||
if (httpAddress.isSecure() ) {
|
if (httpAddress.isSecure() ) {
|
||||||
try {
|
try {
|
||||||
|
if (sslContext == null && privateKey != null && keyCertChain != null) {
|
||||||
trustManagerFactory.init(trustManagerKeyStore);
|
trustManagerFactory.init(trustManagerKeyStore);
|
||||||
SslContextBuilder sslContextBuilder = SslContextBuilder
|
SslContextBuilder sslContextBuilder = SslContextBuilder
|
||||||
.forServer(keyCertChainInputStream, keyInputStream, keyPassword)
|
.forServer(privateKey, keyCertChain)
|
||||||
.trustManager(trustManagerFactory)
|
.trustManager(trustManagerFactory)
|
||||||
.sslProvider(sslProvider)
|
.sslProvider(sslProvider)
|
||||||
.ciphers(ciphers, cipherSuiteFilter);
|
.ciphers(ciphers, cipherSuiteFilter);
|
||||||
|
@ -355,12 +388,18 @@ public class Domain {
|
||||||
if (httpAddress.getVersion().majorVersion() == 2) {
|
if (httpAddress.getVersion().majorVersion() == 2) {
|
||||||
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
||||||
}
|
}
|
||||||
return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, sslContextBuilder.build());
|
this.sslContext = sslContextBuilder.build();
|
||||||
} catch (Throwable t) {
|
}
|
||||||
throw new RuntimeException(t);
|
return new Domain(serverName, aliases,
|
||||||
|
httpAddress, httpEndpointResolvers,
|
||||||
|
sslContext, keyCertChain);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, null);
|
return new Domain(serverName, aliases,
|
||||||
|
httpAddress, httpEndpointResolvers,
|
||||||
|
null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.server;
|
package org.xbib.netty.http.server;
|
||||||
|
|
||||||
import org.xbib.netty.http.server.api.ProtocolProvider;
|
import org.xbib.netty.http.server.api.ServerProtocolProvider;
|
||||||
import org.xbib.netty.http.server.handler.http.Http1ChannelInitializer;
|
import org.xbib.netty.http.server.handler.http.Http1ChannelInitializer;
|
||||||
import org.xbib.netty.http.server.transport.Http1Transport;
|
import org.xbib.netty.http.server.transport.Http1Transport;
|
||||||
|
|
||||||
public class Http1Provider implements ProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
public class Http1 implements ServerProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsMajorVersion(int majorVersion) {
|
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.server;
|
package org.xbib.netty.http.server;
|
||||||
|
|
||||||
import org.xbib.netty.http.server.api.ProtocolProvider;
|
import org.xbib.netty.http.server.api.ServerProtocolProvider;
|
||||||
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
||||||
import org.xbib.netty.http.server.transport.Http2Transport;
|
import org.xbib.netty.http.server.transport.Http2Transport;
|
||||||
|
|
||||||
public class Http2Provider implements ProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
public class Http2 implements ServerProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsMajorVersion(int majorVersion) {
|
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -14,18 +14,23 @@ import io.netty.handler.logging.LoggingHandler;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.util.DomainWildcardMappingBuilder;
|
import io.netty.util.DomainWildcardMappingBuilder;
|
||||||
import io.netty.util.Mapping;
|
import io.netty.util.Mapping;
|
||||||
|
import org.xbib.net.URL;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.NetworkUtils;
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.common.TransportProvider;
|
import org.xbib.netty.http.common.TransportProvider;
|
||||||
import org.xbib.netty.http.server.api.HttpChannelInitializer;
|
import org.xbib.netty.http.server.api.ServerProtocolProvider;
|
||||||
import org.xbib.netty.http.server.api.ProtocolProvider;
|
import org.xbib.netty.http.server.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.common.security.SecurityUtil;
|
import org.xbib.netty.http.server.api.ServerTransport;
|
||||||
|
import org.xbib.netty.http.server.security.CertificateUtils;
|
||||||
import org.xbib.netty.http.server.transport.HttpServerRequest;
|
import org.xbib.netty.http.server.transport.HttpServerRequest;
|
||||||
import org.xbib.netty.http.server.api.Transport;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
|
import java.security.cert.CertificateParsingException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -68,8 +73,6 @@ public final class Server implements AutoCloseable {
|
||||||
|
|
||||||
private final ServerConfig serverConfig;
|
private final ServerConfig serverConfig;
|
||||||
|
|
||||||
private final ByteBufAllocator byteBufAllocator;
|
|
||||||
|
|
||||||
private final EventLoopGroup parentEventLoopGroup;
|
private final EventLoopGroup parentEventLoopGroup;
|
||||||
|
|
||||||
private final EventLoopGroup childEventLoopGroup;
|
private final EventLoopGroup childEventLoopGroup;
|
||||||
|
@ -79,17 +82,14 @@ public final class Server implements AutoCloseable {
|
||||||
*/
|
*/
|
||||||
private final BlockingThreadPoolExecutor executor;
|
private final BlockingThreadPoolExecutor executor;
|
||||||
|
|
||||||
private final Class<? extends ServerSocketChannel> socketChannelClass;
|
|
||||||
|
|
||||||
private final ServerBootstrap bootstrap;
|
private final ServerBootstrap bootstrap;
|
||||||
|
|
||||||
private ChannelFuture channelFuture;
|
private ChannelFuture channelFuture;
|
||||||
|
|
||||||
private final List<ProtocolProvider<HttpChannelInitializer, Transport>> protocolProviders;
|
private final List<ServerProtocolProvider<HttpChannelInitializer, ServerTransport>> protocolProviders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new HTTP server.
|
* Create a new HTTP server.
|
||||||
* Use {@link #builder(HttpAddress)} to build HTTP instance.
|
|
||||||
*
|
*
|
||||||
* @param serverConfig server configuration
|
* @param serverConfig server configuration
|
||||||
* @param byteBufAllocator byte buf allocator
|
* @param byteBufAllocator byte buf allocator
|
||||||
|
@ -106,13 +106,13 @@ public final class Server implements AutoCloseable {
|
||||||
BlockingThreadPoolExecutor executor) {
|
BlockingThreadPoolExecutor executor) {
|
||||||
Objects.requireNonNull(serverConfig);
|
Objects.requireNonNull(serverConfig);
|
||||||
this.serverConfig = serverConfig;
|
this.serverConfig = serverConfig;
|
||||||
this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT;
|
ByteBufAllocator byteBufAllocator1 = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT;
|
||||||
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
||||||
this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup);
|
this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup);
|
||||||
this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass);
|
Class<? extends ServerSocketChannel> socketChannelClass1 = createSocketChannelClass(serverConfig, socketChannelClass);
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.protocolProviders =new ArrayList<>();
|
this.protocolProviders =new ArrayList<>();
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> provider : ServiceLoader.load(ProtocolProvider.class)) {
|
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> provider : ServiceLoader.load(ServerProtocolProvider.class)) {
|
||||||
protocolProviders.add(provider);
|
protocolProviders.add(provider);
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINEST)) {
|
||||||
logger.log(Level.FINEST, "protocol provider up: " + provider.transportClass());
|
logger.log(Level.FINEST, "protocol provider up: " + provider.transportClass());
|
||||||
|
@ -120,13 +120,13 @@ public final class Server implements AutoCloseable {
|
||||||
}
|
}
|
||||||
this.bootstrap = new ServerBootstrap()
|
this.bootstrap = new ServerBootstrap()
|
||||||
.group(this.parentEventLoopGroup, this.childEventLoopGroup)
|
.group(this.parentEventLoopGroup, this.childEventLoopGroup)
|
||||||
.channel(this.socketChannelClass)
|
.channel(socketChannelClass1)
|
||||||
.option(ChannelOption.ALLOCATOR, this.byteBufAllocator)
|
.option(ChannelOption.ALLOCATOR, byteBufAllocator1)
|
||||||
.option(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
|
.option(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
|
||||||
.option(ChannelOption.SO_RCVBUF, serverConfig.getTcpReceiveBufferSize())
|
.option(ChannelOption.SO_RCVBUF, serverConfig.getTcpReceiveBufferSize())
|
||||||
.option(ChannelOption.SO_BACKLOG, serverConfig.getBackLogSize())
|
.option(ChannelOption.SO_BACKLOG, serverConfig.getBackLogSize())
|
||||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConnectTimeoutMillis())
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConnectTimeoutMillis())
|
||||||
.childOption(ChannelOption.ALLOCATOR, this.byteBufAllocator)
|
.childOption(ChannelOption.ALLOCATOR, byteBufAllocator1)
|
||||||
.childOption(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
|
.childOption(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
|
||||||
.childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNodelay())
|
.childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNodelay())
|
||||||
.childOption(ChannelOption.SO_SNDBUF, serverConfig.getTcpSendBufferSize())
|
.childOption(ChannelOption.SO_SNDBUF, serverConfig.getTcpSendBufferSize())
|
||||||
|
@ -137,34 +137,39 @@ public final class Server implements AutoCloseable {
|
||||||
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getTrafficDebugLogLevel()));
|
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getTrafficDebugLogLevel()));
|
||||||
}
|
}
|
||||||
if (serverConfig.getDefaultDomain() == null) {
|
if (serverConfig.getDefaultDomain() == null) {
|
||||||
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue");
|
throw new IllegalStateException("no default domain configured, unable to continue");
|
||||||
}
|
}
|
||||||
|
// translate domains into Netty mapping
|
||||||
Mapping<String, SslContext> domainNameMapping = null;
|
Mapping<String, SslContext> domainNameMapping = null;
|
||||||
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) {
|
Domain defaultDomain = serverConfig.getDefaultDomain();
|
||||||
|
if (serverConfig.getAddress().isSecure() &&
|
||||||
|
defaultDomain != null &&
|
||||||
|
defaultDomain.getSslContext() != null) {
|
||||||
DomainWildcardMappingBuilder<SslContext> mappingBuilder =
|
DomainWildcardMappingBuilder<SslContext> mappingBuilder =
|
||||||
new DomainWildcardMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext());
|
new DomainWildcardMappingBuilder<>(defaultDomain.getSslContext());
|
||||||
for (Domain domain : serverConfig.getDomains()) {
|
for (Domain domain : serverConfig.getDomains()) {
|
||||||
String name = domain.getName();
|
if (!domain.getName().equals(defaultDomain.getName())) {
|
||||||
if (!"*".equals(name)) {
|
mappingBuilder.add(domain.getName(), domain.getSslContext());
|
||||||
mappingBuilder.add(name, domain.getSslContext());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
domainNameMapping = mappingBuilder.build();
|
domainNameMapping = mappingBuilder.build();
|
||||||
|
logger.log(Level.INFO, "domain name mapping: " + domainNameMapping);
|
||||||
}
|
}
|
||||||
bootstrap.childHandler(findChannelInitializer(serverConfig.getAddress().getVersion().majorVersion(),
|
bootstrap.childHandler(findChannelInitializer(serverConfig.getAddress().getVersion().majorVersion(),
|
||||||
serverConfig.getAddress(), domainNameMapping));
|
serverConfig.getAddress(), domainNameMapping));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
@Override
|
||||||
return builder(HttpAddress.http1("localhost", 8008));
|
public void close() {
|
||||||
|
try {
|
||||||
|
shutdownGracefully();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(HttpAddress httpAddress) {
|
public static Builder builder(Domain defaultDomain) {
|
||||||
return new Builder(httpAddress);
|
return new Builder(defaultDomain);
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder(Domain domain) {
|
|
||||||
return new Builder(domain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig getServerConfig() {
|
public ServerConfig getServerConfig() {
|
||||||
|
@ -189,22 +194,64 @@ public final class Server implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the named server with the given name.
|
* Returns the domain with the given host name.
|
||||||
*
|
*
|
||||||
* @param name the name of the virtual host to return or null for the
|
* @param dnsName the name of the virtual host with optional port to return or null for the
|
||||||
* default domain
|
* default domain
|
||||||
* @return the virtual host with the given name or the default domain
|
* @return the virtual host with the given name or the default domain
|
||||||
*/
|
*/
|
||||||
public Domain getNamedServer(String name) {
|
public Domain getDomain(String dnsName) {
|
||||||
Domain domain = serverConfig.getDomain(name);
|
return serverConfig.getDomain(dnsName);
|
||||||
if (domain == null) {
|
|
||||||
domain = serverConfig.getDefaultDomain();
|
|
||||||
}
|
|
||||||
return domain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(Domain domain, HttpServerRequest serverRequest, ServerResponse serverResponse)
|
public Domain getDomain(URL url) {
|
||||||
throws IOException {
|
return getDomain(url.getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getPublishURL() {
|
||||||
|
return getPublishURL(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getPublishURL(ServerRequest serverRequest) {
|
||||||
|
Domain domain = serverRequest != null ? getDomain(serverRequest.getURL()) : serverConfig.getDefaultDomain();
|
||||||
|
URL bindURL = domain.getHttpAddress().base();
|
||||||
|
String scheme = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-proto") : null;
|
||||||
|
if (scheme == null) {
|
||||||
|
scheme = bindURL.getScheme();
|
||||||
|
}
|
||||||
|
String host = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-host") : null;
|
||||||
|
if (host == null) {
|
||||||
|
host = serverRequest != null ? serverRequest.getHeaders().get("host") : null;
|
||||||
|
if (host == null) {
|
||||||
|
host = bindURL.getHost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String port = null;
|
||||||
|
if (host != null) {
|
||||||
|
host = stripPort(host);
|
||||||
|
port = extractPort(host);
|
||||||
|
if (port == null) {
|
||||||
|
port = bindURL.getPort() != null ? Integer.toString(bindURL.getPort()) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String path = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-path") : null;
|
||||||
|
URL.Builder builder = URL.builder().scheme(scheme).host(host);
|
||||||
|
if (port != null) {
|
||||||
|
if (path != null) {
|
||||||
|
return builder.port(Integer.parseInt(port)).path(path).build();
|
||||||
|
} else {
|
||||||
|
return builder.port(Integer.parseInt(port)).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (path != null) {
|
||||||
|
return builder.path(path).build();
|
||||||
|
} else {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
|
Domain domain = getDomain(serverRequest.getURL());
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
|
@ -224,10 +271,6 @@ public final class Server implements AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockingThreadPoolExecutor getExecutor() {
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getRequestCounter() {
|
public AtomicLong getRequestCounter() {
|
||||||
return requestCounter;
|
return requestCounter;
|
||||||
}
|
}
|
||||||
|
@ -236,8 +279,8 @@ public final class Server implements AutoCloseable {
|
||||||
return responseCounter;
|
return responseCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transport newTransport(HttpVersion httpVersion) {
|
public ServerTransport newTransport(HttpVersion httpVersion) {
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
|
||||||
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {
|
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {
|
||||||
try {
|
try {
|
||||||
return protocolProvider.transportClass()
|
return protocolProvider.transportClass()
|
||||||
|
@ -251,15 +294,6 @@ public final class Server implements AutoCloseable {
|
||||||
throw new IllegalStateException("no channel initializer found for major version " + httpVersion.majorVersion());
|
throw new IllegalStateException("no channel initializer found for major version " + httpVersion.majorVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
shutdownGracefully(30L, TimeUnit.SECONDS);
|
shutdownGracefully(30L, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
@ -289,10 +323,26 @@ public final class Server implements AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String stripPort(String hostMaybePort) {
|
||||||
|
if (hostMaybePort == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int i = hostMaybePort.lastIndexOf(':');
|
||||||
|
return i >= 0 ? hostMaybePort.substring(0, i) : hostMaybePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractPort(String hostMaybePort) {
|
||||||
|
if (hostMaybePort == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int i = hostMaybePort.lastIndexOf(':');
|
||||||
|
return i >= 0 ? hostMaybePort.substring(i + 1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
private HttpChannelInitializer findChannelInitializer(int majorVersion,
|
private HttpChannelInitializer findChannelInitializer(int majorVersion,
|
||||||
HttpAddress httpAddress,
|
HttpAddress httpAddress,
|
||||||
Mapping<String, SslContext> domainNameMapping) {
|
Mapping<String, SslContext> domainNameMapping) {
|
||||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
|
||||||
if (protocolProvider.supportsMajorVersion(majorVersion)) {
|
if (protocolProvider.supportsMajorVersion(majorVersion)) {
|
||||||
try {
|
try {
|
||||||
return protocolProvider.initializerClass()
|
return protocolProvider.initializerClass()
|
||||||
|
@ -449,11 +499,7 @@ public final class Server implements AutoCloseable {
|
||||||
|
|
||||||
private Class<? extends ServerSocketChannel> socketChannelClass;
|
private Class<? extends ServerSocketChannel> socketChannelClass;
|
||||||
|
|
||||||
private ServerConfig serverConfig;
|
private final ServerConfig serverConfig;
|
||||||
|
|
||||||
private Builder(HttpAddress httpAddress) {
|
|
||||||
this(Domain.builder(httpAddress, "*").build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder(Domain defaultDomain) {
|
private Builder(Domain defaultDomain) {
|
||||||
this.serverConfig = new ServerConfig();
|
this.serverConfig = new ServerConfig();
|
||||||
|
@ -596,14 +642,13 @@ public final class Server implements AutoCloseable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setTransportLayerSecurityProtocols(String[] protocols) {
|
public Builder setTransportLayerSecurityProtocols(String... protocols) {
|
||||||
this.serverConfig.setProtocols(protocols);
|
this.serverConfig.setProtocols(protocols);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder addDomain(Domain domain) {
|
public Builder addDomain(Domain domain) {
|
||||||
this.serverConfig.putDomain(domain);
|
this.serverConfig.checkAndAddDomain(domain);
|
||||||
logger.log(Level.FINE, "adding named server: " + domain);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,6 +661,45 @@ public final class Server implements AutoCloseable {
|
||||||
executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) ->
|
executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) ->
|
||||||
logger.log(Level.SEVERE, "rejected: " + runnable));
|
logger.log(Level.SEVERE, "rejected: " + runnable));
|
||||||
}
|
}
|
||||||
|
if (serverConfig.isAutoDomain()) {
|
||||||
|
// unpack subject alternative names into separate domains
|
||||||
|
for (Domain domain : serverConfig.getDomains()) {
|
||||||
|
try {
|
||||||
|
CertificateUtils.processSubjectAlternativeNames(domain.getCertificateChain(),
|
||||||
|
new CertificateUtils.SubjectAlternativeNamesProcessor() {
|
||||||
|
@Override
|
||||||
|
public void setServerName(String serverName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubjectAlternativeName(String subjectAlternativeName) {
|
||||||
|
Domain alternativeDomain = Domain.builder(domain)
|
||||||
|
.setServerName(subjectAlternativeName)
|
||||||
|
.build();
|
||||||
|
addDomain(alternativeDomain);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (CertificateParsingException e) {
|
||||||
|
logger.log(Level.SEVERE, "domain " + domain + ": unable to parse certificate: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Domain domain : serverConfig.getDomains()) {
|
||||||
|
if (domain.getCertificateChain() != null) {
|
||||||
|
for (X509Certificate certificate : domain.getCertificateChain()) {
|
||||||
|
try {
|
||||||
|
certificate.checkValidity();
|
||||||
|
logger.log(Level.INFO, "certificate " + certificate.getSubjectDN().getName() + " for " + domain + " is valid");
|
||||||
|
} catch (CertificateNotYetValidException | CertificateExpiredException e) {
|
||||||
|
logger.log(Level.SEVERE, "certificate " + certificate.getSubjectDN().getName() + " for " + domain + " is not valid: " + e.getMessage(), e);
|
||||||
|
if (!serverConfig.isAcceptInvalidCertificates()) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.log(Level.INFO, "configured domains: " + serverConfig.getDomains());
|
||||||
return new Server(serverConfig, byteBufAllocator,
|
return new Server(serverConfig, byteBufAllocator,
|
||||||
parentEventLoopGroup, childEventLoopGroup, socketChannelClass,
|
parentEventLoopGroup, childEventLoopGroup, socketChannelClass,
|
||||||
executor);
|
executor);
|
||||||
|
|
|
@ -262,6 +262,10 @@ public class ServerConfig {
|
||||||
|
|
||||||
private KeyStore trustManagerKeyStore = null;
|
private KeyStore trustManagerKeyStore = null;
|
||||||
|
|
||||||
|
private boolean autoDomain = false;
|
||||||
|
|
||||||
|
private boolean acceptInvalidCertificates = false;
|
||||||
|
|
||||||
public ServerConfig() {
|
public ServerConfig() {
|
||||||
this.domains = new LinkedHashMap<>();
|
this.domains = new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
|
@ -596,7 +600,28 @@ public class ServerConfig {
|
||||||
return cipherSuiteFilter;
|
return cipherSuiteFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig putDomain(Domain domain) {
|
public ServerConfig setAutoDomain(boolean autoDomain) {
|
||||||
|
this.autoDomain = autoDomain;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoDomain() {
|
||||||
|
return autoDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerConfig setAcceptInvalidCertificates(boolean acceptInvalidCertificates) {
|
||||||
|
this.acceptInvalidCertificates = acceptInvalidCertificates;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAcceptInvalidCertificates() {
|
||||||
|
return acceptInvalidCertificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerConfig checkAndAddDomain(Domain domain) {
|
||||||
|
if (domains.containsKey(domain.getName())) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
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);
|
||||||
|
@ -621,12 +646,14 @@ public class ServerConfig {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Domain getDefaultDomain() {
|
|
||||||
return getDomain("*");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Domain getDomain(String name) {
|
public Domain getDomain(String name) {
|
||||||
return domains.get(name);
|
Domain domain = domains.get(name);
|
||||||
|
return domain != null ? domain : getDefaultDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Domain getDefaultDomain() {
|
||||||
|
Domain defaultDomain = domains.get("*");
|
||||||
|
return defaultDomain != null ? defaultDomain :
|
||||||
|
!domains.isEmpty() ? domains.values().iterator().next() : null;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,6 @@ import org.xbib.netty.http.server.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.api.annotation.Endpoint;
|
import org.xbib.netty.http.server.api.annotation.Endpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.service.MethodService;
|
import org.xbib.netty.http.server.endpoint.service.MethodService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -78,7 +77,7 @@ public class HttpEndpointResolver {
|
||||||
|
|
||||||
private String prefix;
|
private String prefix;
|
||||||
|
|
||||||
private List<HttpEndpoint> endpoints;
|
private final List<HttpEndpoint> endpoints;
|
||||||
|
|
||||||
private EndpointDispatcher<HttpEndpoint> endpointDispatcher;
|
private EndpointDispatcher<HttpEndpoint> endpointDispatcher;
|
||||||
|
|
||||||
|
@ -99,7 +98,7 @@ public class HttpEndpointResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add endpoint.
|
* Add endpoint under this endpoint.
|
||||||
*
|
*
|
||||||
* @param endpoint the endpoint
|
* @param endpoint the endpoint
|
||||||
* @return this builder
|
* @return this builder
|
||||||
|
|
|
@ -22,11 +22,11 @@ import io.netty.util.Mapping;
|
||||||
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.api.HttpChannelInitializer;
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
|
import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
|
||||||
import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
|
import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
|
||||||
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
|
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
|
||||||
import org.xbib.netty.http.server.api.Transport;
|
import org.xbib.netty.http.server.api.ServerTransport;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -56,8 +56,8 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initChannel(Channel channel) {
|
public void initChannel(Channel channel) {
|
||||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
ServerTransport transport = server.newTransport(httpAddress.getVersion());
|
||||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||||
if (serverConfig.isTrafficDebug()) {
|
if (serverConfig.isTrafficDebug()) {
|
||||||
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
|
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
|
HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
|
||||||
ctx.channel().writeAndFlush(response);
|
ctx.channel().writeAndFlush(response);
|
||||||
} else {
|
} else {
|
||||||
Transport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
||||||
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
|
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
|
||||||
}
|
}
|
||||||
fullHttpRequest.release();
|
fullHttpRequest.release();
|
||||||
|
|
|
@ -34,11 +34,11 @@ import io.netty.util.Mapping;
|
||||||
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.api.HttpChannelInitializer;
|
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
|
import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
|
||||||
import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
|
import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
|
||||||
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
|
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
|
||||||
import org.xbib.netty.http.server.api.Transport;
|
import org.xbib.netty.http.server.api.ServerTransport;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -68,8 +68,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initChannel(Channel channel) {
|
public void initChannel(Channel channel) {
|
||||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
ServerTransport transport = server.newTransport(httpAddress.getVersion());
|
||||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||||
if (serverConfig.isTrafficDebug()) {
|
if (serverConfig.isTrafficDebug()) {
|
||||||
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
|
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
|
||||||
}
|
}
|
||||||
|
@ -94,8 +94,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
ChannelHandler channelHandler = new ChannelInitializer<Channel>() {
|
ChannelHandler channelHandler = new ChannelInitializer<Channel>() {
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel channel) {
|
protected void initChannel(Channel channel) {
|
||||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
ServerTransport transport = server.newTransport(httpAddress.getVersion());
|
||||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||||
ChannelPipeline pipeline = channel.pipeline();
|
ChannelPipeline pipeline = channel.pipeline();
|
||||||
pipeline.addLast("server-frame-converter",
|
pipeline.addLast("server-frame-converter",
|
||||||
new Http2StreamFrameToHttpObjectCodec(true));
|
new Http2StreamFrameToHttpObjectCodec(true));
|
||||||
|
@ -137,7 +137,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
|
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
transport.requestReceived(ctx, fullHttpRequest, null);
|
transport.requestReceived(ctx, fullHttpRequest, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
if (msg instanceof DefaultHttp2SettingsFrame) {
|
if (msg instanceof DefaultHttp2SettingsFrame) {
|
||||||
DefaultHttp2SettingsFrame http2SettingsFrame = (DefaultHttp2SettingsFrame) msg;
|
DefaultHttp2SettingsFrame http2SettingsFrame = (DefaultHttp2SettingsFrame) msg;
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
transport.settingsReceived(ctx, http2SettingsFrame.settings());
|
transport.settingsReceived(ctx, http2SettingsFrame.settings());
|
||||||
} else if (msg instanceof DefaultHttpRequest) {
|
} else if (msg instanceof DefaultHttpRequest) {
|
||||||
DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
|
DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
|
||||||
|
@ -159,13 +159,13 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
ctx.fireUserEventTriggered(evt);
|
ctx.fireUserEventTriggered(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
transport.exceptionReceived(ctx, cause);
|
transport.exceptionReceived(ctx, cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.xbib.netty.http.server.security;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.CertificateParsingException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CertificateUtils {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static Collection<? extends X509Certificate> toCertificate(InputStream keyCertChainInputStream)
|
||||||
|
throws CertificateException {
|
||||||
|
return (Collection<? extends X509Certificate>) CertificateFactory.getInstance("X509")
|
||||||
|
.generateCertificates(keyCertChainInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void processSubjectAlternativeNames(Collection<? extends X509Certificate> certificates,
|
||||||
|
SubjectAlternativeNamesProcessor processor) throws CertificateParsingException {
|
||||||
|
if (certificates == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
processor.setServerName(new DistinguishedNameParser(certificate.getSubjectX500Principal())
|
||||||
|
.findMostSpecific("CN"));
|
||||||
|
Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
|
||||||
|
if (altNames != null) {
|
||||||
|
for (List<?> altName : altNames) {
|
||||||
|
Integer type = (Integer) altName.get(0);
|
||||||
|
if (type == 2) { // Type DNS
|
||||||
|
String string = altName.get(1).toString();
|
||||||
|
processor.setSubjectAlternativeName(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SubjectAlternativeNamesProcessor {
|
||||||
|
|
||||||
|
void setServerName(String serverName);
|
||||||
|
|
||||||
|
void setSubjectAlternativeName(String subjectAlternativeName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,300 @@
|
||||||
|
package org.xbib.netty.http.server.security;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A distinguished name (DN) parser.
|
||||||
|
* This parser only supports extracting a string value from a DN.
|
||||||
|
* It doesn't support values in the hex-string style.
|
||||||
|
*
|
||||||
|
* Taken from okhttp
|
||||||
|
*/
|
||||||
|
public final class DistinguishedNameParser {
|
||||||
|
|
||||||
|
private final String dn;
|
||||||
|
|
||||||
|
private final int length;
|
||||||
|
|
||||||
|
private int pos;
|
||||||
|
|
||||||
|
private int beg;
|
||||||
|
|
||||||
|
private int end;
|
||||||
|
|
||||||
|
private int cur;
|
||||||
|
|
||||||
|
private char[] chars;
|
||||||
|
|
||||||
|
public DistinguishedNameParser(X500Principal principal) {
|
||||||
|
this.dn = principal.getName(X500Principal.RFC2253);
|
||||||
|
this.length = this.dn.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String nextAT() {
|
||||||
|
while (pos < length && chars[pos] == ' ') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (pos == length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
beg = pos;
|
||||||
|
pos++;
|
||||||
|
while (pos < length && chars[pos] != '=' && chars[pos] != ' ') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (pos >= length) {
|
||||||
|
throw new IllegalStateException("Unexpected end of DN: " + dn);
|
||||||
|
}
|
||||||
|
end = pos;
|
||||||
|
if (chars[pos] == ' ') {
|
||||||
|
while (pos < length && chars[pos] != '=' && chars[pos] == ' ') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (chars[pos] != '=' || pos == length) {
|
||||||
|
throw new IllegalStateException("Unexpected end of DN: " + dn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
while (pos < length && chars[pos] == ' ') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if ((end - beg > 4) && (chars[beg + 3] == '.') &&
|
||||||
|
(chars[beg] == 'O' || chars[beg] == 'o') &&
|
||||||
|
(chars[beg + 1] == 'I' || chars[beg + 1] == 'i') &&
|
||||||
|
(chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
|
||||||
|
beg += 4;
|
||||||
|
}
|
||||||
|
return new String(chars, beg, end - beg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String quotedAV() {
|
||||||
|
pos++;
|
||||||
|
beg = pos;
|
||||||
|
end = beg;
|
||||||
|
while (true) {
|
||||||
|
if (pos == length) {
|
||||||
|
throw new IllegalStateException("Unexpected end of DN: " + dn);
|
||||||
|
}
|
||||||
|
if (chars[pos] == '"') {
|
||||||
|
pos++;
|
||||||
|
break;
|
||||||
|
} else if (chars[pos] == '\\') {
|
||||||
|
chars[end] = getEscaped();
|
||||||
|
} else {
|
||||||
|
chars[end] = chars[pos];
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
while (pos < length && chars[pos] == ' ') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return new String(chars, beg, end - beg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String hexAV() {
|
||||||
|
if (pos + 4 >= length) {
|
||||||
|
throw new IllegalStateException("Unexpected end of DN: " + dn);
|
||||||
|
}
|
||||||
|
beg = pos;
|
||||||
|
pos++;
|
||||||
|
while (true) {
|
||||||
|
if (pos == length || chars[pos] == '+' || chars[pos] == ','
|
||||||
|
|| chars[pos] == ';') {
|
||||||
|
end = pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (chars[pos] == ' ') {
|
||||||
|
end = pos;
|
||||||
|
pos++;
|
||||||
|
while (pos < length && chars[pos] == ' ') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
|
||||||
|
chars[pos] += 32;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
int hexLen = end - beg;
|
||||||
|
if (hexLen < 5 || (hexLen & 1) == 0) {
|
||||||
|
throw new IllegalStateException("Unexpected end of DN: " + dn);
|
||||||
|
}
|
||||||
|
return new String(chars, beg, hexLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapedAV() {
|
||||||
|
beg = pos;
|
||||||
|
end = pos;
|
||||||
|
while (true) {
|
||||||
|
if (pos >= length) {
|
||||||
|
return new String(chars, beg, end - beg);
|
||||||
|
}
|
||||||
|
switch (chars[pos]) {
|
||||||
|
case '+':
|
||||||
|
case ',':
|
||||||
|
case ';':
|
||||||
|
return new String(chars, beg, end - beg);
|
||||||
|
case '\\':
|
||||||
|
chars[end++] = getEscaped();
|
||||||
|
pos++;
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
cur = end;
|
||||||
|
pos++;
|
||||||
|
chars[end++] = ' ';
|
||||||
|
for (; pos < length && chars[pos] == ' '; pos++) {
|
||||||
|
chars[end++] = ' ';
|
||||||
|
}
|
||||||
|
if (pos == length ||
|
||||||
|
chars[pos] == ',' ||
|
||||||
|
chars[pos] == '+' ||
|
||||||
|
chars[pos] == ';') {
|
||||||
|
return new String(chars, beg, cur - beg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
chars[end++] = chars[pos];
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private char getEscaped() {
|
||||||
|
pos++;
|
||||||
|
if (pos == length) {
|
||||||
|
throw new IllegalStateException("Unexpected end of DN: " + dn);
|
||||||
|
}
|
||||||
|
switch (chars[pos]) {
|
||||||
|
case '"':
|
||||||
|
case '\\':
|
||||||
|
case ',':
|
||||||
|
case '=':
|
||||||
|
case '+':
|
||||||
|
case '<':
|
||||||
|
case '>':
|
||||||
|
case '#':
|
||||||
|
case ';':
|
||||||
|
case ' ':
|
||||||
|
case '*':
|
||||||
|
case '%':
|
||||||
|
case '_':
|
||||||
|
return chars[pos];
|
||||||
|
default:
|
||||||
|
return getUTF8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private char getUTF8() {
|
||||||
|
int res = getByte(pos);
|
||||||
|
pos++;
|
||||||
|
if (res < 128) {
|
||||||
|
return (char) res;
|
||||||
|
} else if (res >= 192 && res <= 247) {
|
||||||
|
int count;
|
||||||
|
if (res <= 223) {
|
||||||
|
count = 1;
|
||||||
|
res = res & 0x1F;
|
||||||
|
} else if (res <= 239) {
|
||||||
|
count = 2;
|
||||||
|
res = res & 0x0F;
|
||||||
|
} else {
|
||||||
|
count = 3;
|
||||||
|
res = res & 0x07;
|
||||||
|
}
|
||||||
|
int b;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
pos++;
|
||||||
|
if (pos == length || chars[pos] != '\\') {
|
||||||
|
return 0x3F;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
b = getByte(pos);
|
||||||
|
pos++;
|
||||||
|
if ((b & 0xC0) != 0x80) {
|
||||||
|
return 0x3F;
|
||||||
|
}
|
||||||
|
res = (res << 6) + (b & 0x3F);
|
||||||
|
}
|
||||||
|
return (char) res;
|
||||||
|
} else {
|
||||||
|
return 0x3F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getByte(int position) {
|
||||||
|
if (position + 1 >= length) {
|
||||||
|
throw new IllegalStateException("Malformed DN: " + dn);
|
||||||
|
}
|
||||||
|
int b1, b2;
|
||||||
|
b1 = chars[position];
|
||||||
|
if (b1 >= '0' && b1 <= '9') {
|
||||||
|
b1 = b1 - '0';
|
||||||
|
} else if (b1 >= 'a' && b1 <= 'f') {
|
||||||
|
b1 = b1 - 87;
|
||||||
|
} else if (b1 >= 'A' && b1 <= 'F') {
|
||||||
|
b1 = b1 - 55;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Malformed DN: " + dn);
|
||||||
|
}
|
||||||
|
b2 = chars[position + 1];
|
||||||
|
if (b2 >= '0' && b2 <= '9') {
|
||||||
|
b2 = b2 - '0';
|
||||||
|
} else if (b2 >= 'a' && b2 <= 'f') {
|
||||||
|
b2 = b2 - 87;
|
||||||
|
} else if (b2 >= 'A' && b2 <= 'F') {
|
||||||
|
b2 = b2 - 55;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Malformed DN: " + dn);
|
||||||
|
}
|
||||||
|
return (b1 << 4) + b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String findMostSpecific(String attributeType) {
|
||||||
|
pos = 0;
|
||||||
|
beg = 0;
|
||||||
|
end = 0;
|
||||||
|
cur = 0;
|
||||||
|
chars = dn.toCharArray();
|
||||||
|
String attType = nextAT();
|
||||||
|
if (attType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
String attValue = "";
|
||||||
|
if (pos == length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
switch (chars[pos]) {
|
||||||
|
case '"':
|
||||||
|
attValue = quotedAV();
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
attValue = hexAV();
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
case ',':
|
||||||
|
case ';':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
attValue = escapedAV();
|
||||||
|
}
|
||||||
|
if (attributeType.equalsIgnoreCase(attType)) {
|
||||||
|
return attValue;
|
||||||
|
}
|
||||||
|
if (pos >= length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (chars[pos] != ',' && chars[pos] != ';') {
|
||||||
|
if (chars[pos] != '+') {
|
||||||
|
throw new IllegalStateException("Malformed DN: " + dn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
attType = nextAT();
|
||||||
|
if (attType == null) {
|
||||||
|
throw new IllegalStateException("Malformed DN: " + dn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.xbib.netty.http.server.security;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.base64.Base64;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
|
||||||
|
public class PrivateKeyUtils {
|
||||||
|
|
||||||
|
public static PrivateKey toPrivateKey(InputStream keyInputStream, String keyPassword)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
|
||||||
|
InvalidAlgorithmParameterException, KeyException, IOException {
|
||||||
|
if (keyInputStream == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getPrivateKeyFromByteBuffer(readPrivateKey(keyInputStream), keyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] KEY_TYPES = { "RSA", "DSA", "EC" };
|
||||||
|
|
||||||
|
private static PrivateKey getPrivateKeyFromByteBuffer(ByteBuf encodedKeyBuf, String keyPassword)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
|
||||||
|
InvalidAlgorithmParameterException, KeyException, IOException {
|
||||||
|
byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
|
||||||
|
encodedKeyBuf.readBytes(encodedKey).release();
|
||||||
|
PKCS8EncodedKeySpec encodedKeySpec =
|
||||||
|
generateKeySpec(keyPassword == null ? null : keyPassword.toCharArray(), encodedKey);
|
||||||
|
for (String keyType : KEY_TYPES) {
|
||||||
|
try {
|
||||||
|
return KeyFactory.getInstance(keyType)
|
||||||
|
.generatePrivate(encodedKeySpec);
|
||||||
|
} catch (InvalidKeySpecException ignore) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new InvalidKeySpecException("Neither RSA, DSA nor EC worked");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
|
||||||
|
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
|
||||||
|
InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
if (password == null) {
|
||||||
|
return new PKCS8EncodedKeySpec(key);
|
||||||
|
}
|
||||||
|
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
|
||||||
|
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
|
||||||
|
SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
|
||||||
|
return encryptedPrivateKeyInfo.getKeySpec(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ByteBuf readPrivateKey(InputStream in) throws KeyException, IOException {
|
||||||
|
byte[] content = in.readAllBytes();
|
||||||
|
Matcher m = KEY_PATTERN.matcher(new String(content, StandardCharsets.US_ASCII));
|
||||||
|
if (!m.find()) {
|
||||||
|
throw new KeyException("could not find a PKCS #8 private key in input stream");
|
||||||
|
}
|
||||||
|
ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), StandardCharsets.US_ASCII);
|
||||||
|
ByteBuf der = Base64.decode(base64);
|
||||||
|
base64.release();
|
||||||
|
return der;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pattern KEY_PATTERN = Pattern.compile(
|
||||||
|
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" +
|
||||||
|
"([a-z0-9+/=\\r\\n]+)" +
|
||||||
|
"-+END\\s+.*PRIVATE\\s+KEY[^-]*-+",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
}
|
|
@ -8,13 +8,12 @@ import io.netty.handler.codec.http.HttpVersion;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerRequest;
|
import org.xbib.netty.http.server.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.Domain;
|
import org.xbib.netty.http.server.api.ServerTransport;
|
||||||
import org.xbib.netty.http.server.api.Transport;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
abstract class BaseTransport implements Transport {
|
abstract class BaseTransport implements ServerTransport {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
|
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
|
||||||
|
|
||||||
|
@ -34,14 +33,13 @@ abstract class BaseTransport implements Transport {
|
||||||
* and required special header handling, possibly returning an
|
* and required special header handling, possibly returning an
|
||||||
* appropriate response.
|
* appropriate response.
|
||||||
*
|
*
|
||||||
* @param domain the named server
|
* @param version the HTTP version of the server
|
||||||
* @param serverRequest the request
|
* @param serverRequest the request
|
||||||
* @param serverResponse the response
|
* @param serverResponse the response
|
||||||
* @return whether further processing should be performed
|
* @return whether further processing should be performed
|
||||||
*/
|
*/
|
||||||
static boolean acceptRequest(Domain domain, ServerRequest serverRequest, ServerResponse serverResponse) {
|
static boolean acceptRequest(HttpVersion version, ServerRequest serverRequest, ServerResponse serverResponse) {
|
||||||
HttpHeaders reqHeaders = serverRequest.getHeaders();
|
HttpHeaders reqHeaders = serverRequest.getHeaders();
|
||||||
HttpVersion version = domain.getHttpAddress().getVersion();
|
|
||||||
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
|
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
|
||||||
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
|
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
|
||||||
// RFC2616#14.23: missing Host header gets 400
|
// RFC2616#14.23: missing Host header gets 400
|
||||||
|
|
|
@ -2,13 +2,11 @@ package org.xbib.netty.http.server.transport;
|
||||||
|
|
||||||
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.HttpHeaderNames;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.ssl.SslHandler;
|
import io.netty.handler.ssl.SslHandler;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.Domain;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class Http1Transport extends BaseTransport {
|
public class Http1Transport extends BaseTransport {
|
||||||
|
@ -19,7 +17,6 @@ public class Http1Transport extends BaseTransport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
||||||
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
|
||||||
HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx);
|
HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx);
|
||||||
serverRequest.setSequenceId(sequenceId);
|
serverRequest.setSequenceId(sequenceId);
|
||||||
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
|
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
|
||||||
|
@ -28,9 +25,9 @@ public class Http1Transport extends BaseTransport {
|
||||||
serverRequest.setSession(sslHandler.engine().getSession());
|
serverRequest.setSession(sslHandler.engine().getSession());
|
||||||
}
|
}
|
||||||
HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx);
|
HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx);
|
||||||
if (acceptRequest(domain, serverRequest, serverResponse)) {
|
if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) {
|
||||||
serverRequest.handleParameters();
|
serverRequest.handleParameters();
|
||||||
server.handle(domain, serverRequest, serverResponse);
|
server.handle(serverRequest, serverResponse);
|
||||||
} else {
|
} else {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,11 @@ package org.xbib.netty.http.server.transport;
|
||||||
|
|
||||||
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.HttpHeaderNames;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.Domain;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -24,15 +22,14 @@ public class Http2Transport extends BaseTransport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
||||||
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
|
||||||
HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx);
|
HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx);
|
||||||
serverRequest.setSequenceId(sequenceId);
|
serverRequest.setSequenceId(sequenceId);
|
||||||
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
|
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
|
||||||
serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()));
|
serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()));
|
||||||
ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx);
|
ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx);
|
||||||
if (acceptRequest(domain, serverRequest, serverResponse)) {
|
if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) {
|
||||||
serverRequest.handleParameters();
|
serverRequest.handleParameters();
|
||||||
server.handle(domain, serverRequest, serverResponse);
|
server.handle(serverRequest, serverResponse);
|
||||||
} else {
|
} else {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
org.xbib.netty.http.server.Http1Provider
|
|
||||||
org.xbib.netty.http.server.Http2Provider
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.xbib.netty.http.server.Http1
|
||||||
|
org.xbib.netty.http.server.Http2
|
|
@ -12,12 +12,13 @@ import java.io.IOException;
|
||||||
import java.net.BindException;
|
import java.net.BindException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
class BindExceptionTest {
|
class BindExceptionTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDoubleServer() throws IOException {
|
void testDoubleServer() throws IOException {
|
||||||
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008), "*")
|
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008))
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server1 = Server.builder(domain).build();
|
Server server1 = Server.builder(domain).build();
|
||||||
|
@ -29,6 +30,7 @@ class BindExceptionTest {
|
||||||
assertNotNull(channelFuture1);
|
assertNotNull(channelFuture1);
|
||||||
ChannelFuture channelFuture2 = server2.accept();
|
ChannelFuture channelFuture2 = server2.accept();
|
||||||
// should crash with BindException
|
// should crash with BindException
|
||||||
|
fail();
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
server1.shutdownGracefully();
|
server1.shutdownGracefully();
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.xbib.netty.http.server.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.client.Client;
|
||||||
|
import org.xbib.netty.http.client.api.Request;
|
||||||
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
import org.xbib.netty.http.server.Server;
|
||||||
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
class MultiDomainSecureServerTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MultiDomainSecureServerTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSecureServer() throws Exception {
|
||||||
|
InputStream certInputStream = getClass().getResourceAsStream("/fl-20210906.crt");
|
||||||
|
if (certInputStream == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InputStream keyInputStream = getClass().getResourceAsStream("/fl-20210906.pkcs8");
|
||||||
|
if (keyInputStream == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8443);
|
||||||
|
Domain fl = Domain.builder(httpAddress, "fl.hbz-nrw.de")
|
||||||
|
.setKeyCertChain(certInputStream)
|
||||||
|
.setKey(keyInputStream, null)
|
||||||
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de"))
|
||||||
|
.build();
|
||||||
|
Domain zfl2 = Domain.builder(fl)
|
||||||
|
.setServerName("zfl2.hbz-nrw.de")
|
||||||
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de"))
|
||||||
|
.build();
|
||||||
|
Server server = Server.builder(fl)
|
||||||
|
.addDomain(zfl2)
|
||||||
|
.setTransportLayerSecurityProtocols("TLSv1.3")
|
||||||
|
.build();
|
||||||
|
Client client = Client.builder()
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
server.accept();
|
||||||
|
Request request = Request.get()
|
||||||
|
.setVersion("HTTP/2.0")
|
||||||
|
.url("https://fl.hbz-nrw.de:8443")
|
||||||
|
.setResponseListener(resp -> {
|
||||||
|
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
|
||||||
|
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
|
||||||
|
assertEquals("Hello fl.hbz-nrw.de", response);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
client.execute(request).get();
|
||||||
|
request = Request.get()
|
||||||
|
.setVersion("HTTP/2.0")
|
||||||
|
.url("https://zfl2.hbz-nrw.de:8443")
|
||||||
|
.setResponseListener(resp -> {
|
||||||
|
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
|
||||||
|
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
|
||||||
|
assertEquals("Hello zfl2.hbz-nrw.de", response);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
client.execute(request).get();
|
||||||
|
} finally {
|
||||||
|
client.shutdownGracefully();
|
||||||
|
server.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.xbib.netty.http.server.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.client.Client;
|
||||||
|
import org.xbib.netty.http.client.api.Request;
|
||||||
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.server.Server;
|
||||||
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
class MultiDomainServerTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MultiDomainServerTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testServer() throws Exception {
|
||||||
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
|
Domain fl = Domain.builder(httpAddress, "fl.hbz-nrw.de")
|
||||||
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de"))
|
||||||
|
.build();
|
||||||
|
Domain zfl2 = Domain.builder(fl)
|
||||||
|
.setServerName("zfl2.hbz-nrw.de")
|
||||||
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de"))
|
||||||
|
.build();
|
||||||
|
Server server = Server.builder(fl)
|
||||||
|
.addDomain(zfl2)
|
||||||
|
.build();
|
||||||
|
Client client = Client.builder()
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
server.accept();
|
||||||
|
Request request = Request.get()
|
||||||
|
.url("http://fl.hbz-nrw.de:8008")
|
||||||
|
.setResponseListener(resp -> {
|
||||||
|
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
|
||||||
|
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
|
||||||
|
assertEquals("Hello fl.hbz-nrw.de", response);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
client.execute(request).get();
|
||||||
|
request = Request.get()
|
||||||
|
.url("http://zfl2.hbz-nrw.de:8008")
|
||||||
|
.setResponseListener(resp -> {
|
||||||
|
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
|
||||||
|
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
|
||||||
|
assertEquals("Hello zfl2.hbz-nrw.de", response);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
client.execute(request).get();
|
||||||
|
} finally {
|
||||||
|
client.shutdownGracefully();
|
||||||
|
server.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
package org.xbib.netty.http.server.test;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
|
||||||
import org.xbib.netty.http.server.Server;
|
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
|
||||||
import org.xbib.netty.http.server.Domain;
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
class RunServerTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testServer() throws Exception {
|
|
||||||
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008), "*")
|
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
|
||||||
.build();
|
|
||||||
Server server = Server.builder(domain).build();
|
|
||||||
try {
|
|
||||||
server.accept().channel().closeFuture().sync();
|
|
||||||
} finally {
|
|
||||||
server.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ 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.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
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.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.Domain;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
@ -22,7 +23,7 @@ class ThreadLeakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testForLeaks() throws IOException {
|
void testForLeaks() throws IOException {
|
||||||
Domain domain = Domain.builder()
|
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008))
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
|
|
@ -8,7 +8,7 @@ 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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.ResponseListener;
|
import org.xbib.netty.http.client.api.ResponseListener;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.Domain;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
@ -33,7 +33,7 @@ class TransportLayerSecurityServerTest {
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getContent().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.setTransportLayerSecurityProtocols(new String[]{ "TLSv1.2"})
|
.setTransportLayerSecurityProtocols("TLSv1.2")
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
|
@ -51,8 +51,8 @@ class TransportLayerSecurityServerTest {
|
||||||
.content("Hello Jörg", "text/plain")
|
.content("Hello Jörg", "text/plain")
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
Transport transport = client.execute(request).get();
|
ClientTransport transport = client.execute(request).get();
|
||||||
logger.log(Level.INFO, "HTTP 1.1 TLS protocol = " + transport.getSession().getProtocol());
|
logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol());
|
||||||
assertEquals("TLSv1.2", transport.getSession().getProtocol());
|
assertEquals("TLSv1.2", transport.getSession().getProtocol());
|
||||||
} finally {
|
} finally {
|
||||||
client.shutdownGracefully();
|
client.shutdownGracefully();
|
||||||
|
@ -71,7 +71,7 @@ class TransportLayerSecurityServerTest {
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getContent().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.setTransportLayerSecurityProtocols(new String[]{ "TLSv1.3"})
|
.setTransportLayerSecurityProtocols("TLSv1.3")
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
|
@ -90,8 +90,8 @@ class TransportLayerSecurityServerTest {
|
||||||
.content("Hello Jörg", "text/plain")
|
.content("Hello Jörg", "text/plain")
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
Transport transport = client.execute(request).get();
|
ClientTransport transport = client.execute(request).get();
|
||||||
logger.log(Level.INFO, "HTTP/2 TLS protocol = " + transport.getSession().getProtocol());
|
logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol());
|
||||||
assertEquals("TLSv1.3", transport.getSession().getProtocol());
|
assertEquals("TLSv1.3", transport.getSession().getProtocol());
|
||||||
} finally {
|
} finally {
|
||||||
client.shutdownGracefully();
|
client.shutdownGracefully();
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.ResponseListener;
|
import org.xbib.netty.http.client.api.ResponseListener;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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;
|
||||||
|
@ -92,7 +92,7 @@ class CleartextTest {
|
||||||
.content(Integer.toString(i), "text/plain")
|
.content(Integer.toString(i), "text/plain")
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
Transport transport = client.newTransport();
|
ClientTransport transport = client.newTransport();
|
||||||
transport.execute(request);
|
transport.execute(request);
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||||
|
@ -109,7 +109,7 @@ class CleartextTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMultithreadPooledClearTextHttp1() throws Exception {
|
void testMultithreadPooledClearTextHttp1() throws Exception {
|
||||||
int threads = 2;
|
int threads = 4;
|
||||||
int loop = 1024;
|
int loop = 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
Domain domain = Domain.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
|
@ -144,7 +144,7 @@ class CleartextTest {
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
// note: in HTTP 1, a new transport is created per execution
|
// note: in HTTP 1, a new transport is created per execution
|
||||||
Transport transport = client.newTransport();
|
ClientTransport transport = client.newTransport();
|
||||||
transport.execute(request);
|
transport.execute(request);
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
logger.log(Level.WARNING, "transport failed: " + transport.getFailure().getMessage(), transport.getFailure());
|
logger.log(Level.WARNING, "transport failed: " + transport.getFailure().getMessage(), transport.getFailure());
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.ResponseListener;
|
import org.xbib.netty.http.client.api.ResponseListener;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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;
|
||||||
|
@ -86,7 +86,7 @@ class EncryptedTest {
|
||||||
.content(Integer.toString(i), "text/plain")
|
.content(Integer.toString(i), "text/plain")
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
Transport transport = client.newTransport();
|
ClientTransport transport = client.newTransport();
|
||||||
transport.execute(request);
|
transport.execute(request);
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||||
|
@ -137,7 +137,7 @@ class EncryptedTest {
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
// note: a new transport is created per execution
|
// note: a new transport is created per execution
|
||||||
final Transport transport = client.newTransport();
|
final ClientTransport transport = client.newTransport();
|
||||||
transport.execute(request);
|
transport.execute(request);
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||||
|
|
|
@ -6,7 +6,7 @@ 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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.ResponseListener;
|
import org.xbib.netty.http.client.api.ResponseListener;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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;
|
||||||
|
@ -59,7 +59,7 @@ class CleartextTest {
|
||||||
.content(payload, "text/plain")
|
.content(payload, "text/plain")
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(responseListener)
|
||||||
.build();
|
.build();
|
||||||
Transport transport = client.newTransport(httpAddress);
|
ClientTransport transport = client.newTransport(httpAddress);
|
||||||
transport.execute(request);
|
transport.execute(request);
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||||
|
@ -101,7 +101,7 @@ class CleartextTest {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
// single transport, single thread
|
// single transport, single thread
|
||||||
Transport transport = client.newTransport();
|
ClientTransport transport = client.newTransport();
|
||||||
for (int i = 0; i < loop; i++) {
|
for (int i = 0; i < loop; i++) {
|
||||||
String payload = 0 + "/" + i;
|
String payload = 0 + "/" + i;
|
||||||
Request request = Request.get().setVersion("HTTP/2.0")
|
Request request = Request.get().setVersion("HTTP/2.0")
|
||||||
|
@ -115,7 +115,7 @@ class CleartextTest {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transport.get(60L, TimeUnit.SECONDS);
|
transport.get(10L, TimeUnit.SECONDS);
|
||||||
} finally {
|
} finally {
|
||||||
server.shutdownGracefully();
|
server.shutdownGracefully();
|
||||||
client.shutdownGracefully();
|
client.shutdownGracefully();
|
||||||
|
@ -126,7 +126,7 @@ class CleartextTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMultithreadPooledClearTextHttp2() throws Exception {
|
void testMultithreadPooledClearTextHttp2() throws Exception {
|
||||||
int threads = 2;
|
int threads = 4;
|
||||||
int loop = 1024;
|
int loop = 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
Domain domain = Domain.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
|
@ -148,7 +148,7 @@ class CleartextTest {
|
||||||
};
|
};
|
||||||
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 ClientTransport 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++) {
|
||||||
final int t = n;
|
final int t = n;
|
||||||
|
@ -174,19 +174,20 @@ class CleartextTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
boolean terminated = executorService.awaitTermination(20L, TimeUnit.SECONDS);
|
boolean terminated = executorService.awaitTermination(30L, TimeUnit.SECONDS);
|
||||||
executorService.shutdownNow();
|
executorService.shutdownNow();
|
||||||
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
|
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting 30s for transport to complete");
|
||||||
transport.get(20L, TimeUnit.SECONDS);
|
Thread.sleep(2000L);
|
||||||
|
transport.get(30L, TimeUnit.SECONDS);
|
||||||
logger.log(Level.INFO, "transport complete");
|
logger.log(Level.INFO, "transport complete");
|
||||||
} finally {
|
} finally {
|
||||||
server.shutdownGracefully(20L, TimeUnit.SECONDS);
|
client.shutdownGracefully();
|
||||||
client.shutdownGracefully(20L, TimeUnit.SECONDS);
|
server.shutdownGracefully();
|
||||||
}
|
}
|
||||||
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
|
|
||||||
" server responses = " + server.getResponseCounter());
|
|
||||||
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
||||||
" client responses = " + client.getResponseCounter());
|
" client responses = " + client.getResponseCounter());
|
||||||
|
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
|
||||||
|
" server responses = " + server.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());
|
||||||
}
|
}
|
||||||
|
@ -236,7 +237,7 @@ class CleartextTest {
|
||||||
};
|
};
|
||||||
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 ClientTransport 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++) {
|
||||||
final int t = n;
|
final int t = n;
|
||||||
|
@ -264,12 +265,13 @@ class CleartextTest {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
boolean terminated = executorService.awaitTermination(10L, 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");
|
||||||
|
Thread.sleep(2000L);
|
||||||
transport.get(10L, TimeUnit.SECONDS);
|
transport.get(10L, TimeUnit.SECONDS);
|
||||||
logger.log(Level.INFO, "transport complete");
|
logger.log(Level.INFO, "transport complete");
|
||||||
} finally {
|
} finally {
|
||||||
server1.shutdownGracefully(10L, TimeUnit.SECONDS);
|
server1.shutdownGracefully();
|
||||||
server2.shutdownGracefully(10L, TimeUnit.SECONDS);
|
server2.shutdownGracefully();
|
||||||
client.shutdownGracefully(10L, TimeUnit.SECONDS);
|
client.shutdownGracefully();
|
||||||
}
|
}
|
||||||
logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() +
|
logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() +
|
||||||
" server1 responses = " + server1.getResponseCounter());
|
" server1 responses = " + server1.getResponseCounter());
|
||||||
|
|
|
@ -6,14 +6,13 @@ 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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.ResponseListener;
|
import org.xbib.netty.http.client.api.ResponseListener;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
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;
|
||||||
|
@ -52,7 +51,7 @@ class EncryptedTest {
|
||||||
counter.incrementAndGet();
|
counter.incrementAndGet();
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
Transport transport = client.newTransport(httpAddress);
|
ClientTransport transport = client.newTransport(httpAddress);
|
||||||
String payload = 0 + "/" + 0;
|
String payload = 0 + "/" + 0;
|
||||||
Request request = Request.get()
|
Request request = Request.get()
|
||||||
.setVersion("HTTP/2.0")
|
.setVersion("HTTP/2.0")
|
||||||
|
@ -99,7 +98,7 @@ class EncryptedTest {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
// single transport, single thread
|
// single transport, single thread
|
||||||
Transport transport = client.newTransport();
|
ClientTransport transport = client.newTransport();
|
||||||
for (int i = 0; i < loop; i++) {
|
for (int i = 0; i < loop; i++) {
|
||||||
String payload = 0 + "/" + i;
|
String payload = 0 + "/" + i;
|
||||||
Request request = Request.get().setVersion("HTTP/2.0")
|
Request request = Request.get().setVersion("HTTP/2.0")
|
||||||
|
@ -124,7 +123,7 @@ class EncryptedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMultithreadPooledSecureHttp2() throws Exception {
|
void testMultithreadPooledSecureHttp2() throws Exception {
|
||||||
int threads = 2;
|
int threads = 4;
|
||||||
int loop = 1024;
|
int loop = 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder(Domain.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
|
@ -147,7 +146,7 @@ class EncryptedTest {
|
||||||
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet();
|
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet();
|
||||||
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 ClientTransport 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++) {
|
||||||
final int t = n;
|
final int t = n;
|
||||||
|
@ -166,8 +165,8 @@ class EncryptedTest {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -175,6 +174,7 @@ class EncryptedTest {
|
||||||
boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS);
|
boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS);
|
||||||
executorService.shutdownNow();
|
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");
|
||||||
|
Thread.sleep(2000L);
|
||||||
transport.get(20, TimeUnit.SECONDS);
|
transport.get(20, TimeUnit.SECONDS);
|
||||||
} finally {
|
} finally {
|
||||||
client.shutdownGracefully(20, TimeUnit.SECONDS);
|
client.shutdownGracefully(20, TimeUnit.SECONDS);
|
||||||
|
|
|
@ -6,7 +6,7 @@ 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.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.client.api.Transport;
|
import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Domain;
|
import org.xbib.netty.http.server.Domain;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
|
@ -76,7 +76,7 @@ class MixedProtocolTest {
|
||||||
.build();
|
.build();
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
// HTTP 2 breaks transport
|
// HTTP 2 breaks transport
|
||||||
Transport transport = client.execute(request).get();
|
ClientTransport transport = client.execute(request).get();
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
count.incrementAndGet();
|
count.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ class MixedProtocolTest {
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
//.enableDebug()
|
//.enableDebug()
|
||||||
.setTransportLayerSecurityProtocols(new String[]{"TLSv1.2"})
|
.setTransportLayerSecurityProtocols("TLSv1.2")
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
//.enableDebug()
|
//.enableDebug()
|
||||||
|
@ -119,7 +119,7 @@ class MixedProtocolTest {
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
Transport transport = client.execute(request).get();
|
ClientTransport transport = client.execute(request).get();
|
||||||
if (transport.isFailed()) {
|
if (transport.isFailed()) {
|
||||||
count.incrementAndGet();
|
count.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue