diff --git a/gradle.properties b/gradle.properties index 4b74532..8ac3c28 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http-client -version = 4.1.11.1 +version = 4.1.11.3 netty.version = 4.1.11.Final tcnative.version = 2.0.1.Final diff --git a/src/main/java/org/xbib/netty/http/client/HttpClient.java b/src/main/java/org/xbib/netty/http/client/HttpClient.java index ff7fa8d..eb84574 100755 --- a/src/main/java/org/xbib/netty/http/client/HttpClient.java +++ b/src/main/java/org/xbib/netty/http/client/HttpClient.java @@ -38,6 +38,7 @@ import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.ssl.OpenSsl; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import org.xbib.netty.http.client.internal.HttpClientChannelPoolMap; import org.xbib.netty.http.client.listener.CookieListener; import org.xbib.netty.http.client.listener.ExceptionListener; import org.xbib.netty.http.client.listener.HttpHeadersListener; @@ -230,7 +231,7 @@ public final class HttpClient implements Closeable { logger.log(Level.FINE, () -> "closed"); } - void dispatch(final HttpRequestContext httpRequestContext) { + public void dispatch(final HttpRequestContext httpRequestContext) { final URI uri = httpRequestContext.getURI(); final HttpRequest httpRequest = httpRequestContext.getHttpRequest(); if (!httpRequestContext.getCookies().isEmpty()) { @@ -262,17 +263,17 @@ public final class HttpClient implements Closeable { // set settings promise before adding httpRequestContext as a channel attribute ChannelPromise settingsPromise = channel.newPromise(); httpRequestContext.setSettingsPromise(settingsPromise); - channel.attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).set(pool); - channel.attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).set(httpRequestContext); + channel.attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).set(pool); + channel.attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).set(httpRequestContext); HttpResponseListener httpResponseListener = httpRequestContext.getHttpResponseListener(); - channel.attr(HttpClientChannelContext.RESPONSE_LISTENER_ATTRIBUTE_KEY).set(httpResponseListener); + channel.attr(HttpClientChannelContextDefaults.RESPONSE_LISTENER_ATTRIBUTE_KEY).set(httpResponseListener); HttpPushListener httpPushListener = httpRequestContext.getHttpPushListener(); - channel.attr(HttpClientChannelContext.PUSH_LISTENER_ATTRIBUTE_KEY).set(httpPushListener); + channel.attr(HttpClientChannelContextDefaults.PUSH_LISTENER_ATTRIBUTE_KEY).set(httpPushListener); HttpHeadersListener httpHeadersListener = httpRequestContext.getHttpHeadersListener(); - channel.attr(HttpClientChannelContext.HEADER_LISTENER_ATTRIBUTE_KEY).set(httpHeadersListener); + channel.attr(HttpClientChannelContextDefaults.HEADER_LISTENER_ATTRIBUTE_KEY).set(httpHeadersListener); CookieListener cookieListener = httpRequestContext.getCookieListener(); - channel.attr(HttpClientChannelContext.COOKIE_LISTENER_ATTRIBUTE_KEY).set(cookieListener); - channel.attr(HttpClientChannelContext.EXCEPTION_LISTENER_ATTRIBUTE_KEY).set(exceptionListener); + channel.attr(HttpClientChannelContextDefaults.COOKIE_LISTENER_ATTRIBUTE_KEY).set(cookieListener); + channel.attr(HttpClientChannelContextDefaults.EXCEPTION_LISTENER_ATTRIBUTE_KEY).set(exceptionListener); if (httpRequestContext.isFailed()) { logger.log(Level.FINE, () -> "detected fail, close channel"); future.cancel(true); @@ -314,7 +315,7 @@ public final class HttpClient implements Closeable { exceptionListener.onException(illegalStateException); httpRequestContext.fail(illegalStateException.getMessage()); final ChannelPool channelPool = channelFuture.channel() - .attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + .attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(channelFuture.channel()); } throw illegalStateException; @@ -334,7 +335,7 @@ public final class HttpClient implements Closeable { httpRequestContext.fail(illegalStateException.getMessage()); if (channelFuture != null) { final ChannelPool channelPool = channelFuture.channel() - .attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + .attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(channelFuture.channel()); } } @@ -347,7 +348,7 @@ public final class HttpClient implements Closeable { httpRequestContext.fail(runtimeException.getMessage()); if (channelFuture != null) { final ChannelPool channelPool = channelFuture.channel() - .attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + .attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(channelFuture.channel()); } } @@ -364,7 +365,7 @@ public final class HttpClient implements Closeable { }); } - boolean tryRedirect(Channel channel, FullHttpResponse httpResponse, HttpRequestContext httpRequestContext) + public boolean tryRedirect(Channel channel, FullHttpResponse httpResponse, HttpRequestContext httpRequestContext) throws IOException { if (httpRequestContext.isFollowRedirect()) { String redirUrl = findRedirect(httpRequestContext, httpResponse); @@ -376,7 +377,7 @@ public final class HttpClient implements Closeable { } else { httpRequestContext.fail("too many redirections"); final ChannelPool channelPool = - channel.attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + channel.attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(channel); } return true; diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientBuilder.java b/src/main/java/org/xbib/netty/http/client/HttpClientBuilder.java index ee2b047..2f74b52 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientBuilder.java +++ b/src/main/java/org/xbib/netty/http/client/HttpClientBuilder.java @@ -28,6 +28,7 @@ import io.netty.handler.proxy.Socks4ProxyHandler; import io.netty.handler.proxy.Socks5ProxyHandler; import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.SslProvider; +import org.xbib.netty.http.client.internal.HttpClientThreadFactory; import org.xbib.netty.http.client.util.ClientAuthMode; import javax.net.ssl.TrustManagerFactory; diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientChannelContext.java b/src/main/java/org/xbib/netty/http/client/HttpClientChannelContext.java index a0ee4d0..eb38aba 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientChannelContext.java +++ b/src/main/java/org/xbib/netty/http/client/HttpClientChannelContext.java @@ -15,18 +15,11 @@ */ package org.xbib.netty.http.client; -import io.netty.channel.pool.ChannelPool; import io.netty.handler.proxy.HttpProxyHandler; import io.netty.handler.proxy.Socks4ProxyHandler; import io.netty.handler.proxy.Socks5ProxyHandler; import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.SslProvider; -import io.netty.util.AttributeKey; -import org.xbib.netty.http.client.listener.CookieListener; -import org.xbib.netty.http.client.listener.ExceptionListener; -import org.xbib.netty.http.client.listener.HttpHeadersListener; -import org.xbib.netty.http.client.listener.HttpPushListener; -import org.xbib.netty.http.client.listener.HttpResponseListener; import org.xbib.netty.http.client.util.ClientAuthMode; import javax.net.ssl.TrustManagerFactory; @@ -34,28 +27,7 @@ import java.io.InputStream; /** */ -final class HttpClientChannelContext { - - static final AttributeKey CHANNEL_POOL_ATTRIBUTE_KEY = - AttributeKey.valueOf("httpClientChannelPool"); - - static final AttributeKey REQUEST_CONTEXT_ATTRIBUTE_KEY = - AttributeKey.valueOf("httpClientRequestContext"); - - static final AttributeKey RESPONSE_LISTENER_ATTRIBUTE_KEY = - AttributeKey.valueOf("httpClientResponseListener"); - - static final AttributeKey HEADER_LISTENER_ATTRIBUTE_KEY = - AttributeKey.valueOf("httpHeaderListener"); - - static final AttributeKey COOKIE_LISTENER_ATTRIBUTE_KEY = - AttributeKey.valueOf("cookieListener"); - - static final AttributeKey PUSH_LISTENER_ATTRIBUTE_KEY = - AttributeKey.valueOf("pushListener"); - - static final AttributeKey EXCEPTION_LISTENER_ATTRIBUTE_KEY = - AttributeKey.valueOf("httpClientExceptionListener"); +public final class HttpClientChannelContext { private final int maxInitialLineLength; @@ -139,83 +111,83 @@ final class HttpClientChannelContext { this.socks5ProxyHandler = socks5ProxyHandler; } - int getMaxInitialLineLength() { + public int getMaxInitialLineLength() { return maxInitialLineLength; } - int getMaxHeaderSize() { + public int getMaxHeaderSize() { return maxHeaderSize; } - int getMaxChunkSize() { + public int getMaxChunkSize() { return maxChunkSize; } - int getMaxContentLength() { + public int getMaxContentLength() { return maxContentLength; } - int getMaxCompositeBufferComponents() { + public int getMaxCompositeBufferComponents() { return maxCompositeBufferComponents; } - int getReadTimeoutMillis() { + public int getReadTimeoutMillis() { return readTimeoutMillis; } - boolean isGzipEnabled() { + public boolean isGzipEnabled() { return enableGzip; } - boolean isInstallHttp2Upgrade() { + public boolean isInstallHttp2Upgrade() { return installHttp2Upgrade; } - SslProvider getSslProvider() { + public SslProvider getSslProvider() { return sslProvider; } - Iterable getCiphers() { + public Iterable getCiphers() { return ciphers; } - CipherSuiteFilter getCipherSuiteFilter() { + public CipherSuiteFilter getCipherSuiteFilter() { return cipherSuiteFilter; } - TrustManagerFactory getTrustManagerFactory() { + public TrustManagerFactory getTrustManagerFactory() { return trustManagerFactory; } - InputStream getKeyCertChainInputStream() { + public InputStream getKeyCertChainInputStream() { return keyCertChainInputStream; } - InputStream getKeyInputStream() { + public InputStream getKeyInputStream() { return keyInputStream; } - String getKeyPassword() { + public String getKeyPassword() { return keyPassword; } - boolean isUseServerNameIdentification() { + public boolean isUseServerNameIdentification() { return useServerNameIdentification; } - ClientAuthMode getClientAuthMode() { + public ClientAuthMode getClientAuthMode() { return clientAuthMode; } - HttpProxyHandler getHttpProxyHandler() { + public HttpProxyHandler getHttpProxyHandler() { return httpProxyHandler; } - Socks4ProxyHandler getSocks4ProxyHandler() { + public Socks4ProxyHandler getSocks4ProxyHandler() { return socks4ProxyHandler; } - Socks5ProxyHandler getSocks5ProxyHandler() { + public Socks5ProxyHandler getSocks5ProxyHandler() { return socks5ProxyHandler; } } diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientChannelContextDefaults.java b/src/main/java/org/xbib/netty/http/client/HttpClientChannelContextDefaults.java index eb24327..0a2d23e 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientChannelContextDefaults.java +++ b/src/main/java/org/xbib/netty/http/client/HttpClientChannelContextDefaults.java @@ -15,12 +15,19 @@ */ package org.xbib.netty.http.client; +import io.netty.channel.pool.ChannelPool; import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SupportedCipherSuiteFilter; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.util.AttributeKey; +import org.xbib.netty.http.client.listener.CookieListener; +import org.xbib.netty.http.client.listener.ExceptionListener; +import org.xbib.netty.http.client.listener.HttpHeadersListener; +import org.xbib.netty.http.client.listener.HttpPushListener; +import org.xbib.netty.http.client.listener.HttpResponseListener; import org.xbib.netty.http.client.util.ClientAuthMode; import org.xbib.netty.http.client.util.InetAddressKey; @@ -30,6 +37,27 @@ import javax.net.ssl.TrustManagerFactory; */ public interface HttpClientChannelContextDefaults { + AttributeKey CHANNEL_POOL_ATTRIBUTE_KEY = + AttributeKey.valueOf("httpClientChannelPool"); + + AttributeKey REQUEST_CONTEXT_ATTRIBUTE_KEY = + AttributeKey.valueOf("httpClientRequestContext"); + + AttributeKey RESPONSE_LISTENER_ATTRIBUTE_KEY = + AttributeKey.valueOf("httpClientResponseListener"); + + AttributeKey HEADER_LISTENER_ATTRIBUTE_KEY = + AttributeKey.valueOf("httpHeaderListener"); + + AttributeKey COOKIE_LISTENER_ATTRIBUTE_KEY = + AttributeKey.valueOf("cookieListener"); + + AttributeKey PUSH_LISTENER_ATTRIBUTE_KEY = + AttributeKey.valueOf("pushListener"); + + AttributeKey EXCEPTION_LISTENER_ATTRIBUTE_KEY = + AttributeKey.valueOf("httpClientExceptionListener"); + /** * Default for TCP_NODELAY. */ diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientRequestBuilder.java b/src/main/java/org/xbib/netty/http/client/HttpClientRequestBuilder.java index ba3cd64..bb51172 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientRequestBuilder.java +++ b/src/main/java/org/xbib/netty/http/client/HttpClientRequestBuilder.java @@ -1,3 +1,18 @@ +/* + * Copyright 2017 Jörg Prante + * + * Jörg Prante licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ package org.xbib.netty.http.client; import io.netty.buffer.ByteBuf; @@ -99,7 +114,7 @@ public class HttpClientRequestBuilder implements HttpRequestBuilder, HttpRequest private HttpPushListener httpPushListener; - HttpClientRequestBuilder(HttpMethod httpMethod, + protected HttpClientRequestBuilder(HttpMethod httpMethod, ByteBufAllocator byteBufAllocator, int streamId) { this(null, httpMethod, byteBufAllocator, streamId); } diff --git a/src/main/java/org/xbib/netty/http/client/HttpRequestDefaults.java b/src/main/java/org/xbib/netty/http/client/HttpRequestDefaults.java index db53686..694dbd9 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpRequestDefaults.java +++ b/src/main/java/org/xbib/netty/http/client/HttpRequestDefaults.java @@ -16,6 +16,7 @@ package org.xbib.netty.http.client; import io.netty.handler.codec.http.HttpVersion; +import org.xbib.netty.http.client.internal.HttpClientUserAgent; import java.net.URI; diff --git a/src/main/java/org/xbib/netty/http/client/Http2EventHandler.java b/src/main/java/org/xbib/netty/http/client/handler/Http2EventHandler.java similarity index 97% rename from src/main/java/org/xbib/netty/http/client/Http2EventHandler.java rename to src/main/java/org/xbib/netty/http/client/handler/Http2EventHandler.java index 117e111..215f461 100644 --- a/src/main/java/org/xbib/netty/http/client/Http2EventHandler.java +++ b/src/main/java/org/xbib/netty/http/client/handler/Http2EventHandler.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.xbib.netty.http.client; +package org.xbib.netty.http.client.handler; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -37,6 +37,8 @@ import io.netty.handler.codec.http2.Http2LocalFlowController; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Stream; import io.netty.handler.codec.http2.HttpConversionUtil; +import org.xbib.netty.http.client.HttpClientChannelContextDefaults; +import org.xbib.netty.http.client.HttpRequestContext; import java.util.logging.Level; import java.util.logging.Logger; @@ -67,7 +69,7 @@ public class Http2EventHandler extends Http2EventAdapter { * @param maxContentLength the maximum content length * @param validateHeaders true if headers should be validated */ - Http2EventHandler(Http2Connection connection, int maxContentLength, boolean validateHeaders) { + public Http2EventHandler(Http2Connection connection, int maxContentLength, boolean validateHeaders) { this.connection = connection; this.maxContentLength = maxContentLength; this.validateHttpHeaders = validateHeaders; @@ -87,7 +89,7 @@ public class Http2EventHandler extends Http2EventAdapter { logger.log(Level.FINEST, () -> "settings received " + settings); Channel channel = ctx.channel(); final HttpRequestContext httpRequestContext = - channel.attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + channel.attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); final HttpRequest httpRequest = httpRequestContext.getHttpRequest(); ChannelPromise channelPromise = channel.newPromise(); Http2Headers headers = toHttp2Headers(httpRequestContext); @@ -242,7 +244,7 @@ public class Http2EventHandler extends Http2EventAdapter { removeMessage(stream, true); } final HttpRequestContext httpRequestContext = - ctx.channel().attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); httpRequestContext.getPushMap().remove(streamId); } @@ -285,7 +287,7 @@ public class Http2EventHandler extends Http2EventAdapter { } Channel channel = ctx.channel(); final HttpRequestContext httpRequestContext = - channel.attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + channel.attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); httpRequestContext.receiveStreamID(promisedStreamId, headers, channel.newPromise()); } diff --git a/src/main/java/org/xbib/netty/http/client/handler/Http2NegotiationHandler.java b/src/main/java/org/xbib/netty/http/client/handler/Http2NegotiationHandler.java new file mode 100644 index 0000000..7361c8e --- /dev/null +++ b/src/main/java/org/xbib/netty/http/client/handler/Http2NegotiationHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Jörg Prante + * + * Jörg Prante licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.xbib.netty.http.client.handler; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler; +import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.xbib.netty.http.client.handler.HttpClientChannelInitializer.configureHttp1Pipeline; +import static org.xbib.netty.http.client.handler.HttpClientChannelInitializer.configureHttp2Pipeline; +import static org.xbib.netty.http.client.handler.HttpClientChannelInitializer.createHttp1ConnectionHandler; +import static org.xbib.netty.http.client.handler.HttpClientChannelInitializer.createHttp2ConnectionHandler; + +/** + * + */ +class Http2NegotiationHandler extends ApplicationProtocolNegotiationHandler { + + private static final Logger logger = Logger.getLogger(Http2NegotiationHandler.class.getName()); + + private final HttpClientChannelInitializer initializer; + + Http2NegotiationHandler(String fallbackProtocol, HttpClientChannelInitializer initializer) { + super(fallbackProtocol); + this.initializer = initializer; + } + + @Override + protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + HttpToHttp2ConnectionHandler http2connectionHandler = createHttp2ConnectionHandler(initializer.getContext()); + ctx.pipeline().addLast(http2connectionHandler); + configureHttp2Pipeline(ctx.pipeline(), initializer.getHttp2ResponseHandler()); + logger.log(Level.FINE, () -> "negotiated HTTP/2: handler = " + ctx.pipeline().names()); + return; + } + if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { + HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(initializer.getContext()); + ctx.pipeline().addLast(http1connectionHandler); + configureHttp1Pipeline(ctx.pipeline(), initializer.getContext(), initializer.getHttpHandler()); + logger.log(Level.FINE, () -> "negotiated HTTP/1.1: handler = " + ctx.pipeline().names()); + return; + } + ctx.close(); + throw new IllegalStateException("unexpected protocol: " + protocol); + } +} diff --git a/src/main/java/org/xbib/netty/http/client/Http2ResponseHandler.java b/src/main/java/org/xbib/netty/http/client/handler/Http2ResponseHandler.java similarity index 82% rename from src/main/java/org/xbib/netty/http/client/Http2ResponseHandler.java rename to src/main/java/org/xbib/netty/http/client/handler/Http2ResponseHandler.java index 476812e..aeae063 100644 --- a/src/main/java/org/xbib/netty/http/client/Http2ResponseHandler.java +++ b/src/main/java/org/xbib/netty/http/client/handler/Http2ResponseHandler.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.xbib.netty.http.client; +package org.xbib.netty.http.client.handler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; @@ -28,6 +28,9 @@ import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.HttpConversionUtil; +import org.xbib.netty.http.client.HttpClient; +import org.xbib.netty.http.client.HttpClientChannelContextDefaults; +import org.xbib.netty.http.client.HttpRequestContext; import org.xbib.netty.http.client.listener.CookieListener; import org.xbib.netty.http.client.listener.ExceptionListener; import org.xbib.netty.http.client.listener.HttpHeadersListener; @@ -48,7 +51,7 @@ public class Http2ResponseHandler extends SimpleChannelInboundHandler "firing onHeaders"); httpHeadersListener.onHeaders(httpHeaders); } CookieListener cookieListener = - ctx.channel().attr(HttpClientChannelContext.COOKIE_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.COOKIE_LISTENER_ATTRIBUTE_KEY).get(); for (String cookieString : httpHeaders.getAll(HttpHeaderNames.SET_COOKIE)) { Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString); httpRequestContext.addCookie(cookie); @@ -82,7 +85,7 @@ public class Http2ResponseHandler extends SimpleChannelInboundHandler pushEntry = httpRequestContext.getPushMap().get(streamId); if (pushEntry != null) { final HttpPushListener httpPushListener = - ctx.channel().attr(HttpClientChannelContext.PUSH_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.PUSH_LISTENER_ATTRIBUTE_KEY).get(); if (httpPushListener != null) { httpPushListener.onPushReceived(pushEntry.getKey(), httpResponse); } @@ -98,7 +101,7 @@ public class Http2ResponseHandler extends SimpleChannelInboundHandler promiseEntry = httpRequestContext.getStreamIdPromiseMap().get(streamId); if (promiseEntry != null) { final HttpResponseListener httpResponseListener = - ctx.channel().attr(HttpClientChannelContext.RESPONSE_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.RESPONSE_LISTENER_ATTRIBUTE_KEY).get(); if (httpResponseListener != null) { httpResponseListener.onResponse(httpResponse); } @@ -124,7 +127,7 @@ public class Http2ResponseHandler extends SimpleChannelInboundHandler "exception caught: " + cause); ExceptionListener exceptionListener = - ctx.channel().attr(HttpClientChannelContext.EXCEPTION_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.EXCEPTION_LISTENER_ATTRIBUTE_KEY).get(); if (exceptionListener != null) { exceptionListener.onException(cause); } final HttpRequestContext httpRequestContext = - ctx.channel().attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); httpRequestContext.fail(cause.getMessage()); final ChannelPool channelPool = - ctx.channel().attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(ctx.channel()); } } diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientChannelInitializer.java b/src/main/java/org/xbib/netty/http/client/handler/HttpClientChannelInitializer.java similarity index 58% rename from src/main/java/org/xbib/netty/http/client/HttpClientChannelInitializer.java rename to src/main/java/org/xbib/netty/http/client/handler/HttpClientChannelInitializer.java index 7656f2b..e168b87 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientChannelInitializer.java +++ b/src/main/java/org/xbib/netty/http/client/handler/HttpClientChannelInitializer.java @@ -13,41 +13,29 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.xbib.netty.http.client; +package org.xbib.netty.http.client.handler; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; -import io.netty.channel.ChannelPromise; -import io.netty.channel.socket.ChannelInputShutdownReadComplete; import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpClientUpgradeHandler; import io.netty.handler.codec.http.HttpContentDecompressor; -import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObjectAggregator; -import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http2.DefaultHttp2Connection; import io.netty.handler.codec.http2.DelegatingDecompressorFrameListener; import io.netty.handler.codec.http2.Http2ClientUpgradeCodec; import io.netty.handler.codec.http2.Http2Connection; -import io.netty.handler.codec.http2.Http2ConnectionPrefaceWrittenEvent; import io.netty.handler.codec.http2.Http2FrameLogger; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder; import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; -import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; -import io.netty.handler.ssl.SslCloseCompletionEvent; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.ReadTimeoutHandler; -import org.xbib.netty.http.client.listener.ExceptionListener; +import org.xbib.netty.http.client.HttpClientChannelContext; import org.xbib.netty.http.client.util.InetAddressKey; import javax.net.ssl.SNIHostName; @@ -63,7 +51,7 @@ import java.util.logging.Logger; /** * Netty HTTP client channel initializer. */ -class HttpClientChannelInitializer extends ChannelInitializer { +public class HttpClientChannelInitializer extends ChannelInitializer { private static final Logger logger = Logger.getLogger(HttpClientChannelInitializer.class.getName()); @@ -81,13 +69,25 @@ class HttpClientChannelInitializer extends ChannelInitializer { * @param httpHandler the HTTP 1.x handler * @param http2ResponseHandler the HTTP 2 handler */ - HttpClientChannelInitializer(HttpClientChannelContext context, HttpHandler httpHandler, + public HttpClientChannelInitializer(HttpClientChannelContext context, HttpHandler httpHandler, Http2ResponseHandler http2ResponseHandler) { this.context = context; this.httpHandler = httpHandler; this.http2ResponseHandler = http2ResponseHandler; } + HttpClientChannelContext getContext() { + return context; + } + + HttpHandler getHttpHandler() { + return httpHandler; + } + + Http2ResponseHandler getHttp2ResponseHandler() { + return http2ResponseHandler; + } + /** * Sets up a {@link InetAddressKey} for the channel initialization and initializes the channel. * Using this method, the channel initializer can handle secure channels, the HTTP protocol version, @@ -96,7 +96,7 @@ class HttpClientChannelInitializer extends ChannelInitializer { * @param key the key of the internet address * @throws Exception if channel */ - void initChannel(SocketChannel ch, InetAddressKey key) throws Exception { + public void initChannel(SocketChannel ch, InetAddressKey key) throws Exception { this.key = key; initChannel(ch); } @@ -130,14 +130,14 @@ class HttpClientChannelInitializer extends ChannelInitializer { private void configureClearText(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); if (key.getVersion().majorVersion() == 1) { - HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(); + HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(context); pipeline.addLast(http1connectionHandler); - configureHttp1Pipeline(pipeline); + configureHttp1Pipeline(pipeline, context, httpHandler); } else if (key.getVersion().majorVersion() == 2) { - HttpToHttp2ConnectionHandler http2connectionHandler = createHttp2ConnectionHandler(); + HttpToHttp2ConnectionHandler http2connectionHandler = createHttp2ConnectionHandler(context); // using the upgrade handler means mixed HTTP 1 and HTTP 2 on the same connection if (context.isInstallHttp2Upgrade()) { - HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(); + HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(context); Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(http2connectionHandler); HttpClientUpgradeHandler upgradeHandler = @@ -149,8 +149,8 @@ class HttpClientChannelInitializer extends ChannelInitializer { } else { pipeline.addLast(http2connectionHandler); } - configureHttp2Pipeline(pipeline); - configureHttp1Pipeline(pipeline); + configureHttp2Pipeline(pipeline, http2ResponseHandler); + configureHttp1Pipeline(pipeline, context, httpHandler); } } @@ -192,15 +192,15 @@ class HttpClientChannelInitializer extends ChannelInitializer { break; } if (key.getVersion().majorVersion() == 1) { - HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(); + HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(context); pipeline.addLast(http1connectionHandler); - configureHttp1Pipeline(pipeline); + configureHttp1Pipeline(pipeline, context, httpHandler); } else if (key.getVersion().majorVersion() == 2) { - pipeline.addLast(new Http2NegotiationHandler(ApplicationProtocolNames.HTTP_1_1)); + pipeline.addLast(new Http2NegotiationHandler(ApplicationProtocolNames.HTTP_1_1, this)); } } - private void configureHttp1Pipeline(ChannelPipeline pipeline) { + static void configureHttp1Pipeline(ChannelPipeline pipeline, HttpClientChannelContext context, HttpHandler httpHandler) { if (context.isGzipEnabled()) { pipeline.addLast(new HttpContentDecompressor()); } @@ -211,16 +211,16 @@ class HttpClientChannelInitializer extends ChannelInitializer { pipeline.addLast(httpHandler); } - private void configureHttp2Pipeline(ChannelPipeline pipeline) { + static void configureHttp2Pipeline(ChannelPipeline pipeline, Http2ResponseHandler http2ResponseHandler) { pipeline.addLast(new UserEventLogger()); pipeline.addLast(http2ResponseHandler); } - private HttpClientCodec createHttp1ConnectionHandler() { + static HttpClientCodec createHttp1ConnectionHandler(HttpClientChannelContext context) { return new HttpClientCodec(context.getMaxInitialLineLength(), context.getMaxHeaderSize(), context.getMaxChunkSize()); } - private HttpToHttp2ConnectionHandler createHttp2ConnectionHandler() { + static HttpToHttp2ConnectionHandler createHttp2ConnectionHandler(HttpClientChannelContext context) { final Http2Connection http2Connection = new DefaultHttp2Connection(false); return new HttpToHttp2ConnectionHandlerBuilder() .connection(http2Connection) @@ -229,124 +229,4 @@ class HttpClientChannelInitializer extends ChannelInitializer { new Http2EventHandler(http2Connection, context.getMaxContentLength(), false))) .build(); } - - private class Http2NegotiationHandler extends ApplicationProtocolNegotiationHandler { - - Http2NegotiationHandler(String fallbackProtocol) { - super(fallbackProtocol); - } - - @Override - protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception { - if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { - HttpToHttp2ConnectionHandler http2connectionHandler = createHttp2ConnectionHandler(); - ctx.pipeline().addLast(http2connectionHandler); - configureHttp2Pipeline(ctx.pipeline()); - logger.log(Level.FINE, () -> "negotiated HTTP/2: handler = " + ctx.pipeline().names()); - return; - } - if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { - HttpClientCodec http1connectionHandler = createHttp1ConnectionHandler(); - ctx.pipeline().addLast(http1connectionHandler); - configureHttp1Pipeline(ctx.pipeline()); - logger.log(Level.FINE, () -> "negotiated HTTP/1.1: handler = " + ctx.pipeline().names()); - return; - } - // close and fail - ctx.close(); - throw new IllegalStateException("unexpected protocol: " + protocol); - } - } - - @Sharable - private static class UpgradeRequestHandler extends ChannelInboundHandlerAdapter { - - /** - * Send an upgrade request if channel becomes active. - * @param ctx the channel handler context - * @throws Exception if upgrade request sending fails - */ - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - DefaultFullHttpRequest upgradeRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"); - ctx.writeAndFlush(upgradeRequest); - super.channelActive(ctx); - ctx.pipeline().remove(this); - logger.log(Level.FINE, () -> "upgrade request handler removed, pipeline = " + ctx.pipeline().names()); - } - - /** - * Forward channel exceptions to the exception listener. - * @param ctx the channel handler context - * @param cause the cause of the exception - * @throws Exception if forwarding fails - */ - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - logger.log(Level.FINE, () -> "exceptionCaught " + cause.getMessage()); - ExceptionListener exceptionListener = - ctx.channel().attr(HttpClientChannelContext.EXCEPTION_LISTENER_ATTRIBUTE_KEY).get(); - if (exceptionListener != null) { - exceptionListener.onException(cause); - } - final HttpRequestContext httpRequestContext = - ctx.channel().attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); - httpRequestContext.fail(cause.getMessage()); - } - } - - /** - * A Netty handler that logs user events and find expetced ones. - */ - @Sharable - private static class UserEventLogger extends ChannelInboundHandlerAdapter { - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - logger.log(Level.FINE, () -> "got user event " + evt); - if (evt instanceof Http2ConnectionPrefaceWrittenEvent || - evt instanceof SslCloseCompletionEvent || - evt instanceof ChannelInputShutdownReadComplete) { - // log expected events - logger.log(Level.FINE, () -> "user event is expected: " + evt); - return; - } - super.userEventTriggered(ctx, evt); - } - } - - /** - * A Netty handler that logs the I/O traffic of a connection. - */ - @Sharable - private static class TrafficLoggingHandler extends LoggingHandler { - - TrafficLoggingHandler() { - super("client", LogLevel.TRACE); - } - - @Override - public void channelRegistered(ChannelHandlerContext ctx) throws Exception { - ctx.fireChannelRegistered(); - } - - @Override - public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { - ctx.fireChannelUnregistered(); - } - - @Override - public void flush(ChannelHandlerContext ctx) throws Exception { - ctx.flush(); - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) { - ctx.write(msg, promise); - } else { - super.write(ctx, msg, promise); - } - } - } } diff --git a/src/main/java/org/xbib/netty/http/client/HttpHandler.java b/src/main/java/org/xbib/netty/http/client/handler/HttpHandler.java similarity index 77% rename from src/main/java/org/xbib/netty/http/client/HttpHandler.java rename to src/main/java/org/xbib/netty/http/client/handler/HttpHandler.java index b33aedc..70f116d 100755 --- a/src/main/java/org/xbib/netty/http/client/HttpHandler.java +++ b/src/main/java/org/xbib/netty/http/client/handler/HttpHandler.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.xbib.netty.http.client; +package org.xbib.netty.http.client.handler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -24,6 +24,9 @@ import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; +import org.xbib.netty.http.client.HttpClient; +import org.xbib.netty.http.client.HttpClientChannelContextDefaults; +import org.xbib.netty.http.client.HttpRequestContext; import org.xbib.netty.http.client.listener.CookieListener; import org.xbib.netty.http.client.listener.ExceptionListener; import org.xbib.netty.http.client.listener.HttpHeadersListener; @@ -36,13 +39,13 @@ import java.util.logging.Logger; * HTTP 1.x Netty channel handler. */ @ChannelHandler.Sharable -final class HttpHandler extends ChannelInboundHandlerAdapter { +public final class HttpHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = Logger.getLogger(HttpHandler.class.getName()); private final HttpClient httpClient; - HttpHandler(HttpClient httpClient) { + public HttpHandler(HttpClient httpClient) { this.httpClient = httpClient; } @@ -57,18 +60,18 @@ final class HttpHandler extends ChannelInboundHandlerAdapter { public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { logger.log(Level.FINE, () -> "channelRead msg " + msg.getClass().getName()); final HttpRequestContext httpRequestContext = - ctx.channel().attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); if (msg instanceof FullHttpResponse) { FullHttpResponse httpResponse = (FullHttpResponse) msg; HttpHeaders httpHeaders = httpResponse.headers(); HttpHeadersListener httpHeadersListener = - ctx.channel().attr(HttpClientChannelContext.HEADER_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.HEADER_LISTENER_ATTRIBUTE_KEY).get(); if (httpHeadersListener != null) { logger.log(Level.FINE, () -> "firing onHeaders"); httpHeadersListener.onHeaders(httpHeaders); } CookieListener cookieListener = - ctx.channel().attr(HttpClientChannelContext.COOKIE_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.COOKIE_LISTENER_ATTRIBUTE_KEY).get(); for (String cookieString : httpHeaders.getAll(HttpHeaderNames.SET_COOKIE)) { Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString); httpRequestContext.addCookie(cookie); @@ -78,7 +81,7 @@ final class HttpHandler extends ChannelInboundHandlerAdapter { } } HttpResponseListener httpResponseListener = - ctx.channel().attr(HttpClientChannelContext.RESPONSE_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.RESPONSE_LISTENER_ATTRIBUTE_KEY).get(); if (httpResponseListener != null) { logger.log(Level.FINE, () -> "firing onResponse"); httpResponseListener.onResponse(httpResponse); @@ -89,7 +92,7 @@ final class HttpHandler extends ChannelInboundHandlerAdapter { } httpRequestContext.success("response finished"); final ChannelPool channelPool = - ctx.channel().attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(ctx.channel()); } } @@ -98,12 +101,12 @@ final class HttpHandler extends ChannelInboundHandlerAdapter { public void channelInactive(ChannelHandlerContext ctx) throws Exception { logger.log(Level.FINE, () -> "channelInactive " + ctx); final HttpRequestContext httpRequestContext = - ctx.channel().attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); if (httpRequestContext.getRedirectCount().get() == 0 && !httpRequestContext.isSucceeded()) { httpRequestContext.fail("channel inactive"); } final ChannelPool channelPool = - ctx.channel().attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(ctx.channel()); } @@ -116,16 +119,16 @@ final class HttpHandler extends ChannelInboundHandlerAdapter { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ExceptionListener exceptionListener = - ctx.channel().attr(HttpClientChannelContext.EXCEPTION_LISTENER_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.EXCEPTION_LISTENER_ATTRIBUTE_KEY).get(); logger.log(Level.FINE, () -> "exceptionCaught"); if (exceptionListener != null) { exceptionListener.onException(cause); } final HttpRequestContext httpRequestContext = - ctx.channel().attr(HttpClientChannelContext.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); httpRequestContext.fail(cause.getMessage()); final ChannelPool channelPool = - ctx.channel().attr(HttpClientChannelContext.CHANNEL_POOL_ATTRIBUTE_KEY).get(); + ctx.channel().attr(HttpClientChannelContextDefaults.CHANNEL_POOL_ATTRIBUTE_KEY).get(); channelPool.release(ctx.channel()); } } diff --git a/src/main/java/org/xbib/netty/http/client/handler/TrafficLoggingHandler.java b/src/main/java/org/xbib/netty/http/client/handler/TrafficLoggingHandler.java new file mode 100644 index 0000000..b35d8c5 --- /dev/null +++ b/src/main/java/org/xbib/netty/http/client/handler/TrafficLoggingHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Jörg Prante + * + * Jörg Prante licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.xbib.netty.http.client.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + +/** + * A Netty handler that logs the I/O traffic of a connection. + */ +@ChannelHandler.Sharable +class TrafficLoggingHandler extends LoggingHandler { + + TrafficLoggingHandler() { + super("client", LogLevel.TRACE); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelRegistered(); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelUnregistered(); + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + ctx.flush(); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) { + ctx.write(msg, promise); + } else { + super.write(ctx, msg, promise); + } + } +} diff --git a/src/main/java/org/xbib/netty/http/client/handler/UpgradeRequestHandler.java b/src/main/java/org/xbib/netty/http/client/handler/UpgradeRequestHandler.java new file mode 100644 index 0000000..efa5dde --- /dev/null +++ b/src/main/java/org/xbib/netty/http/client/handler/UpgradeRequestHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Jörg Prante + * + * Jörg Prante licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.xbib.netty.http.client.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; +import org.xbib.netty.http.client.HttpClientChannelContextDefaults; +import org.xbib.netty.http.client.HttpRequestContext; +import org.xbib.netty.http.client.listener.ExceptionListener; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + */ +@ChannelHandler.Sharable +class UpgradeRequestHandler extends ChannelInboundHandlerAdapter { + + private static final Logger logger = Logger.getLogger(UpgradeRequestHandler.class.getName()); + + /** + * Send an upgrade request if channel becomes active. + * @param ctx the channel handler context + * @throws Exception if upgrade request sending fails + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + DefaultFullHttpRequest upgradeRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"); + ctx.writeAndFlush(upgradeRequest); + super.channelActive(ctx); + ctx.pipeline().remove(this); + logger.log(Level.FINE, () -> "upgrade request handler removed, pipeline = " + ctx.pipeline().names()); + } + + /** + * Forward channel exceptions to the exception listener. + * @param ctx the channel handler context + * @param cause the cause of the exception + * @throws Exception if forwarding fails + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.log(Level.FINE, () -> "exceptionCaught " + cause.getMessage()); + ExceptionListener exceptionListener = + ctx.channel().attr(HttpClientChannelContextDefaults.EXCEPTION_LISTENER_ATTRIBUTE_KEY).get(); + if (exceptionListener != null) { + exceptionListener.onException(cause); + } + final HttpRequestContext httpRequestContext = + ctx.channel().attr(HttpClientChannelContextDefaults.REQUEST_CONTEXT_ATTRIBUTE_KEY).get(); + httpRequestContext.fail(cause.getMessage()); + } +} diff --git a/src/main/java/org/xbib/netty/http/client/handler/UserEventLogger.java b/src/main/java/org/xbib/netty/http/client/handler/UserEventLogger.java new file mode 100644 index 0000000..18e2721 --- /dev/null +++ b/src/main/java/org/xbib/netty/http/client/handler/UserEventLogger.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Jörg Prante + * + * Jörg Prante licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.xbib.netty.http.client.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.ChannelInputShutdownReadComplete; +import io.netty.handler.codec.http2.Http2ConnectionPrefaceWrittenEvent; +import io.netty.handler.ssl.SslCloseCompletionEvent; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A Netty handler that logs user events and find expetced ones. + */ +@ChannelHandler.Sharable +class UserEventLogger extends ChannelInboundHandlerAdapter { + + private static final Logger logger = Logger.getLogger(UserEventLogger.class.getName()); + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + logger.log(Level.FINE, () -> "got user event " + evt); + if (evt instanceof Http2ConnectionPrefaceWrittenEvent || + evt instanceof SslCloseCompletionEvent || + evt instanceof ChannelInputShutdownReadComplete) { + // log expected events + logger.log(Level.FINE, () -> "user event is expected: " + evt); + return; + } + super.userEventTriggered(ctx, evt); + } +} diff --git a/src/main/java/org/xbib/netty/http/client/handler/package-info.java b/src/main/java/org/xbib/netty/http/client/handler/package-info.java new file mode 100644 index 0000000..4550e8e --- /dev/null +++ b/src/main/java/org/xbib/netty/http/client/handler/package-info.java @@ -0,0 +1,4 @@ +/** + * Handlers for Netty HTTP client. + */ +package org.xbib.netty.http.client.handler; diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientChannelPoolHandler.java b/src/main/java/org/xbib/netty/http/client/internal/HttpClientChannelPoolHandler.java similarity index 90% rename from src/main/java/org/xbib/netty/http/client/HttpClientChannelPoolHandler.java rename to src/main/java/org/xbib/netty/http/client/internal/HttpClientChannelPoolHandler.java index 1d4b90d..ca18dee 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientChannelPoolHandler.java +++ b/src/main/java/org/xbib/netty/http/client/internal/HttpClientChannelPoolHandler.java @@ -13,11 +13,12 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.xbib.netty.http.client; +package org.xbib.netty.http.client.internal; import io.netty.channel.Channel; import io.netty.channel.pool.ChannelPoolHandler; import io.netty.channel.socket.SocketChannel; +import org.xbib.netty.http.client.handler.HttpClientChannelInitializer; import org.xbib.netty.http.client.util.InetAddressKey; import java.util.concurrent.atomic.AtomicInteger; @@ -39,7 +40,7 @@ public class HttpClientChannelPoolHandler implements ChannelPoolHandler { private int peak; - HttpClientChannelPoolHandler(HttpClientChannelInitializer channelInitializer, InetAddressKey key) { + public HttpClientChannelPoolHandler(HttpClientChannelInitializer channelInitializer, InetAddressKey key) { this.channelInitializer = channelInitializer; this.key = key; } diff --git a/src/main/java/org/xbib/netty/http/client/HttpClientChannelPoolMap.java b/src/main/java/org/xbib/netty/http/client/internal/HttpClientChannelPoolMap.java similarity index 85% rename from src/main/java/org/xbib/netty/http/client/HttpClientChannelPoolMap.java rename to src/main/java/org/xbib/netty/http/client/internal/HttpClientChannelPoolMap.java index 3706be0..46fef1c 100644 --- a/src/main/java/org/xbib/netty/http/client/HttpClientChannelPoolMap.java +++ b/src/main/java/org/xbib/netty/http/client/internal/HttpClientChannelPoolMap.java @@ -13,11 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.xbib.netty.http.client; +package org.xbib.netty.http.client.internal; import io.netty.bootstrap.Bootstrap; import io.netty.channel.pool.AbstractChannelPoolMap; import io.netty.channel.pool.FixedChannelPool; +import org.xbib.netty.http.client.HttpClient; +import org.xbib.netty.http.client.HttpClientChannelContext; +import org.xbib.netty.http.client.handler.Http2ResponseHandler; +import org.xbib.netty.http.client.handler.HttpClientChannelInitializer; +import org.xbib.netty.http.client.handler.HttpHandler; import org.xbib.netty.http.client.util.InetAddressKey; /** @@ -37,7 +42,7 @@ public class HttpClientChannelPoolMap extends AbstractChannelPoolMap