/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.impl.wsdl.submit.transports.http;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.AbstractHttpRequestInterface;
import com.eviware.soapui.impl.support.HttpUtils;
import com.eviware.soapui.impl.support.http.HttpRequestInterface;
import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.submit.RequestFilter;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.attachments.MimeMessageResponse;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedDeleteMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedGetMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedHeadMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedOptionsMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPatchMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPostMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPutMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedTraceMethod;
import com.eviware.soapui.impl.wsdl.support.PathUtils;
import com.eviware.soapui.impl.wsdl.support.http.HeaderRequestInterceptor;
import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport;
import com.eviware.soapui.impl.wsdl.support.http.SoapUIHttpRoute;
import com.eviware.soapui.impl.wsdl.support.wss.WssCrypto;
import com.eviware.soapui.model.iface.Request;
import com.eviware.soapui.model.iface.Response;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.settings.Settings;
import com.eviware.soapui.model.support.ModelSupport;
import com.eviware.soapui.settings.HttpSettings;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.types.StringToStringsMap;
import org.apache.commons.httpclient.URI;
import org.apache.http.Header;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import javax.annotation.CheckForNull;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
/**
* HTTP transport that uses HttpClient to send/receive SOAP messages
*
* @author Ole.Matzura
*/
public class HttpClientRequestTransport implements BaseHttpRequestTransport {
private List<RequestFilter> filters = new ArrayList<RequestFilter>();
public HttpClientRequestTransport() {
}
public void addRequestFilter(RequestFilter filter) {
filters.add(filter);
}
public void removeRequestFilter(RequestFilter filterToRemove) {
if (!filters.remove(filterToRemove)) {
for (RequestFilter requestFilter : filters) {
if (requestFilter.getClass().equals(filterToRemove.getClass())) {
filters.remove(requestFilter);
break;
}
}
}
}
@Override
public void insertRequestFilter(RequestFilter filter, RequestFilter refFilter) {
int ix = filters.indexOf( refFilter );
if( ix == -1 )
filters.add( filter );
else
filters.add( ix, filter );
}
public <T> void removeRequestFilter(Class<T> filterClass) {
RequestFilter filter = findFilterByType(filterClass);
if (filter != null) {
removeRequestFilter(filter);
}
}
public <T> void replaceRequestFilter(Class<T> filterClass, RequestFilter newFilter) {
RequestFilter filter = findFilterByType(filterClass);
if (filter != null) {
for (int i = 0; i < filters.size(); i++) {
RequestFilter oldFilter = filters.get(i);
if (oldFilter == filter) {
filters.remove(i);
filters.add(i, newFilter);
break;
}
}
}
}
@CheckForNull
public <T extends Object> RequestFilter findFilterByType(Class<T> filterType) {
for (int i = 0; i < filters.size(); i++) {
RequestFilter filter = filters.get(i);
if (filter.getClass() == filterType) {
return filter;
}
}
return null;
}
public void abortRequest(SubmitContext submitContext) {
HttpRequestBase postMethod = (HttpRequestBase) submitContext.getProperty(HTTP_METHOD);
if (postMethod != null) {
postMethod.abort();
}
}
public Response sendRequest(SubmitContext submitContext, Request request) throws Exception {
AbstractHttpRequestInterface<?> httpRequest = (AbstractHttpRequestInterface<?>) request;
HttpClientSupport.SoapUIHttpClient httpClient = getSoapUIHttpClient();
ExtendedHttpMethod httpMethod = createHttpMethod(httpRequest);
boolean createdContext = false;
HttpContext httpContext = (HttpContext) submitContext.getProperty(SubmitContext.HTTP_STATE_PROPERTY);
if (httpContext == null) {
httpContext = HttpClientSupport.createEmptyContext();
submitContext.setProperty(SubmitContext.HTTP_STATE_PROPERTY, httpContext);
createdContext = true;
}
String localAddress = System.getProperty("soapui.bind.address", httpRequest.getBindAddress());
if (localAddress == null || localAddress.trim().length() == 0) {
localAddress = SoapUI.getSettings().getString(HttpSettings.BIND_ADDRESS, null);
}
org.apache.http.HttpResponse httpResponse;
if (localAddress != null && localAddress.trim().length() > 0) {
try {
httpMethod.getParams().setParameter(ConnRoutePNames.LOCAL_ADDRESS, InetAddress.getByName(localAddress));
} catch (Exception e) {
SoapUI.logError(e, "Failed to set localAddress to [" + localAddress + "]");
}
}
submitContext.removeProperty(RESPONSE);
submitContext.setProperty(HTTP_METHOD, httpMethod);
submitContext.setProperty(POST_METHOD, httpMethod);
submitContext.setProperty(HTTP_CLIENT, httpClient);
submitContext.setProperty(REQUEST_CONTENT, httpRequest.getRequestContent());
submitContext.setProperty(WSDL_REQUEST, httpRequest);
submitContext.setProperty(RESPONSE_PROPERTIES, new StringToStringMap());
for (RequestFilter filter : filters) {
filter.filterRequest(submitContext, httpRequest);
}
try {
Settings settings = httpRequest.getSettings();
// custom http headers last so they can be overridden
StringToStringsMap headers = httpRequest.getRequestHeaders();
// clear headers specified in GUI, and re-add them, with property expansion
for (String headerName : headers.keySet()) {
String expandedHeaderName = PropertyExpander.expandProperties(submitContext, headerName);
httpMethod.removeHeaders(expandedHeaderName);
for (String headerValue : headers.get(headerName)) {
headerValue = PropertyExpander.expandProperties(submitContext, headerValue);
httpMethod.addHeader(expandedHeaderName, headerValue);
}
}
// do request
WsdlProject project = (WsdlProject) ModelSupport.getModelItemProject(httpRequest);
WssCrypto crypto = null;
if (project != null && project.getWssContainer() != null) {
crypto = project.getWssContainer().getCryptoByName(
PropertyExpander.expandProperties(submitContext, httpRequest.getSslKeystore()));
}
if (crypto != null && WssCrypto.STATUS_OK.equals(crypto.getStatus())) {
httpMethod.getParams().setParameter(SoapUIHttpRoute.SOAPUI_SSL_CONFIG,
crypto.getSource() + " " + crypto.getPassword());
}
// dump file?
httpMethod.setDumpFile(PathUtils.expandPath(httpRequest.getDumpFile(),
(AbstractWsdlModelItem<?>) httpRequest, submitContext));
// include request time?
if (settings.getBoolean(HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN)) {
httpMethod.initStartTime();
}
if (httpMethod.getMetrics() != null) {
httpMethod.getMetrics().setHttpMethod(httpMethod.getMethod());
captureMetrics(httpMethod, httpClient);
httpMethod.getMetrics().getTotalTimer().start();
}
// submit!
httpResponse = submitRequest(httpMethod, httpContext);
// save request headers captured by interceptor
saveRequestHeaders(httpMethod, httpContext);
if (httpMethod.getMetrics() != null) {
httpMethod.getMetrics().getReadTimer().stop();
httpMethod.getMetrics().getTotalTimer().stop();
}
if (isRedirectResponse(httpResponse.getStatusLine().getStatusCode()) && httpRequest.isFollowRedirects()) {
if (httpResponse.getEntity() != null) {
EntityUtils.consume(httpResponse.getEntity());
}
httpMethod = followRedirects(httpClient, 0, httpMethod, httpResponse, httpContext);
submitContext.setProperty(HTTP_METHOD, httpMethod);
}
} catch (Throwable t) {
httpMethod.setFailed(t);
if (t instanceof Exception) {
throw (Exception) t;
}
SoapUI.logError(t);
throw new Exception(t);
} finally {
if (!httpMethod.isFailed()) {
if (httpMethod.getMetrics() != null) {
if (httpMethod.getMetrics().getReadTimer().getStop() == 0) {
httpMethod.getMetrics().getReadTimer().stop();
}
if (httpMethod.getMetrics().getTotalTimer().getStop() == 0) {
httpMethod.getMetrics().getTotalTimer().stop();
}
}
} else {
httpMethod.getMetrics().reset();
httpMethod.getMetrics().setTimestamp(System.currentTimeMillis());
captureMetrics(httpMethod, httpClient);
}
for (int c = filters.size() - 1; c >= 0; c--) {
RequestFilter filter = filters.get(c);
filter.afterRequest(submitContext, httpRequest);
}
if (!submitContext.hasProperty(RESPONSE)) {
createDefaultResponse(submitContext, httpRequest, httpMethod);
}
Response response = (Response) submitContext.getProperty(BaseHttpRequestTransport.RESPONSE);
StringToStringMap responseProperties = (StringToStringMap) submitContext
.getProperty(BaseHttpRequestTransport.RESPONSE_PROPERTIES);
for (String key : responseProperties.keySet()) {
response.setProperty(key, responseProperties.get(key));
}
if (createdContext) {
submitContext.setProperty(SubmitContext.HTTP_STATE_PROPERTY, null);
}
}
return (Response) submitContext.getProperty(BaseHttpRequestTransport.RESPONSE);
}
protected org.apache.http.HttpResponse submitRequest(ExtendedHttpMethod httpMethod, HttpContext httpContext) throws IOException {
return HttpClientSupport.execute(httpMethod, httpContext);
}
protected HttpClientSupport.SoapUIHttpClient getSoapUIHttpClient() {
return HttpClientSupport.getHttpClient();
}
private boolean isRedirectResponse(int statusCode) {
switch (statusCode) {
case 301:
case 302:
case 303:
case 307:
return true;
}
return false;
}
private ExtendedGetMethod followRedirects(HttpClient httpClient, int redirectCount, ExtendedHttpMethod httpMethod,
org.apache.http.HttpResponse httpResponse, HttpContext httpContext) throws Exception {
ExtendedGetMethod getMethod = new ExtendedGetMethod();
getMethod
.getMetrics()
.getTotalTimer()
.set(httpMethod.getMetrics().getTotalTimer().getStart(), httpMethod.getMetrics().getTotalTimer().getStop());
getMethod.getMetrics().setHttpMethod(httpMethod.getMethod());
captureMetrics(httpMethod, httpClient);
String location = httpResponse.getFirstHeader("Location").getValue();
URI uri = new URI(new URI(httpMethod.getURI().toString(), true), location, true);
java.net.URI newUri = HttpUtils.createUri(uri.getScheme(), uri.getEscapedUserinfo(), uri.getHost(), uri.getPort(),
uri.getEscapedPath(), uri.getEscapedQuery(), uri.getEscapedFragment());
getMethod.setURI(newUri);
// Thijs Brentjens: if the location contains a different Host, due to the redirect, then use that instead of the already existing Host-header. So just set the Host header to the new host of the URI
getMethod.setHeader("Host",uri.getHost());
org.apache.http.HttpResponse response = submitRequest(getMethod, httpContext);
if (isRedirectResponse(response.getStatusLine().getStatusCode())) {
if (redirectCount == 10) {
throw new Exception("Maximum number of Redirects reached [10]");
}
try {
getMethod = followRedirects(httpClient, redirectCount + 1, getMethod, response, httpContext);
} finally {
//TODO: check if this is necessary!
//getMethod.releaseConnection();
}
}
for (Header header : httpMethod.getAllHeaders()) {
getMethod.addHeader(header);
}
return getMethod;
}
private void createDefaultResponse(SubmitContext submitContext, AbstractHttpRequestInterface<?> httpRequest,
ExtendedHttpMethod httpMethod) {
String requestContent = (String) submitContext.getProperty(BaseHttpRequestTransport.REQUEST_CONTENT);
// check content-type for multipart
String responseContentTypeHeader = null;
if (httpMethod.hasHttpResponse() && httpMethod.getHttpResponse().getEntity() != null) {
Header h = httpMethod.getHttpResponse().getEntity().getContentType();
responseContentTypeHeader = h.toString();
}
Response response;
if (responseContentTypeHeader != null && responseContentTypeHeader.toUpperCase().startsWith("MULTIPART")) {
response = new MimeMessageResponse(httpRequest, httpMethod, requestContent, submitContext);
} else {
response = new SinglePartHttpResponse(httpRequest, httpMethod, requestContent, submitContext);
}
submitContext.setProperty(BaseHttpRequestTransport.RESPONSE, response);
}
private ExtendedHttpMethod createHttpMethod(AbstractHttpRequestInterface<?> httpRequest) {
if (httpRequest instanceof HttpRequestInterface<?>) {
HttpRequestInterface<?> restRequest = (HttpRequestInterface<?>) httpRequest;
switch (restRequest.getMethod()) {
case GET:
return new ExtendedGetMethod();
case HEAD:
return new ExtendedHeadMethod();
case DELETE:
return new ExtendedDeleteMethod();
case PUT:
return new ExtendedPutMethod();
case OPTIONS:
return new ExtendedOptionsMethod();
case TRACE:
return new ExtendedTraceMethod();
case PATCH:
return new ExtendedPatchMethod();
}
}
ExtendedPostMethod extendedPostMethod = new ExtendedPostMethod();
extendedPostMethod.setAfterRequestInjection(httpRequest.getAfterRequestInjection());
return extendedPostMethod;
}
private void captureMetrics(ExtendedHttpMethod httpMethod, HttpClient httpClient) {
try {
httpMethod.getMetrics().setIpAddress(InetAddress.getByName(httpMethod.getURI().getHost()).getHostAddress());
httpMethod.getMetrics().setPort(
httpMethod.getURI().getPort(),
getDefaultHttpPort(httpMethod, httpClient));
} catch (UnknownHostException uhe) {
/* ignore */
} catch (IllegalStateException ise) {
/* ignore */
}
}
protected int getDefaultHttpPort(ExtendedHttpMethod httpMethod, HttpClient httpClient) {
return httpClient.getConnectionManager().getSchemeRegistry().getScheme(httpMethod.getURI().getScheme())
.getDefaultPort();
}
private void saveRequestHeaders(ExtendedHttpMethod httpMethod, HttpContext httpContext) {
List<Header> requestHeaders = (List<Header>) httpContext
.getAttribute(HeaderRequestInterceptor.SOAPUI_REQUEST_HEADERS);
if (requestHeaders != null) {
for (Header header : requestHeaders) {
Header[] existingHeaders = httpMethod.getHeaders(header.getName());
int c = 0;
for (; c < existingHeaders.length; c++) {
if (existingHeaders[c].getValue().equals(header.getValue())) {
break;
}
}
if (c == existingHeaders.length) {
httpMethod.addHeader(header);
}
}
}
}
}