back to 4.1.16, fix SSL, fix pool, fix tests

This commit is contained in:
Jörg Prante 2018-03-09 00:37:08 +01:00
parent 5cadb716bc
commit 12a5f6418c
23 changed files with 343 additions and 215 deletions

View file

@ -67,7 +67,7 @@ test {
jvmArgs "-javaagent:" + configurations.alpnagent.asPath jvmArgs "-javaagent:" + configurations.alpnagent.asPath
} }
testLogging { testLogging {
showStandardStreams = true showStandardStreams = false
exceptionFormat = 'full' exceptionFormat = 'full'
} }
} }

View file

@ -1,8 +1,8 @@
group = org.xbib group = org.xbib
name = netty-http-client name = netty-http-client
version = 4.1.22.2 version = 4.1.16.1
netty.version = 4.1.22.Final netty.version = 4.1.16.Final
tcnative.version = 2.0.7.Final tcnative.version = 2.0.7.Final
conscrypt.version = 1.0.1 conscrypt.version = 1.0.1
xbib-net-url.version = 1.1.0 xbib-net-url.version = 1.1.0

View file

@ -40,6 +40,7 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -136,7 +137,7 @@ public final class Client {
this.http2SettingsHandler = new Http2SettingsHandler(); this.http2SettingsHandler = new Http2SettingsHandler();
this.http2ResponseHandler = new Http2ResponseHandler(); this.http2ResponseHandler = new Http2ResponseHandler();
this.transports = new CopyOnWriteArrayList<>(); this.transports = new CopyOnWriteArrayList<>();
if (hasPooledConnections()) { if (!clientConfig.getPoolNodes().isEmpty()) {
List<HttpAddress> nodes = clientConfig.getPoolNodes(); List<HttpAddress> nodes = clientConfig.getPoolNodes();
Integer limit = clientConfig.getPoolNodeConnectionLimit(); Integer limit = clientConfig.getPoolNodeConnectionLimit();
if (limit == null || limit < 1) { if (limit == null || limit < 1) {
@ -183,7 +184,7 @@ public final class Client {
} }
public boolean hasPooledConnections() { public boolean hasPooledConnections() {
return !clientConfig.getPoolNodes().isEmpty(); return pool != null && !clientConfig.getPoolNodes().isEmpty();
} }
public BoundedChannelPool<HttpAddress> getPool() { public BoundedChannelPool<HttpAddress> getPool() {
@ -222,13 +223,13 @@ public final class Client {
} else { } else {
transport = new Http2Transport(this, null); transport = new Http2Transport(this, null);
} }
} else {
throw new IllegalStateException();
} }
if (transport != null) { if (transportListener != null) {
if (transportListener != null) { transportListener.onOpen(transport);
transportListener.onOpen(transport);
}
transports.add(transport);
} }
transports.add(transport);
return transport; return transport;
} }
@ -270,15 +271,15 @@ public final class Client {
} }
public void releaseChannel(Channel channel) throws IOException{ public void releaseChannel(Channel channel) throws IOException{
if (hasPooledConnections()) { if (channel != null) {
try { if (hasPooledConnections()) {
pool.release(channel); try {
} catch (Exception e) { pool.release(channel);
throw new IOException(e); } catch (Exception e) {
} throw new IOException(e);
} else { }
if (channel != null) { } else {
channel.close(); channel.close();
} }
} }
} }
@ -378,14 +379,16 @@ public final class Client {
private static SslHandler newSslHandler(ClientConfig clientConfig, ByteBufAllocator allocator, HttpAddress httpAddress) { private static SslHandler newSslHandler(ClientConfig clientConfig, ByteBufAllocator allocator, HttpAddress httpAddress) {
try { try {
SslContext sslContext = newSslContext(clientConfig); SslContext sslContext = newSslContext(clientConfig, httpAddress.getVersion());
SslHandler sslHandler = sslContext.newHandler(allocator); InetSocketAddress peer = httpAddress.getInetSocketAddress();
SslHandler sslHandler = sslContext.newHandler(allocator, peer.getHostName(), peer.getPort());
SSLEngine engine = sslHandler.engine(); SSLEngine engine = sslHandler.engine();
List<String> serverNames = clientConfig.getServerNamesForIdentification(); List<String> serverNames = clientConfig.getServerNamesForIdentification();
if (serverNames.isEmpty()) { if (serverNames.isEmpty()) {
serverNames = Collections.singletonList(httpAddress.getInetSocketAddress().getHostName()); serverNames = Collections.singletonList(peer.getHostName());
} }
SSLParameters params = engine.getSSLParameters(); SSLParameters params = engine.getSSLParameters();
// use sslContext.newHandler(allocator, peerHost, peerPort) when using params.setEndpointIdentificationAlgorithm
params.setEndpointIdentificationAlgorithm("HTTPS"); params.setEndpointIdentificationAlgorithm("HTTPS");
List<SNIServerName> sniServerNames = new ArrayList<>(); List<SNIServerName> sniServerNames = new ArrayList<>();
for (String serverName : serverNames) { for (String serverName : serverNames) {
@ -409,11 +412,11 @@ public final class Client {
} }
} }
private static SslContext newSslContext(ClientConfig clientConfig) throws SSLException { private static SslContext newSslContext(ClientConfig clientConfig, HttpVersion httpVersion) throws SSLException {
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient() SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
.sslProvider(clientConfig.getSslProvider()) .sslProvider(clientConfig.getSslProvider())
.ciphers(Http2SecurityUtil.CIPHERS, clientConfig.getCipherSuiteFilter()) .ciphers(Http2SecurityUtil.CIPHERS, clientConfig.getCipherSuiteFilter())
.applicationProtocolConfig(newApplicationProtocolConfig()); .applicationProtocolConfig(newApplicationProtocolConfig(httpVersion));
if (clientConfig.getSslContextProvider() != null) { if (clientConfig.getSslContextProvider() != null) {
sslContextBuilder.sslContextProvider(clientConfig.getSslContextProvider()); sslContextBuilder.sslContextProvider(clientConfig.getSslContextProvider());
} }
@ -423,11 +426,16 @@ public final class Client {
return sslContextBuilder.build(); return sslContextBuilder.build();
} }
private static ApplicationProtocolConfig newApplicationProtocolConfig() { private static ApplicationProtocolConfig newApplicationProtocolConfig(HttpVersion httpVersion) {
return new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN, return httpVersion.majorVersion() == 1 ?
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolNames.HTTP_2); ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_1_1) :
new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2);
} }
public interface TransportListener { public interface TransportListener {

View file

@ -192,6 +192,8 @@ public class ClientBuilder {
public ClientBuilder addPoolNode(HttpAddress httpAddress) { public ClientBuilder addPoolNode(HttpAddress httpAddress) {
clientConfig.addPoolNode(httpAddress); clientConfig.addPoolNode(httpAddress);
clientConfig.setPoolVersion(httpAddress.getVersion());
clientConfig.setPoolSecure(httpAddress.isSecure());
return this; return this;
} }
@ -205,16 +207,6 @@ public class ClientBuilder {
return this; return this;
} }
public ClientBuilder setPoolVersion(HttpVersion poolVersion) {
clientConfig.setPoolVersion(poolVersion);
return this;
}
public ClientBuilder setPoolSecure(boolean poolSecure) {
clientConfig.setPoolSecure(poolSecure);
return this;
}
public ClientBuilder addServerNameForIdentification(String serverName) { public ClientBuilder addServerNameForIdentification(String serverName) {
clientConfig.addServerNameForIdentification(serverName); clientConfig.addServerNameForIdentification(serverName);
return this; return this;

View file

@ -56,7 +56,7 @@ public class HttpAddress implements PoolKey {
} }
public static HttpAddress of(Request request) { public static HttpAddress of(Request request) {
return new HttpAddress(request.base(), request.httpVersion()); return new HttpAddress(request.url(), request.httpVersion());
} }
public static HttpAddress of(URL url, HttpVersion httpVersion) { public static HttpAddress of(URL url, HttpVersion httpVersion) {
@ -76,7 +76,7 @@ public class HttpAddress implements PoolKey {
public InetSocketAddress getInetSocketAddress() { public InetSocketAddress getInetSocketAddress() {
if (inetSocketAddress == null) { if (inetSocketAddress == null) {
// this may execute DNS // this may execute DNS lookup
this.inetSocketAddress = new InetSocketAddress(host, port); this.inetSocketAddress = new InetSocketAddress(host, port);
} }
return inetSocketAddress; return inetSocketAddress;

View file

@ -1,6 +1,8 @@
package org.xbib.netty.http.client; package org.xbib.netty.http.client;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
@ -12,8 +14,6 @@ import org.xbib.netty.http.client.listener.HttpHeadersListener;
import org.xbib.netty.http.client.listener.HttpResponseListener; import org.xbib.netty.http.client.listener.HttpResponseListener;
import org.xbib.netty.http.client.retry.BackOff; import org.xbib.netty.http.client.retry.BackOff;
import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -21,9 +21,9 @@ import java.util.concurrent.CompletableFuture;
/** /**
* HTTP client request. * HTTP client request.
*/ */
public class Request implements Closeable { public class Request {
private final URL base; private final URL url;
private final HttpVersion httpVersion; private final HttpVersion httpVersion;
@ -62,7 +62,7 @@ public class Request implements Closeable {
String uri, ByteBuf content, String uri, ByteBuf content,
long timeoutInMillis, boolean followRedirect, int maxRedirect, int redirectCount, long timeoutInMillis, boolean followRedirect, int maxRedirect, int redirectCount,
boolean isBackOff, BackOff backOff) { boolean isBackOff, BackOff backOff) {
this.base = url; this.url = url;
this.httpVersion = httpVersion; this.httpVersion = httpVersion;
this.httpMethod = httpMethod; this.httpMethod = httpMethod;
this.headers = headers; this.headers = headers;
@ -77,8 +77,8 @@ public class Request implements Closeable {
this.backOff = backOff; this.backOff = backOff;
} }
public URL base() { public URL url() {
return base; return url;
} }
public HttpVersion httpVersion() { public HttpVersion httpVersion() {
@ -139,12 +139,14 @@ public class Request implements Closeable {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Request[base='").append(base) sb.append("Request[url='").append(url)
.append("',version=").append(httpVersion) .append("',version=").append(httpVersion)
.append(",method=").append(httpMethod) .append(",method=").append(httpMethod)
.append(",uri=").append(uri) .append(",uri=").append(uri)
.append(",headers=").append(headers.entries()) .append(",headers=").append(headers.entries())
.append(",content=").append(content != null ? content.copy(0,16).toString(StandardCharsets.UTF_8) : "") .append(",content=").append(content != null && content.readableBytes() >= 16 ?
content.copy(0,16).toString(StandardCharsets.UTF_8) + "..." :
content != null ? content.toString(StandardCharsets.UTF_8) : "")
.append("]"); .append("]");
return sb.toString(); return sb.toString();
} }
@ -222,13 +224,10 @@ public class Request implements Closeable {
} }
public static RequestBuilder builder(HttpMethod httpMethod) { public static RequestBuilder builder(HttpMethod httpMethod) {
return new RequestBuilder().setMethod(httpMethod); return new RequestBuilder(PooledByteBufAllocator.DEFAULT).setMethod(httpMethod);
} }
@Override public static RequestBuilder builder(ByteBufAllocator allocator, HttpMethod httpMethod) {
public void close() throws IOException { return new RequestBuilder(allocator).setMethod(httpMethod);
if (content != null) {
content.release();
}
} }
} }

View file

@ -1,7 +1,11 @@
package org.xbib.netty.http.client; package org.xbib.netty.http.client;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaderValues;
@ -51,6 +55,8 @@ public class RequestBuilder {
private static final HttpVersion HTTP_2_0 = HttpVersion.valueOf("HTTP/2.0"); private static final HttpVersion HTTP_2_0 = HttpVersion.valueOf("HTTP/2.0");
private final ByteBufAllocator allocator;
private final List<String> removeHeaders; private final List<String> removeHeaders;
private final Collection<Cookie> cookies; private final Collection<Cookie> cookies;
@ -85,7 +91,8 @@ public class RequestBuilder {
private BackOff backOff; private BackOff backOff;
RequestBuilder() { RequestBuilder(ByteBufAllocator allocator) {
this.allocator = allocator;
httpMethod = DEFAULT_METHOD; httpMethod = DEFAULT_METHOD;
httpVersion = DEFAULT_HTTP_VERSION; httpVersion = DEFAULT_HTTP_VERSION;
userAgent = DEFAULT_USER_AGENT; userAgent = DEFAULT_USER_AGENT;
@ -341,11 +348,11 @@ public class RequestBuilder {
} }
private void content(CharSequence charSequence, AsciiString contentType) { private void content(CharSequence charSequence, AsciiString contentType) {
content(charSequence.toString().getBytes(StandardCharsets.UTF_8), contentType); content(ByteBufUtil.writeUtf8(allocator, charSequence), contentType);
} }
private void content(byte[] buf, AsciiString contentType) { private void content(byte[] buf, AsciiString contentType) {
content(PooledByteBufAllocator.DEFAULT.buffer(buf.length).writeBytes(buf), contentType); content(allocator.buffer().writeBytes(buf), contentType);
} }
private void content(ByteBuf body, AsciiString contentType) { private void content(ByteBuf body, AsciiString contentType) {

View file

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -85,10 +86,10 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
} }
this.numberOfNodes = nodes.size(); this.numberOfNodes = nodes.size();
bootstraps = new HashMap<>(numberOfNodes); bootstraps = new HashMap<>(numberOfNodes);
channels = new HashMap<>(numberOfNodes); channels = new ConcurrentHashMap<>(numberOfNodes);
availableChannels = new HashMap<>(numberOfNodes); availableChannels = new ConcurrentHashMap<>(numberOfNodes);
counts = new HashMap<>(numberOfNodes); counts = new ConcurrentHashMap<>(numberOfNodes);
failedCounts = new HashMap<>(numberOfNodes); failedCounts = new ConcurrentHashMap<>(numberOfNodes);
for (K node : nodes) { for (K node : nodes) {
ChannelPoolInitializer initializer = new ChannelPoolInitializer(node, channelPoolHandler); ChannelPoolInitializer initializer = new ChannelPoolInitializer(node, channelPoolHandler);
bootstraps.put(node, bootstrap.clone().remoteAddress(node.getInetSocketAddress()) bootstraps.put(node, bootstrap.clone().remoteAddress(node.getInetSocketAddress())
@ -247,6 +248,9 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
int i = ThreadLocalRandom.current().nextInt(numberOfNodes); int i = ThreadLocalRandom.current().nextInt(numberOfNodes);
for (int j = i; j < numberOfNodes; j ++) { for (int j = i; j < numberOfNodes; j ++) {
nextKey = nodes.get(j % numberOfNodes); nextKey = nodes.get(j % numberOfNodes);
if (counts == null) {
throw new ConnectException("strange");
}
next = counts.get(nextKey); next = counts.get(nextKey);
if (next == 0) { if (next == 0) {
key = nextKey; key = nextKey;
@ -293,9 +297,7 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
channel.closeFuture().addListener(new CloseChannelListener(key, channel)); channel.closeFuture().addListener(new CloseChannelListener(key, channel));
channel.attr(attributeKey).set(key); channel.attr(attributeKey).set(key);
channels.computeIfAbsent(key, node -> new ArrayList<>()).add(channel); channels.computeIfAbsent(key, node -> new ArrayList<>()).add(channel);
synchronized (counts) { counts.put(key, counts.get(key) + 1);
counts.put(key, counts.get(key) + 1);
}
if (retriesPerNode > 0) { if (retriesPerNode > 0) {
failedCounts.put(key, 0); failedCounts.put(key, 0);
} }
@ -343,16 +345,12 @@ public class BoundedChannelPool<K extends PoolKey> implements Pool<Channel> {
logger.log(Level.FINE,"connection to " + key + " closed"); logger.log(Level.FINE,"connection to " + key + " closed");
lock.lock(); lock.lock();
try { try {
synchronized (counts) { if (counts.containsKey(key)) {
if (counts.containsKey(key)) { counts.put(key, counts.get(key) - 1);
counts.put(key, counts.get(key) - 1);
}
} }
synchronized (channels) { List<Channel> channels = BoundedChannelPool.this.channels.get(key);
List<Channel> channels = BoundedChannelPool.this.channels.get(key); if (channels != null) {
if (channels != null) { channels.remove(channel);
channels.remove(channel);
}
} }
semaphore.release(); semaphore.release();
} finally { } finally {

View file

@ -74,38 +74,34 @@ abstract class BaseTransport implements Transport {
// Our algorithm is: use always "origin form" for HTTP 1, use absolute form for HTTP 2. // Our algorithm is: use always "origin form" for HTTP 1, use absolute form for HTTP 2.
// The reason is that Netty derives the HTTP/2 scheme header from the absolute form. // The reason is that Netty derives the HTTP/2 scheme header from the absolute form.
String uri = request.httpVersion().majorVersion() == 1 ? String uri = request.httpVersion().majorVersion() == 1 ?
request.base().relativeReference() : request.base().toString(); request.url().relativeReference() : request.url().toString();
FullHttpRequest fullHttpRequest = request.content() == null ? FullHttpRequest fullHttpRequest = request.content() == null ?
new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri) : new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri) :
new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri, new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri,
request.content()); request.content());
try { Integer streamId = nextStream();
Integer streamId = nextStream(); if (streamId != null && streamId > 0) {
if (streamId != null && streamId > 0) { request.headers().set(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), Integer.toString(streamId));
request.headers().set(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), Integer.toString(streamId)); } else {
} else { if (request.httpVersion().majorVersion() == 2) {
if (request.httpVersion().majorVersion() == 2) { logger.log(Level.WARNING, "no streamId but HTTP/2 request. Strange!!! " + getClass().getName());
logger.log(Level.WARNING, "no streamId but HTTP/2 request. Strange!!! " + getClass().getName());
}
} }
// add matching cookies from box (previous requests) and new cookies from request builder }
Collection<Cookie> cookies = new ArrayList<>(); // add matching cookies from box (previous requests) and new cookies from request builder
cookies.addAll(matchCookiesFromBox(request)); Collection<Cookie> cookies = new ArrayList<>();
cookies.addAll(matchCookies(request)); cookies.addAll(matchCookiesFromBox(request));
if (!cookies.isEmpty()) { cookies.addAll(matchCookies(request));
request.headers().set(HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode(cookies)); if (!cookies.isEmpty()) {
} request.headers().set(HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode(cookies));
// add stream-id and cookie headers }
fullHttpRequest.headers().set(request.headers()); // add stream-id and cookie headers
if (streamId != null) { fullHttpRequest.headers().set(request.headers());
requests.put(streamId, request); if (streamId != null) {
} requests.put(streamId, request);
if (channel.isWritable()) { }
channel.writeAndFlush(fullHttpRequest); if (channel.isWritable()) {
channel.writeAndFlush(fullHttpRequest);
}
} finally {
request.close();
} }
return this; return this;
} }
@ -214,14 +210,14 @@ abstract class BaseTransport implements Transport {
location = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()).decode(location); location = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()).decode(location);
if (location != null) { if (location != null) {
logger.log(Level.FINE, "found redirect location: " + location); logger.log(Level.FINE, "found redirect location: " + location);
URL redirUrl = URL.base(request.base()).resolve(location); URL redirUrl = URL.base(request.url()).resolve(location);
HttpMethod method = httpResponse.status().code() == 303 ? HttpMethod.GET : request.httpMethod(); HttpMethod method = httpResponse.status().code() == 303 ? HttpMethod.GET : request.httpMethod();
RequestBuilder newHttpRequestBuilder = Request.builder(method) RequestBuilder newHttpRequestBuilder = Request.builder(method)
.url(redirUrl) .url(redirUrl)
.setVersion(request.httpVersion()) .setVersion(request.httpVersion())
.setHeaders(request.headers()) .setHeaders(request.headers())
.content(request.content()); .content(request.content());
request.base().getQueryParams().forEach(pair -> request.url().getQueryParams().forEach(pair ->
newHttpRequestBuilder.addParameter(pair.getFirst(), pair.getSecond()) newHttpRequestBuilder.addParameter(pair.getFirst(), pair.getSecond())
); );
request.cookies().forEach(newHttpRequestBuilder::addCookie); request.cookies().forEach(newHttpRequestBuilder::addCookie);
@ -309,13 +305,13 @@ abstract class BaseTransport implements Transport {
private List<Cookie> matchCookiesFromBox(Request request) { private List<Cookie> matchCookiesFromBox(Request request) {
return cookieBox == null ? Collections.emptyList() : cookieBox.keySet().stream().filter(cookie -> return cookieBox == null ? Collections.emptyList() : cookieBox.keySet().stream().filter(cookie ->
matchCookie(request.base(), cookie) matchCookie(request.url(), cookie)
).collect(Collectors.toList()); ).collect(Collectors.toList());
} }
private List<Cookie> matchCookies(Request request) { private List<Cookie> matchCookies(Request request) {
return request.cookies().stream().filter(cookie -> return request.cookies().stream().filter(cookie ->
matchCookie(request.base(), cookie) matchCookie(request.url(), cookie)
).collect(Collectors.toList()); ).collect(Collectors.toList());
} }

View file

@ -69,13 +69,11 @@ public class HttpTransport extends BaseTransport {
if (retryRequest != null) { if (retryRequest != null) {
// retry transport, wait for completion // retry transport, wait for completion
client.retry(this, retryRequest); client.retry(this, retryRequest);
retryRequest.close();
} else { } else {
Request continueRequest = continuation(request, fullHttpResponse); Request continueRequest = continuation(request, fullHttpResponse);
if (continueRequest != null) { if (continueRequest != null) {
// continue with new transport, synchronous call here, wait for completion // continue with new transport, synchronous call here, wait for completion
client.continuation(this, continueRequest); client.continuation(this, continueRequest);
continueRequest.close();
} }
} }
} catch (URLSyntaxException | IOException e) { } catch (URLSyntaxException | IOException e) {
@ -94,7 +92,7 @@ public class HttpTransport extends BaseTransport {
} }
@Override @Override
public void awaitResponse(Integer streamId) throws IOException { public void awaitResponse(Integer streamId) throws IOException, TimeoutException {
if (streamId == null) { if (streamId == null) {
return; return;
} }
@ -103,9 +101,17 @@ public class HttpTransport extends BaseTransport {
} }
CompletableFuture<Boolean> promise = sequentialPromiseMap.get(streamId); CompletableFuture<Boolean> promise = sequentialPromiseMap.get(streamId);
if (promise != null) { if (promise != null) {
long millis = client.getClientConfig().getReadTimeoutMillis();
Request request = fromStreamId(streamId);
if (request != null && request.getTimeoutInMillis() > 0) {
millis = request.getTimeoutInMillis();
}
try { try {
promise.get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS); promise.get(millis, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) { } catch (TimeoutException e) {
this.throwable = e;
throw new TimeoutException("timeout of " + millis + " milliseconds exceeded");
} catch (InterruptedException | ExecutionException e) {
this.throwable = e; this.throwable = e;
throw new IOException(e); throw new IOException(e);
} finally { } finally {
@ -121,7 +127,7 @@ public class HttpTransport extends BaseTransport {
awaitResponse(streamId); awaitResponse(streamId);
client.releaseChannel(channel); client.releaseChannel(channel);
} }
} catch (IOException e) { } catch (IOException | TimeoutException e) {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.WARNING, e.getMessage(), e);
} finally { } finally {
sequentialPromiseMap.clear(); sequentialPromiseMap.clear();

View file

@ -12,6 +12,7 @@ import org.xbib.netty.http.client.Request;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.function.Function; import java.util.function.Function;
public interface Transport { public interface Transport {
@ -38,7 +39,7 @@ public interface Transport {
void pushPromiseReceived(Integer streamId, Integer promisedStreamId, Http2Headers headers); void pushPromiseReceived(Integer streamId, Integer promisedStreamId, Http2Headers headers);
void awaitResponse(Integer streamId) throws IOException; void awaitResponse(Integer streamId) throws IOException, TimeoutException;
Transport get(); Transport get();

View file

@ -14,19 +14,19 @@ import java.util.logging.Logger;
public class CompletableFutureTest { public class CompletableFutureTest {
private static final Logger logger = Logger.getLogger(ElasticsearchTest.class.getName()); private static final Logger logger = Logger.getLogger(CompletableFutureTest.class.getName());
/** /**
* Get some weird content from one URL and post it to another URL, by composing completable futures. * Get some weird content from one URL and post it to another URL, by composing completable futures.
*/ */
@Test @Test
public void testComposeCompletableFutures() throws IOException { public void testComposeCompletableFutures() throws IOException {
Client client = new Client(); Client client = Client.builder().build();
try { try {
final Function<FullHttpResponse, String> httpResponseStringFunction = response -> final Function<FullHttpResponse, String> httpResponseStringFunction = response ->
response.content().toString(StandardCharsets.UTF_8); response.content().toString(StandardCharsets.UTF_8);
Request request = Request.get() Request request = Request.get()
.url("https://repo.maven.apache.org/maven2/org/xbib/netty-http-client/maven-metadata.xml.sha1") .url("http://repo.maven.apache.org/maven2/org/xbib/netty-http-client/maven-metadata.xml.sha1")
.build(); .build();
CompletableFuture<String> completableFuture = client.execute(request, httpResponseStringFunction) CompletableFuture<String> completableFuture = client.execute(request, httpResponseStringFunction)
.exceptionally(Throwable::getMessage) .exceptionally(Throwable::getMessage)

View file

@ -1,6 +1,7 @@
package org.xbib.netty.http.client.test; package org.xbib.netty.http.client.test;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
@ -17,13 +18,14 @@ public class ConscryptTest extends LoggingBase {
@Test @Test
public void testConscrypt() throws IOException { public void testConscrypt() throws IOException {
Client client = Client.builder() Client client = Client.builder()
.enableDebug()
.setJdkSslProvider() .setJdkSslProvider()
.setSslContextProvider(Conscrypt.newProvider()) .setSslContextProvider(Conscrypt.newProvider())
.build(); .build();
logger.log(Level.INFO, client.getClientConfig().toString()); logger.log(Level.INFO, client.getClientConfig().toString());
try { try {
Request request = Request.get() Request request = Request.get()
.url("https://xbib.org") .url("https://google.com")
.setVersion("HTTP/1.1") .setVersion("HTTP/1.1")
.build() .build()
.setResponseListener(fullHttpResponse -> { .setResponseListener(fullHttpResponse -> {

View file

@ -3,13 +3,16 @@ package org.xbib.netty.http.client.test;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.HttpAddress;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.client.transport.Transport;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -22,8 +25,27 @@ public class ElasticsearchTest extends LoggingBase {
private static final Logger logger = Logger.getLogger(ElasticsearchTest.class.getName()); private static final Logger logger = Logger.getLogger(ElasticsearchTest.class.getName());
@Test @Test
@Ignore
public void testElasticsearch() throws IOException {
Client client = Client.builder().enableDebug().build();
try {
Request request = Request.get().url("http://localhost:9200")
.build()
.setResponseListener(fullHttpResponse -> {
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
});
logger.info("request = " + request.toString());
client.execute(request);
} finally {
client.shutdownGracefully();
}
}
@Test
@Ignore
public void testElasticsearchCreateDocument() throws IOException { public void testElasticsearchCreateDocument() throws IOException {
Client client = new Client(); Client client = Client.builder().enableDebug().build();
try { try {
Request request = Request.put().url("http://localhost:9200/test/test/1") Request request = Request.put().url("http://localhost:9200/test/test/1")
.json("{\"text\":\"Hello World\"}") .json("{\"text\":\"Hello World\"}")
@ -32,6 +54,7 @@ public class ElasticsearchTest extends LoggingBase {
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8); String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response); logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
}); });
logger.info("request = " + request.toString());
client.execute(request); client.execute(request);
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();
@ -39,6 +62,7 @@ public class ElasticsearchTest extends LoggingBase {
} }
@Test @Test
@Ignore
public void testElasticsearchMatchQuery() throws IOException { public void testElasticsearchMatchQuery() throws IOException {
Client client = new Client(); Client client = new Client();
try { try {
@ -55,24 +79,46 @@ public class ElasticsearchTest extends LoggingBase {
} }
} }
/**
* This shows the usage of 4 concurrent pooled connections on 4 threads, querying Elasticsearch.
* @throws IOException if test fails
*/
@Test @Test
public void testElasticsearchConcurrent() throws IOException { public void testElasticsearchPooled() throws IOException {
Client client = Client.builder().setReadTimeoutMillis(20000).build(); HttpAddress httpAddress = HttpAddress.http1("localhost", 9200);
int limit = 4;
Client client = Client.builder()
.addPoolNode(httpAddress)
.setPoolNodeConnectionLimit(limit)
.build();
int max = 1000; int max = 1000;
int threads = 4;
try { try {
List<Request> queries = new ArrayList<>(); ExecutorService executorService = Executors.newFixedThreadPool(threads);
for (int i = 0; i < max; i++) { for (int n = 0; n < threads; n++) {
queries.add(newRequest()); executorService.submit(() -> {
} List<Request> queries = new ArrayList<>();
Transport transport = client.execute(queries.get(0)).get(); for (int i = 0; i < max; i++) {
for (int i = 1; i < max; i++) { queries.add(newRequest());
transport.execute(queries.get(i)).get(); }
try {
for (int i = 0; i < max; i++) {
client.pooledExecute(queries.get(i)).get();
}
} catch (IOException e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
});
} }
executorService.shutdown();
executorService.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.log(Level.WARNING, e.getMessage(), e);
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();
logger.log(Level.INFO, "count=" + count);
} }
assertEquals(max, count.get()); logger.log(Level.INFO, "count=" + count);
assertEquals(max * threads, count.get());
} }
private Request newRequest() { private Request newRequest() {
@ -81,10 +127,12 @@ public class ElasticsearchTest extends LoggingBase {
.json("{\"query\":{\"match\":{\"text\":\"Hello World\"}}}") .json("{\"query\":{\"match\":{\"text\":\"Hello World\"}}}")
.addHeader("connection", "keep-alive") .addHeader("connection", "keep-alive")
.build() .build()
.setResponseListener(fullHttpResponse -> .setResponseListener(fullHttpResponse -> {
logger.log(Level.FINE, "status = " + fullHttpResponse.status() + count.getAndIncrement();
" counter = " + count.getAndIncrement() + if (fullHttpResponse.status().code() != 200) {
" response body = " + fullHttpResponse.content().toString(StandardCharsets.UTF_8))); logger.log(Level.WARNING,"error: " + fullHttpResponse.toString());
}
});
} }
private final AtomicInteger count = new AtomicInteger(); private final AtomicInteger count = new AtomicInteger();

View file

@ -1,15 +1,12 @@
package org.xbib.netty.http.client.test; package org.xbib.netty.http.client.test;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -17,16 +14,9 @@ public class Http1Test extends LoggingBase {
private static final Logger logger = Logger.getLogger(Http1Test.class.getName()); private static final Logger logger = Logger.getLogger(Http1Test.class.getName());
@After
public void checkThreads() {
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
logger.log(Level.INFO, "threads = " + threadSet.size() );
threadSet.forEach( thread -> logger.log(Level.INFO, thread.toString()));
}
@Test @Test
public void testHttp1() throws Exception { public void testHttp1() throws Exception {
Client client = new Client(); Client client = Client.builder().enableDebug().build();
try { try {
Request request = Request.get().url("http://xbib.org").build() Request request = Request.get().url("http://xbib.org").build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " + .setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
@ -40,7 +30,6 @@ public class Http1Test extends LoggingBase {
} }
@Test @Test
@Ignore
public void testParallelRequests() throws IOException { public void testParallelRequests() throws IOException {
Client client = Client.builder().enableDebug().build(); Client client = Client.builder().enableDebug().build();
try { try {
@ -70,7 +59,6 @@ public class Http1Test extends LoggingBase {
} }
@Test @Test
@Ignore
public void testSequentialRequests() throws Exception { public void testSequentialRequests() throws Exception {
Client client = Client.builder().enableDebug().build(); Client client = Client.builder().enableDebug().build();
try { try {
@ -79,7 +67,7 @@ public class Http1Test extends LoggingBase {
msg.content().toString(StandardCharsets.UTF_8))); msg.content().toString(StandardCharsets.UTF_8)));
client.execute(request1).get(); client.execute(request1).get();
Request request2 = Request.get().url("https://google.com").setVersion("HTTP/2.0").build() Request request2 = Request.get().url("http://google.com").setVersion("HTTP/1.1").build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " + .setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
msg.content().toString(StandardCharsets.UTF_8))); msg.content().toString(StandardCharsets.UTF_8)));
client.execute(request2).get(); client.execute(request2).get();

View file

@ -25,7 +25,7 @@ public class Http2Test extends LoggingBase {
* *
* demo/h2_demo_frame.html sends no content, only a push promise, and does not continue * demo/h2_demo_frame.html sends no content, only a push promise, and does not continue
* *
* @throws IOException * @throws IOException if test fails
*/ */
@Test @Test
@Ignore @Ignore

View file

@ -21,7 +21,7 @@ public class LeakTest {
} }
@Test @Test
public void testForLeaks() throws IOException, InterruptedException { public void testForLeaks() throws IOException {
Client client = new Client(); Client client = new Client();
client.shutdownGracefully(); client.shutdownGracefully();
} }

View file

@ -1,6 +1,8 @@
package org.xbib.netty.http.client.test; package org.xbib.netty.http.client.test;
import io.netty.handler.codec.http.HttpVersion;
import org.junit.Test; import org.junit.Test;
import org.xbib.net.URL;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.HttpAddress; import org.xbib.netty.http.client.HttpAddress;
import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.Request;
@ -21,15 +23,15 @@ public class PooledClientTest extends LoggingBase {
@Test @Test
public void testPooledClientWithSingleNode() throws IOException { public void testPooledClientWithSingleNode() throws IOException {
int loop = 10; int loop = 10;
HttpAddress httpAddress = HttpAddress.http1("xbib.org", 80); int threads = Runtime.getRuntime().availableProcessors();
URL url = URL.from("http://xbib.org");
HttpAddress httpAddress = HttpAddress.of(url, HttpVersion.HTTP_1_1);
Client client = Client.builder() Client client = Client.builder()
.addPoolNode(httpAddress) .addPoolNode(httpAddress)
.setPoolSecure(httpAddress.isSecure()) .setPoolNodeConnectionLimit(threads)
.setPoolNodeConnectionLimit(16)
.build(); .build();
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
try { try {
int threads = 16;
ExecutorService executorService = Executors.newFixedThreadPool(threads); ExecutorService executorService = Executors.newFixedThreadPool(threads);
for (int n = 0; n < threads; n++) { for (int n = 0; n < threads; n++) {
executorService.submit(() -> { executorService.submit(() -> {
@ -37,25 +39,26 @@ public class PooledClientTest extends LoggingBase {
logger.log(Level.INFO, "starting " + Thread.currentThread()); logger.log(Level.INFO, "starting " + Thread.currentThread());
for (int i = 0; i < loop; i++) { for (int i = 0; i < loop; i++) {
Request request = Request.get() Request request = Request.get()
.url("http://xbib.org/repository/") .url(url.toString())
.setVersion("HTTP/1.1") .setVersion(httpAddress.getVersion())
//.setTimeoutInMillis(25000L)
.build() .build()
.setResponseListener(fullHttpResponse -> { .setResponseListener(fullHttpResponse -> {
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8); String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response); //logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
count.getAndIncrement();
}); });
client.pooledExecute(request).get(); client.pooledExecute(request).get();
count.getAndIncrement();
} }
logger.log(Level.INFO, "done " + Thread.currentThread()); logger.log(Level.INFO, "done " + Thread.currentThread());
} catch (IOException e) { } catch (Throwable e) {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.WARNING, e.getMessage(), e);
} }
}); });
} }
executorService.shutdown(); executorService.shutdown();
executorService.awaitTermination(60, TimeUnit.SECONDS); executorService.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (Throwable e) {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.WARNING, e.getMessage(), e);
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();

View file

@ -13,7 +13,7 @@ public class RequestBuilderTest {
@Test @Test
public void testSimpleRequest() { public void testSimpleRequest() {
Request request = Request.builder(HttpMethod.GET).build(); Request request = Request.builder(HttpMethod.GET).content("Hello", "text/plain").build();
logger.log(Level.INFO, request.toString()); logger.log(Level.INFO, request.toString());
} }
} }

View file

@ -0,0 +1,81 @@
package org.xbib.netty.http.client.test;
import io.netty.handler.codec.http.HttpMethod;
import org.junit.Ignore;
import org.junit.Test;
import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SecureHttp1Test extends LoggingBase {
private static final Logger logger = Logger.getLogger(SecureHttp1Test.class.getName());
@Test
public void testHttp1() throws Exception {
Client client = Client.builder().enableDebug().build();
try {
Request request = Request.get().url("https://www.google.com/").build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
msg.headers().entries() +
msg.content().toString(StandardCharsets.UTF_8) +
" status=" + msg.status().code()));
client.execute(request).get();
} finally {
client.shutdown();
}
}
@Test
@Ignore
public void testParallelRequests() throws IOException {
Client client = Client.builder().enableDebug().build();
try {
Request request1 = Request.builder(HttpMethod.GET)
.url("https://google.com").setVersion("HTTP/1.1")
.build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
msg.headers().entries() +
//msg.content().toString(StandardCharsets.UTF_8) +
" status=" + msg.status().code()));
Request request2 = Request.builder(HttpMethod.GET)
.url("https://google.com").setVersion("HTTP/1.1")
.build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
msg.headers().entries() +
//msg.content().toString(StandardCharsets.UTF_8) +
" status=" + msg.status().code()));
for (int i = 0; i < 10; i++) {
client.execute(request1);
client.execute(request2);
}
} finally {
client.shutdownGracefully();
}
}
@Test
@Ignore
public void testSequentialRequests() throws Exception {
Client client = Client.builder().enableDebug().build();
try {
Request request1 = Request.get().url("https://google.com").build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
msg.content().toString(StandardCharsets.UTF_8)));
client.execute(request1).get();
Request request2 = Request.get().url("https://google.com").setVersion("HTTP/2.0").build()
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
msg.content().toString(StandardCharsets.UTF_8)));
client.execute(request2).get();
} finally {
client.shutdown();
}
}
}

View file

@ -40,14 +40,14 @@ public class EpollTest {
private static final Logger logger = Logger.getLogger(EpollTest.class.getName()); private static final Logger logger = Logger.getLogger(EpollTest.class.getName());
private static final int CONCURRENCY = 10; private static final int CONCURRENCY = 4;
private static final List<HttpAddress> NODES = private static final List<HttpAddress> NODES =
Collections.singletonList(HttpAddress.http1("localhost", 12345)); Collections.singletonList(HttpAddress.http1("localhost", 12345));
private static final long TEST_TIME_SECONDS = 100; private static final long TEST_TIME_SECONDS = 100;
private static final int ATTEMPTS = 10_000; private static final int ATTEMPTS = 1_000;
private static final int FAIL_EVERY_ATTEMPT = 10; private static final int FAIL_EVERY_ATTEMPT = 10;

View file

@ -37,14 +37,14 @@ public class NioTest {
private static final Logger logger = Logger.getLogger(NioTest.class.getName()); private static final Logger logger = Logger.getLogger(NioTest.class.getName());
private static final int CONCURRENCY = 10; private static final int CONCURRENCY = 4;
private static final List<HttpAddress> NODES = private static final List<HttpAddress> NODES =
Collections.singletonList(HttpAddress.http1("localhost", 12345)); Collections.singletonList(HttpAddress.http1("localhost", 12345));
private static final long TEST_TIME_SECONDS = 100; private static final long TEST_TIME_SECONDS = 100;
private static final int ATTEMPTS = 10_000; private static final int ATTEMPTS = 1_000;
private static final int FAIL_EVERY_ATTEMPT = 10; private static final int FAIL_EVERY_ATTEMPT = 10;

View file

@ -44,7 +44,6 @@ public class Http2FramesTest {
private static final Logger logger = Logger.getLogger(Http2FramesTest.class.getName()); private static final Logger logger = Logger.getLogger(Http2FramesTest.class.getName());
@Test @Test
@Ignore
public void testHttp2Frames() throws Exception { public void testHttp2Frames() throws Exception {
final InetSocketAddress inetSocketAddress = new InetSocketAddress("webtide.com", 443); final InetSocketAddress inetSocketAddress = new InetSocketAddress("webtide.com", 443);
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>(); CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
@ -52,59 +51,59 @@ public class Http2FramesTest {
Channel clientChannel = null; Channel clientChannel = null;
try { try {
Bootstrap bootstrap = new Bootstrap() Bootstrap bootstrap = new Bootstrap()
.group(eventLoopGroup) .group(eventLoopGroup)
.channel(NioSocketChannel.class) .channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
SslContext sslContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.JDK)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
.applicationProtocolConfig(new ApplicationProtocolConfig(
ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2))
.build();
SslHandler sslHandler = sslContext.newHandler(ch.alloc());
SSLEngine engine = sslHandler.engine();
String fullQualifiedHostname = inetSocketAddress.getHostName();
SSLParameters params = engine.getSSLParameters();
params.setServerNames(Arrays.asList(new SNIServerName[]{new SNIHostName(fullQualifiedHostname)}));
engine.setSSLParameters(params);
ch.pipeline().addLast(sslHandler);
Http2FrameAdapter frameAdapter = new Http2FrameAdapter() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
SslContext sslContext = SslContextBuilder.forClient() logger.log(Level.FINE, "settings received, now writing request");
.sslProvider(SslProvider.JDK) Http2ConnectionHandler handler = ctx.pipeline().get(Http2ConnectionHandler.class);
.trustManager(InsecureTrustManagerFactory.INSTANCE) handler.encoder().writeHeaders(ctx, 3,
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) new DefaultHttp2Headers().method(HttpMethod.GET.asciiName())
.applicationProtocolConfig(new ApplicationProtocolConfig( .path("/")
ApplicationProtocolConfig.Protocol.ALPN, .scheme("https")
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, .authority(inetSocketAddress.getHostName()),
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, 0, true, ctx.newPromise());
ApplicationProtocolNames.HTTP_2)) ctx.channel().flush();
.build();
SslHandler sslHandler = sslContext.newHandler(ch.alloc());
SSLEngine engine = sslHandler.engine();
String fullQualifiedHostname = inetSocketAddress.getHostName();
SSLParameters params = engine.getSSLParameters();
params.setServerNames(Arrays.asList(new SNIServerName[]{new SNIHostName(fullQualifiedHostname)}));
engine.setSSLParameters(params);
ch.pipeline().addLast(sslHandler);
Http2FrameAdapter frameAdapter = new Http2FrameAdapter() {
@Override
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
logger.log(Level.FINE, "settings received, now writing request");
Http2ConnectionHandler handler = ctx.pipeline().get(Http2ConnectionHandler.class);
handler.encoder().writeHeaders(ctx, 3,
new DefaultHttp2Headers().method(HttpMethod.GET.asciiName())
.path("/")
.scheme("https")
.authority(inetSocketAddress.getHostName()),
0, true, ctx.newPromise());
ctx.channel().flush();
}
@Override
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream) throws Http2Exception {
int i = super.onDataRead(ctx, streamId, data, padding, endOfStream);
if (endOfStream) {
completableFuture.complete(true);
}
return i;
}
};
Http2ConnectionHandlerBuilder builder = new Http2ConnectionHandlerBuilder()
.server(false)
.frameListener(frameAdapter)
.frameLogger(new Http2FrameLogger(LogLevel.INFO, "client"));
ch.pipeline().addLast(builder.build());
} }
});
@Override
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream) throws Http2Exception {
int i = super.onDataRead(ctx, streamId, data, padding, endOfStream);
if (endOfStream) {
completableFuture.complete(true);
}
return i;
}
};
Http2ConnectionHandlerBuilder builder = new Http2ConnectionHandlerBuilder()
.server(false)
.frameListener(frameAdapter)
.frameLogger(new Http2FrameLogger(LogLevel.INFO, "client"));
ch.pipeline().addLast(builder.build());
}
});
logger.log(Level.INFO, () -> "connecting"); logger.log(Level.INFO, () -> "connecting");
clientChannel = bootstrap.connect(inetSocketAddress).sync().channel(); clientChannel = bootstrap.connect(inetSocketAddress).sync().channel();
logger.log(Level.INFO, () -> "waiting for end of stream"); logger.log(Level.INFO, () -> "waiting for end of stream");