
com.netflix.client.netty.http.NettyHttpClient Maven / Gradle / Ivy
/*
*
* Copyright 2014 Netflix, Inc.
*
* 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 com.netflix.client.netty.http;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelOption;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.reactivex.netty.channel.ObservableConnection;
import io.reactivex.netty.client.ClientMetricsEvent;
import io.reactivex.netty.client.CompositePoolLimitDeterminationStrategy;
import io.reactivex.netty.client.RxClient;
import io.reactivex.netty.contexts.ContextPipelineConfigurators;
import io.reactivex.netty.contexts.RxContexts;
import io.reactivex.netty.contexts.http.HttpRequestIdProvider;
import io.reactivex.netty.metrics.MetricEventsListener;
import io.reactivex.netty.pipeline.PipelineConfigurator;
import io.reactivex.netty.pipeline.PipelineConfigurators;
import io.reactivex.netty.pipeline.ssl.DefaultFactories;
import io.reactivex.netty.protocol.http.client.HttpClient;
import io.reactivex.netty.protocol.http.client.HttpClientBuilder;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import io.reactivex.netty.protocol.text.sse.ServerSentEvent;
import io.reactivex.netty.servo.http.HttpClientListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import rx.Observable;
import rx.Subscription;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.IClientConfigKey;
import com.netflix.client.netty.LoadBalancingRxClientWithPoolOptions;
import com.netflix.client.ssl.AbstractSslContextFactory;
import com.netflix.client.ssl.ClientSslSocketFactoryException;
import com.netflix.client.ssl.URLSslContextFactory;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.LoadBalancerExecutor;
import com.netflix.loadbalancer.LoadBalancerObservableCommand;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
/**
* A Netty HttpClient that can connect to different servers. Internally it caches the RxNetty's HttpClient, with each created with
* a connection pool governed by {@link CompositePoolLimitDeterminationStrategy} that has a global limit and per server limit.
*
* @author awang
*/
public class NettyHttpClient extends LoadBalancingRxClientWithPoolOptions, HttpClientResponse, HttpClient>
implements HttpClient {
protected static final PipelineConfigurator, HttpClientRequest> DEFAULT_PIPELINE_CONFIGURATOR =
PipelineConfigurators.httpClientConfigurator();
protected static final PipelineConfigurator, HttpClientRequest> DEFAULT_SSE_PIPELINE_CONFIGURATOR =
PipelineConfigurators.sseClientConfigurator();
private String requestIdHeaderName;
private HttpRequestIdProvider requestIdProvider;
public NettyHttpClient(ILoadBalancer lb, PipelineConfigurator, HttpClientRequest> pipeLineConfigurator,
ScheduledExecutorService poolCleanerScheduler) {
this(lb, DefaultClientConfigImpl.getClientConfigWithDefaultValues(),
new NettyHttpLoadBalancerErrorHandler(), pipeLineConfigurator, poolCleanerScheduler);
}
public NettyHttpClient(
IClientConfig config,
RetryHandler retryHandler,
PipelineConfigurator, HttpClientRequest> pipelineConfigurator,
ScheduledExecutorService poolCleanerScheduler) {
this(LoadBalancerBuilder.newBuilder().withClientConfig(config).buildLoadBalancerFromConfigWithReflection(),
config, retryHandler, pipelineConfigurator, poolCleanerScheduler);
}
public NettyHttpClient(
ILoadBalancer lb,
IClientConfig config,
RetryHandler retryHandler,
PipelineConfigurator, HttpClientRequest> pipelineConfigurator,
ScheduledExecutorService poolCleanerScheduler) {
super(lb, config, retryHandler, pipelineConfigurator, poolCleanerScheduler);
requestIdHeaderName = getProperty(IClientConfigKey.Keys.RequestIdHeaderName, null, null);
if (requestIdHeaderName != null) {
requestIdProvider = new HttpRequestIdProvider(requestIdHeaderName, RxContexts.DEFAULT_CORRELATOR);
}
}
private RequestSpecificRetryHandler getRequestRetryHandler(HttpClientRequest> request, IClientConfig requestConfig) {
boolean okToRetryOnAllErrors = request.getMethod().equals(HttpMethod.GET);
return new RequestSpecificRetryHandler(true, okToRetryOnAllErrors, lbExecutor.getRetryHandler(), requestConfig);
}
protected void setHost(HttpClientRequest> request, String host) {
request.getHeaders().set(HttpHeaders.Names.HOST, host);
}
public Observable> submit(String host, int port, final HttpClientRequest request) {
return submit(host, port, request, getRxClientConfig(null));
}
public Observable> submit(String host, int port, final HttpClientRequest request, ClientConfig rxClientConfig) {
Preconditions.checkNotNull(host);
Preconditions.checkNotNull(request);
HttpClient rxClient = getRxClient(host, port);
setHost(request, host);
return rxClient.submit(request, rxClientConfig);
}
private RxClient.ClientConfig getRxClientConfig(IClientConfig requestConfig) {
if (requestConfig == null) {
return HttpClientConfig.Builder.newDefaultConfig();
}
int requestReadTimeout = getProperty(IClientConfigKey.Keys.ReadTimeout, requestConfig,
DefaultClientConfigImpl.DEFAULT_READ_TIMEOUT);
Boolean followRedirect = getProperty(IClientConfigKey.Keys.FollowRedirects, requestConfig, null);
HttpClientConfig.Builder builder = new HttpClientConfig.Builder().readTimeout(requestReadTimeout, TimeUnit.MILLISECONDS);
if (followRedirect != null) {
builder.setFollowRedirect(followRedirect);
}
return builder.build();
}
public Observable> submit(String host, int port, final HttpClientRequest request, @Nullable final IClientConfig requestConfig) {
return submit(host, port, request, getRxClientConfig(requestConfig));
}
/**
* Submit a request to server chosen by the load balancer to execute. An error will be emitted from the returned {@link Observable} if
* there is no server available from load balancer.
*
* @param errorHandler A handler to determine the load balancer retry logic. If null, the default one will be used.
* @param requestConfig An {@link IClientConfig} to override the default configuration for the client. Can be null.
* @return
*/
public Observable> submit(final HttpClientRequest request, final RetryHandler errorHandler, final IClientConfig requestConfig) {
final RetryHandler retryHandler = (errorHandler == null) ? getRequestRetryHandler(request, requestConfig) : errorHandler;
final ClientConfig rxClientConfig = getRxClientConfig(requestConfig);
Observable> result = submitToServerInURI(request, rxClientConfig, retryHandler);
if (result != null) {
return result;
}
return lbExecutor.create(new LoadBalancerObservableCommand>() {
@Override
public Observable> run(
Server server) {
return submit(server.getHost(), server.getPort(), request, rxClientConfig);
}
}, retryHandler);
}
@VisibleForTesting
ServerStats getServerStats(Server server) {
return lbExecutor.getServerStats(server);
}
/**
* Submit a request to server chosen by the load balancer to execute. An error will be emitted from the returned {@link Observable} if
* there is no server available from load balancer.
*/
@Override
public Observable> submit(HttpClientRequest request) {
return submit(request, null, null);
}
/**
* Submit a request to server chosen by the load balancer to execute. An error will be emitted from the returned {@link Observable} if
* there is no server available from load balancer.
*
* @param config An {@link ClientConfig} to override the default configuration for the client. Can be null.
* @return
*/
@Override
public Observable> submit(final HttpClientRequest request, final ClientConfig config) {
final RetryHandler retryHandler = getRequestRetryHandler(request, null);
Observable> result = submitToServerInURI(request, config, retryHandler);
if (result != null) {
return result;
}
return lbExecutor.create(new LoadBalancerObservableCommand>() {
@Override
public Observable> run(
Server server) {
return submit(server.getHost(), server.getPort(), request, config);
}
}, retryHandler);
}
private Observable> submitToServerInURI(HttpClientRequest request, ClientConfig config, RetryHandler errorHandler) {
URI uri;
try {
uri = new URI(request.getUri());
} catch (URISyntaxException e) {
return Observable.error(e);
}
String host = uri.getHost();
if (host == null) {
return null;
}
int port = uri.getPort();
if (port < 0) {
if (clientConfig.getPropertyAsBoolean(IClientConfigKey.Keys.IsSecure, false)) {
port = 443;
} else {
port = 80;
}
}
if (errorHandler.getMaxRetriesOnSameServer() == 0) {
return submit(host, port, request, config);
}
Server server = new Server(host, port);
return lbExecutor.retryWithSameServer(server, submit(server.getHost(), server.getPort(), request, config), errorHandler);
}
/**
* Create an {@link ObservableConnection} with a server chosen by the load balancer.
*/
@Override
public Observable, HttpClientRequest>> connect() {
return lbExecutor.create(new LoadBalancerObservableCommand, HttpClientRequest>>() {
@Override
public Observable, HttpClientRequest>> run(
Server server) {
HttpClient rxClient = getRxClient(server.getHost(), server.getPort());
return rxClient.connect();
}
});
}
@Override
protected HttpClient cacheLoadRxClient(Server server) {
HttpClientBuilder clientBuilder;
if (requestIdProvider != null) {
clientBuilder = RxContexts.newHttpClientBuilder(server.getHost(), server.getPort(),
requestIdProvider, RxContexts.DEFAULT_CORRELATOR, pipelineConfigurator);
} else {
clientBuilder = RxContexts.newHttpClientBuilder(server.getHost(), server.getPort(),
RxContexts.DEFAULT_CORRELATOR, pipelineConfigurator);
}
Integer connectTimeout = getProperty(IClientConfigKey.Keys.ConnectTimeout, null, DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT);
Integer readTimeout = getProperty(IClientConfigKey.Keys.ReadTimeout, null, DefaultClientConfigImpl.DEFAULT_READ_TIMEOUT);
Boolean followRedirect = getProperty(IClientConfigKey.Keys.FollowRedirects, null, null);
HttpClientConfig.Builder builder = new HttpClientConfig.Builder().readTimeout(readTimeout, TimeUnit.MILLISECONDS);
if (followRedirect != null) {
builder.setFollowRedirect(followRedirect);
}
RxClient.ClientConfig rxClientConfig = builder.build();
clientBuilder.channelOption(
ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)
.config(rxClientConfig);
if (isPoolEnabled()) {
clientBuilder.withConnectionPoolLimitStrategy(poolStrategy)
.withIdleConnectionsTimeoutMillis(idleConnectionEvictionMills)
.withPoolIdleCleanupScheduler(poolCleanerScheduler);
} else {
clientBuilder.withNoConnectionPooling();
}
if (sslContextFactory != null) {
try {
clientBuilder.withSslEngineFactory(DefaultFactories.fromSSLContext(sslContextFactory.getSSLContext()));
} catch (ClientSslSocketFactoryException e) {
throw new RuntimeException(e);
}
}
HttpClient client = clientBuilder.build();
return client;
}
HttpClientListener getListener() {
return (HttpClientListener) listener;
}
LoadBalancerExecutor getLoadBalancerExecutor() {
return lbExecutor;
}
@Override
protected MetricEventsListener extends ClientMetricsEvent>> createListener(
String name) {
return HttpClientListener.newHttpListener(name);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy