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
|
||||
name = netty-http
|
||||
version = 4.1.50.1
|
||||
version = 4.1.50.2
|
||||
|
||||
gradle.wrapper.version = 6.4.1
|
||||
netty.version = 4.1.50.Final
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import java.time.Duration
|
||||
|
||||
apply plugin: "de.marcphilipp.nexus-publish"
|
||||
|
||||
|
@ -61,4 +62,5 @@ nexusPublishing {
|
|||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
||||
clientTimeout = Duration.ofSeconds(600)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import java.time.Duration
|
||||
|
||||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
dependencies {
|
||||
api project(":netty-http-common")
|
||||
api project(":netty-http-server-api")
|
||||
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;
|
||||
|
||||
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.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
|
|
|
@ -28,8 +28,6 @@ import java.security.NoSuchProviderException;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Generates a temporary self-signed certificate for testing purposes.
|
||||
|
@ -133,11 +131,6 @@ public final class SelfSignedCertificate {
|
|||
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 {
|
||||
byte[] buf = new byte[64];
|
||||
byte[] base64 = Base64.encode(bytes);
|
||||
|
|
|
@ -3,17 +3,23 @@ package org.xbib.netty.http.bouncycastle;
|
|||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class SelfSignedCertificateTest {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("test");
|
||||
|
||||
@Test
|
||||
void testSelfSignedCertificate() throws Exception {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
|
||||
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 {
|
||||
exports org.xbib.netty.http.client.api;
|
||||
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;
|
||||
|
||||
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);
|
||||
|
|
@ -7,21 +7,21 @@ import io.netty.handler.codec.http2.Http2Settings;
|
|||
import io.netty.util.AttributeKey;
|
||||
import org.xbib.netty.http.common.HttpAddress;
|
||||
import org.xbib.netty.http.common.HttpResponse;
|
||||
import org.xbib.netty.http.common.Transport;
|
||||
import org.xbib.netty.http.common.cookie.CookieBox;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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();
|
||||
|
||||
Transport execute(Request request) throws IOException;
|
||||
ClientTransport execute(Request request) 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 fail(Channel channel, Throwable throwable);
|
||||
|
||||
void inactive(Channel channel);
|
||||
|
||||
void setCookieBox(CookieBox cookieBox);
|
||||
|
||||
CookieBox getCookieBox();
|
||||
|
||||
Transport get();
|
||||
ClientTransport get();
|
||||
|
||||
Transport get(long value, TimeUnit timeUnit);
|
||||
ClientTransport get(long value, TimeUnit timeUnit);
|
||||
|
||||
void cancel();
|
||||
|
||||
void fail(Throwable throwable);
|
||||
|
||||
boolean isFailed();
|
||||
|
||||
Throwable getFailure();
|
|
@ -1,7 +1,4 @@
|
|||
module org.xbib.netty.http.client.rest {
|
||||
exports org.xbib.netty.http.client.rest;
|
||||
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 {
|
||||
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.cookie;
|
||||
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.transport;
|
||||
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.transport;
|
||||
requires java.logging;
|
||||
provides org.xbib.netty.http.client.api.ProtocolProvider with
|
||||
org.xbib.netty.http.client.Http1Provider,
|
||||
org.xbib.netty.http.client.Http2Provider;
|
||||
provides org.xbib.netty.http.client.api.ClientProtocolProvider with Http1, Http2;
|
||||
}
|
||||
|
|
|
@ -22,17 +22,16 @@ import io.netty.handler.ssl.SslHandler;
|
|||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import org.xbib.netty.http.client.api.HttpChannelInitializer;
|
||||
import org.xbib.netty.http.client.api.ProtocolProvider;
|
||||
import org.xbib.netty.http.client.api.ClientProtocolProvider;
|
||||
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.api.Transport;
|
||||
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.NetworkUtils;
|
||||
import org.xbib.netty.http.common.TransportProvider;
|
||||
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||
|
||||
import javax.net.ssl.SNIHostName;
|
||||
import javax.net.ssl.SNIServerName;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
@ -89,9 +88,9 @@ public final class Client implements AutoCloseable {
|
|||
|
||||
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;
|
||||
|
||||
|
@ -118,7 +117,7 @@ public final class Client implements AutoCloseable {
|
|||
this.closed = new AtomicBoolean(false);
|
||||
this.clientConfig = clientConfig;
|
||||
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);
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.log(Level.FINEST, "protocol provider: " + provider.transportClass());
|
||||
|
@ -199,7 +198,7 @@ public final class Client implements AutoCloseable {
|
|||
return new Builder();
|
||||
}
|
||||
|
||||
public List<ProtocolProvider<HttpChannelInitializer, Transport>> getProtocolProviders() {
|
||||
public List<ClientProtocolProvider<HttpChannelInitializer, ClientTransport>> getProtocolProviders() {
|
||||
return protocolProviders;
|
||||
}
|
||||
|
||||
|
@ -223,14 +222,14 @@ public final class Client implements AutoCloseable {
|
|||
return responseCounter;
|
||||
}
|
||||
|
||||
public Transport newTransport() {
|
||||
public ClientTransport newTransport() {
|
||||
return newTransport(null);
|
||||
}
|
||||
|
||||
public Transport newTransport(HttpAddress httpAddress) {
|
||||
Transport transport = null;
|
||||
public ClientTransport newTransport(HttpAddress httpAddress) {
|
||||
ClientTransport transport = null;
|
||||
if (httpAddress != null) {
|
||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
||||
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
|
||||
if (protocolProvider.supportsMajorVersion(httpAddress.getVersion().majorVersion())) {
|
||||
try {
|
||||
transport = protocolProvider.transportClass()
|
||||
|
@ -245,7 +244,7 @@ public final class Client implements AutoCloseable {
|
|||
throw new UnsupportedOperationException("no protocol support for " + httpAddress);
|
||||
}
|
||||
} else if (hasPooledConnections()) {
|
||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
||||
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
|
||||
if (protocolProvider.supportsMajorVersion(pool.getVersion().majorVersion())) {
|
||||
try {
|
||||
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()))
|
||||
.execute(request);
|
||||
}
|
||||
|
@ -337,8 +336,8 @@ public final class Client implements AutoCloseable {
|
|||
* @param request the new request for continuing the request.
|
||||
* @throws IOException if continuation fails
|
||||
*/
|
||||
public void continuation(Transport transport, Request request) throws IOException {
|
||||
Transport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion()));
|
||||
public void continuation(ClientTransport transport, Request request) throws IOException {
|
||||
ClientTransport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion()));
|
||||
nextTransport.setCookieBox(transport.getCookieBox());
|
||||
nextTransport.execute(request);
|
||||
nextTransport.get();
|
||||
|
@ -352,7 +351,7 @@ public final class Client implements AutoCloseable {
|
|||
* @param request the request to retry
|
||||
* @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.get();
|
||||
closeAndRemove(transport);
|
||||
|
@ -370,7 +369,7 @@ public final class Client implements AutoCloseable {
|
|||
public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException {
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
try {
|
||||
for (Transport transport : transports) {
|
||||
for (ClientTransport transport : transports) {
|
||||
transport.close();
|
||||
}
|
||||
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 {
|
||||
transport.close();
|
||||
} catch (Exception e) {
|
||||
|
@ -400,7 +399,7 @@ public final class Client implements AutoCloseable {
|
|||
HttpAddress httpAddress,
|
||||
SslHandlerFactory sslHandlerFactory,
|
||||
HttpChannelInitializer helper) {
|
||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
||||
for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
|
||||
if (protocolProvider.supportsMajorVersion(majorVersion)) {
|
||||
try {
|
||||
return protocolProvider.initializerClass()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
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.transport.Http1Transport;
|
||||
|
||||
public class Http1Provider implements ProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||
public class Http1 implements ClientProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||
|
||||
@Override
|
||||
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -1,10 +1,10 @@
|
|||
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.transport.Http2Transport;
|
||||
|
||||
public class Http2Provider implements ProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||
public class Http2 implements ClientProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||
|
||||
@Override
|
||||
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -14,10 +14,9 @@ import io.netty.handler.ssl.SslHandler;
|
|||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import org.xbib.netty.http.client.Client;
|
||||
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.common.HttpAddress;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
|
|
@ -4,22 +4,25 @@ import io.netty.channel.ChannelHandler;
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
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
|
||||
public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
||||
|
||||
@Override
|
||||
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);
|
||||
// do not close ctx here
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||
transport.fail(cause);
|
||||
// do not close ctx here
|
||||
ctx.fireExceptionCaught(cause);
|
||||
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||
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 org.xbib.netty.http.client.Client;
|
||||
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.api.Transport;
|
||||
import org.xbib.netty.http.client.api.ClientTransport;
|
||||
import org.xbib.netty.http.common.HttpAddress;
|
||||
|
||||
import org.xbib.netty.http.common.HttpChannelInitializer;
|
||||
import java.util.logging.Level;
|
||||
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 {
|
||||
if (msg instanceof DefaultHttp2SettingsFrame) {
|
||||
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) {
|
||||
transport.settingsReceived(settingsFrame.settings());
|
||||
}
|
||||
|
@ -100,7 +99,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
|
|||
if (evt instanceof Http2ConnectionPrefaceAndSettingsFrameWrittenEvent) {
|
||||
Http2ConnectionPrefaceAndSettingsFrameWrittenEvent event =
|
||||
(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) {
|
||||
transport.settingsReceived(null);
|
||||
}
|
||||
|
@ -110,9 +109,9 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
|
|||
|
||||
@Override
|
||||
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) {
|
||||
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,
|
||||
Http2Headers headers, int 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) {
|
||||
transport.pushPromiseReceived(ctx.channel(), streamId, promisedStreamId, headers);
|
||||
}
|
||||
|
|
|
@ -5,23 +5,37 @@ import io.netty.channel.ChannelHandlerContext;
|
|||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
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
|
||||
public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
||||
|
||||
@Override
|
||||
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());
|
||||
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||
if (transport != null) {
|
||||
transport.responseReceived(ctx.channel(), streamId, httpResponse);
|
||||
}
|
||||
// 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
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||
transport.fail(cause);
|
||||
ctx.fireExceptionCaught(cause);
|
||||
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||
if (transport != null) {
|
||||
transport.fail(ctx.channel(), cause);
|
||||
}
|
||||
// do not close ctx here
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,13 @@ import org.xbib.net.PercentDecoder;
|
|||
import org.xbib.net.URL;
|
||||
import org.xbib.net.URLSyntaxException;
|
||||
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.client.api.Request;
|
||||
import org.xbib.netty.http.client.api.BackOff;
|
||||
import org.xbib.netty.http.common.HttpResponse;
|
||||
import org.xbib.netty.http.common.cookie.Cookie;
|
||||
import org.xbib.netty.http.common.cookie.CookieBox;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
|
@ -38,7 +37,7 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
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());
|
||||
|
||||
|
@ -54,15 +53,15 @@ public abstract class BaseTransport implements Transport {
|
|||
|
||||
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;
|
||||
|
||||
protected HttpDataFactory httpDataFactory;
|
||||
|
||||
BaseTransport(Client client, HttpAddress httpAddress) {
|
||||
public BaseTransport(Client client, HttpAddress httpAddress) {
|
||||
this.client = client;
|
||||
this.httpAddress = httpAddress;
|
||||
this.channels = new ConcurrentHashMap<>();
|
||||
|
@ -103,9 +102,7 @@ public abstract class BaseTransport implements Transport {
|
|||
@Override
|
||||
public void close() {
|
||||
// channels are present, maybe forgot a get() to receive responses?
|
||||
if (!channels.isEmpty()) {
|
||||
get();
|
||||
}
|
||||
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.
|
||||
* @param throwable the exception
|
||||
*/
|
||||
@Override
|
||||
public void fail(Throwable throwable) {
|
||||
public void fail(Channel channel, Throwable throwable) {
|
||||
// do not fail more than once
|
||||
if (this.throwable != null) {
|
||||
return;
|
||||
}
|
||||
logger.log(Level.SEVERE, "failing: " + throwable.getMessage(), throwable);
|
||||
this.throwable = throwable;
|
||||
logger.log(Level.SEVERE, "channel " + channel + " failing: " + throwable.getMessage(), throwable);
|
||||
for (Flow flow : flowMap.values()) {
|
||||
flow.fail(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transport get() {
|
||||
public void inactive(Channel channel) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientTransport get() {
|
||||
return get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transport get(long value, TimeUnit timeUnit) {
|
||||
if (channels.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
public ClientTransport get(long value, TimeUnit timeUnit) {
|
||||
if (!flowMap.isEmpty()) {
|
||||
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
|
||||
Flow flow = entry.getValue();
|
||||
if (!flow.isClosed()) {
|
||||
|
@ -165,6 +165,8 @@ public abstract class BaseTransport implements Transport {
|
|||
flow.close();
|
||||
}
|
||||
}
|
||||
flowMap.clear();
|
||||
}
|
||||
channels.values().forEach(channel -> {
|
||||
try {
|
||||
client.releaseChannel(channel, true);
|
||||
|
@ -177,20 +179,14 @@ public abstract class BaseTransport implements Transport {
|
|||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (channels.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!flowMap.isEmpty()) {
|
||||
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
|
||||
Flow flow = entry.getValue();
|
||||
for (Integer key : flow.keys()) {
|
||||
try {
|
||||
flow.get(key).cancel(true);
|
||||
} catch (Exception e) {
|
||||
String requestKey = getRequestKey(entry.getKey(), key);
|
||||
Request request = requests.get(requestKey);
|
||||
if (request != null && request.getCompletableFuture() != null) {
|
||||
request.getCompletableFuture().completeExceptionally(e);
|
||||
}
|
||||
completeRequestExceptionally(getRequestKey(entry.getKey(), key), e);
|
||||
flow.fail(e);
|
||||
} finally {
|
||||
flow.remove(key);
|
||||
|
@ -198,6 +194,7 @@ public abstract class BaseTransport implements Transport {
|
|||
}
|
||||
flow.close();
|
||||
}
|
||||
}
|
||||
channels.values().forEach(channel -> {
|
||||
try {
|
||||
client.releaseChannel(channel, true);
|
||||
|
|
|
@ -6,13 +6,13 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class Flow {
|
||||
public class Flow {
|
||||
|
||||
private final AtomicInteger counter;
|
||||
|
||||
private final SortedMap<Integer, CompletableFuture<Boolean>> map;
|
||||
|
||||
Flow() {
|
||||
public Flow() {
|
||||
this.counter = new AtomicInteger(3);
|
||||
this.map = new ConcurrentSkipListMap<>();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.xbib.netty.http.client.transport;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
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 org.xbib.net.URLSyntaxException;
|
||||
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.ClientCookieEncoder;
|
||||
import org.xbib.netty.http.common.DefaultHttpResponse;
|
||||
import org.xbib.netty.http.common.HttpAddress;
|
||||
import org.xbib.netty.http.client.api.Request;
|
||||
import org.xbib.netty.http.common.cookie.Cookie;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -36,7 +35,7 @@ public class Http1Transport extends BaseTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Transport execute(Request request) throws IOException {
|
||||
public ClientTransport execute(Request request) throws IOException {
|
||||
Channel channel = mapChannel(request);
|
||||
if (throwable != null) {
|
||||
return this;
|
||||
|
@ -175,6 +174,11 @@ public class Http1Transport extends BaseTransport {
|
|||
|
||||
@Override
|
||||
protected String getRequestKey(String channelId, Integer streamId) {
|
||||
try {
|
||||
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 org.xbib.net.URLSyntaxException;
|
||||
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.ClientCookieEncoder;
|
||||
import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler;
|
||||
|
@ -49,7 +49,7 @@ public class Http2Transport extends BaseTransport {
|
|||
public Http2Transport(Client client, HttpAddress httpAddress) {
|
||||
super(client, httpAddress);
|
||||
this.settingsPromise = httpAddress != null ? new CompletableFuture<>() : null;
|
||||
final Transport transport = this;
|
||||
final ClientTransport transport = this;
|
||||
this.initializer = new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) {
|
||||
|
@ -68,7 +68,7 @@ public class Http2Transport extends BaseTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Transport execute(Request request) throws IOException {
|
||||
public ClientTransport execute(Request request) throws IOException {
|
||||
Channel channel = mapChannel(request);
|
||||
if (throwable != null) {
|
||||
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 {
|
||||
exports org.xbib.netty.http.common;
|
||||
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.security;
|
||||
exports org.xbib.netty.http.common.util;
|
||||
requires transitive org.xbib.net.url;
|
||||
requires io.netty.buffer;
|
||||
requires io.netty.common;
|
||||
requires io.netty.transport;
|
||||
requires io.netty.handler;
|
||||
requires io.netty.codec.http;
|
||||
requires transitive io.netty.buffer;
|
||||
requires transitive io.netty.common;
|
||||
requires transitive io.netty.transport;
|
||||
requires transitive io.netty.handler;
|
||||
requires transitive io.netty.codec;
|
||||
requires transitive io.netty.codec.http;
|
||||
requires transitive io.netty.codec.http2;
|
||||
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.ChannelHandler;
|
||||
|
@ -6,5 +6,4 @@ import io.netty.channel.ChannelHandler;
|
|||
public interface HttpChannelInitializer extends ChannelHandler {
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
module 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.security;
|
||||
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;
|
||||
|
||||
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);
|
||||
|
|
@ -4,12 +4,12 @@ import io.netty.channel.ChannelHandlerContext;
|
|||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http2.Http2Settings;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import org.xbib.netty.http.common.Transport;
|
||||
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;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.netty.http.common;
|
||||
package org.xbib.netty.http.server.api.security;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
@ -28,5 +28,4 @@ public interface ServerCertificateProvider {
|
|||
* @return key password
|
||||
*/
|
||||
String getKeyPassword();
|
||||
|
||||
}
|
|
@ -320,6 +320,7 @@ public class HandlerPublisher<T> extends ChannelDuplexHandler implements Publish
|
|||
case DEMANDING:
|
||||
case IDLE:
|
||||
cancelled();
|
||||
// fall through
|
||||
case DRAINING:
|
||||
state = DONE;
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
module org.xbib.netty.http.server.rest {
|
||||
exports org.xbib.netty.http.server.rest;
|
||||
exports org.xbib.netty.http.server.rest.util;
|
||||
requires org.xbib.netty.http.server;
|
||||
requires io.netty.transport;
|
||||
requires transitive org.xbib.netty.http.server;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import org.xbib.netty.http.server.Http1;
|
||||
import org.xbib.netty.http.server.Http2;
|
||||
|
||||
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.cookie;
|
||||
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.util;
|
||||
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;
|
||||
provides org.xbib.netty.http.server.api.ProtocolProvider with
|
||||
org.xbib.netty.http.server.Http1Provider,
|
||||
org.xbib.netty.http.server.Http2Provider;
|
||||
provides org.xbib.netty.http.server.api.ServerProtocolProvider with Http1, Http2;
|
||||
}
|
||||
|
|
|
@ -8,22 +8,31 @@ import io.netty.handler.ssl.SslContext;
|
|||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
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.server.api.ServerRequest;
|
||||
import org.xbib.netty.http.server.api.ServerResponse;
|
||||
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
||||
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
||||
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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -49,6 +58,8 @@ public class Domain {
|
|||
|
||||
private final List<HttpEndpointResolver> httpEndpointResolvers;
|
||||
|
||||
private final Collection<? extends X509Certificate> certificates;
|
||||
|
||||
/**
|
||||
* Constructs a {@code NamedServer} with the given name.
|
||||
*
|
||||
|
@ -58,29 +69,41 @@ public class Domain {
|
|||
* @param httpEndpointResolvers the endpoint resolvers
|
||||
* @param sslContext SSL context or null
|
||||
*/
|
||||
protected Domain(String name, Set<String> aliases,
|
||||
private Domain(String name,
|
||||
Set<String> aliases,
|
||||
HttpAddress httpAddress,
|
||||
List<HttpEndpointResolver> httpEndpointResolvers,
|
||||
SslContext sslContext) {
|
||||
SslContext sslContext,
|
||||
Collection<? extends X509Certificate> certificates) {
|
||||
this.httpAddress = httpAddress;
|
||||
this.name = name;
|
||||
this.sslContext = sslContext;
|
||||
this.aliases = Collections.unmodifiableSet(aliases);
|
||||
this.aliases = aliases;
|
||||
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) {
|
||||
return builder(httpAddress, "*");
|
||||
return builder(httpAddress, httpAddress.getInetSocketAddress().getHostString());
|
||||
}
|
||||
|
||||
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() {
|
||||
return httpAddress;
|
||||
}
|
||||
|
@ -94,10 +117,6 @@ public class Domain {
|
|||
return name;
|
||||
}
|
||||
|
||||
public SslContext getSslContext() {
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases.
|
||||
*
|
||||
|
@ -107,6 +126,22 @@ public class Domain {
|
|||
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.
|
||||
* @param serverRequest the server request
|
||||
|
@ -114,7 +149,6 @@ public class Domain {
|
|||
* @throws IOException if handling server request fails
|
||||
*/
|
||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||
if (httpEndpointResolvers != null) {
|
||||
boolean found = false;
|
||||
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
|
||||
List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest);
|
||||
|
@ -125,27 +159,28 @@ public class Domain {
|
|||
}
|
||||
}
|
||||
if (!found) {
|
||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
} else {
|
||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
|
||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED,
|
||||
"text/plain", "No endpoint match for request " + serverRequest +
|
||||
" endpoints = " + httpEndpointResolvers);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (" + httpAddress + ") " + aliases;
|
||||
return name + " (" + httpAddress + ") aliases=" + aliases;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private HttpAddress httpAddress;
|
||||
private final HttpAddress httpAddress;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -159,17 +194,13 @@ public class Domain {
|
|||
|
||||
private CipherSuiteFilter cipherSuiteFilter;
|
||||
|
||||
private InputStream keyCertChainInputStream;
|
||||
private Collection<? extends X509Certificate> keyCertChain;
|
||||
|
||||
private InputStream keyInputStream;
|
||||
private PrivateKey privateKey;
|
||||
|
||||
private String keyPassword;
|
||||
|
||||
Builder(HttpAddress httpAddress, String serverName) {
|
||||
private Builder(HttpAddress httpAddress) {
|
||||
Objects.requireNonNull(httpAddress);
|
||||
Objects.requireNonNull(serverName);
|
||||
this.httpAddress = httpAddress;
|
||||
this.serverName = serverName;
|
||||
this.aliases = new LinkedHashSet<>();
|
||||
this.httpEndpointResolvers = new ArrayList<>();
|
||||
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
|
||||
|
@ -178,6 +209,26 @@ public class Domain {
|
|||
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) {
|
||||
Objects.requireNonNull(trustManagerFactory);
|
||||
this.trustManagerFactory = trustManagerFactory;
|
||||
|
@ -226,56 +277,36 @@ public class Domain {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) {
|
||||
public Builder setKeyCertChain(InputStream keyCertChainInputStream)
|
||||
throws CertificateException {
|
||||
Objects.requireNonNull(keyCertChainInputStream);
|
||||
this.keyCertChainInputStream = keyCertChainInputStream;
|
||||
this.keyCertChain = CertificateUtils.toCertificate(keyCertChainInputStream);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeyInputStream(InputStream keyInputStream) {
|
||||
public Builder setKey(InputStream keyInputStream, String keyPassword)
|
||||
throws NoSuchPaddingException, NoSuchAlgorithmException, IOException,
|
||||
KeyException, InvalidAlgorithmParameterException, InvalidKeySpecException {
|
||||
Objects.requireNonNull(keyInputStream);
|
||||
this.keyInputStream = keyInputStream;
|
||||
this.privateKey = PrivateKeyUtils.toPrivateKey(keyInputStream, keyPassword);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeyPassword(String keyPassword) {
|
||||
// null in keyPassword allowed, it means no password
|
||||
this.keyPassword = keyPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
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);
|
||||
public Builder setSelfCert() throws CertificateException, NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, IOException, KeyException, InvalidAlgorithmParameterException,
|
||||
InvalidKeySpecException {
|
||||
ServiceLoader<ServerCertificateProvider> serverCertificateProviders =
|
||||
ServiceLoader.load(ServerCertificateProvider.class);
|
||||
for (ServerCertificateProvider serverCertificateProvider : serverCertificateProviders) {
|
||||
if ("org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider".equals(serverCertificateProvider.getClass().getName())) {
|
||||
serverCertificateProvider.prepare(serverName);
|
||||
setKeyCertChainInputStream(serverCertificateProvider.getCertificateChain());
|
||||
setKeyInputStream(serverCertificateProvider.getPrivateKey());
|
||||
setKeyPassword(serverCertificateProvider.getKeyPassword());
|
||||
setKeyCertChain(serverCertificateProvider.getCertificateChain());
|
||||
setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword());
|
||||
logger.log(Level.INFO, "self signed certificate installed");
|
||||
}
|
||||
}
|
||||
if (keyCertChainInputStream == null) {
|
||||
logger.log(Level.WARNING, "unable to install self signed certificate. Is netty-http-bouncycastle present?");
|
||||
if (keyCertChain == null) {
|
||||
throw new CertificateException("unable to set self certificate");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -301,7 +332,8 @@ public class Domain {
|
|||
public Builder singleEndpoint(String path, Filter filter) {
|
||||
Objects.requireNonNull(path);
|
||||
Objects.requireNonNull(filter);
|
||||
addEndpointResolver(HttpEndpointResolver.builder()
|
||||
this.httpEndpointResolvers.clear();
|
||||
this.httpEndpointResolvers.add(HttpEndpointResolver.builder()
|
||||
.addEndpoint(HttpEndpoint.builder()
|
||||
.setPath(path)
|
||||
.build())
|
||||
|
@ -343,9 +375,10 @@ public class Domain {
|
|||
public Domain build() {
|
||||
if (httpAddress.isSecure() ) {
|
||||
try {
|
||||
if (sslContext == null && privateKey != null && keyCertChain != null) {
|
||||
trustManagerFactory.init(trustManagerKeyStore);
|
||||
SslContextBuilder sslContextBuilder = SslContextBuilder
|
||||
.forServer(keyCertChainInputStream, keyInputStream, keyPassword)
|
||||
.forServer(privateKey, keyCertChain)
|
||||
.trustManager(trustManagerFactory)
|
||||
.sslProvider(sslProvider)
|
||||
.ciphers(ciphers, cipherSuiteFilter);
|
||||
|
@ -355,12 +388,18 @@ public class Domain {
|
|||
if (httpAddress.getVersion().majorVersion() == 2) {
|
||||
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
||||
}
|
||||
return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, sslContextBuilder.build());
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
this.sslContext = sslContextBuilder.build();
|
||||
}
|
||||
return new Domain(serverName, aliases,
|
||||
httpAddress, httpEndpointResolvers,
|
||||
sslContext, keyCertChain);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} 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;
|
||||
|
||||
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.transport.Http1Transport;
|
||||
|
||||
public class Http1Provider implements ProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||
public class Http1 implements ServerProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||
|
||||
@Override
|
||||
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -1,10 +1,10 @@
|
|||
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.transport.Http2Transport;
|
||||
|
||||
public class Http2Provider implements ProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||
public class Http2 implements ServerProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||
|
||||
@Override
|
||||
public boolean supportsMajorVersion(int majorVersion) {
|
|
@ -14,18 +14,23 @@ import io.netty.handler.logging.LoggingHandler;
|
|||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.util.DomainWildcardMappingBuilder;
|
||||
import io.netty.util.Mapping;
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.netty.http.common.HttpAddress;
|
||||
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.server.api.HttpChannelInitializer;
|
||||
import org.xbib.netty.http.server.api.ProtocolProvider;
|
||||
import org.xbib.netty.http.server.api.ServerProtocolProvider;
|
||||
import org.xbib.netty.http.server.api.ServerRequest;
|
||||
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.api.Transport;
|
||||
|
||||
import java.io.IOException;
|
||||
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.List;
|
||||
import java.util.Objects;
|
||||
|
@ -68,8 +73,6 @@ public final class Server implements AutoCloseable {
|
|||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private final ByteBufAllocator byteBufAllocator;
|
||||
|
||||
private final EventLoopGroup parentEventLoopGroup;
|
||||
|
||||
private final EventLoopGroup childEventLoopGroup;
|
||||
|
@ -79,17 +82,14 @@ public final class Server implements AutoCloseable {
|
|||
*/
|
||||
private final BlockingThreadPoolExecutor executor;
|
||||
|
||||
private final Class<? extends ServerSocketChannel> socketChannelClass;
|
||||
|
||||
private final ServerBootstrap bootstrap;
|
||||
|
||||
private ChannelFuture channelFuture;
|
||||
|
||||
private final List<ProtocolProvider<HttpChannelInitializer, Transport>> protocolProviders;
|
||||
private final List<ServerProtocolProvider<HttpChannelInitializer, ServerTransport>> protocolProviders;
|
||||
|
||||
/**
|
||||
* Create a new HTTP server.
|
||||
* Use {@link #builder(HttpAddress)} to build HTTP instance.
|
||||
*
|
||||
* @param serverConfig server configuration
|
||||
* @param byteBufAllocator byte buf allocator
|
||||
|
@ -106,13 +106,13 @@ public final class Server implements AutoCloseable {
|
|||
BlockingThreadPoolExecutor executor) {
|
||||
Objects.requireNonNull(serverConfig);
|
||||
this.serverConfig = serverConfig;
|
||||
this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT;
|
||||
ByteBufAllocator byteBufAllocator1 = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT;
|
||||
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
||||
this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup);
|
||||
this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass);
|
||||
Class<? extends ServerSocketChannel> socketChannelClass1 = createSocketChannelClass(serverConfig, socketChannelClass);
|
||||
this.executor = executor;
|
||||
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);
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.log(Level.FINEST, "protocol provider up: " + provider.transportClass());
|
||||
|
@ -120,13 +120,13 @@ public final class Server implements AutoCloseable {
|
|||
}
|
||||
this.bootstrap = new ServerBootstrap()
|
||||
.group(this.parentEventLoopGroup, this.childEventLoopGroup)
|
||||
.channel(this.socketChannelClass)
|
||||
.option(ChannelOption.ALLOCATOR, this.byteBufAllocator)
|
||||
.channel(socketChannelClass1)
|
||||
.option(ChannelOption.ALLOCATOR, byteBufAllocator1)
|
||||
.option(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
|
||||
.option(ChannelOption.SO_RCVBUF, serverConfig.getTcpReceiveBufferSize())
|
||||
.option(ChannelOption.SO_BACKLOG, serverConfig.getBackLogSize())
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConnectTimeoutMillis())
|
||||
.childOption(ChannelOption.ALLOCATOR, this.byteBufAllocator)
|
||||
.childOption(ChannelOption.ALLOCATOR, byteBufAllocator1)
|
||||
.childOption(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
|
||||
.childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNodelay())
|
||||
.childOption(ChannelOption.SO_SNDBUF, serverConfig.getTcpSendBufferSize())
|
||||
|
@ -137,34 +137,39 @@ public final class Server implements AutoCloseable {
|
|||
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getTrafficDebugLogLevel()));
|
||||
}
|
||||
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;
|
||||
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) {
|
||||
Domain defaultDomain = serverConfig.getDefaultDomain();
|
||||
if (serverConfig.getAddress().isSecure() &&
|
||||
defaultDomain != null &&
|
||||
defaultDomain.getSslContext() != null) {
|
||||
DomainWildcardMappingBuilder<SslContext> mappingBuilder =
|
||||
new DomainWildcardMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext());
|
||||
new DomainWildcardMappingBuilder<>(defaultDomain.getSslContext());
|
||||
for (Domain domain : serverConfig.getDomains()) {
|
||||
String name = domain.getName();
|
||||
if (!"*".equals(name)) {
|
||||
mappingBuilder.add(name, domain.getSslContext());
|
||||
if (!domain.getName().equals(defaultDomain.getName())) {
|
||||
mappingBuilder.add(domain.getName(), domain.getSslContext());
|
||||
}
|
||||
}
|
||||
domainNameMapping = mappingBuilder.build();
|
||||
logger.log(Level.INFO, "domain name mapping: " + domainNameMapping);
|
||||
}
|
||||
bootstrap.childHandler(findChannelInitializer(serverConfig.getAddress().getVersion().majorVersion(),
|
||||
serverConfig.getAddress(), domainNameMapping));
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return builder(HttpAddress.http1("localhost", 8008));
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
shutdownGracefully();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder(HttpAddress httpAddress) {
|
||||
return new Builder(httpAddress);
|
||||
}
|
||||
|
||||
public static Builder builder(Domain domain) {
|
||||
return new Builder(domain);
|
||||
public static Builder builder(Domain defaultDomain) {
|
||||
return new Builder(defaultDomain);
|
||||
}
|
||||
|
||||
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
|
||||
* @return the virtual host with the given name or the default domain
|
||||
*/
|
||||
public Domain getNamedServer(String name) {
|
||||
Domain domain = serverConfig.getDomain(name);
|
||||
if (domain == null) {
|
||||
domain = serverConfig.getDefaultDomain();
|
||||
}
|
||||
return domain;
|
||||
public Domain getDomain(String dnsName) {
|
||||
return serverConfig.getDomain(dnsName);
|
||||
}
|
||||
|
||||
public void handle(Domain domain, HttpServerRequest serverRequest, ServerResponse serverResponse)
|
||||
throws IOException {
|
||||
public Domain getDomain(URL url) {
|
||||
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) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
|
@ -224,10 +271,6 @@ public final class Server implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public BlockingThreadPoolExecutor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public AtomicLong getRequestCounter() {
|
||||
return requestCounter;
|
||||
}
|
||||
|
@ -236,8 +279,8 @@ public final class Server implements AutoCloseable {
|
|||
return responseCounter;
|
||||
}
|
||||
|
||||
public Transport newTransport(HttpVersion httpVersion) {
|
||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
||||
public ServerTransport newTransport(HttpVersion httpVersion) {
|
||||
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
|
||||
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {
|
||||
try {
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
shutdownGracefully();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdownGracefully() throws IOException {
|
||||
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,
|
||||
HttpAddress httpAddress,
|
||||
Mapping<String, SslContext> domainNameMapping) {
|
||||
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) {
|
||||
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
|
||||
if (protocolProvider.supportsMajorVersion(majorVersion)) {
|
||||
try {
|
||||
return protocolProvider.initializerClass()
|
||||
|
@ -449,11 +499,7 @@ public final class Server implements AutoCloseable {
|
|||
|
||||
private Class<? extends ServerSocketChannel> socketChannelClass;
|
||||
|
||||
private ServerConfig serverConfig;
|
||||
|
||||
private Builder(HttpAddress httpAddress) {
|
||||
this(Domain.builder(httpAddress, "*").build());
|
||||
}
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private Builder(Domain defaultDomain) {
|
||||
this.serverConfig = new ServerConfig();
|
||||
|
@ -596,14 +642,13 @@ public final class Server implements AutoCloseable {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setTransportLayerSecurityProtocols(String[] protocols) {
|
||||
public Builder setTransportLayerSecurityProtocols(String... protocols) {
|
||||
this.serverConfig.setProtocols(protocols);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDomain(Domain domain) {
|
||||
this.serverConfig.putDomain(domain);
|
||||
logger.log(Level.FINE, "adding named server: " + domain);
|
||||
this.serverConfig.checkAndAddDomain(domain);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -616,6 +661,45 @@ public final class Server implements AutoCloseable {
|
|||
executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) ->
|
||||
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,
|
||||
parentEventLoopGroup, childEventLoopGroup, socketChannelClass,
|
||||
executor);
|
||||
|
|
|
@ -262,6 +262,10 @@ public class ServerConfig {
|
|||
|
||||
private KeyStore trustManagerKeyStore = null;
|
||||
|
||||
private boolean autoDomain = false;
|
||||
|
||||
private boolean acceptInvalidCertificates = false;
|
||||
|
||||
public ServerConfig() {
|
||||
this.domains = new LinkedHashMap<>();
|
||||
}
|
||||
|
@ -596,7 +600,28 @@ public class ServerConfig {
|
|||
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);
|
||||
for (String alias : domain.getAliases()) {
|
||||
domains.put(alias, domain);
|
||||
|
@ -621,12 +646,14 @@ public class ServerConfig {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Domain getDefaultDomain() {
|
||||
return getDomain("*");
|
||||
}
|
||||
|
||||
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.annotation.Endpoint;
|
||||
import org.xbib.netty.http.server.endpoint.service.MethodService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
@ -78,7 +77,7 @@ public class HttpEndpointResolver {
|
|||
|
||||
private String prefix;
|
||||
|
||||
private List<HttpEndpoint> endpoints;
|
||||
private final List<HttpEndpoint> endpoints;
|
||||
|
||||
private EndpointDispatcher<HttpEndpoint> endpointDispatcher;
|
||||
|
||||
|
@ -99,7 +98,7 @@ public class HttpEndpointResolver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add endpoint.
|
||||
* Add endpoint under this endpoint.
|
||||
*
|
||||
* @param endpoint the endpoint
|
||||
* @return this builder
|
||||
|
|
|
@ -22,11 +22,11 @@ import io.netty.util.Mapping;
|
|||
import org.xbib.netty.http.common.HttpAddress;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
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.IdleTimeoutHandler;
|
||||
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.util.logging.Level;
|
||||
|
@ -56,8 +56,8 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
|
|||
|
||||
@Override
|
||||
public void initChannel(Channel channel) {
|
||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||
ServerTransport transport = server.newTransport(httpAddress.getVersion());
|
||||
channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||
if (serverConfig.isTrafficDebug()) {
|
||||
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
|
|||
HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
|
||||
ctx.channel().writeAndFlush(response);
|
||||
} else {
|
||||
Transport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
||||
ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
||||
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
|
||||
}
|
||||
fullHttpRequest.release();
|
||||
|
|
|
@ -34,11 +34,11 @@ import io.netty.util.Mapping;
|
|||
import org.xbib.netty.http.common.HttpAddress;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
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.IdleTimeoutHandler;
|
||||
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.util.logging.Level;
|
||||
|
@ -68,8 +68,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
|||
|
||||
@Override
|
||||
public void initChannel(Channel channel) {
|
||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||
ServerTransport transport = server.newTransport(httpAddress.getVersion());
|
||||
channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||
if (serverConfig.isTrafficDebug()) {
|
||||
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
|
||||
}
|
||||
|
@ -94,8 +94,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
|||
ChannelHandler channelHandler = new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) {
|
||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||
ServerTransport transport = server.newTransport(httpAddress.getVersion());
|
||||
channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast("server-frame-converter",
|
||||
new Http2StreamFrameToHttpObjectCodec(true));
|
||||
|
@ -137,7 +137,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
|||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof DefaultHttp2SettingsFrame) {
|
||||
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());
|
||||
} else if (msg instanceof DefaultHttpRequest) {
|
||||
DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
|
||||
|
@ -159,13 +159,13 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.api.ServerRequest;
|
||||
import org.xbib.netty.http.server.api.ServerResponse;
|
||||
import org.xbib.netty.http.server.Domain;
|
||||
import org.xbib.netty.http.server.api.Transport;
|
||||
import org.xbib.netty.http.server.api.ServerTransport;
|
||||
|
||||
import java.util.logging.Level;
|
||||
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());
|
||||
|
||||
|
@ -34,14 +33,13 @@ abstract class BaseTransport implements Transport {
|
|||
* and required special header handling, possibly returning an
|
||||
* appropriate response.
|
||||
*
|
||||
* @param domain the named server
|
||||
* @param version the HTTP version of the server
|
||||
* @param serverRequest the request
|
||||
* @param serverResponse the response
|
||||
* @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();
|
||||
HttpVersion version = domain.getHttpAddress().getVersion();
|
||||
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
|
||||
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
|
||||
// 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.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http2.Http2Settings;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
import org.xbib.netty.http.server.api.ServerResponse;
|
||||
import org.xbib.netty.http.server.Domain;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Http1Transport extends BaseTransport {
|
||||
|
@ -19,7 +17,6 @@ public class Http1Transport extends BaseTransport {
|
|||
|
||||
@Override
|
||||
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);
|
||||
serverRequest.setSequenceId(sequenceId);
|
||||
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
|
||||
|
@ -28,9 +25,9 @@ public class Http1Transport extends BaseTransport {
|
|||
serverRequest.setSession(sslHandler.engine().getSession());
|
||||
}
|
||||
HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx);
|
||||
if (acceptRequest(domain, serverRequest, serverResponse)) {
|
||||
if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) {
|
||||
serverRequest.handleParameters();
|
||||
server.handle(domain, serverRequest, serverResponse);
|
||||
server.handle(serverRequest, serverResponse);
|
||||
} else {
|
||||
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.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http2.Http2Settings;
|
||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
import org.xbib.netty.http.server.api.ServerResponse;
|
||||
import org.xbib.netty.http.server.Domain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
@ -24,15 +22,14 @@ public class Http2Transport extends BaseTransport {
|
|||
|
||||
@Override
|
||||
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);
|
||||
serverRequest.setSequenceId(sequenceId);
|
||||
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
|
||||
serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()));
|
||||
ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx);
|
||||
if (acceptRequest(domain, serverRequest, serverResponse)) {
|
||||
if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) {
|
||||
serverRequest.handleParameters();
|
||||
server.handle(domain, serverRequest, serverResponse);
|
||||
server.handle(serverRequest, serverResponse);
|
||||
} else {
|
||||
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 static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class BindExceptionTest {
|
||||
|
||||
@Test
|
||||
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"))
|
||||
.build();
|
||||
Server server1 = Server.builder(domain).build();
|
||||
|
@ -29,6 +30,7 @@ class BindExceptionTest {
|
|||
assertNotNull(channelFuture1);
|
||||
ChannelFuture channelFuture2 = server2.accept();
|
||||
// should crash with BindException
|
||||
fail();
|
||||
});
|
||||
} finally {
|
||||
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.TestInstance;
|
||||
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.api.ServerResponse;
|
||||
import org.xbib.netty.http.server.Domain;
|
||||
|
@ -22,7 +23,7 @@ class ThreadLeakTest {
|
|||
|
||||
@Test
|
||||
void testForLeaks() throws IOException {
|
||||
Domain domain = Domain.builder()
|
||||
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008))
|
||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
||||
.build();
|
||||
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.api.Request;
|
||||
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.HttpResponse;
|
||||
import org.xbib.netty.http.server.Domain;
|
||||
|
@ -33,7 +33,7 @@ class TransportLayerSecurityServerTest {
|
|||
.withContentType("text/plain")
|
||||
.write(request.getContent().retain()))
|
||||
.build())
|
||||
.setTransportLayerSecurityProtocols(new String[]{ "TLSv1.2"})
|
||||
.setTransportLayerSecurityProtocols("TLSv1.2")
|
||||
.build();
|
||||
Client client = Client.builder()
|
||||
.trustInsecure()
|
||||
|
@ -51,8 +51,8 @@ class TransportLayerSecurityServerTest {
|
|||
.content("Hello Jörg", "text/plain")
|
||||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
Transport transport = client.execute(request).get();
|
||||
logger.log(Level.INFO, "HTTP 1.1 TLS protocol = " + transport.getSession().getProtocol());
|
||||
ClientTransport transport = client.execute(request).get();
|
||||
logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol());
|
||||
assertEquals("TLSv1.2", transport.getSession().getProtocol());
|
||||
} finally {
|
||||
client.shutdownGracefully();
|
||||
|
@ -71,7 +71,7 @@ class TransportLayerSecurityServerTest {
|
|||
.withContentType("text/plain")
|
||||
.write(request.getContent().retain()))
|
||||
.build())
|
||||
.setTransportLayerSecurityProtocols(new String[]{ "TLSv1.3"})
|
||||
.setTransportLayerSecurityProtocols("TLSv1.3")
|
||||
.build();
|
||||
Client client = Client.builder()
|
||||
.trustInsecure()
|
||||
|
@ -90,8 +90,8 @@ class TransportLayerSecurityServerTest {
|
|||
.content("Hello Jörg", "text/plain")
|
||||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
Transport transport = client.execute(request).get();
|
||||
logger.log(Level.INFO, "HTTP/2 TLS protocol = " + transport.getSession().getProtocol());
|
||||
ClientTransport transport = client.execute(request).get();
|
||||
logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol());
|
||||
assertEquals("TLSv1.3", transport.getSession().getProtocol());
|
||||
} finally {
|
||||
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.api.Request;
|
||||
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.HttpResponse;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
|
@ -92,7 +92,7 @@ class CleartextTest {
|
|||
.content(Integer.toString(i), "text/plain")
|
||||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
Transport transport = client.newTransport();
|
||||
ClientTransport transport = client.newTransport();
|
||||
transport.execute(request);
|
||||
if (transport.isFailed()) {
|
||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||
|
@ -109,7 +109,7 @@ class CleartextTest {
|
|||
|
||||
@Test
|
||||
void testMultithreadPooledClearTextHttp1() throws Exception {
|
||||
int threads = 2;
|
||||
int threads = 4;
|
||||
int loop = 1024;
|
||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||
Domain domain = Domain.builder(httpAddress)
|
||||
|
@ -144,7 +144,7 @@ class CleartextTest {
|
|||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
// note: in HTTP 1, a new transport is created per execution
|
||||
Transport transport = client.newTransport();
|
||||
ClientTransport transport = client.newTransport();
|
||||
transport.execute(request);
|
||||
if (transport.isFailed()) {
|
||||
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.api.Request;
|
||||
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.HttpResponse;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
|
@ -86,7 +86,7 @@ class EncryptedTest {
|
|||
.content(Integer.toString(i), "text/plain")
|
||||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
Transport transport = client.newTransport();
|
||||
ClientTransport transport = client.newTransport();
|
||||
transport.execute(request);
|
||||
if (transport.isFailed()) {
|
||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||
|
@ -137,7 +137,7 @@ class EncryptedTest {
|
|||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
// note: a new transport is created per execution
|
||||
final Transport transport = client.newTransport();
|
||||
final ClientTransport transport = client.newTransport();
|
||||
transport.execute(request);
|
||||
if (transport.isFailed()) {
|
||||
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.api.Request;
|
||||
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.HttpResponse;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
|
@ -59,7 +59,7 @@ class CleartextTest {
|
|||
.content(payload, "text/plain")
|
||||
.setResponseListener(responseListener)
|
||||
.build();
|
||||
Transport transport = client.newTransport(httpAddress);
|
||||
ClientTransport transport = client.newTransport(httpAddress);
|
||||
transport.execute(request);
|
||||
if (transport.isFailed()) {
|
||||
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
|
||||
|
@ -101,7 +101,7 @@ class CleartextTest {
|
|||
};
|
||||
try {
|
||||
// single transport, single thread
|
||||
Transport transport = client.newTransport();
|
||||
ClientTransport transport = client.newTransport();
|
||||
for (int i = 0; i < loop; i++) {
|
||||
String payload = 0 + "/" + i;
|
||||
Request request = Request.get().setVersion("HTTP/2.0")
|
||||
|
@ -115,7 +115,7 @@ class CleartextTest {
|
|||
break;
|
||||
}
|
||||
}
|
||||
transport.get(60L, TimeUnit.SECONDS);
|
||||
transport.get(10L, TimeUnit.SECONDS);
|
||||
} finally {
|
||||
server.shutdownGracefully();
|
||||
client.shutdownGracefully();
|
||||
|
@ -126,7 +126,7 @@ class CleartextTest {
|
|||
|
||||
@Test
|
||||
void testMultithreadPooledClearTextHttp2() throws Exception {
|
||||
int threads = 2;
|
||||
int threads = 4;
|
||||
int loop = 1024;
|
||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||
Domain domain = Domain.builder(httpAddress)
|
||||
|
@ -148,7 +148,7 @@ class CleartextTest {
|
|||
};
|
||||
try {
|
||||
// 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);
|
||||
for (int n = 0; n < threads; n++) {
|
||||
final int t = n;
|
||||
|
@ -174,19 +174,20 @@ class CleartextTest {
|
|||
});
|
||||
}
|
||||
executorService.shutdown();
|
||||
boolean terminated = executorService.awaitTermination(20L, TimeUnit.SECONDS);
|
||||
boolean terminated = executorService.awaitTermination(30L, TimeUnit.SECONDS);
|
||||
executorService.shutdownNow();
|
||||
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
|
||||
transport.get(20L, TimeUnit.SECONDS);
|
||||
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting 30s for transport to complete");
|
||||
Thread.sleep(2000L);
|
||||
transport.get(30L, TimeUnit.SECONDS);
|
||||
logger.log(Level.INFO, "transport complete");
|
||||
} finally {
|
||||
server.shutdownGracefully(20L, TimeUnit.SECONDS);
|
||||
client.shutdownGracefully(20L, TimeUnit.SECONDS);
|
||||
client.shutdownGracefully();
|
||||
server.shutdownGracefully();
|
||||
}
|
||||
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
|
||||
" server responses = " + server.getResponseCounter());
|
||||
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
||||
" client responses = " + client.getResponseCounter());
|
||||
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
|
||||
" server responses = " + server.getResponseCounter());
|
||||
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
|
||||
assertEquals(threads * loop , counter.get());
|
||||
}
|
||||
|
@ -236,7 +237,7 @@ class CleartextTest {
|
|||
};
|
||||
try {
|
||||
// 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);
|
||||
for (int n = 0; n < threads; n++) {
|
||||
final int t = n;
|
||||
|
@ -264,12 +265,13 @@ class CleartextTest {
|
|||
executorService.shutdown();
|
||||
boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS);
|
||||
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
|
||||
Thread.sleep(2000L);
|
||||
transport.get(10L, TimeUnit.SECONDS);
|
||||
logger.log(Level.INFO, "transport complete");
|
||||
} finally {
|
||||
server1.shutdownGracefully(10L, TimeUnit.SECONDS);
|
||||
server2.shutdownGracefully(10L, TimeUnit.SECONDS);
|
||||
client.shutdownGracefully(10L, TimeUnit.SECONDS);
|
||||
server1.shutdownGracefully();
|
||||
server2.shutdownGracefully();
|
||||
client.shutdownGracefully();
|
||||
}
|
||||
logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() +
|
||||
" 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.api.Request;
|
||||
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.HttpResponse;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
import org.xbib.netty.http.server.Domain;
|
||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
@ -52,7 +51,7 @@ class EncryptedTest {
|
|||
counter.incrementAndGet();
|
||||
};
|
||||
try {
|
||||
Transport transport = client.newTransport(httpAddress);
|
||||
ClientTransport transport = client.newTransport(httpAddress);
|
||||
String payload = 0 + "/" + 0;
|
||||
Request request = Request.get()
|
||||
.setVersion("HTTP/2.0")
|
||||
|
@ -99,7 +98,7 @@ class EncryptedTest {
|
|||
};
|
||||
try {
|
||||
// single transport, single thread
|
||||
Transport transport = client.newTransport();
|
||||
ClientTransport transport = client.newTransport();
|
||||
for (int i = 0; i < loop; i++) {
|
||||
String payload = 0 + "/" + i;
|
||||
Request request = Request.get().setVersion("HTTP/2.0")
|
||||
|
@ -124,7 +123,7 @@ class EncryptedTest {
|
|||
|
||||
@Test
|
||||
void testMultithreadPooledSecureHttp2() throws Exception {
|
||||
int threads = 2;
|
||||
int threads = 4;
|
||||
int loop = 1024;
|
||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||
Server server = Server.builder(Domain.builder(httpAddress)
|
||||
|
@ -147,7 +146,7 @@ class EncryptedTest {
|
|||
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet();
|
||||
try {
|
||||
// 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);
|
||||
for (int n = 0; n < threads; n++) {
|
||||
final int t = n;
|
||||
|
@ -166,8 +165,8 @@ class EncryptedTest {
|
|||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -175,6 +174,7 @@ class EncryptedTest {
|
|||
boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS);
|
||||
executorService.shutdownNow();
|
||||
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
|
||||
Thread.sleep(2000L);
|
||||
transport.get(20, TimeUnit.SECONDS);
|
||||
} finally {
|
||||
client.shutdownGracefully(20, TimeUnit.SECONDS);
|
||||
|
|
|
@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.xbib.netty.http.client.Client;
|
||||
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.server.Domain;
|
||||
import org.xbib.netty.http.server.Server;
|
||||
|
@ -76,7 +76,7 @@ class MixedProtocolTest {
|
|||
.build();
|
||||
for (int i = 0; i < max; i++) {
|
||||
// HTTP 2 breaks transport
|
||||
Transport transport = client.execute(request).get();
|
||||
ClientTransport transport = client.execute(request).get();
|
||||
if (transport.isFailed()) {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class MixedProtocolTest {
|
|||
.build();
|
||||
Server server = Server.builder(domain)
|
||||
//.enableDebug()
|
||||
.setTransportLayerSecurityProtocols(new String[]{"TLSv1.2"})
|
||||
.setTransportLayerSecurityProtocols("TLSv1.2")
|
||||
.build();
|
||||
Client client = Client.builder()
|
||||
//.enableDebug()
|
||||
|
@ -119,7 +119,7 @@ class MixedProtocolTest {
|
|||
})
|
||||
.build();
|
||||
for (int i = 0; i < max; i++) {
|
||||
Transport transport = client.execute(request).get();
|
||||
ClientTransport transport = client.execute(request).get();
|
||||
if (transport.isFailed()) {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue