smarter domains, certificate classes, API cleanup

This commit is contained in:
Jörg Prante 2020-07-06 00:08:07 +02:00
parent aa82f681e3
commit 2c25413323
70 changed files with 1376 additions and 715 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -1,3 +1,4 @@
import java.time.Duration
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {

View file

@ -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')}"
}

View 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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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();

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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()

View file

@ -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) {

View file

@ -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) {

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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
}
}

View file

@ -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);

View file

@ -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<>();
}

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -0,0 +1,2 @@
org.xbib.netty.http.client.Http1
org.xbib.netty.http.client.Http2

View file

@ -1,2 +0,0 @@
org.xbib.netty.http.client.Http1Provider
org.xbib.netty.http.client.Http2Provider

View file

@ -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;
}

View file

@ -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);
}

View file

@ -0,0 +1,4 @@
package org.xbib.netty.http.common;
public interface Transport {
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -1,9 +0,0 @@
package org.xbib.netty.http.common.net;
/**
* The network classes.
*/
public enum NetworkClass {
ANY, LOOPBACK, LOCAL, PUBLIC
}

View file

@ -1,9 +0,0 @@
package org.xbib.netty.http.common.net;
/**
* The TCP/IP network protocol versions.
*/
public enum NetworkProtocolVersion {
IPV4, IPV6, IPV46, NONE
}

View file

@ -51,5 +51,4 @@ public class SecurityUtil {
CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
}
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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;

View file

@ -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();
}

View file

@ -320,6 +320,7 @@ public class HandlerPublisher<T> extends ChannelDuplexHandler implements Publish
case DEMANDING:
case IDLE:
cancelled();
// fall through
case DRAINING:
state = DONE;
break;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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())
@ -341,11 +373,12 @@ public class Domain {
}
public Domain build() {
if (httpAddress.isSecure()) {
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);
}
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -1,2 +0,0 @@
org.xbib.netty.http.server.Http1Provider
org.xbib.netty.http.server.Http2Provider

View file

@ -0,0 +1,2 @@
org.xbib.netty.http.server.Http1
org.xbib.netty.http.server.Http2

View file

@ -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();

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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)

View file

@ -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();

View file

@ -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());

View file

@ -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());

View file

@ -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());

View file

@ -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);

View file

@ -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();
}