package com.intuit.tank.httpclient3;
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.io.UnsupportedEncodingException;
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 org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.PartSource;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.intuit.tank.http.AuthCredentials;
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 TankHttpClient3 implements TankHttpClient {
static Logger logger = LogManager.getLogger(TankHttpClient3.class);
private HttpClient httpclient;
/**
* no-arg constructor for client
*/
public TankHttpClient3() {
httpclient = new HttpClient();
httpclient.getParams().setCookiePolicy(org.apache.commons.httpclient.cookie.CookiePolicy.BROWSER_COMPATIBILITY);
httpclient.getParams().setBooleanParameter("http.protocol.single-cookie-header", true);
httpclient.getParams().setBooleanParameter("http.protocol.allow-circular-redirects", true);
httpclient.getParams().setIntParameter("http.protocol.max-redirects", 100);
httpclient.setState(new HttpState());
@SuppressWarnings("deprecation")
Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
Protocol.registerProtocol("https", easyhttps);
}
public void setConnectionTimeout(long connectionTimeout) {
httpclient.getParams().setConnectionManagerTimeout(connectionTimeout);
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#doGet(com.intuit.tank.http.
* BaseRequest)
*/
@Override
public void doGet(BaseRequest request) {
GetMethod httpget = new GetMethod(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) {
try {
PutMethod httpput = new PutMethod(request.getRequestUrl());
// Multiple calls can be expensive, so get it once
String requestBody = request.getBody();
StringRequestEntity entity = new StringRequestEntity(requestBody, request.getContentType(), request.getContentTypeCharSet());
httpput.setRequestEntity(entity);
sendRequest(request, httpput, requestBody);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
*
*/
@Override
public void setCookie(TankCookie cookie) {
Cookie c = new Cookie(cookie.getDomain(), cookie.getName(), cookie.getValue());
c.setPath(cookie.getPath());
httpclient.getState().addCookie(c);
}
/*
* (non-Javadoc)
*
* @see
* com.intuit.tank.httpclient3.TankHttpClient#doDelete(com.intuit.tank.http.
* BaseRequest)
*/
@Override
public void doDelete(BaseRequest request) {
DeleteMethod httpdelete = new DeleteMethod(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) {
try {
PostMethod httppost = new PostMethod(request.getRequestUrl());
String requestBody = request.getBody();
RequestEntity entity = null;
if (BaseRequest.CONTENT_TYPE_MULTIPART.equalsIgnoreCase(request.getContentType())) {
List<Part> parts = buildParts(request);
entity = new MultipartRequestEntity(parts.toArray(new Part[parts.size()]), httppost.getParams());
} else {
entity = new StringRequestEntity(requestBody, request.getContentType(), request.getContentTypeCharSet());
}
httppost.setRequestEntity(entity);
sendRequest(request, httppost, requestBody);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/*
* (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;
Credentials defaultcreds = new UsernamePasswordCredentials(creds.getUserName(), creds.getPassword());
httpclient.getState().setCredentials(new AuthScope(host, port, realm, scheme), defaultcreds);
}
/*
* (non-Javadoc)
*
* @see com.intuit.tank.httpclient3.TankHttpClient#clearSession()
*/
@Override
public void clearSession() {
httpclient.setState(new HttpState());
httpclient.getHttpConnectionManager().closeIdleConnections(0);
}
@Override
public void setProxy(String proxyhost, int proxyport) {
if (StringUtils.isNotBlank(proxyhost)) {
httpclient.getHostConfiguration().setProxy(proxyhost, proxyport);
} else {
httpclient.getHostConfiguration().setProxyHost(null);
}
}
private void sendRequest(BaseRequest request, @Nonnull HttpMethod method, String requestBody) {
String uri = null;
long waitTime = 0L;
try {
uri = method.getURI().toString();
logger.debug(request.getLogUtil().getLogMessage("About to " + method.getName() + " request to " + uri + " with requestBody " + requestBody, LogEventType.Informational));
List<String> cookies = new ArrayList<String>();
if (httpclient != null && httpclient.getState() != null && httpclient.getState().getCookies() != null) {
for (Cookie cookie : httpclient.getState().getCookies()) {
cookies.add("REQUEST COOKIE: " + cookie.toExternalForm() + " (domain=" + cookie.getDomain() + " : path=" + cookie.getPath() + ")");
}
}
request.logRequest(uri, requestBody, method.getName(), request.getHeaderInformation(), cookies, false);
setHeaders(request, method, request.getHeaderInformation());
long startTime = System.currentTimeMillis();
request.setTimestamp(new Date(startTime));
httpclient.executeMethod(method);
// read response body
byte[] responseBody = new byte[0];
// check for no content headers
if (method.getStatusCode() != 203 && method.getStatusCode() != 202 && method.getStatusCode() != 204) {
try {
InputStream httpInputStream = method.getResponseBodyAsStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
int curByte = httpInputStream.read();
while (curByte >= 0) {
out.write(curByte);
curByte = httpInputStream.read();
}
responseBody = out.toByteArray();
} catch (Exception e) {
logger.warn("could not get response body: " + e);
}
}
long endTime = System.currentTimeMillis();
processResponse(responseBody, startTime, endTime, request, method.getStatusText(), method.getStatusCode(), method.getResponseHeaders(), httpclient.getState());
waitTime = endTime - startTime;
} catch (Exception ex) {
logger.error(request.getLogUtil().getLogMessage("Could not do " + method.getName() + " to url " + uri + " | error: " + ex.toString(), LogEventType.IO), ex);
throw new RuntimeException(ex);
} finally {
try {
method.releaseConnection();
} catch (Exception e) {
logger.warn("Could not release connection: " + e, e);
}
if (method.getName().equalsIgnoreCase("post") && request.getLogUtil().getAgentConfig().getLogPostResponse()) {
logger.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);
logger.warn(request.getLogUtil().getLogMessage("Response time to slow | delaying " + waitTime + " ms | url --> " + uri, LogEventType.Script));
Thread.sleep(waitTime);
}
} catch (InterruptedException e) {
logger.warn("Interrupted", e);
}
}
/**
* Process the response data
*/
private void processResponse(byte[] bResponse, long startTime, long endTime, BaseRequest request, String message, int httpCode, Header[] headers, HttpState httpstate) {
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());
}
Cookie[] cookies = httpstate.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
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) {
logger.warn(request.getLogUtil().getLogMessage("cannot decode gzip stream: " + e, LogEventType.System));
}
}
response.setResponseBody(bResponse);
} catch (Exception ex) {
logger.warn("Unable to get response: " + ex.getMessage());
} finally {
response.logResponse();
}
}
/**
* Set all the header keys
*
* @param connection
*/
@SuppressWarnings("rawtypes")
private void setHeaders(BaseRequest request, HttpMethod 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.setRequestHeader((String) mapEntry.getKey(), (String) mapEntry.getValue());
}
} catch (Exception ex) {
logger.warn(request.getLogUtil().getLogMessage("Unable to set header: " + ex.getMessage(), LogEventType.System));
}
}
private List<Part> buildParts(BaseRequest request) {
List<Part> parts = new ArrayList<Part>();
for (PartHolder h : TankHttpUtil.getPartsFromBody(request)) {
if (h.getFileName() == null) {
StringPart stringPart = new StringPart(h.getPartName(), new String(h.getBodyAsString()));
if (h.isContentTypeSet()) {
stringPart.setContentType(h.getContentType());
}
parts.add(stringPart);
} else {
PartSource partSource = new ByteArrayPartSource(h.getFileName(), h.getBody());
FilePart p = new FilePart(h.getPartName(), partSource);
if (h.isContentTypeSet()) {
p.setContentType(h.getContentType());
}
parts.add(p);
}
}
return parts;
}
}