Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.hyperfoil.http.connection.Http2Connection Maven / Gradle / Ivy
package io.hyperfoil.http.connection;
import java.io.IOException;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import io.hyperfoil.api.connection.Connection;
import io.hyperfoil.api.connection.Request;
import io.hyperfoil.http.api.HttpVersion;
import io.hyperfoil.api.session.Session;
import io.hyperfoil.http.api.HttpCache;
import io.hyperfoil.http.api.HttpClientPool;
import io.hyperfoil.http.api.HttpConnection;
import io.hyperfoil.http.api.HttpConnectionPool;
import io.hyperfoil.http.api.HttpRequest;
import io.hyperfoil.http.api.HttpRequestWriter;
import io.hyperfoil.http.api.HttpResponseHandlers;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.Http2ConnectionDecoder;
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
import io.netty.handler.codec.http2.Http2EventAdapter;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import io.netty.util.internal.AppendableCharSequence;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
/**
* @author Julien Viet
*/
class Http2Connection extends Http2EventAdapter implements HttpConnection {
private static final Logger log = LoggerFactory.getLogger(Http2Connection.class);
private static final boolean trace = log.isTraceEnabled();
private final ChannelHandlerContext context;
private final io.netty.handler.codec.http2.Http2Connection connection;
private final Http2ConnectionEncoder encoder;
private final IntObjectMap streams = new IntObjectHashMap<>();
private final long clientMaxStreams;
private final boolean secure;
private HttpConnectionPool pool;
private int numStreams;
private long maxStreams;
private Status status = Status.OPEN;
private HttpRequest dispatchedRequest;
Http2Connection(ChannelHandlerContext context,
io.netty.handler.codec.http2.Http2Connection connection,
Http2ConnectionEncoder encoder,
Http2ConnectionDecoder decoder,
HttpClientPool clientPool) {
this.context = context;
this.connection = connection;
this.encoder = encoder;
this.clientMaxStreams = this.maxStreams = clientPool.config().maxHttp2Streams();
this.secure = clientPool.isSecure();
Http2EventAdapter listener = new EventAdapter();
connection.addListener(listener);
decoder.frameListener(listener);
}
@Override
public ChannelHandlerContext context() {
return context;
}
@Override
public boolean isAvailable() {
return numStreams < maxStreams;
}
@Override
public int inFlight() {
return numStreams;
}
public void incrementConnectionWindowSize(int increment) {
try {
io.netty.handler.codec.http2.Http2Stream stream = connection.connectionStream();
connection.local().flowController().incrementWindowSize(stream, increment);
} catch (Http2Exception e) {
e.printStackTrace();
}
}
@Override
public void close() {
if (status == Status.OPEN) {
status = Status.CLOSING;
cancelRequests(Connection.SELF_CLOSED_EXCEPTION);
}
context.close();
}
@Override
public String host() {
return pool.clientPool().host();
}
@Override
public void onTimeout(Request request) {
for (IntObjectMap.PrimitiveEntry entry : streams.entries()) {
if (entry.value() == request) {
connection.stream(entry.key()).close();
break;
}
}
}
@Override
public void attach(HttpConnectionPool pool) {
this.pool = pool;
}
public void request(HttpRequest request,
BiConsumer[] headerAppenders,
boolean injectHostHeader,
BiFunction bodyGenerator) {
numStreams++;
HttpClientPool httpClientPool = pool.clientPool();
ByteBuf buf = bodyGenerator != null ? bodyGenerator.apply(request.session, this) : null;
if (request.path.contains(" ")) {
int length = request.path.length();
AppendableCharSequence temp = new AppendableCharSequence(length);
boolean beforeQuestion = true;
for (int i = 0; i < length; ++i) {
if (request.path.charAt(i) == ' ') {
if (beforeQuestion) {
temp.append('%');
temp.append('2');
temp.append('0');
} else {
temp.append('+');
}
} else {
if (request.path.charAt(i) == '?') {
beforeQuestion = false;
}
temp.append(request.path.charAt(i));
}
}
request.path = temp.toString();
}
Http2Headers headers = new DefaultHttp2Headers().method(request.method.name()).scheme(httpClientPool.scheme())
.path(request.path).authority(httpClientPool.authority());
// HTTPS selects host via SNI headers, duplicate Host header could confuse the server/proxy
if (injectHostHeader && !pool.clientPool().config().protocol().secure()) {
headers.add(HttpHeaderNames.HOST, httpClientPool.authority());
}
if (buf != null && buf.readableBytes() > 0) {
headers.add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(buf.readableBytes()));
}
HttpRequestWriterImpl writer = new HttpRequestWriterImpl(request, headers);
if (headerAppenders != null) {
for (BiConsumer headerAppender : headerAppenders) {
headerAppender.accept(request.session, writer);
}
}
if (HttpCache.get(request.session).isCached(request, writer)) {
if (trace) {
log.trace("#{} Request is completed from cache", request.session.uniqueId());
}
--numStreams;
request.handleCached();
tryReleaseToPool();
return;
}
assert context.executor().inEventLoop();
int id = nextStreamId();
streams.put(id, request);
dispatchedRequest = request;
ChannelPromise writePromise = context.newPromise();
encoder.writeHeaders(context, id, headers, 0, buf == null, writePromise);
if (buf != null) {
writePromise = context.newPromise();
encoder.writeData(context, id, buf, 0, true, writePromise);
}
writePromise.addListener(request);
context.flush();
dispatchedRequest = null;
}
@Override
public HttpRequest dispatchedRequest() {
return dispatchedRequest;
}
@Override
public HttpRequest peekRequest(int streamId) {
return streams.get(streamId);
}
@Override
public void removeRequest(int streamId, HttpRequest request) {
throw new UnsupportedOperationException();
}
@Override
public void setClosed() {
status = Status.CLOSED;
}
@Override
public boolean isClosed() {
return status == Status.CLOSED;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public HttpVersion version() {
return HttpVersion.HTTP_2_0;
}
private int nextStreamId() {
return connection.local().incrementAndGetNextStreamId();
}
@Override
public String toString() {
return "Http2Connection{" +
context.channel().localAddress() + " -> " + context.channel().remoteAddress() +
", streams=" + streams +
'}';
}
void cancelRequests(Throwable cause) {
for (IntObjectMap.PrimitiveEntry entry : streams.entries()) {
HttpRequest request = entry.value();
request.cancel(cause);
}
streams.clear();
}
private class EventAdapter extends Http2EventAdapter {
@Override
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
if (settings.maxConcurrentStreams() != null) {
// The settings frame may be sent at any moment, e.g. when the connection
// does not have ongoing request and therefore the pool == null
maxStreams = Math.min(clientMaxStreams, settings.maxConcurrentStreams());
}
}
@Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) {
HttpRequest request = streams.get(streamId);
if (request != null && !request.isCompleted()) {
HttpResponseHandlers handlers = request.handlers();
int code = -1;
try {
code = Integer.parseInt(headers.status().toString());
} catch (NumberFormatException ignore) {
}
request.enter();
try {
handlers.handleStatus(request, code, "");
for (Map.Entry header : headers) {
handlers.handleHeader(request, header.getKey(), header.getValue());
}
if (endStream) {
handlers.handleBodyPart(request, Unpooled.EMPTY_BUFFER, 0, 0, true);
}
} finally {
request.exit();
}
request.session.proceed();
}
if (endStream) {
endStream(streamId);
}
}
@Override
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception {
int ack = super.onDataRead(ctx, streamId, data, padding, endOfStream);
HttpRequest request = streams.get(streamId);
if (request != null && !request.isCompleted()) {
HttpResponseHandlers handlers = request.handlers();
request.enter();
try {
handlers.handleBodyPart(request, data, data.readerIndex(), data.readableBytes(), endOfStream);
} finally {
request.exit();
}
request.session.proceed();
}
if (endOfStream) {
endStream(streamId);
}
return ack;
}
@Override
public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) {
HttpRequest request = streams.remove(streamId);
if (request != null) {
numStreams--;
HttpResponseHandlers handlers = request.handlers();
if (!request.isCompleted()) {
request.enter();
try {
// TODO: maybe add a specific handler because we don't need to terminate other streams
handlers.handleThrowable(request, new IOException("HTTP2 stream was reset"));
} finally {
request.exit();
}
request.session.proceed();
}
request.release();
tryReleaseToPool();
}
}
private void endStream(int streamId) {
HttpRequest request = streams.remove(streamId);
if (request != null) {
numStreams--;
if (!request.isCompleted()) {
request.enter();
try {
request.handlers().handleEnd(request, true);
if (trace) {
log.trace("Completed response on {}", this);
}
} finally {
request.exit();
}
request.session.proceed();
}
request.release();
tryReleaseToPool();
}
}
}
private void tryReleaseToPool() {
HttpConnectionPool pool = this.pool;
if (pool != null) {
// If this connection was not available we make it available
// TODO: it would be better to check this in connection pool
if (numStreams == maxStreams - 1) {
pool.release(Http2Connection.this);
this.pool = null;
}
pool.pulse();
}
}
private class HttpRequestWriterImpl implements HttpRequestWriter {
private final HttpRequest request;
private final Http2Headers headers;
HttpRequestWriterImpl(HttpRequest request, Http2Headers headers) {
this.request = request;
this.headers = headers;
}
@Override
public HttpConnection connection() {
return Http2Connection.this;
}
@Override
public HttpRequest request() {
return request;
}
@Override
public void putHeader(CharSequence header, CharSequence value) {
headers.add(header, value);
HttpCache.get(request.session).requestHeader(request, header, value);
}
}
}