io.servicetalk.http.netty.DefaultPartitionedHttpClientBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of servicetalk-http-netty Show documentation
Show all versions of servicetalk-http-netty Show documentation
A networking framework that evolves with your application
The newest version!
/*
* Copyright © 2018-2019, 2022 Apple Inc. and the ServiceTalk project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.servicetalk.http.netty;
import io.servicetalk.buffer.api.BufferAllocator;
import io.servicetalk.client.api.ClientGroup;
import io.servicetalk.client.api.ServiceDiscoverer;
import io.servicetalk.client.api.internal.DefaultPartitionedClientGroup;
import io.servicetalk.client.api.internal.DefaultPartitionedClientGroup.PartitionedClientFactory;
import io.servicetalk.client.api.internal.partition.PowerSetPartitionMapFactory;
import io.servicetalk.client.api.partition.ClosedPartitionException;
import io.servicetalk.client.api.partition.PartitionAttributes;
import io.servicetalk.client.api.partition.PartitionAttributesBuilder;
import io.servicetalk.client.api.partition.PartitionMapFactory;
import io.servicetalk.client.api.partition.PartitionedServiceDiscovererEvent;
import io.servicetalk.client.api.partition.UnknownPartitionException;
import io.servicetalk.concurrent.api.BiIntFunction;
import io.servicetalk.concurrent.api.Completable;
import io.servicetalk.concurrent.api.Executor;
import io.servicetalk.concurrent.api.ListenableAsyncCloseable;
import io.servicetalk.concurrent.api.Publisher;
import io.servicetalk.concurrent.api.Single;
import io.servicetalk.http.api.DefaultHttpHeadersFactory;
import io.servicetalk.http.api.DefaultStreamingHttpRequestResponseFactory;
import io.servicetalk.http.api.FilterableReservedStreamingHttpConnection;
import io.servicetalk.http.api.FilterableStreamingHttpClient;
import io.servicetalk.http.api.HttpExecutionContext;
import io.servicetalk.http.api.HttpExecutionStrategy;
import io.servicetalk.http.api.HttpHeadersFactory;
import io.servicetalk.http.api.HttpRequestMetaData;
import io.servicetalk.http.api.HttpRequestMethod;
import io.servicetalk.http.api.PartitionedHttpClientBuilder;
import io.servicetalk.http.api.ReservedStreamingHttpConnection;
import io.servicetalk.http.api.SingleAddressHttpClientBuilder;
import io.servicetalk.http.api.StreamingHttpClient;
import io.servicetalk.http.api.StreamingHttpRequest;
import io.servicetalk.http.api.StreamingHttpRequestResponseFactory;
import io.servicetalk.http.api.StreamingHttpResponse;
import io.servicetalk.http.api.StreamingHttpResponseFactory;
import io.servicetalk.transport.api.IoExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import static io.servicetalk.client.api.ServiceDiscovererEvent.Status.UNAVAILABLE;
import static io.servicetalk.concurrent.api.AsyncCloseables.emptyAsyncCloseable;
import static io.servicetalk.concurrent.api.Single.defer;
import static io.servicetalk.concurrent.api.Single.failed;
import static io.servicetalk.http.api.HttpProtocolVersion.HTTP_1_1;
import static io.servicetalk.http.netty.DefaultSingleAddressHttpClientBuilder.setExecutionContext;
import static java.util.Objects.requireNonNull;
import static java.util.function.Function.identity;
@Deprecated // FIXME: 0.43 - remove deprecated class
final class DefaultPartitionedHttpClientBuilder implements PartitionedHttpClientBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPartitionedHttpClientBuilder.class);
private static final AtomicInteger CLIENT_ID = new AtomicInteger();
private final U address;
private final Function partitionAttributesBuilderFactory;
private final Supplier> builderFactory;
private final HttpExecutionContextBuilder executionContextBuilder = new HttpExecutionContextBuilder();
private ServiceDiscoverer> serviceDiscoverer;
@Nullable
private BiIntFunction serviceDiscovererRetryStrategy;
private int serviceDiscoveryMaxQueueSize = 32;
@Nullable
private HttpHeadersFactory headersFactory;
@Nullable
private SingleAddressInitializer clientInitializer;
private PartitionMapFactory partitionMapFactory = PowerSetPartitionMapFactory.INSTANCE;
DefaultPartitionedHttpClientBuilder(
final U address,
final Supplier> builderFactory,
final ServiceDiscoverer> serviceDiscoverer,
final Function partitionAttributesBuilderFactory) {
this.address = requireNonNull(address);
this.builderFactory = requireNonNull(builderFactory);
this.serviceDiscoverer = requireNonNull(serviceDiscoverer);
this.partitionAttributesBuilderFactory = requireNonNull(partitionAttributesBuilderFactory);
}
@Override
public StreamingHttpClient buildStreaming() {
final String targetResource = targetResource(address);
final HttpExecutionContext executionContext = executionContextBuilder.build();
final ServiceDiscoverer> psd =
new RetryingServiceDiscoverer<>(targetResource, serviceDiscoverer, serviceDiscovererRetryStrategy,
executionContext, DefaultPartitionedHttpClientBuilder::makeUnavailable);
final PartitionedClientFactory clientFactory = (pa, sd) -> {
// build new context, user may have changed anything on the builder from the filter
final SingleAddressHttpClientBuilder builder = requireNonNull(builderFactory.get());
builder.serviceDiscoverer(sd);
// Disable retries at the single-address client level because the original stream is already retried and
// partitioned streams never fail.
builder.retryServiceDiscoveryErrors(HttpClients.NoRetriesStrategy.INSTANCE);
setExecutionContext(builder, executionContext);
if (clientInitializer != null) {
clientInitializer.initialize(pa, builder);
}
return builder.buildStreaming();
};
final Publisher> psdEvents = psd.discover(address)
.flatMapConcatIterable(identity());
final HttpHeadersFactory headersFactory = this.headersFactory;
final DefaultPartitionedStreamingHttpClientFilter partitionedClient =
new DefaultPartitionedStreamingHttpClientFilter<>(psdEvents, serviceDiscoveryMaxQueueSize,
clientFactory, partitionAttributesBuilderFactory,
new DefaultStreamingHttpRequestResponseFactory(executionContext.bufferAllocator(),
headersFactory != null ? headersFactory : DefaultHttpHeadersFactory.INSTANCE, HTTP_1_1),
executionContext, partitionMapFactory);
LOGGER.debug("Partitioned client created with base strategy {}", executionContext.executionStrategy());
return new FilterableClientToClient(partitionedClient, executionContext);
}
private static String targetResource(final U address) {
return address + "/" + CLIENT_ID.incrementAndGet();
}
private static PartitionedServiceDiscovererEvent makeUnavailable(
final PartitionedServiceDiscovererEvent event) {
return new PartitionedServiceDiscovererEvent() {
@Override
public PartitionAttributes partitionAddress() {
return event.partitionAddress();
}
@Override
public R address() {
return event.address();
}
@Override
public Status status() {
return UNAVAILABLE;
}
};
}
private static final class DefaultPartitionedStreamingHttpClientFilter implements
FilterableStreamingHttpClient {
private static final Function PARTITION_CLOSED = pa ->
new NoopPartitionClient(new ClosedPartitionException(pa, "Partition closed"));
private static final Function PARTITION_UNKNOWN = pa ->
new NoopPartitionClient(new UnknownPartitionException(pa, "Partition unknown"));
private final ClientGroup group;
private final Function pabf;
private final HttpExecutionContext executionContext;
private final StreamingHttpRequestResponseFactory reqRespFactory;
DefaultPartitionedStreamingHttpClientFilter(
final Publisher> psdEvents,
final int psdMaxQueueSize,
final PartitionedClientFactory clientFactory,
final Function pabf,
final StreamingHttpRequestResponseFactory reqRespFactory,
final HttpExecutionContext executionContext,
final PartitionMapFactory partitionMapFactory) {
this.pabf = pabf;
this.executionContext = executionContext;
this.group = new DefaultPartitionedClientGroup<>(PARTITION_CLOSED, PARTITION_UNKNOWN, clientFactory,
partitionMapFactory, psdEvents, psdMaxQueueSize);
this.reqRespFactory = requireNonNull(reqRespFactory);
}
private FilterableStreamingHttpClient selectClient(
final HttpRequestMetaData metaData) {
return group.get(pabf.apply(metaData).build());
}
@Override
public Single reserveConnection(
final HttpRequestMetaData metaData) {
return defer(() -> selectClient(metaData).reserveConnection(metaData).shareContextOnSubscribe());
}
@Override
public Single request(final StreamingHttpRequest request) {
return defer(() -> selectClient(request).request(request).shareContextOnSubscribe());
}
@Override
public HttpExecutionContext executionContext() {
return executionContext;
}
@Override
public StreamingHttpResponseFactory httpResponseFactory() {
return reqRespFactory;
}
@Override
public Completable onClose() {
return group.onClose();
}
@Override
public Completable onClosing() {
return group.onClosing();
}
@Override
public Completable closeAsync() {
return group.closeAsync();
}
@Override
public Completable closeAsyncGracefully() {
return group.closeAsyncGracefully();
}
@Override
public StreamingHttpRequest newRequest(final HttpRequestMethod method, final String requestTarget) {
return reqRespFactory.newRequest(method, requestTarget);
}
}
private static final class NoopPartitionClient implements FilterableStreamingHttpClient {
private final ListenableAsyncCloseable close = emptyAsyncCloseable();
private final RuntimeException ex;
NoopPartitionClient(RuntimeException ex) {
this.ex = ex;
}
@Override
public Single request(final StreamingHttpRequest request) {
return failed(ex);
}
@Override
public HttpExecutionContext executionContext() {
throw ex;
}
@Override
public StreamingHttpResponseFactory httpResponseFactory() {
throw ex;
}
@Override
public Completable onClose() {
return close.onClose();
}
@Override
public Completable onClosing() {
return close.onClosing();
}
@Override
public Completable closeAsync() {
return close.closeAsync();
}
@Override
public Completable closeAsyncGracefully() {
return close.closeAsyncGracefully();
}
@Override
public Single reserveConnection(final HttpRequestMetaData metaData) {
return failed(ex);
}
@Override
public StreamingHttpRequest newRequest(final HttpRequestMethod method, final String requestTarget) {
throw ex;
}
}
@Override
public PartitionedHttpClientBuilder executor(final Executor executor) {
executionContextBuilder.executor(executor);
return this;
}
@Override
public PartitionedHttpClientBuilder ioExecutor(final IoExecutor ioExecutor) {
executionContextBuilder.ioExecutor(ioExecutor);
return this;
}
@Override
public PartitionedHttpClientBuilder bufferAllocator(final BufferAllocator allocator) {
executionContextBuilder.bufferAllocator(allocator);
return this;
}
@Override
public PartitionedHttpClientBuilder serviceDiscoverer(
final ServiceDiscoverer> serviceDiscoverer) {
this.serviceDiscoverer = requireNonNull(serviceDiscoverer);
return this;
}
@Override
public PartitionedHttpClientBuilder retryServiceDiscoveryErrors(
final BiIntFunction retryStrategy) {
this.serviceDiscovererRetryStrategy = requireNonNull(retryStrategy);
return this;
}
@Override
public PartitionedHttpClientBuilder serviceDiscoveryMaxQueueSize(final int serviceDiscoveryMaxQueueSize) {
this.serviceDiscoveryMaxQueueSize = serviceDiscoveryMaxQueueSize;
return this;
}
@Override
public PartitionedHttpClientBuilder partitionMapFactory(final PartitionMapFactory partitionMapFactory) {
this.partitionMapFactory = partitionMapFactory;
return this;
}
@Override
public PartitionedHttpClientBuilder initializer(final SingleAddressInitializer initializer) {
this.clientInitializer = requireNonNull(initializer);
return this;
}
@Override
public PartitionedHttpClientBuilder executionStrategy(final HttpExecutionStrategy strategy) {
this.executionContextBuilder.executionStrategy(strategy);
return this;
}
@Override
public PartitionedHttpClientBuilder headersFactory(final HttpHeadersFactory headersFactory) {
this.headersFactory = requireNonNull(headersFactory);
return this;
}
}