use of io.servicetalk.http.api.FilterableStreamingHttpConnection in project servicetalk by apple.
the class ClientEffectiveStrategyTest method clientStrategy.
@ParameterizedTest(name = "API={0} builder={1} filter={2} LB={3} CF={4}")
@MethodSource("casesSupplier")
void clientStrategy(ClientType clientType, @Nullable final HttpExecutionStrategy builderStrategy, @Nullable final HttpExecutionStrategy filterStrategy, @Nullable final HttpExecutionStrategy lbStrategy, @Nullable final HttpExecutionStrategy cfStrategy) throws Exception {
HttpExecutionStrategy effectiveStrategy = computeClientExecutionStrategy(builderStrategy, filterStrategy, lbStrategy, cfStrategy);
SingleAddressHttpClientBuilder<HostAndPort, InetSocketAddress> clientBuilder = HttpClients.forSingleAddress(serverHostAndPort(context));
if (builderStrategy != null) {
clientBuilder.executionStrategy(builderStrategy);
}
ClientInvokingThreadRecorder invokingThreadsRecorder = new ClientInvokingThreadRecorder(clientType, effectiveStrategy);
clientBuilder.appendClientFilter(invokingThreadsRecorder);
if (null != filterStrategy) {
clientBuilder.appendClientFilter(new StreamingHttpClientFilterFactory() {
@Override
public StreamingHttpClientFilter create(final FilterableStreamingHttpClient client) {
return new StreamingHttpClientFilter(client) {
};
}
@Override
public HttpExecutionStrategy requiredOffloads() {
return filterStrategy;
}
});
}
if (null != lbStrategy) {
HttpLoadBalancerFactory<InetSocketAddress> lfFactory = DefaultHttpLoadBalancerFactory.Builder.from(new LoadBalancerFactoryImpl() {
@Override
public ExecutionStrategy requiredOffloads() {
return lbStrategy;
}
}).build();
clientBuilder.loadBalancerFactory(lfFactory);
}
if (null != cfStrategy) {
clientBuilder.appendConnectionFilter(new StreamingHttpConnectionFilterFactory() {
@Override
public StreamingHttpConnectionFilter create(final FilterableStreamingHttpConnection connection) {
return new StreamingHttpConnectionFilter(connection) {
};
}
@Override
public HttpExecutionStrategy requiredOffloads() {
return cfStrategy;
}
});
}
// Exercise the client
try (StreamingHttpClient client = clientBuilder.buildStreaming()) {
String responseBody = getResponse(clientType, client);
assertThat(responseBody, is(GREETING));
invokingThreadsRecorder.verifyOffloads();
}
}
use of io.servicetalk.http.api.FilterableStreamingHttpConnection in project servicetalk by apple.
the class DefaultSingleAddressHttpClientBuilder method buildStreaming.
private static <U, R> StreamingHttpClient buildStreaming(final HttpClientBuildContext<U, R> ctx) {
final ReadOnlyHttpClientConfig roConfig = ctx.httpConfig().asReadOnly();
final HttpExecutionContext executionContext = ctx.builder.executionContextBuilder.build();
if (roConfig.h2Config() != null && roConfig.hasProxy()) {
throw new IllegalStateException("Proxying is not yet supported with HTTP/2");
}
// Track resources that potentially need to be closed when an exception is thrown during buildStreaming
final CompositeCloseable closeOnException = newCompositeCloseable();
try {
final Publisher<? extends Collection<? extends ServiceDiscovererEvent<R>>> sdEvents = ctx.serviceDiscoverer(executionContext).discover(ctx.address());
ConnectionFactoryFilter<R, FilterableStreamingHttpConnection> connectionFactoryFilter = ctx.builder.connectionFactoryFilter;
ExecutionStrategy connectionFactoryStrategy = ctx.builder.strategyComputation.buildForConnectionFactory();
final SslContext sslContext = roConfig.tcpConfig().sslContext();
if (roConfig.hasProxy() && sslContext != null) {
assert roConfig.connectAddress() != null;
ConnectionFactoryFilter<R, FilterableStreamingHttpConnection> proxy = new ProxyConnectConnectionFactoryFilter<>(roConfig.connectAddress());
connectionFactoryFilter = proxy.append(connectionFactoryFilter);
connectionFactoryStrategy = connectionFactoryStrategy.merge(proxy.requiredOffloads());
}
final HttpExecutionStrategy builderStrategy = executionContext.executionStrategy();
// closed by the LoadBalancer
final ConnectionFactory<R, LoadBalancedStreamingHttpConnection> connectionFactory;
final StreamingHttpRequestResponseFactory reqRespFactory = defaultReqRespFactory(roConfig, executionContext.bufferAllocator());
if (roConfig.isH2PriorKnowledge()) {
H2ProtocolConfig h2Config = roConfig.h2Config();
assert h2Config != null;
connectionFactory = new H2LBHttpConnectionFactory<>(roConfig, executionContext, ctx.builder.connectionFilterFactory, reqRespFactory, connectionFactoryStrategy, connectionFactoryFilter, ctx.builder.loadBalancerFactory::toLoadBalancedConnection);
} else if (roConfig.tcpConfig().preferredAlpnProtocol() != null) {
H1ProtocolConfig h1Config = roConfig.h1Config();
H2ProtocolConfig h2Config = roConfig.h2Config();
connectionFactory = new AlpnLBHttpConnectionFactory<>(roConfig, executionContext, ctx.builder.connectionFilterFactory, new AlpnReqRespFactoryFunc(executionContext.bufferAllocator(), h1Config == null ? null : h1Config.headersFactory(), h2Config == null ? null : h2Config.headersFactory()), connectionFactoryStrategy, connectionFactoryFilter, ctx.builder.loadBalancerFactory::toLoadBalancedConnection);
} else {
H1ProtocolConfig h1Config = roConfig.h1Config();
assert h1Config != null;
connectionFactory = new PipelinedLBHttpConnectionFactory<>(roConfig, executionContext, ctx.builder.connectionFilterFactory, reqRespFactory, connectionFactoryStrategy, connectionFactoryFilter, ctx.builder.loadBalancerFactory::toLoadBalancedConnection);
}
final LoadBalancer<LoadBalancedStreamingHttpConnection> lb = closeOnException.prepend(ctx.builder.loadBalancerFactory.newLoadBalancer(targetAddress(ctx), sdEvents, connectionFactory));
ContextAwareStreamingHttpClientFilterFactory currClientFilterFactory = ctx.builder.clientFilterFactory;
if (roConfig.hasProxy() && sslContext == null) {
// If we're talking to a proxy over http (not https), rewrite the request-target to absolute-form, as
// specified by the RFC: https://tools.ietf.org/html/rfc7230#section-5.3.2
currClientFilterFactory = appendFilter(currClientFilterFactory, ctx.builder.proxyAbsoluteAddressFilterFactory());
}
if (ctx.builder.addHostHeaderFallbackFilter) {
currClientFilterFactory = appendFilter(currClientFilterFactory, new HostHeaderHttpRequesterFilter(ctx.builder.hostToCharSequenceFunction.apply(ctx.builder.address)));
}
FilterableStreamingHttpClient lbClient = closeOnException.prepend(new LoadBalancedStreamingHttpClient(executionContext, lb, reqRespFactory));
if (ctx.builder.retryingHttpRequesterFilter == null) {
ctx.builder.retryingHttpRequesterFilter = DEFAULT_AUTO_RETRIES;
currClientFilterFactory = appendFilter(currClientFilterFactory, ctx.builder.retryingHttpRequesterFilter);
}
HttpExecutionStrategy computedStrategy = ctx.builder.strategyComputation.buildForClient(builderStrategy);
if (builderStrategy != defaultStrategy() && builderStrategy.missing(computedStrategy) != offloadNone()) {
LOGGER.info("Client for {} created with the builder strategy {} but resulting computed strategy is " + "{}. One of the filters enforces additional offloading. To find out what filter is " + "it, enable debug level logging for {}.", targetAddress(ctx), builderStrategy, computedStrategy, ClientStrategyInfluencerChainBuilder.class);
} else if (builderStrategy == computedStrategy) {
LOGGER.debug("Client for {} created with the execution strategy {}.", targetAddress(ctx), computedStrategy);
} else {
LOGGER.debug("Client for {} created with the builder strategy {}, resulting computed strategy is {}.", targetAddress(ctx), builderStrategy, computedStrategy);
}
return new FilterableClientToClient(currClientFilterFactory != null ? currClientFilterFactory.create(lbClient, lb.eventStream(), ctx.sdStatus) : lbClient, computedStrategy);
} catch (final Throwable t) {
closeOnException.closeAsync().subscribe();
throw t;
}
}
use of io.servicetalk.http.api.FilterableStreamingHttpConnection in project servicetalk by apple.
the class H2PriorKnowledgeFeatureParityTest method clientRespectsSettingsFrame.
@ParameterizedTest(name = "{displayName} [{index}] client={0}, h2PriorKnowledge={1}")
@MethodSource("clientExecutors")
void clientRespectsSettingsFrame(HttpTestExecutionStrategy strategy, boolean h2PriorKnowledge) throws Exception {
setUp(strategy, h2PriorKnowledge);
assumeTrue(h2PriorKnowledge, "Only HTTP/2 supports SETTINGS frames");
int expectedMaxConcurrent = 1;
BlockingQueue<FilterableStreamingHttpConnection> connectionQueue = new LinkedBlockingQueue<>();
BlockingQueue<Publisher<? extends ConsumableEvent<Integer>>> maxConcurrentPubQueue = new LinkedBlockingQueue<>();
AtomicReference<Channel> serverParentChannelRef = new AtomicReference<>();
CountDownLatch serverChannelLatch = new CountDownLatch(1);
CountDownLatch serverSettingsAckLatch = new CountDownLatch(2);
serverAcceptorChannel = bindH2Server(serverEventLoopGroup, new ChannelInitializer<Channel>() {
@Override
protected void initChannel(final Channel ch) {
ch.pipeline().addLast(new EchoHttp2Handler());
}
}, parentPipeline -> parentPipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (serverParentChannelRef.compareAndSet(null, ctx.channel())) {
serverChannelLatch.countDown();
}
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof Http2SettingsAckFrame) {
serverSettingsAckLatch.countDown();
}
super.channelRead(ctx, msg);
}
}), identity());
InetSocketAddress serverAddress = (InetSocketAddress) serverAcceptorChannel.localAddress();
try (StreamingHttpClient client = forSingleAddress(HostAndPort.of(serverAddress)).protocols(h2PriorKnowledge ? h2Default() : h1Default()).executionStrategy(clientExecutionStrategy).appendConnectionFilter(conn -> new TestConnectionFilter(conn, connectionQueue, maxConcurrentPubQueue)).buildStreaming()) {
Processor<Buffer, Buffer> requestPayload = newProcessor();
client.request(client.post("/0").payloadBody(fromSource(requestPayload))).toFuture().get();
serverChannelLatch.await();
Channel serverParentChannel = serverParentChannelRef.get();
serverParentChannel.writeAndFlush(new DefaultHttp2SettingsFrame(new Http2Settings().maxConcurrentStreams(expectedMaxConcurrent))).sync();
Iterator<? extends ConsumableEvent<Integer>> maxItr = maxConcurrentPubQueue.take().toIterable().iterator();
// Verify that the initial maxConcurrency value is the default number
assertThat("No initial maxConcurrency value", maxItr.hasNext(), is(true));
ConsumableEvent<Integer> next = maxItr.next();
assertThat(next, is(notNullValue()));
assertThat("First event is not the default", next.event(), is(SMALLEST_MAX_CONCURRENT_STREAMS));
// We previously made a request, and intentionally didn't complete the request body. We want to verify
// that we have received the SETTINGS frame reducing the total number of streams to 1.
assertThat("No maxConcurrency value received", maxItr.hasNext(), is(true));
next = maxItr.next();
assertThat(next, is(notNullValue()));
assertThat("maxConcurrency did not change to the expected value", next.event(), is(expectedMaxConcurrent));
// Wait for a server to receive a settings ack
serverSettingsAckLatch.await();
// After this point we want to issue a new request and verify that client selects a new connection.
Processor<Buffer, Buffer> requestPayload2 = newProcessor();
client.request(client.post("/1").payloadBody(fromSource(requestPayload2))).toFuture().get();
// We expect 2 connections to be created.
assertNotSame(connectionQueue.take(), connectionQueue.take());
requestPayload.onComplete();
requestPayload2.onComplete();
}
}
use of io.servicetalk.http.api.FilterableStreamingHttpConnection in project servicetalk by apple.
the class RetryRequestWithNonRepeatablePayloadTest method setUp.
private void setUp(HttpProtocol protocol, TestPublisher<Buffer> payloadBody, Queue<Throwable> errors, boolean offloading) {
protocol(protocol.config);
ChannelOutboundHandler firstWriteHandler = new FailingFirstWriteHandler();
connectionFactoryFilter(ConnectionFactoryFilter.withStrategy(factory -> new DelegatingConnectionFactory<InetSocketAddress, FilterableStreamingHttpConnection>(factory) {
@Override
public Single<FilterableStreamingHttpConnection> newConnection(InetSocketAddress address, @Nullable ContextMap context, @Nullable TransportObserver observer) {
return delegate().newConnection(address, context, observer).map(c -> {
final Channel channel = ((NettyConnectionContext) c.connectionContext()).nettyChannel();
if (protocol == HTTP_1) {
// Insert right before HttpResponseDecoder to avoid seeing failed frames on wire logs
channel.pipeline().addBefore(HttpRequestEncoder.class.getSimpleName() + "#0", null, firstWriteHandler);
} else if (protocol == HTTP_2) {
// Insert right before Http2MultiplexHandler to avoid failing connection-level frames and
// seeing failed stream frames on frame/wire logs
channel.pipeline().addBefore(Http2MultiplexHandler.class.getSimpleName() + "#0", null, firstWriteHandler);
}
return new StreamingHttpConnectionFilter(c) {
@Override
public Single<StreamingHttpResponse> request(StreamingHttpRequest request) {
return delegate().request(request).whenOnError(t -> {
try {
assertThat("Unexpected exception type", t, instanceOf(RetryableException.class));
assertThat("Unexpected exception type", t.getCause(), instanceOf(DeliberateException.class));
assertThat("Unexpected subscribe to payload body", payloadBody.isSubscribed(), is(false));
} catch (Throwable error) {
errors.add(error);
}
});
}
};
});
}
}, ExecutionStrategy.offloadNone()));
setUp(offloading ? CACHED : IMMEDIATE, offloading ? CACHED_SERVER : IMMEDIATE);
}
use of io.servicetalk.http.api.FilterableStreamingHttpConnection in project servicetalk by apple.
the class AbstractLBHttpConnectionFactory method newConnection.
@Override
public final Single<LoadBalancedStreamingHttpConnection> newConnection(final ResolvedAddress resolvedAddress, @Nullable final TransportObserver observer) {
return filterableConnectionFactory.newConnection(resolvedAddress, observer).map(conn -> {
FilterableStreamingHttpConnection filteredConnection = connectionFilterFunction != null ? connectionFilterFunction.create(conn) : conn;
ConnectionContext ctx = filteredConnection.connectionContext();
Completable onClosing;
if (ctx instanceof NettyConnectionContext) {
// bondolo - I don't believe this is necessary, always the same as filteredConnection.onClose()
// perhaps this is only needed for testing where the mock doesn't implement onClose()?
onClosing = ((NettyConnectionContext) ctx).onClosing();
} else {
onClosing = filteredConnection.onClose();
}
final Publisher<? extends ConsumableEvent<Integer>> maxConcurrency = filteredConnection.transportEventStream(MAX_CONCURRENCY);
final ReservableRequestConcurrencyController concurrencyController = newConcurrencyController(maxConcurrency, onClosing);
return new LoadBalancedStreamingHttpConnection(protocolBinding.apply(filteredConnection), concurrencyController, connectStrategy instanceof HttpExecutionStrategy ? (HttpExecutionStrategy) connectStrategy : HttpExecutionStrategies.offloadNone());
});
}
Aggregations