use of io.servicetalk.http.api.HttpHeaderValues.CHUNKED 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.http.api.HttpHeaderValues.CHUNKED in project servicetalk by apple.
the class DefaultContainerResponseWriter method sendResponse.
private void sendResponse(final long contentLength, @Nullable final Publisher<Buffer> content, final ContainerResponse containerResponse) {
final HttpResponseStatus status = getStatus(containerResponse);
final StreamingHttpResponse response;
if (content != null && !isHeadRequest()) {
final HttpExecutionStrategy executionStrategy = getResponseExecutionStrategy(request);
// TODO(scott): use request factory methods that accept a payload body to avoid overhead of payloadBody.
final Publisher<Buffer> payloadBody = (executionStrategy != null && executionStrategy.isSendOffloaded() ? content.subscribeOn(serviceCtx.executionContext().executor(), IoThreadFactory.IoThread::currentThreadIsIoThread) : content).beforeCancel(// Cleanup internal state if server cancels response body
this::cancelResponse);
response = responseFactory.newResponse(status).version(protocolVersion).payloadBody(payloadBody);
} else {
response = responseFactory.newResponse(status).version(protocolVersion);
}
final HttpHeaders headers = response.headers();
// If we use HTTP/2 protocol all headers MUST be in lower case
final boolean isH2 = response.version().major() == 2;
containerResponse.getHeaders().forEach((k, vs) -> vs.forEach(v -> {
headers.add(isH2 ? k.toLowerCase() : k, v == null ? emptyAsciiString() : asCharSequence(v));
}));
if (!headers.contains(CONTENT_LENGTH)) {
if (contentLength == UNKNOWN_RESPONSE_LENGTH) {
// We can omit Transfer-Encoding for HEAD per https://tools.ietf.org/html/rfc7231#section-4.3.2
if (!isHeadRequest() && !HTTP_1_0.equals(protocolVersion)) {
headers.set(TRANSFER_ENCODING, CHUNKED);
}
} else {
headers.set(CONTENT_LENGTH, contentLength == 0 ? ZERO : Long.toString(contentLength));
headers.removeIgnoreCase(TRANSFER_ENCODING, CHUNKED);
}
}
responseSubscriber.onSuccess(response);
}
use of io.servicetalk.http.api.HttpHeaderValues.CHUNKED in project servicetalk by apple.
the class AbstractEchoServerBasedHttpRequesterTest method makeRequestValidateResponseAndClose.
static void makeRequestValidateResponseAndClose(StreamingHttpRequester requester) throws ExecutionException, InterruptedException {
try {
StreamingHttpRequest request = requester.get("/request?foo=bar&foo=baz").payloadBody(from(DEFAULT_ALLOCATOR.fromAscii("Testing123")));
request.headers().set(HOST, "mock.servicetalk.io");
StreamingHttpResponse resp = awaitIndefinitelyNonNull(requester.request(request).retryWhen(retryWithExponentialBackoffFullJitter(10, t -> true, ofMillis(100), ofDays(10), CTX.executor())));
assertThat(resp.status(), equalTo(OK));
Single<String> respBody = resp.payloadBody().collect(StringBuilder::new, (sb, buf) -> {
sb.append(buf.toString(UTF_8));
return sb;
}).map(StringBuilder::toString);
HttpHeaders headers = resp.headers();
assertThat(headers.get("test-req-method"), hasToString(GET.toString()));
assertThat(headers.get("test-req-target"), hasToString("/request?foo=bar&foo=baz"));
assertThat(headers.get("test-req-header-host"), hasToString("mock.servicetalk.io"));
assertThat(headers.get("test-req-header-transfer-encoding"), equalTo(CHUNKED));
assertThat(respBody.toFuture().get(), equalTo("Testing123"));
} finally {
requester.closeAsync().toFuture().get();
}
}
Aggregations