use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class TcpServerBinder method bind.
/**
* Create a {@link ServerContext} that represents a socket which is bound and listening on the
* {@code listenAddress}.
*
* @param listenAddress The address to bind to.
* @param config The {@link ReadOnlyTcpServerConfig} to use for the bind socket and newly accepted sockets.
* @param autoRead if {@code true} auto read will be enabled for new {@link Channel}s.
* @param executionContext The {@link ExecutionContext} to use for the bind socket.
* @param connectionAcceptor The {@link ConnectionAcceptor} used to filter newly accepted sockets.
* @param connectionFunction Used to create a new {@link NettyConnection} from a {@link Channel}.
* @param connectionConsumer Used to consume the result of {@code connectionFunction} after initialization and
* filtering is done. This can be used for protocol specific initialization and to start data flow.
* @param <CC> The type of {@link ConnectionContext} that is created for each accepted socket.
* @return a {@link Single} that completes with a {@link ServerContext} that represents a socket which is bound and
* listening on the {@code listenAddress}.
*/
public static <CC extends ConnectionContext> Single<ServerContext> bind(SocketAddress listenAddress, final ReadOnlyTcpServerConfig config, final boolean autoRead, final ExecutionContext<?> executionContext, @Nullable final InfluencerConnectionAcceptor connectionAcceptor, final BiFunction<Channel, ConnectionObserver, Single<CC>> connectionFunction, final Consumer<CC> connectionConsumer) {
requireNonNull(connectionFunction);
requireNonNull(connectionConsumer);
listenAddress = toNettyAddress(listenAddress);
EventLoopAwareNettyIoExecutor nettyIoExecutor = toEventLoopAwareNettyIoExecutor(executionContext.ioExecutor());
ServerBootstrap bs = new ServerBootstrap();
configure(config, autoRead, bs, nettyIoExecutor.eventLoopGroup(), listenAddress.getClass());
ChannelSet channelSet = new ChannelSet(executionContext.executionStrategy().isCloseOffloaded() ? executionContext.executor() : immediate());
bs.handler(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
// Verify that we do not leak pooled memory in the "accept" pipeline
if (msg instanceof ReferenceCounted) {
try {
throw new IllegalArgumentException("Unexpected ReferenceCounted msg in 'accept' pipeline: " + msg);
} finally {
((ReferenceCounted) msg).release();
}
}
if (msg instanceof Channel) {
final Channel channel = (Channel) msg;
if (!channel.isActive()) {
channel.close();
LOGGER.debug("Channel ({}) is accepted, but was already inactive", msg);
return;
} else if (!channelSet.addIfAbsent(channel)) {
LOGGER.warn("Channel ({}) not added to ChannelSet", msg);
return;
}
}
ctx.fireChannelRead(msg);
}
});
bs.childHandler(new io.netty.channel.ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
Single<CC> connectionSingle = connectionFunction.apply(channel, config.transportObserver().onNewConnection(channel.localAddress(), channel.remoteAddress()));
if (connectionAcceptor != null) {
connectionSingle = connectionSingle.flatMap(conn -> defer(() -> connectionAcceptor.accept(conn).concat(succeeded(conn))).subscribeOn(connectionAcceptor.requiredOffloads().isConnectOffloaded() ? executionContext.executor() : immediate()));
}
connectionSingle.beforeOnError(cause -> {
// Getting the remote-address may involve volatile reads and potentially a syscall, so guard it.
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Failed to create a connection for remote address {}", channel.remoteAddress(), cause);
}
close(channel, cause);
}).subscribe(connectionConsumer);
}
});
ChannelFuture future = bs.bind(listenAddress);
return new SubscribableSingle<ServerContext>() {
@Override
protected void handleSubscribe(Subscriber<? super ServerContext> subscriber) {
subscriber.onSubscribe(() -> future.cancel(true));
future.addListener((ChannelFuture f) -> {
Channel channel = f.channel();
Throwable cause = f.cause();
if (cause == null) {
subscriber.onSuccess(NettyServerContext.wrap(channel, channelSet, connectionAcceptor, executionContext));
} else {
close(channel, f.cause());
subscriber.onError(f.cause());
}
});
}
};
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class ProtocolCompatibilityTest method serviceTalkServer.
private static TestServerContext serviceTalkServer(final ErrorMode errorMode, final boolean ssl, final GrpcExecutionStrategy strategy, @Nullable final String compression, @Nullable final Duration timeout, Queue<Throwable> reqStreamError) throws Exception {
final Compat.CompatService compatService = new Compat.CompatService() {
@Override
public Publisher<CompatResponse> bidirectionalStreamingCall(final GrpcServiceContext ctx, final Publisher<CompatRequest> pub) {
reqStreamError.add(SERVER_PROCESSED_TOKEN);
maybeThrowFromRpc(errorMode);
return pub.map(req -> response(req.getId())).beforeFinally(errorConsumer());
}
@Override
public Single<CompatResponse> clientStreamingCall(final GrpcServiceContext ctx, final Publisher<CompatRequest> pub) {
reqStreamError.add(SERVER_PROCESSED_TOKEN);
maybeThrowFromRpc(errorMode);
return pub.collect(() -> 0, (sum, req) -> sum + req.getId()).map(this::response).beforeFinally(errorConsumer());
}
@Override
public Single<CompatResponse> scalarCall(final GrpcServiceContext ctx, final CompatRequest req) {
maybeThrowFromRpc(errorMode);
return succeeded(response(req.getId()));
}
@Override
public Publisher<CompatResponse> serverStreamingCall(final GrpcServiceContext ctx, final CompatRequest req) {
maybeThrowFromRpc(errorMode);
return Publisher.fromIterable(() -> IntStream.range(0, req.getId()).iterator()).map(this::response);
}
private CompatResponse response(final int value) {
if (errorMode == ErrorMode.SIMPLE_IN_RESPONSE) {
throwGrpcStatusException();
} else if (errorMode == ErrorMode.STATUS_IN_RESPONSE) {
throwGrpcStatusExceptionWithStatus();
}
return computeResponse(value);
}
private TerminalSignalConsumer errorConsumer() {
return new TerminalSignalConsumer() {
@Override
public void onComplete() {
}
@Override
public void onError(final Throwable throwable) {
reqStreamError.add(throwable);
}
@Override
public void cancel() {
reqStreamError.add(new IOException("cancelled"));
}
};
}
};
final ServiceFactory serviceFactory = new ServiceFactory.Builder().bufferEncoders(serviceTalkCompressions(compression)).bufferDecoderGroup(serviceTalkDecompression(compression)).bidirectionalStreamingCall(strategy, compatService).clientStreamingCall(strategy, compatService).scalarCall(strategy, compatService).serverStreamingCall(strategy, compatService).build();
final ServerContext serverContext = serviceTalkServerBuilder(errorMode, ssl, timeout, b -> b.executionStrategy(strategy)).listenAndAwait(serviceFactory);
return TestServerContext.fromServiceTalkServerContext(serverContext);
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class SingleRequestOrResponseApiTest method setUp.
private void setUp(boolean streamingService, boolean streamingClient) throws Exception {
this.streamingService = streamingService;
this.streamingClient = streamingClient;
serverContext = GrpcServers.forAddress(localAddress(0)).listenAndAwait(streamingService ? new ServiceFactory(new TesterServiceImpl()) : new ServiceFactory(new BlockingTesterServiceImpl()));
clientBuilder = GrpcClients.forAddress(serverHostAndPort(serverContext)).initializeHttp(builder -> builder.appendClientFilter(origin -> new StreamingHttpClientFilter(origin) {
@Override
protected Single<StreamingHttpResponse> request(StreamingHttpRequester delegate, StreamingHttpRequest request) {
// and generates requested number of response items:
return defer(() -> {
request.requestTarget(BlockingTestResponseStreamRpc.PATH);
return delegate.request(request).shareContextOnSubscribe();
});
}
}));
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class ExecutionStrategyInContextTest method testStreaming.
@ParameterizedTest(name = "customStrategy={0}")
@ValueSource(booleans = { false, true })
void testStreaming(boolean customStrategy) throws Exception {
StreamingHttpClient client = initClientAndServer(builder -> builder.listenStreaming(new StreamingHttpService() {
@Override
public Single<StreamingHttpResponse> handle(final HttpServiceContext ctx, final StreamingHttpRequest request, final StreamingHttpResponseFactory responseFactory) {
serviceStrategyRef.set(ctx.executionContext().executionStrategy());
return succeeded(responseFactory.ok());
}
@Override
public HttpExecutionStrategy requiredOffloads() {
return offloadNone();
}
}), customStrategy).buildStreaming();
clientAsCloseable = client;
if (!customStrategy) {
assert expectedClientStrategy == null;
expectedClientStrategy = defaultStrategy();
assert expectedServerStrategy == null;
expectedServerStrategy = offloadNone();
}
HttpExecutionStrategy clientStrat = client.executionContext().executionStrategy();
assertThat("Unexpected client strategy.", clientStrat, equalStrategies(expectedClientStrategy));
client.request(client.get("/")).toFuture().get();
assertThat("Unexpected service strategy", serviceStrategyRef.get(), equalStrategies(expectedServerStrategy));
ReservedStreamingHttpConnection conn = client.reserveConnection(client.get("/")).toFuture().get();
assertThat("Unexpected connection strategy (from execution context).", conn.executionContext().executionStrategy(), equalStrategies(expectedClientStrategy));
assertThat("Unexpected connection strategy (from execution context).", conn.connectionContext().executionContext().executionStrategy(), equalStrategies(expectedClientStrategy));
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class HttpConnectionEmptyPayloadTest method headRequestContentEmpty.
@Test
void headRequestContentEmpty() throws Exception {
try (CompositeCloseable closeable = AsyncCloseables.newCompositeCloseable()) {
final int expectedContentLength = 128;
byte[] expectedPayload = new byte[expectedContentLength];
ThreadLocalRandom.current().nextBytes(expectedPayload);
ServerContext serverContext = closeable.merge(HttpServers.forAddress(localAddress(0)).ioExecutor(executionContextRule.ioExecutor()).executionStrategy(offloadNone()).listenStreamingAndAwait((ctx, req, factory) -> {
StreamingHttpResponse resp = factory.ok().payloadBody(from(HEAD.equals(req.method()) ? EMPTY_BUFFER : ctx.executionContext().bufferAllocator().newBuffer(expectedContentLength).writeBytes(expectedPayload)));
resp.addHeader(CONTENT_LENGTH, String.valueOf(expectedContentLength));
return succeeded(resp);
}));
StreamingHttpClient client = closeable.merge(forResolvedAddress(serverHostAndPort(serverContext)).ioExecutor(executionContextRule.ioExecutor()).protocols(h1().maxPipelinedRequests(3).build()).executor(executionContextRule.executor()).executionStrategy(defaultStrategy()).buildStreaming());
StreamingHttpConnection connection = closeable.merge(client.reserveConnection(client.get("/")).toFuture().get());
// Request HEAD, GET, HEAD to verify that we can keep reading data despite a HEAD request providing a hint
// about content-length (and not actually providing the content).
Single<StreamingHttpResponse> response1Single = connection.request(connection.newRequest(HEAD, "/"));
Single<StreamingHttpResponse> response2Single = connection.request(connection.get("/"));
Single<StreamingHttpResponse> response3Single = connection.request(connection.newRequest(HEAD, "/"));
StreamingHttpResponse response = awaitIndefinitelyNonNull(response1Single);
assertEquals(OK, response.status());
CharSequence contentLength = response.headers().get(CONTENT_LENGTH);
assertNotNull(contentLength);
assertEquals(expectedContentLength, parseInt(contentLength.toString()));
// Drain the current response content so we will be able to read the next response.
response.messageBody().ignoreElements().toFuture().get();
response = awaitIndefinitelyNonNull(response2Single);
assertEquals(OK, response.status());
contentLength = response.headers().get(CONTENT_LENGTH);
assertNotNull(contentLength);
assertEquals(expectedContentLength, parseInt(contentLength.toString()));
Buffer buffer = awaitIndefinitelyNonNull(response.payloadBody().collect(() -> connection.connectionContext().executionContext().bufferAllocator().newBuffer(), Buffer::writeBytes));
byte[] actualBytes = new byte[buffer.readableBytes()];
buffer.readBytes(actualBytes);
assertArrayEquals(expectedPayload, actualBytes);
response = awaitIndefinitelyNonNull(response3Single);
assertEquals(OK, response.status());
contentLength = response.headers().get(CONTENT_LENGTH);
assertNotNull(contentLength);
assertEquals(expectedContentLength, parseInt(contentLength.toString()));
response.messageBody().ignoreElements().toFuture().get();
}
}
Aggregations