/* * Copyright 2012-2015, the original author or 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 com.flipkart.phantom.http.impl; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.http.HttpResponse; import org.apache.http.client.utils.HttpClientUtils; import com.flipkart.phantom.event.ServiceProxyEvent; import com.flipkart.phantom.task.spi.Executor; import com.flipkart.phantom.task.spi.TaskContext; import com.flipkart.phantom.task.spi.interceptor.RequestInterceptor; import com.flipkart.phantom.task.spi.interceptor.ResponseInterceptor; import com.github.kristofa.brave.Brave; import com.google.common.base.Optional; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; /** * Implements the HystrixCommand class for executing HTTP proxy requests * * @author kartikbu * @version 1.0 * @created 16/7/13 1:54 AM */ public class HttpProxyExecutor extends HystrixCommand<HttpResponse> implements Executor<HttpRequestWrapper, HttpResponse> { /** Event Type for publishing all events which are generated here */ private final static String HTTP_HANDLER = "HTTP_HANDLER"; /** Http Request Wrapper */ private HttpRequestWrapper httpRequestWrapper; /** the proxy client */ private HttpProxy proxy; /** Event which records various parameters of this request execution & published later */ protected ServiceProxyEvent.Builder eventBuilder; /** List of request and response interceptors */ private List<RequestInterceptor<HttpRequestWrapper>> requestInterceptors = new LinkedList<RequestInterceptor<HttpRequestWrapper>>(); private List<ResponseInterceptor<HttpResponse>> responseInterceptors = new LinkedList<ResponseInterceptor<HttpResponse>>(); /** only constructor uses the proxy client, task context and the http requestWrapper */ public HttpProxyExecutor(HttpProxy proxy, TaskContext taskContext, HttpRequestWrapper httpRequestWrapper) { super( Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(proxy.getGroupKey())) .andCommandKey(HystrixCommandKey.Factory.asKey(proxy.getCommandKey())) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(proxy.getThreadPoolKey())) .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(proxy.getThreadPoolSize())) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds (proxy.getPool().getOperationTimeout()))); this.proxy = proxy; this.httpRequestWrapper = httpRequestWrapper; this.eventBuilder = new ServiceProxyEvent.Builder(this.httpRequestWrapper.getUri(), HTTP_HANDLER); } /** * Interface method implementation * @return response HttpResponse for the give request * @throws Exception */ @Override protected HttpResponse run() throws Exception { this.eventBuilder.withRequestExecutionStartTime(System.currentTimeMillis()); if (this.httpRequestWrapper.getRequestContext().isPresent() && this.httpRequestWrapper.getRequestContext().get().getCurrentServerSpan() != null) { Brave.getServerSpanThreadBinder().setCurrentSpan(this.httpRequestWrapper.getRequestContext().get().getCurrentServerSpan()); } for (RequestInterceptor<HttpRequestWrapper> requestInterceptor : this.requestInterceptors) { requestInterceptor.process(this.httpRequestWrapper); } Optional<RuntimeException> transportException = Optional.absent(); HttpResponse response = null; try { response = this.proxy.doRequest(this.httpRequestWrapper); } catch (RuntimeException e) { transportException = Optional.of(e); throw e; // rethrow this for it to handled by other layers in the call stack } finally { // close the response if the command timed out if (this.isResponseTimedOut()) { if( response!= null ) { HttpClientUtils.closeQuietly(response); } } for (ResponseInterceptor<HttpResponse> responseInterceptor : this.responseInterceptors) { responseInterceptor.process(response, transportException); } } return response; } /** * Interface method implementation. Returns the name of the {@link HttpProxy} used by this Executor * @see com.flipkart.phantom.task.spi.Executor#getServiceName() */ public Optional<String> getServiceName() { return Optional.of(this.proxy.getName()); } /** * Interface method implementation. Returns the HttpRequestWrapper instance that this Executor was created with * @see com.flipkart.phantom.task.spi.Executor#getRequestWrapper() */ public HttpRequestWrapper getRequestWrapper() { return this.httpRequestWrapper; } /** * Interface method implementation. Adds the RequestInterceptor to the list of request interceptors that will be invoked * @see com.flipkart.phantom.task.spi.Executor#addRequestInterceptor(com.flipkart.phantom.task.spi.interceptor.RequestInterceptor) */ public void addRequestInterceptor(RequestInterceptor<HttpRequestWrapper> requestInterceptor) { this.requestInterceptors.add(requestInterceptor); } /** * Interface method implementation. Adds the ResponseInterceptor to the list of response interceptors that will be invoked * @see com.flipkart.phantom.task.spi.Executor#addResponseInterceptor(com.flipkart.phantom.task.spi.interceptor.ResponseInterceptor) */ public void addResponseInterceptor(ResponseInterceptor<HttpResponse> responseInterceptor){ this.responseInterceptors.add(responseInterceptor); } /** * Interface method implementation * @return response HttpResponse from the fallback * @throws Exception */ @Override protected HttpResponse getFallback() { Map<String, Object> controlparams = new HashMap<String,Object>(); // check and populate execution error root cause, if any, for use in fallback if (this.isFailedExecution()) { controlparams.put(Executor.EXECUTION_ERROR_CAUSE, this.getFailedExecutionException()); } return proxy.fallbackRequest(this.httpRequestWrapper,controlparams); } /** * Retruns the event builder instance */ public ServiceProxyEvent.Builder getEventBuilder() { return eventBuilder; } }