com.netflix.zuul.rxnetty.RxNettyOrigin Maven / Gradle / Ivy
/**
* Copyright 2015 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.zuul.rxnetty;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.zuul.rx.UnicastDisposableCachingSubject;
import com.netflix.zuul.context.HttpRequestMessage;
import com.netflix.zuul.context.HttpResponseMessage;
import com.netflix.zuul.context.SessionContext;
import com.netflix.zuul.metrics.OriginStats;
import com.netflix.zuul.origins.LoadBalancer;
import com.netflix.zuul.origins.Origin;
import com.netflix.zuul.origins.ServerInfo;
import com.netflix.zuul.stats.Timing;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.client.RxClient;
import io.reactivex.netty.metrics.MetricEventsListener;
import io.reactivex.netty.metrics.MetricEventsListenerFactory;
import io.reactivex.netty.protocol.http.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* User: [email protected]
* Date: 2/20/15
* Time: 2:19 PM
*/
public class RxNettyOrigin implements Origin {
private static final Logger LOG = LoggerFactory.getLogger(RxNettyOrigin.class);
private final DynamicIntProperty ORIGIN_MAX_CONNS_PER_HOST = DynamicPropertyFactory.getInstance().getIntProperty("origin.max_conns_per_host", 250);
private final DynamicIntProperty ORIGIN_READ_TIMEOUT = DynamicPropertyFactory.getInstance().getIntProperty("origin.read_timeout", 15000);
private final String name;
private final String vip;
private final LoadBalancer loadBalancer;
private final OriginStats stats;
private CompositeHttpClient client;
public RxNettyOrigin(String originName, String vip, LoadBalancer loadBalancer, OriginStats stats, MetricEventsListenerFactory metricEventsListenerFactory)
{
if (originName == null) {
throw new IllegalArgumentException("Requires a non-null originName.");
}
if (vip == null) {
throw new IllegalArgumentException("Requires a non-null VIP.");
}
this.name = originName;
this.vip = vip;
this.loadBalancer = loadBalancer;
this.stats = stats;
this.client = createCompositeHttpClient();
// Configure rxnetty httpclient metrics for this client.
MetricEventsListener httpClientListener = metricEventsListenerFactory.forHttpClient(client);
this.client.subscribe(httpClientListener);
}
@Override
public String getName() {
return name;
}
public String getVip() {
return vip;
}
public LoadBalancer getLoadBalancer() {
return loadBalancer;
}
@Override
public Observable request(HttpRequestMessage requestMsg)
{
ServerInfo serverInfo = getLoadBalancer().getNextServer();
HttpClientRequest clientRequest = RxNettyUtils.createHttpClientRequest(requestMsg);
// Convert to rxnetty ServerInfo impl.
RxClient.ServerInfo rxNettyServerInfo = new RxClient.ServerInfo(serverInfo.getHost(), serverInfo.getPort());
// Start timing.
SessionContext ctx = requestMsg.getContext();
final Timing timing = ctx.getTimings().getRequestProxy();
timing.start();
if (stats != null)
stats.started();
final AtomicBoolean isSuccess = new AtomicBoolean(true);
// Construct.
Observable> respObs = client.submit(rxNettyServerInfo, clientRequest)
.doOnError(t -> {
isSuccess.set(false);
// Flag this as a proxy failure in the RequestContext. Error filter will then use this flag.
ctx.setShouldSendErrorResponse(true);
LOG.error(String.format("Error making http request to Origin. vip=%s, url=%s, target-host=%s",
this.vip, requestMsg.getPathAndQuery(), serverInfo.getHost()), t);
}
)
.finallyDo(() -> {
timing.end();
if (stats != null)
stats.completed(isSuccess.get(), timing.getDuration());
});
return bufferHttpClientResponse(requestMsg, respObs);
}
private CompositeHttpClient createCompositeHttpClient()
{
// Override the max HTTP header size.
int maxHeaderSize = 20000;
HttpClientPipelineConfigurator clientPipelineConfigurator = new HttpClientPipelineConfigurator<>(
HttpClientPipelineConfigurator.MAX_INITIAL_LINE_LENGTH_DEFAULT,
maxHeaderSize,
HttpClientPipelineConfigurator.MAX_CHUNK_SIZE_DEFAULT,
// don't validate headers.
false);
CompositeHttpClient client = new CompositeHttpClientBuilder(new Bootstrap())
.withMaxConnections(ORIGIN_MAX_CONNS_PER_HOST.get())
.pipelineConfigurator(clientPipelineConfigurator)
.config(new HttpClient.HttpClientConfig.Builder()
.setFollowRedirect(false)
.readTimeout(ORIGIN_READ_TIMEOUT.get(), TimeUnit.MILLISECONDS)
.build()
)
.build();
return client;
}
protected Observable bufferHttpClientResponse(HttpRequestMessage zuulReq,
Observable> clientResp)
{
return clientResp.map(resp -> {
HttpResponseMessage zuulResp = RxNettyUtils.clientResponseToZuulResponse(zuulReq, resp);
//PublishSubject cachedContent = PublishSubject.create();
UnicastDisposableCachingSubject cachedContent = UnicastDisposableCachingSubject.create();
// Subscribe to the response-content observable (retaining the ByteBufS first).
Observable content = resp.getContent();
content.map(ByteBuf::retain).subscribe(cachedContent);
// Store this content ref on the zuul response object.
zuulResp.setBodyStream(cachedContent);
return zuulResp;
});
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy