package com.intuit.tank.httpclient4;
import java.io.ByteArrayInputStream;
/*
* #%L
* Intuit Tank Agent (apiharness)
* %%
* Copyright (C) 2011 - 2015 Intuit Inc.
* %%
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* #L%
*/
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nonnull;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.intuit.tank.http.AuthCredentials;
import com.intuit.tank.http.AuthScheme;
import com.intuit.tank.http.BaseRequest;
import com.intuit.tank.http.BaseResponse;
import com.intuit.tank.http.TankCookie;
import com.intuit.tank.http.TankHttpClient;
import com.intuit.tank.http.TankHttpUtil;
import com.intuit.tank.http.TankHttpUtil.PartHolder;
import com.intuit.tank.logging.LogEventType;
import com.intuit.tank.vm.settings.AgentConfig;
public class TankHttpClient4 implements TankHttpClient {
static Logger LOG = LogManager.getLogger(TankHttpClient4.class);
private CloseableHttpClient httpclient;
private HttpClientContext context;
private RequestConfig requestConfig;
private SSLConnectionSocketFactory sslsf;
/**
* no-arg constructor for client
*/
public TankHttpClient4() {
try {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
sslsf = new SSLConnectionSocketFactory(builder.build(), new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (Exception e) {
LOG.error("Error setting accept all: " + e, e);
}
httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
requestConfig = RequestConfig.custom().setSocketTimeout(30000)
.setConnectTimeout(30000)
.setCircularRedirectsAllowed(true)
.setAuthenticationEnabled(true)
.setRedirectsEnabled(true)
.setCookieSpec(CookieSpecs.DEFAULT)
.setMaxRedirects(100).build();
// Make sure the same context is used to execute logically related
// requests
context = HttpClientContext.create();
context.setCredentialsProvider(new BasicCredentialsProvider());
context.setCookieStore(new BasicCookieStore());
context.setRequestConfig(requestConfig);
}
public void setConnectionTimeout(long connectionTimeout) {
requestConfig = RequestConfig.custom().setSocketTimeout(30000)
.setConnectTimeout((int) connectionTimeout)
.setCircularRedirectsAllowed(true)
.setAuthenticationEnabled(true)
.setRedirectsEnabled(true)
.setCookieSpec(CookieSpecs.DEFAULT)
.setMaxRedirects(100).build();
context.setRequestConfig(requestConfig);
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#doGet(com.intuit.tank.http.
* BaseRequest)
*/
@Override
public void doGet(BaseRequest request) {
HttpGet httpget = new HttpGet(request.getRequestUrl());
sendRequest(request, httpget, request.getBody());
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#doPut(com.intuit.tank.http.
* BaseRequest)
*/
@Override
public void doPut(BaseRequest request) {
HttpPut httpput = new HttpPut(request.getRequestUrl());
// Multiple calls can be expensive, so get it once
String requestBody = request.getBody();
HttpEntity entity = new StringEntity(requestBody, ContentType.create(request.getContentType(), request.getContentTypeCharSet()));
httpput.setEntity(entity);
sendRequest(request, httpput, requestBody);
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#doDelete(com.intuit.tank.http.
* BaseRequest)
*/
@Override
public void doDelete(BaseRequest request) {
HttpDelete httpdelete = new HttpDelete(request.getRequestUrl());
// Multiple calls can be expensive, so get it once
String requestBody = request.getBody();
String type = request.getHeaderInformation().get("Content-Type");
if (StringUtils.isBlank(type)) {
request.getHeaderInformation().put("Content-Type", "application/x-www-form-urlencoded");
}
sendRequest(request, httpdelete, requestBody);
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#doPost(com.intuit.tank.http.
* BaseRequest)
*/
@Override
public void doPost(BaseRequest request) {
HttpPost httppost = new HttpPost(request.getRequestUrl());
String requestBody = request.getBody();
HttpEntity entity = null;
if (BaseRequest.CONTENT_TYPE_MULTIPART.equalsIgnoreCase(request.getContentType())) {
entity = buildParts(request);
} else {
entity = new StringEntity(requestBody, ContentType.create(request.getContentType(), request.getContentTypeCharSet()));
}
httppost.setEntity(entity);
sendRequest(request, httppost, requestBody);
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#addAuth(com.intuit.tank.http.
* AuthCredentials)
*/
@Override
public void addAuth(AuthCredentials creds) {
String host = (StringUtils.isBlank(creds.getHost()) || "*".equals(creds.getHost())) ? AuthScope.ANY_HOST : creds.getHost();
String realm = (StringUtils.isBlank(creds.getRealm()) || "*".equals(creds.getRealm())) ? AuthScope.ANY_REALM : creds.getRealm();
int port = NumberUtils.toInt(creds.getPortString(), AuthScope.ANY_PORT);
String scheme = creds.getScheme() != null ? creds.getScheme().getRepresentation() : AuthScope.ANY_SCHEME;
AuthScope scope = new AuthScope(host, port, realm, scheme);
if (AuthScheme.NTLM == creds.getScheme()) {
context.getCredentialsProvider().setCredentials(scope, new NTCredentials(creds.getUserName(), creds.getPassword(), "tank-test", creds.getRealm()));
} else {
context.getCredentialsProvider().setCredentials(scope, new UsernamePasswordCredentials(creds.getUserName(), creds.getPassword()));
}
}
/*
* (non-Javadoc)
*
* @see com.intuit.tank.httpclient3.TankHttpClient#clearSession()
*/
@Override
public void clearSession() {
context.getCookieStore().clear();
}
/**
*
*/
@Override
public void setCookie(TankCookie cookie) {
BasicClientCookie c = new BasicClientCookie(cookie.getName(), cookie.getValue());
c.setDomain(cookie.getDomain());
c.setPath(cookie.getPath());
context.getCookieStore().addCookie(c);
}
@Override
public void setProxy(String proxyhost, int proxyport) {
if (StringUtils.isNotBlank(proxyhost)) {
HttpHost proxy = new HttpHost(proxyhost, proxyport);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).setRoutePlanner(routePlanner).build();
} else {
httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
}
}
private void sendRequest(BaseRequest request, @Nonnull HttpRequestBase method, String requestBody) {
String uri = null;
long waitTime = 0L;
CloseableHttpResponse response = null;
try {
uri = method.getURI().toString();
LOG.debug(request.getLogUtil().getLogMessage("About to " + method.getMethod() + " request to " + uri + " with requestBody " + requestBody, LogEventType.Informational));
List<String> cookies = new ArrayList<String>();
if (context.getCookieStore().getCookies() != null) {
for (Cookie cookie : context.getCookieStore().getCookies()) {
cookies.add("REQUEST COOKIE: " + cookie.toString());
}
}
request.logRequest(uri, requestBody, method.getMethod(), request.getHeaderInformation(), cookies, false);
setHeaders(request, method, request.getHeaderInformation());
long startTime = System.currentTimeMillis();
request.setTimestamp(new Date(startTime));
response = httpclient.execute(method, context);
// read response body
byte[] responseBody = new byte[0];
// check for no content headers
if (response.getStatusLine().getStatusCode() != 203 && response.getStatusLine().getStatusCode() != 202 && response.getStatusLine().getStatusCode() != 204) {
try {
InputStream httpInputStream = response.getEntity().getContent();
responseBody = IOUtils.toByteArray(httpInputStream);
} catch (Exception e) {
LOG.warn("could not get response body: " + e);
}
}
long endTime = System.currentTimeMillis();
processResponse(responseBody, startTime, endTime, request, response.getStatusLine().getReasonPhrase(), response.getStatusLine().getStatusCode(), response.getAllHeaders());
waitTime = endTime - startTime;
} catch (Exception ex) {
LOG.error(request.getLogUtil().getLogMessage("Could not do " + method.getMethod() + " to url " + uri + " | error: " + ex.toString(), LogEventType.IO), ex);
throw new RuntimeException(ex);
} finally {
try {
method.releaseConnection();
if (response != null) {
response.close();
}
} catch (Exception e) {
LOG.warn("Could not release connection: " + e, e);
}
if (method.getMethod().equalsIgnoreCase("post") && request.getLogUtil().getAgentConfig().getLogPostResponse()) {
LOG.info(request.getLogUtil().getLogMessage(
"Response from POST to " + request.getRequestUrl() + " got status code " + request.getResponse().getHttpCode() + " BODY { " + request.getResponse().getBody() + " }",
LogEventType.Informational));
}
}
if (waitTime != 0) {
doWaitDueToLongResponse(request, waitTime, uri);
}
}
/**
* Wait for the amount of time it took to get a response from the system if
* the response time is over some threshold specified in the properties
* file. This will ensure users don't bunch up together after a blip on the
* system under test
*
* @param responseTime
* - response time of the request; this will also be the time to
* sleep
* @param uri
*/
private void doWaitDueToLongResponse(BaseRequest request, long responseTime, String uri) {
try {
AgentConfig config = request.getLogUtil().getAgentConfig();
long maxAgentResponseTime = config.getMaxAgentResponseTime();
if (maxAgentResponseTime < responseTime) {
long waitTime = Math.min(config.getMaxAgentWaitTime(), responseTime);
LOG.warn(request.getLogUtil().getLogMessage("Response time to slow | delaying " + waitTime + " ms | url --> " + uri, LogEventType.Script));
Thread.sleep(waitTime);
}
} catch (InterruptedException e) {
LOG.warn("Interrupted", e);
}
}
/**
* Process the response data
*/
private void processResponse(byte[] bResponse, long startTime, long endTime, BaseRequest request, String message, int httpCode, Header[] headers) {
BaseResponse response = request.getResponse();
try {
if (response == null) {
// Get response header information
String contentType = "";
for (Header h : headers) {
if ("ContentType".equalsIgnoreCase(h.getName())) {
contentType = h.getValue();
break;
}
}
response = TankHttpUtil.newResponseObject(contentType);
request.setResponse(response);
}
// Get response detail information
response.setHttpMessage(message);
response.setHttpCode(httpCode);
// Get response header information
for (int h = 0; h < headers.length; h++) {
response.setHeader(headers[h].getName(), headers[h].getValue());
}
if (context.getCookieStore().getCookies() != null) {
for (Cookie cookie : context.getCookieStore().getCookies()) {
response.setCookie(cookie.getName(), cookie.getValue());
}
}
response.setResponseTime(endTime - startTime);
String contentType = response.getHttpHeader("Content-Type");
String contentEncode = response.getHttpHeader("Content-Encoding");
if (BaseResponse.isDataType(contentType) && contentEncode != null && contentEncode.toLowerCase().contains("gzip")) {
// decode gzip for data types
try {
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(bResponse));
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
bResponse = out.toByteArray();
} catch (Exception e) {
LOG.warn(request.getLogUtil().getLogMessage("cannot decode gzip stream: " + e, LogEventType.System));
}
}
response.setResponseBody(bResponse);
} catch (Exception ex) {
LOG.warn("Unable to get response: " + ex.getMessage());
} finally {
response.logResponse();
}
}
/**
* Set all the header keys
*
* @param connection
*/
@SuppressWarnings("rawtypes")
private void setHeaders(BaseRequest request, HttpRequestBase method, HashMap<String, String> headerInformation) {
try {
Set set = headerInformation.entrySet();
Iterator iter = set.iterator();
while (iter.hasNext()) {
Map.Entry mapEntry = (Map.Entry) iter.next();
method.setHeader((String) mapEntry.getKey(), (String) mapEntry.getValue());
}
} catch (Exception ex) {
LOG.warn(request.getLogUtil().getLogMessage("Unable to set header: " + ex.getMessage(), LogEventType.System));
}
}
private HttpEntity buildParts(BaseRequest request) {
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
for (PartHolder h : TankHttpUtil.getPartsFromBody(request)) {
if (h.getFileName() == null) {
if (h.isContentTypeSet()) {
builder.addTextBody(h.getPartName(), new String(h.getBodyAsString()), ContentType.create(h.getContentType()));
} else {
builder.addTextBody(h.getPartName(), new String(h.getBodyAsString()));
}
} else {
if (h.isContentTypeSet()) {
builder.addBinaryBody(h.getPartName(), h.getBody(), ContentType.create(h.getContentType()), h.getFileName());
} else {
builder.addBinaryBody(h.getFileName(), h.getBody());
}
}
}
return builder.build();
}
}