use of io.servicetalk.concurrent.api.Publisher 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.Publisher in project servicetalk by apple.
the class HelloWorldJaxRsResource method randomHello.
/**
* Resource that only relies on {@link Single}/{@link Publisher} for consuming and producing data,
* and returns a JAX-RS {@link Response} in order to set its status.
* No OIO adaptation is involved when requests are dispatched to it,
* allowing it to fully benefit from ReactiveStream's features like flow control.
* Behind the scene, ServiceTalk's aggregation mechanism is used to provide the resource with a
* {@link Single Single<Buffer>} that contains the whole request entity as a {@link Buffer}.
* Note that the {@link ConnectionContext} could also be injected into a class-level {@code @Context} field.
* <p>
* Test with:
* <pre>
* curl -i -H 'content-type: text/plain' -d 'kitty' http://localhost:8080/greetings/random-hello
* </pre>
*
* @param who the recipient of the greetings.
* @param ctx the {@link ConnectionContext}.
* @return greetings as a JAX-RS {@link Response}.
*/
@POST
@Path("random-hello")
@Consumes(TEXT_PLAIN)
@Produces(TEXT_PLAIN)
public Response randomHello(final Single<Buffer> who, @Context final ConnectionContext ctx) {
if (random() < .5) {
return accepted("greetings accepted, call again for a response").build();
}
final BufferAllocator allocator = ctx.executionContext().bufferAllocator();
final Publisher<Buffer> payload = from(allocator.fromAscii("hello ")).concat(who);
// Wrap content Publisher to capture its generic type (i.e. Buffer) so it is handled correctly
final GenericEntity<Publisher<Buffer>> entity = new GenericEntity<Publisher<Buffer>>(payload) {
};
return ok(entity).build();
}
use of io.servicetalk.concurrent.api.Publisher in project servicetalk by apple.
the class DefaultDnsClient method dnsSrvQuery.
@Override
public Publisher<Collection<ServiceDiscovererEvent<InetSocketAddress>>> dnsSrvQuery(final String serviceName) {
requireNonNull(serviceName);
return defer(() -> {
// State per subscribe requires defer so each subscribe gets independent state.
final Map<String, ARecordPublisher> aRecordMap = new HashMap<>(8);
final Map<InetSocketAddress, Integer> availableAddresses = srvFilterDuplicateEvents ? new HashMap<>(8) : emptyMap();
final DnsDiscoveryObserver discoveryObserver = newDiscoveryObserver(serviceName);
// inactive events if necessary.
return recoverWithInactiveEvents(new SrvRecordPublisher(serviceName, discoveryObserver), true).flatMapConcatIterable(identity()).flatMapMerge(srvEvent -> {
assertInEventloop();
if (AVAILABLE.equals(srvEvent.status())) {
return defer(() -> {
final ARecordPublisher aPublisher = new ARecordPublisher(srvEvent.address().hostName(), discoveryObserver);
final ARecordPublisher prevAPublisher = aRecordMap.putIfAbsent(srvEvent.address().hostName(), aPublisher);
if (prevAPublisher != null) {
return newDuplicateSrv(serviceName, srvEvent.address().hostName());
}
Publisher<? extends Collection<ServiceDiscovererEvent<InetAddress>>> returnPub = recoverWithInactiveEvents(aPublisher, false);
return srvFilterDuplicateEvents ? srvFilterDups(returnPub, availableAddresses, srvEvent.address().port()) : returnPub.map(events -> mapEventList(events, inetAddress -> new InetSocketAddress(inetAddress, srvEvent.address().port())));
}).retryWhen((i, cause) -> {
assertInEventloop();
// don't retry. Otherwise this is a resolution exception (e.g. UnknownHostException), and retry.
return cause == SrvAddressRemovedException.DNS_SRV_ADDR_REMOVED || aRecordMap.remove(srvEvent.address().hostName()) == null ? Completable.failed(cause) : srvHostNameRepeater.apply(i);
}).onErrorComplete();
} else if (srvEvent instanceof SrvInactiveEvent) {
// Unwrap the list so we can use it in SrvInactiveCombinerOperator below.
return from(((SrvInactiveEvent<HostAndPort, InetSocketAddress>) srvEvent).aggregatedEvents);
} else {
final ARecordPublisher aPublisher = aRecordMap.remove(srvEvent.address().hostName());
if (aPublisher != null) {
aPublisher.cancelAndFail0(SrvAddressRemovedException.DNS_SRV_ADDR_REMOVED);
}
return empty();
}
}, srvConcurrency).liftSync(inactiveEventsOnError ? SrvInactiveCombinerOperator.EMIT : SrvInactiveCombinerOperator.NO_EMIT);
});
}
use of io.servicetalk.concurrent.api.Publisher in project servicetalk by apple.
the class ProtobufSerializerMessageBodyReaderWriter method writeTo.
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void writeTo(final Object o, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws WebApplicationException {
final BufferAllocator allocator = ctxRefProvider.get().get().executionContext().bufferAllocator();
final Publisher<Buffer> bufferPublisher;
if (o instanceof Single) {
final Class<? extends MessageLite> clazz = genericType instanceof Class ? (Class) genericType : getSourceClass(genericType);
Serializer serializer = getSerializerFactory(mediaType).serializerDeserializer(clazz);
bufferPublisher = ((Single) o).map(t -> serializer.serialize(t, allocator)).toPublisher();
} else if (o instanceof Publisher) {
final Class<? extends MessageLite> clazz = genericType instanceof Class ? (Class) genericType : getSourceClass(genericType);
StreamingSerializer serializer = getSerializerFactory(mediaType).streamingSerializerDeserializer(clazz);
bufferPublisher = serializer.serialize((Publisher) o, allocator);
} else {
Serializer serializer = getSerializerFactory(mediaType).serializerDeserializer((Class<? extends MessageLite>) o.getClass());
bufferPublisher = Publisher.from(serializer.serialize(o, allocator));
}
setResponseBufferPublisher(bufferPublisher, requestCtxProvider.get());
}
use of io.servicetalk.concurrent.api.Publisher 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();
}
};
}
Aggregations