/* * * 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.handler.codec.http.HttpMethod; import io.reactivex.netty.channel.ObservableConnection; import io.reactivex.netty.client.CompositePoolLimitDeterminationStrategy; import io.reactivex.netty.pipeline.PipelineConfigurator; import io.reactivex.netty.pipeline.PipelineConfigurators; import io.reactivex.netty.protocol.http.client.HttpClient; import io.reactivex.netty.protocol.http.client.HttpClientRequest; import io.reactivex.netty.protocol.http.client.HttpClientResponse; import io.reactivex.netty.protocol.http.client.RepeatableContentHttpRequest; import io.reactivex.netty.protocol.text.sse.ServerSentEvent; import java.io.Closeable; import rx.Observable; 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.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ClientObservableProvider; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.LoadBalancerBuilder; import com.netflix.loadbalancer.LoadBalancerExecutor; 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 abstract class NettyHttpClient extends AbstractNettyHttpClient implements Closeable { protected static final PipelineConfigurator, HttpClientRequest> DEFAULT_PIPELINE_CONFIGURATOR = PipelineConfigurators.httpClientConfigurator(); protected static final PipelineConfigurator, HttpClientRequest> DEFAULT_SSE_PIPELINE_CONFIGURATOR = PipelineConfigurators.sseClientConfigurator(); protected final PipelineConfigurator, HttpClientRequest> pipeLineConfigurator; protected LoadBalancerExecutor lbExecutor; public static NettyHttpClient createDefaultHttpClient() { return createDefaultHttpClient(DefaultClientConfigImpl.getClientConfigWithDefaultValues()); } public static NettyHttpClient createDefaultHttpClient(ILoadBalancer lb) { return createDefaultHttpClient(lb, DefaultClientConfigImpl.getClientConfigWithDefaultValues(), null); } public static NettyHttpClient createDefaultHttpClient(ILoadBalancer lb, IClientConfig config) { return createDefaultHttpClient(lb, config, null); } public static NettyHttpClient createDefaultHttpClient(IClientConfig config) { ILoadBalancer lb = LoadBalancerBuilder.newBuilder() .withClientConfig(config) .buildLoadBalancerFromConfigWithReflection(); return createDefaultHttpClient(lb, config, null); } public static NettyHttpClient createDefaultHttpClient(ILoadBalancer lb, IClientConfig config, RetryHandler handler) { return new CachedHttpClientWithConnectionPool(lb, config, NettyHttpClient.DEFAULT_PIPELINE_CONFIGURATOR, handler); } public static NettyHttpClient createHttpClient(ILoadBalancer lb, PipelineConfigurator, HttpClientRequest> pipelineConfigurator) { return createHttpClient(lb, pipelineConfigurator, DefaultClientConfigImpl.getClientConfigWithDefaultValues(), null); } public static NettyHttpClient createHttpClient(ILoadBalancer lb, PipelineConfigurator, HttpClientRequest> pipelineConfigurator, IClientConfig config, RetryHandler handler) { return new CachedHttpClientWithConnectionPool(lb, config, pipelineConfigurator, handler); } public static NettyHttpClient createDefaultSSEClient(ILoadBalancer lb) { return createDefaultSSEClient(lb, DefaultClientConfigImpl.getClientConfigWithDefaultValues(), null); } public static NettyHttpClient createDefaultSSEClient(ILoadBalancer lb, IClientConfig config) { return createDefaultSSEClient(lb, config, null); } public static NettyHttpClient createDefaultSSEClient(IClientConfig config) { ILoadBalancer lb = LoadBalancerBuilder.newBuilder() .withClientConfig(config) .buildLoadBalancerFromConfigWithReflection(); return createDefaultSSEClient(lb, config); } public static NettyHttpClient createDefaultSSEClient() { return createDefaultSSEClient(DefaultClientConfigImpl.getClientConfigWithDefaultValues()); } public static NettyHttpClient createDefaultSSEClient(ILoadBalancer lb, IClientConfig config, RetryHandler handler) { return new SSEClient(lb, config, DEFAULT_SSE_PIPELINE_CONFIGURATOR, handler); } public NettyHttpClient(ILoadBalancer lb, PipelineConfigurator, HttpClientRequest> pipeLineConfigurator) { this(lb, DefaultClientConfigImpl.getClientConfigWithDefaultValues(), pipeLineConfigurator); } public NettyHttpClient(ILoadBalancer lb, IClientConfig config, PipelineConfigurator, HttpClientRequest> pipeLineConfigurator) { this(lb, config, pipeLineConfigurator, null); } public NettyHttpClient(ILoadBalancer lb, IClientConfig config, PipelineConfigurator, HttpClientRequest> pipelineConfigurator, RetryHandler errorHandler) { super(config); Preconditions.checkNotNull(pipelineConfigurator); this.pipeLineConfigurator = pipelineConfigurator; RetryHandler handler = (errorHandler == null) ? new NettyHttpLoadBalancerErrorHandler(config) : errorHandler; lbExecutor = new LoadBalancerExecutor(lb, config, handler); } private RequestSpecificRetryHandler getRequestRetryHandler(HttpClientRequest request, IClientConfig requestConfig) { boolean okToRetryOnAllErrors = request.getMethod().equals(HttpMethod.GET); return new RequestSpecificRetryHandler(true, okToRetryOnAllErrors, lbExecutor.getErrorHandler(), 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 RepeatableContentHttpRequest repeatableRequest = getRepeatableRequest(request); final RetryHandler retryHandler = (errorHandler == null) ? getRequestRetryHandler(request, requestConfig) : errorHandler; return lbExecutor.executeWithLoadBalancer(new ClientObservableProvider>() { @Override public Observable> getObservableForEndpoint( Server server) { return submit(server.getHost(), server.getPort(), repeatableRequest, requestConfig); } }, 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(HttpClientRequest request, final ClientConfig config) { final RepeatableContentHttpRequest repeatableRequest = getRepeatableRequest(request); return lbExecutor.executeWithLoadBalancer(new ClientObservableProvider>() { @Override public Observable> getObservableForEndpoint( Server server) { return submit(server.getHost(), server.getPort(), repeatableRequest, config); } }); } /** * Create an {@link ObservableConnection} with a server chosen by the load balancer. */ @Override public Observable, HttpClientRequest>> connect() { return lbExecutor.executeWithLoadBalancer(new ClientObservableProvider, HttpClientRequest>>() { @Override public Observable, HttpClientRequest>> getObservableForEndpoint( Server server) { HttpClient rxClient = getRxClient(server.getHost(), server.getPort()); return rxClient.connect(); } }); } }