use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class ContentEncodingHttpServiceFilter method create.
@Override
public StreamingHttpServiceFilter create(final StreamingHttpService service) {
return new StreamingHttpServiceFilter(service) {
@Override
public Single<StreamingHttpResponse> handle(final HttpServiceContext ctx, final StreamingHttpRequest request, final StreamingHttpResponseFactory responseFactory) {
return Single.defer(() -> {
final StreamingHttpRequest requestDecompressed;
Iterator<? extends CharSequence> contentEncodingItr = request.headers().valuesIterator(CONTENT_ENCODING);
final boolean hasContentEncoding = contentEncodingItr.hasNext();
if (hasContentEncoding) {
BufferDecoder decoder = matchAndRemoveEncoding(decompressors.decoders(), BufferDecoder::encodingName, contentEncodingItr, request.headers());
if (decoder == null) {
return succeeded(responseFactory.unsupportedMediaType()).shareContextOnSubscribe();
}
requestDecompressed = request.transformPayloadBody(pub -> decoder.streamingDecoder().deserialize(pub, ctx.executionContext().bufferAllocator()));
} else {
requestDecompressed = request;
}
return super.handle(ctx, requestDecompressed, responseFactory).map(response -> {
final CharSequence reqAcceptEncoding;
if (isPassThrough(request.method(), response) || (reqAcceptEncoding = request.headers().get(ACCEPT_ENCODING)) == null) {
return response;
}
BufferEncoder encoder = negotiateAcceptedEncodingRaw(reqAcceptEncoding, compressors, BufferEncoder::encodingName);
if (encoder == null || identityEncoder().equals(encoder)) {
return response;
}
addContentEncoding(response.headers(), encoder.encodingName());
return response.transformPayloadBody(bufPub -> encoder.streamingEncoder().serialize(bufPub, ctx.executionContext().bufferAllocator()));
}).shareContextOnSubscribe();
});
}
};
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class BlockingStreamingToStreamingService method handle.
@Override
public Single<StreamingHttpResponse> handle(final HttpServiceContext ctx, final StreamingHttpRequest request, final StreamingHttpResponseFactory responseFactory) {
return new Single<StreamingHttpResponse>() {
@Override
protected void handleSubscribe(final Subscriber<? super StreamingHttpResponse> subscriber) {
final ThreadInterruptingCancellable tiCancellable = new ThreadInterruptingCancellable(currentThread());
try {
subscriber.onSubscribe(tiCancellable);
} catch (Throwable cause) {
handleExceptionFromOnSubscribe(subscriber, cause);
return;
}
// This exists to help users with error propagation. If the user closes the payloadWriter and they throw
// (e.g. try-with-resources) this processor is merged with the payloadWriter Publisher so the error will
// still be propagated.
final Processor exceptionProcessor = newCompletableProcessor();
final BufferHttpPayloadWriter payloadWriter = new BufferHttpPayloadWriter(ctx.headersFactory().newTrailers());
DefaultBlockingStreamingHttpServerResponse response = null;
try {
final Consumer<DefaultHttpResponseMetaData> sendMeta = (metaData) -> {
final DefaultStreamingHttpResponse result;
try {
// transfer-encoding takes precedence over content-length.
// > When a message does not have a Transfer-Encoding header field, a
// Content-Length header field can provide the anticipated size.
// https://tools.ietf.org/html/rfc7230#section-3.3.2
final HttpHeaders headers = metaData.headers();
final HttpProtocolVersion version = metaData.version();
boolean addTrailers = version.major() > 1 || isTransferEncodingChunked(headers);
if (!addTrailers && h1TrailersSupported(version) && !hasContentLength(headers) && // breaks our HttpResponseDecoder
!HEAD.equals(request.method())) {
// this is likely not supported in http/1.0 and it is possible that a response has
// neither header and the connection close indicates the end of the response.
// https://tools.ietf.org/html/rfc7230#section-3.3.3
headers.add(TRANSFER_ENCODING, CHUNKED);
addTrailers = true;
}
Publisher<Object> messageBody = fromSource(exceptionProcessor).merge(payloadWriter.connect());
if (addTrailers) {
messageBody = messageBody.concat(succeeded(payloadWriter.trailers()));
}
messageBody = messageBody.beforeSubscription(() -> new Subscription() {
@Override
public void request(final long n) {
}
@Override
public void cancel() {
tiCancellable.cancel();
}
});
result = new DefaultStreamingHttpResponse(metaData.status(), version, headers, metaData.context0(), ctx.executionContext().bufferAllocator(), messageBody, forTransportReceive(false, version, headers), ctx.headersFactory());
} catch (Throwable t) {
subscriber.onError(t);
throw t;
}
subscriber.onSuccess(result);
};
response = new DefaultBlockingStreamingHttpServerResponse(OK, request.version(), ctx.headersFactory().newHeaders(), payloadWriter, ctx.executionContext().bufferAllocator(), sendMeta);
original.handle(ctx, request.toBlockingStreamingRequest(), response);
// The user code has returned successfully, complete the processor so the response stream can
// complete. If the user handles the request asynchronously (e.g. on another thread) they are
// responsible for closing the payloadWriter.
exceptionProcessor.onComplete();
} catch (Throwable cause) {
tiCancellable.setDone(cause);
if (response == null || response.markMetaSent()) {
safeOnError(subscriber, cause);
} else {
try {
exceptionProcessor.onError(cause);
} finally {
safeClose(payloadWriter, cause);
}
}
return;
}
tiCancellable.setDone();
}
};
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class HttpDataSourceTransformations method aggregatePayloadAndTrailers.
static Single<PayloadAndTrailers> aggregatePayloadAndTrailers(final DefaultPayloadInfo payloadInfo, final Publisher<?> payloadAndTrailers, final BufferAllocator allocator) {
if (payloadAndTrailers == empty()) {
payloadInfo.setEmpty(true).setMayHaveTrailersAndGenericTypeBuffer(false);
return succeeded(EMPTY_PAYLOAD_AND_TRAILERS);
}
return payloadAndTrailers.collect(PayloadAndTrailers::new, (pair, nextItem) -> {
if (nextItem instanceof Buffer) {
try {
Buffer buffer = (Buffer) nextItem;
if (isAlwaysEmpty(pair.payload)) {
pair.payload = buffer;
} else if (pair.payload instanceof CompositeBuffer) {
((CompositeBuffer) pair.payload).addBuffer(buffer);
} else {
Buffer oldBuffer = pair.payload;
pair.payload = allocator.newCompositeBuffer(MAX_VALUE).addBuffer(oldBuffer).addBuffer(buffer);
}
} catch (IllegalArgumentException cause) {
BufferOverflowException ex = new BufferOverflowException();
ex.initCause(cause);
throw ex;
}
} else if (nextItem instanceof HttpHeaders) {
pair.trailers = (HttpHeaders) nextItem;
} else {
throw new UnsupportedHttpChunkException(nextItem);
}
return pair;
}).map(pair -> {
if (isAlwaysEmpty(pair.payload)) {
payloadInfo.setEmpty(true);
}
if (pair.trailers == null) {
payloadInfo.setMayHaveTrailersAndGenericTypeBuffer(false);
}
return pair;
});
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class H2PriorKnowledgeFeatureParityTest method clientFilterAsyncContext.
@ParameterizedTest(name = "{displayName} [{index}] client={0}, h2PriorKnowledge={1}")
@MethodSource("clientExecutors")
void clientFilterAsyncContext(HttpTestExecutionStrategy strategy, boolean h2PriorKnowledge) throws Exception {
setUp(strategy, h2PriorKnowledge);
InetSocketAddress serverAddress = bindHttpEchoServer();
final Queue<Throwable> errorQueue = new ConcurrentLinkedQueue<>();
try (BlockingHttpClient client = forSingleAddress(HostAndPort.of(serverAddress)).protocols(h2PriorKnowledge ? h2Default() : h1Default()).executionStrategy(clientExecutionStrategy).appendClientFilter(client2 -> new StreamingHttpClientFilter(client2) {
@Override
protected Single<StreamingHttpResponse> request(final StreamingHttpRequester delegate, final StreamingHttpRequest request) {
return asyncContextTestRequest(errorQueue, delegate, request);
}
}).buildBlocking()) {
final String responseBody = "foo";
HttpResponse response = client.request(client.post("/0").payloadBody(responseBody, textSerializerUtf8()));
assertEquals(responseBody, response.payloadBody(textSerializerUtf8()));
assertNoAsyncErrors(errorQueue);
}
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class H2PriorKnowledgeFeatureParityTest method clientSendsInvalidContentLength.
private void clientSendsInvalidContentLength(boolean addTrailers, BiConsumer<HttpHeaders, Integer> headersModifier) throws Exception {
assumeFalse(!h2PriorKnowledge && addTrailers, "HTTP/1.1 does not support Content-Length with trailers");
InetSocketAddress serverAddress = bindHttpEchoServer();
try (BlockingHttpClient client = forSingleAddress(HostAndPort.of(serverAddress)).protocols(h2PriorKnowledge ? h2Default() : h1Default()).executionStrategy(clientExecutionStrategy).appendClientFilter(client1 -> new StreamingHttpClientFilter(client1) {
@Override
protected Single<StreamingHttpResponse> request(final StreamingHttpRequester delegate, final StreamingHttpRequest request) {
return request.toRequest().map(req -> {
req.headers().remove(TRANSFER_ENCODING);
headersModifier.accept(req.headers(), req.payloadBody().readableBytes());
return req.toStreamingRequest();
}).flatMap(delegate::request);
}
}).buildBlocking()) {
HttpRequest request = client.get("/").payloadBody("a", textSerializerUtf8());
if (addTrailers) {
request.trailers().set("mytrailer", "myvalue");
}
if (h2PriorKnowledge) {
assertThrows(H2StreamResetException.class, () -> client.request(request));
} else {
try (ReservedBlockingHttpConnection reservedConn = client.reserveConnection(request)) {
assertThrows(IOException.class, () -> {
// Either the current request or the next one should fail
reservedConn.request(request);
reservedConn.request(client.get("/"));
});
}
}
}
}
Aggregations