use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class HttpLifecycleObserverTest method testClientCancelsRequestAfterResponse.
@ParameterizedTest(name = "{displayName} [{index}] protocol={0}")
@EnumSource(HttpProtocol.class)
void testClientCancelsRequestAfterResponse(HttpProtocol protocol) throws Exception {
TestPublisher<Buffer> serverResponsePayload = new TestPublisher<>();
serviceFilterFactory(service -> new StreamingHttpServiceFilter(service) {
@Override
public Single<StreamingHttpResponse> handle(HttpServiceContext ctx, StreamingHttpRequest request, StreamingHttpResponseFactory responseFactory) {
return request.payloadBody().ignoreElements().concat(succeeded(responseFactory.ok().payloadBody(serverResponsePayload)));
}
});
setUp(protocol);
StreamingHttpConnection connection = streamingHttpConnection();
StreamingHttpRequest request = connection.post("/").payloadBody(Publisher.from(CONTENT.duplicate())).transform(// adds empty trailers
new StatelessTrailersTransformer<>());
StreamingHttpResponse response = connection.request(request).toFuture().get();
assertResponse(response, protocol.version, OK);
Future<Collection<Buffer>> payload = response.payloadBody().toFuture();
payload.cancel(true);
if (protocol == HttpProtocol.HTTP_1) {
// wait for cancellation to close the connection:
connection.onClose().toFuture().get();
}
// try to write server content to trigger write failure and close the server-side connection:
serverResponsePayload.onNext(CONTENT.duplicate());
bothTerminate.await();
clientInOrder.verify(clientLifecycleObserver).onNewExchange();
clientInOrder.verify(clientExchangeObserver).onConnectionSelected(any(ConnectionInfo.class));
clientInOrder.verify(clientExchangeObserver).onRequest(any(StreamingHttpRequest.class));
clientInOrder.verify(clientExchangeObserver).onResponse(any(StreamingHttpResponse.class));
clientInOrder.verify(clientResponseObserver, atLeastOnce()).onResponseDataRequested(anyLong());
clientInOrder.verify(clientResponseObserver).onResponseCancel();
verify(clientRequestObserver, atLeastOnce()).onRequestDataRequested(anyLong());
clientRequestInOrder.verify(clientRequestObserver).onRequestData(any(Buffer.class));
clientRequestInOrder.verify(clientRequestObserver).onRequestTrailers(any(HttpHeaders.class));
clientRequestInOrder.verify(clientRequestObserver).onRequestComplete();
clientInOrder.verify(clientExchangeObserver).onExchangeFinally();
verifyNoMoreInteractions(clientLifecycleObserver, clientExchangeObserver, clientRequestObserver, clientResponseObserver);
serverInOrder.verify(serverLifecycleObserver).onNewExchange();
serverInOrder.verify(serverExchangeObserver).onConnectionSelected(any(ConnectionInfo.class));
serverInOrder.verify(serverExchangeObserver).onRequest(any(StreamingHttpRequest.class));
serverInOrder.verify(serverExchangeObserver).onResponse(any(StreamingHttpResponse.class));
verify(serverResponseObserver, atLeastOnce()).onResponseDataRequested(anyLong());
verify(serverResponseObserver, atMostOnce()).onResponseData(any(Buffer.class));
serverInOrder.verify(serverResponseObserver).onResponseCancel();
serverRequestInOrder.verify(serverRequestObserver, atLeastOnce()).onRequestDataRequested(anyLong());
serverRequestInOrder.verify(serverRequestObserver).onRequestData(any(Buffer.class));
serverRequestInOrder.verify(serverRequestObserver).onRequestComplete();
serverInOrder.verify(serverExchangeObserver).onExchangeFinally();
verifyNoMoreInteractions(serverLifecycleObserver, serverExchangeObserver, serverRequestObserver, serverResponseObserver);
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class DeferredServerChannelBinder method bind.
static Single<HttpServerContext> bind(final HttpExecutionContext executionContext, final ReadOnlyHttpServerConfig config, final SocketAddress listenAddress, @Nullable final InfluencerConnectionAcceptor connectionAcceptor, final StreamingHttpService service, final boolean drainRequestPayloadBody, final boolean sniOnly) {
final ReadOnlyTcpServerConfig tcpConfig = config.tcpConfig();
assert tcpConfig.sslContext() != null;
final BiFunction<Channel, ConnectionObserver, Single<NettyConnectionContext>> channelInit = sniOnly ? (channel, connectionObserver) -> sniInitChannel(listenAddress, channel, config, executionContext, service, drainRequestPayloadBody, connectionObserver) : (channel, connectionObserver) -> alpnInitChannel(listenAddress, channel, config, executionContext, service, drainRequestPayloadBody, connectionObserver);
// In case ALPN negotiates h2, h2 connection MUST enable auto read for its Channel.
return TcpServerBinder.bind(listenAddress, tcpConfig, false, executionContext, connectionAcceptor, channelInit, serverConnection -> {
// Start processing requests on http/1.1 connection:
if (serverConnection instanceof NettyHttpServerConnection) {
((NettyHttpServerConnection) serverConnection).process(true);
}
// Nothing to do otherwise as h2 uses auto read on the parent channel
}).map(delegate -> {
LOGGER.debug("Started HTTP server with ALPN for address {}", delegate.listenAddress());
// The ServerContext returned by TcpServerBinder takes care of closing the connectionAcceptor.
return new NettyHttpServer.NettyHttpServerContext(delegate, service, executionContext);
});
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class AbstractLifecycleObserverHttpFilter method trackLifecycle.
/**
* Returns a {@link Single} tracking the request/response, capturing lifecycle events as they are observed.
*
* @param connInfo {@link ConnectionInfo} connection information.
* @param request the {@link StreamingHttpRequest}.
* @param responseFunction produces {@link Single}<{@link StreamingHttpResponses}>.
* @return a {@link Single} tracking the request/response, capturing lifecycle events as they are observed.
*/
final Single<StreamingHttpResponse> trackLifecycle(@Nullable final ConnectionInfo connInfo, final StreamingHttpRequest request, final Function<StreamingHttpRequest, Single<StreamingHttpResponse>> responseFunction) {
return defer(() -> {
final HttpExchangeObserver onExchange = safeReport(observer::onNewExchange, observer, "onNewExchange", NoopHttpExchangeObserver.INSTANCE);
final boolean clearAsyncContext;
if (connInfo != null) {
safeReport(onExchange::onConnectionSelected, connInfo, onExchange, "onConnectionSelected");
clearAsyncContext = false;
} else {
// Pass it down to LoadBalancedStreamingHttpClient
// FIXME: switch to RequestContext when it's available
AsyncContext.put(ON_CONNECTION_SELECTED_CONSUMER, selectedConnection -> safeReport(onExchange::onConnectionSelected, selectedConnection, onExchange, "onConnectionSelected"));
clearAsyncContext = true;
}
final ExchangeContext exchangeContext = new ExchangeContext(onExchange, client, clearAsyncContext);
final HttpRequestObserver onRequest = safeReport(onExchange::onRequest, request, onExchange, "onRequest", NoopHttpRequestObserver.INSTANCE);
final StreamingHttpRequest transformed = request.transformMessageBody(p -> {
if (client) {
p = p.beforeSubscriber(() -> {
exchangeContext.requestMessageBodyStarts();
return NoopSubscriber.INSTANCE;
});
}
return p.beforeRequest(n -> safeReport(onRequest::onRequestDataRequested, n, onRequest, "onRequestDataRequested")).beforeOnNext(item -> {
if (item instanceof Buffer) {
safeReport(onRequest::onRequestData, (Buffer) item, onRequest, "onRequestData");
} else if (item instanceof HttpHeaders) {
safeReport(onRequest::onRequestTrailers, (HttpHeaders) item, onRequest, "onRequestTrailers");
} else {
LOGGER.warn("Programming mistake: unexpected message body item is received on the request: {}", item.getClass().getName());
}
}).beforeFinally(new TerminalSignalConsumer() {
@Override
public void onComplete() {
safeReport(onRequest::onRequestComplete, onRequest, "onRequestComplete");
exchangeContext.decrementRemaining();
}
@Override
public void onError(final Throwable cause) {
safeReport(onRequest::onRequestError, cause, onRequest, "onRequestError");
exchangeContext.decrementRemaining();
}
@Override
public void cancel() {
safeReport(onRequest::onRequestCancel, onRequest, "onRequestCancel");
exchangeContext.decrementRemaining();
}
});
});
final Single<StreamingHttpResponse> responseSingle;
try {
responseSingle = responseFunction.apply(transformed);
} catch (Throwable t) {
onExchange.onResponseError(t);
return Single.<StreamingHttpResponse>failed(t).shareContextOnSubscribe();
}
return responseSingle.liftSync(new BeforeFinallyHttpOperator(exchangeContext, /* discardEventsAfterCancel */
true)).map(resp -> {
exchangeContext.onResponse(resp);
return resp.transformMessageBody(p -> p.beforeRequest(exchangeContext::onResponseDataRequested).beforeOnNext(exchangeContext::onResponseBody));
}).shareContextOnSubscribe();
});
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class AbstractStreamingHttpConnection method request.
@Override
public Single<StreamingHttpResponse> request(final StreamingHttpRequest request) {
return defer(() -> {
Publisher<Object> flatRequest;
// See https://tools.ietf.org/html/rfc7230#section-3.3.3
if (canAddRequestContentLength(request)) {
flatRequest = setRequestContentLength(connectionContext().protocol(), request);
} else {
if (emptyMessageBody(request, request.messageBody())) {
flatRequest = flatEmptyMessage(connectionContext().protocol(), request, request.messageBody());
} else {
// Defer subscribe to the messageBody until transport requests it to allow clients retry failed
// requests with non-replayable messageBody
flatRequest = Single.<Object>succeeded(request).concat(request.messageBody(), true);
if (shouldAppendTrailers(connectionContext().protocol(), request)) {
flatRequest = flatRequest.scanWith(HeaderUtils::appendTrailersMapper);
}
}
addRequestTransferEncodingIfNecessary(request);
}
final HttpExecutionStrategy strategy = requestExecutionStrategy(request, executionContext().executionStrategy());
if (strategy.isSendOffloaded()) {
flatRequest = flatRequest.subscribeOn(connectionContext.executionContext().executor(), IoThreadFactory.IoThread::currentThreadIsIoThread);
}
Single<StreamingHttpResponse> resp = invokeClient(flatRequest, determineFlushStrategyForApi(request));
if (strategy.isMetadataReceiveOffloaded()) {
resp = resp.publishOn(connectionContext.executionContext().executor(), IoThreadFactory.IoThread::currentThreadIsIoThread);
}
if (strategy.isDataReceiveOffloaded()) {
resp = resp.map(response -> response.transformMessageBody(payload -> payload.publishOn(connectionContext.executionContext().executor(), IoThreadFactory.IoThread::currentThreadIsIoThread)));
}
return resp.shareContextOnSubscribe();
});
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class CompressionFilterExampleClient method main.
public static void main(String... args) throws Exception {
try (HttpClient client = HttpClients.forSingleAddress("localhost", 8080).appendClientFilter(new ContentEncodingHttpRequesterFilter(new BufferDecoderGroupBuilder().add(deflateDefault(), true).add(identityEncoder(), false).build())).build()) {
// Make a request with an uncompressed payload.
HttpRequest request = client.post("/sayHello1").contentEncoding(identityEncoder()).payloadBody("World1", textSerializerUtf8());
Single<HttpResponse> respSingle1 = client.request(request).whenOnSuccess(resp -> {
System.out.println(resp.toString((name, value) -> value));
System.out.println(resp.payloadBody(textSerializerUtf8()));
});
// Make a request with an gzip compressed payload.
request = client.post("/sayHello2").contentEncoding(gzipDefault()).payloadBody("World2", textSerializerUtf8());
Single<HttpResponse> respSingle2 = client.request(request).whenOnSuccess(resp -> {
System.out.println(resp.toString((name, value) -> value));
System.out.println(resp.payloadBody(textSerializerUtf8()));
});
// Issue the requests sequentially with concat.
respSingle1.concat(respSingle2).toFuture().get();
}
}
Aggregations