Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.jmeter.protocol.http.sampler; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.URI; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.Charset; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.security.auth.Subject; import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.HttpConnection; import org.apache.http.HttpConnectionMetrics; import org.apache.http.HttpEntity; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.NTCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPatch; 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.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.protocol.ResponseContentEncoding; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.entity.ContentType; import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.FormBodyPart; import org.apache.http.entity.mime.FormBodyPartBuilder; import org.apache.http.entity.mime.MIME; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.client.DefaultClientConnectionReuseStrategy; import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.conn.SystemDefaultDnsResolver; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.DefaultedHttpParams; import org.apache.http.params.HttpParams; import org.apache.http.params.SyncBasicHttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpCoreContext; import org.apache.jmeter.protocol.http.control.AuthManager; import org.apache.jmeter.protocol.http.control.CacheManager; import org.apache.jmeter.protocol.http.control.CookieManager; import org.apache.jmeter.protocol.http.control.HeaderManager; import org.apache.jmeter.protocol.http.util.EncoderCache; import org.apache.jmeter.protocol.http.util.HTTPArgument; import org.apache.jmeter.protocol.http.util.HTTPConstants; import org.apache.jmeter.protocol.http.util.HTTPFileArg; import org.apache.jmeter.protocol.http.util.SlowHC4SocketFactory; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.services.FileServer; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.testelement.property.PropertyIterator; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.util.JsseSSLManager; import org.apache.jmeter.util.SSLManager; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; /** * HTTP Sampler using Apache HttpClient 4.x. * */ public class HTTPHC4Impl extends HTTPHCAbstractImpl { private static final Logger log = LoggingManager.getLoggerForClass(); /** retry count to be used (default 0); 0 = disable retries */ private static final int RETRY_COUNT = JMeterUtils.getPropDefault("httpclient4.retrycount", 0); /** Idle timeout to be applied to connections if no Keep-Alive header is sent by the server (default 0 = disable) */ private static final int IDLE_TIMEOUT = JMeterUtils.getPropDefault("httpclient4.idletimeout", 0); private static final int VALIDITY_AFTER_INACTIVITY_TIMEOUT = JMeterUtils .getPropDefault("httpclient4.validate_after_inactivity", 2000); private static final int TIME_TO_LIVE = JMeterUtils.getPropDefault("httpclient4.time_to_live", 2000); private static final String CONTEXT_METRICS = "jmeter_metrics"; // TODO hack for metrics related to HTTPCLIENT-1081, to be removed later private static final ConnectionKeepAliveStrategy IDLE_STRATEGY = new DefaultConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { long duration = super.getKeepAliveDuration(response, context); if (duration <= 0 && IDLE_TIMEOUT > 0) {// none found by the superclass if (log.isDebugEnabled()) { log.debug("Setting keepalive to " + IDLE_TIMEOUT); } return IDLE_TIMEOUT; } return duration; // return the super-class value } }; /** * Special interceptor made to keep metrics when connection is released for some method like HEAD * Otherwise calling directly ((HttpConnection) localContext.getAttribute(HttpCoreContext.HTTP_CONNECTION)).getMetrics(); * would throw org.apache.http.impl.conn.ConnectionShutdownException * See <a href="https://bz.apache.org/jira/browse/HTTPCLIENT-1081">HTTPCLIENT-1081</a> */ private static final HttpResponseInterceptor METRICS_SAVER = new HttpResponseInterceptor() { @Override public void process(HttpResponse response, HttpContext context) throws HttpException, IOException { HttpConnectionMetrics metrics = ((HttpConnection) context.getAttribute(HttpCoreContext.HTTP_CONNECTION)) .getMetrics(); context.setAttribute(CONTEXT_METRICS, metrics); } }; private static final HttpRequestInterceptor METRICS_RESETTER = new HttpRequestInterceptor() { @Override public void process(HttpRequest request, HttpContext context) throws HttpException, IOException { HttpConnectionMetrics metrics = ((HttpConnection) context.getAttribute(HttpCoreContext.HTTP_CONNECTION)) .getMetrics(); metrics.reset(); } }; /** * Headers to save */ private static final String[] HEADERS_TO_SAVE = new String[] { "content-length", "content-encoding", "content-md5" }; /** * Custom implementation that backups headers related to Compressed responses * that HC core {@link ResponseContentEncoding} removes after uncompressing * See Bug 59401 */ private static final HttpResponseInterceptor RESPONSE_CONTENT_ENCODING = new ResponseContentEncoding() { @Override public void process(HttpResponse response, HttpContext context) throws HttpException, IOException { ArrayList<Header[]> headersToSave = null; final HttpEntity entity = response.getEntity(); final HttpClientContext clientContext = HttpClientContext.adapt(context); final RequestConfig requestConfig = clientContext.getRequestConfig(); // store the headers if necessary if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) { final Header ceheader = entity.getContentEncoding(); if (ceheader != null) { headersToSave = new ArrayList<>(3); for (String name : HEADERS_TO_SAVE) { Header[] hdr = response.getHeaders(name); // empty if none headersToSave.add(hdr); } } } // Now invoke original parent code super.process(response, clientContext); // Should this be in a finally ? if (headersToSave != null) { for (Header[] headers : headersToSave) { for (Header headerToRestore : headers) { if (response.containsHeader(headerToRestore.getName())) { break; } response.addHeader(headerToRestore); } } } } }; /** * 1 HttpClient instance per combination of (HttpClient,HttpClientKey) */ private static final ThreadLocal<Map<HttpClientKey, HttpClient>> HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY = new InheritableThreadLocal<Map<HttpClientKey, HttpClient>>() { @Override protected Map<HttpClientKey, HttpClient> initialValue() { return new HashMap<>(); } }; // Scheme used for slow HTTP sockets. Cannot be set as a default, because must be set on an HttpClient instance. private static final Scheme SLOW_HTTP; /* * Create a set of default parameters from the ones initially created. * This allows the defaults to be overridden if necessary from the properties file. */ private static final HttpParams DEFAULT_HTTP_PARAMS; private static final String USER_TOKEN = "__jmeter.USER_TOKEN__"; //$NON-NLS-1$ static final String SAMPLER_RESULT_TOKEN = "__jmeter.SAMPLER_RESULT__"; //$NON-NLS-1$ private static final String HTTPCLIENT_TOKEN = "__jmeter.HTTPCLIENT_TOKEN__"; static { log.info("HTTP request retry count = " + RETRY_COUNT); DEFAULT_HTTP_PARAMS = new SyncBasicHttpParams(); // Could we drop the Sync here? DEFAULT_HTTP_PARAMS.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false); DEFAULT_HTTP_PARAMS.setIntParameter(ClientPNames.MAX_REDIRECTS, HTTPSamplerBase.MAX_REDIRECTS); DefaultHttpClient.setDefaultHttpParams(DEFAULT_HTTP_PARAMS); // Process Apache HttpClient parameters file String file = JMeterUtils.getProperty("hc.parameters.file"); // $NON-NLS-1$ if (file != null) { HttpClientDefaultParameters.load(file, DEFAULT_HTTP_PARAMS); } // Set up HTTP scheme override if necessary if (CPS_HTTP > 0) { log.info("Setting up HTTP SlowProtocol, cps=" + CPS_HTTP); SLOW_HTTP = new Scheme(HTTPConstants.PROTOCOL_HTTP, HTTPConstants.DEFAULT_HTTP_PORT, new SlowHC4SocketFactory(CPS_HTTP)); } else { SLOW_HTTP = null; } if (localAddress != null) { DEFAULT_HTTP_PARAMS.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); } } private volatile HttpUriRequest currentRequest; // Accessed from multiple threads private volatile boolean resetSSLContext; protected HTTPHC4Impl(HTTPSamplerBase testElement) { super(testElement); } public static final class HttpDelete extends HttpEntityEnclosingRequestBase { public HttpDelete(final URI uri) { super(); setURI(uri); } @Override public String getMethod() { return HTTPConstants.DELETE; } } @Override protected HTTPSampleResult sample(URL url, String method, boolean areFollowingRedirect, int frameDepth) { if (log.isDebugEnabled()) { log.debug("Start : sample " + url.toString()); log.debug("method " + method + " followingRedirect " + areFollowingRedirect + " depth " + frameDepth); } HTTPSampleResult res = createSampleResult(url, method); HttpClient httpClient = setupClient(url, res); HttpRequestBase httpRequest = null; try { URI uri = url.toURI(); if (method.equals(HTTPConstants.POST)) { httpRequest = new HttpPost(uri); } else if (method.equals(HTTPConstants.GET)) { httpRequest = new HttpGet(uri); } else if (method.equals(HTTPConstants.PUT)) { httpRequest = new HttpPut(uri); } else if (method.equals(HTTPConstants.HEAD)) { httpRequest = new HttpHead(uri); } else if (method.equals(HTTPConstants.TRACE)) { httpRequest = new HttpTrace(uri); } else if (method.equals(HTTPConstants.OPTIONS)) { httpRequest = new HttpOptions(uri); } else if (method.equals(HTTPConstants.DELETE)) { httpRequest = new HttpDelete(uri); } else if (method.equals(HTTPConstants.PATCH)) { httpRequest = new HttpPatch(uri); } else if (HttpWebdav.isWebdavMethod(method)) { httpRequest = new HttpWebdav(method, uri); } else { throw new IllegalArgumentException("Unexpected method: '" + method + "'"); } setupRequest(url, httpRequest, res); // can throw IOException } catch (Exception e) { res.sampleStart(); res.sampleEnd(); errorResult(e, res); return res; } HttpContext localContext = new BasicHttpContext(); setupClientContextBeforeSample(localContext); res.sampleStart(); final CacheManager cacheManager = getCacheManager(); if (cacheManager != null && HTTPConstants.GET.equalsIgnoreCase(method)) { if (cacheManager.inCache(url)) { return updateSampleResultForResourceInCache(res); } } try { currentRequest = httpRequest; handleMethod(method, res, httpRequest, localContext); // store the SampleResult in LocalContext to compute connect time localContext.setAttribute(SAMPLER_RESULT_TOKEN, res); // perform the sample HttpResponse httpResponse = executeRequest(httpClient, httpRequest, localContext, url); // Needs to be done after execute to pick up all the headers final HttpRequest request = (HttpRequest) localContext.getAttribute(HttpCoreContext.HTTP_REQUEST); extractClientContextAfterSample(localContext); // We've finished with the request, so we can add the LocalAddress to it for display final InetAddress localAddr = (InetAddress) httpRequest.getParams() .getParameter(ConnRoutePNames.LOCAL_ADDRESS); if (localAddr != null) { request.addHeader(HEADER_LOCAL_ADDRESS, localAddr.toString()); } res.setRequestHeaders(getConnectionHeaders(request)); Header contentType = httpResponse.getLastHeader(HTTPConstants.HEADER_CONTENT_TYPE); if (contentType != null) { String ct = contentType.getValue(); res.setContentType(ct); res.setEncodingAndType(ct); } HttpEntity entity = httpResponse.getEntity(); if (entity != null) { res.setResponseData(readResponse(res, entity.getContent(), (int) entity.getContentLength())); } res.sampleEnd(); // Done with the sampling proper. currentRequest = null; // Now collect the results into the HTTPSampleResult: StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); res.setResponseCode(Integer.toString(statusCode)); res.setResponseMessage(statusLine.getReasonPhrase()); res.setSuccessful(isSuccessCode(statusCode)); res.setResponseHeaders(getResponseHeaders(httpResponse, localContext)); if (res.isRedirect()) { final Header headerLocation = httpResponse.getLastHeader(HTTPConstants.HEADER_LOCATION); if (headerLocation == null) { // HTTP protocol violation, but avoids NPE throw new IllegalArgumentException( "Missing location header in redirect for " + httpRequest.getRequestLine()); } String redirectLocation = headerLocation.getValue(); res.setRedirectLocation(redirectLocation); } // record some sizes to allow HTTPSampleResult.getBytes() with different options HttpConnectionMetrics metrics = (HttpConnectionMetrics) localContext.getAttribute(CONTEXT_METRICS); long headerBytes = res.getResponseHeaders().length() // condensed length (without \r) + httpResponse.getAllHeaders().length // Add \r for each header + 1 // Add \r for initial header + 2; // final \r\n before data long totalBytes = metrics.getReceivedBytesCount(); res.setHeadersSize((int) headerBytes); res.setBodySize((int) (totalBytes - headerBytes)); if (log.isDebugEnabled()) { log.debug("ResponseHeadersSize=" + res.getHeadersSize() + " Content-Length=" + res.getBodySize() + " Total=" + (res.getHeadersSize() + res.getBodySize())); } // If we redirected automatically, the URL may have changed if (getAutoRedirects()) { HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(HttpCoreContext.HTTP_REQUEST); HttpHost target = (HttpHost) localContext.getAttribute(HttpCoreContext.HTTP_TARGET_HOST); URI redirectURI = req.getURI(); if (redirectURI.isAbsolute()) { res.setURL(redirectURI.toURL()); } else { res.setURL(new URL(new URL(target.toURI()), redirectURI.toString())); } } // Store any cookies received in the cookie manager: saveConnectionCookies(httpResponse, res.getURL(), getCookieManager()); // Save cache information if (cacheManager != null) { cacheManager.saveDetails(httpResponse, res); } // Follow redirects and download page resources if appropriate: res = resultProcessing(areFollowingRedirect, frameDepth, res); } catch (IOException e) { log.debug("IOException", e); if (res.getEndTime() == 0) { res.sampleEnd(); } // pick up headers if failed to execute the request if (res.getRequestHeaders() != null) { log.debug("Overwriting request old headers: " + res.getRequestHeaders()); } res.setRequestHeaders( getConnectionHeaders((HttpRequest) localContext.getAttribute(HttpCoreContext.HTTP_REQUEST))); errorResult(e, res); return res; } catch (RuntimeException e) { log.debug("RuntimeException", e); if (res.getEndTime() == 0) { res.sampleEnd(); } errorResult(e, res); return res; } finally { currentRequest = null; JMeterContextService.getContext().getSamplerContext().remove(HTTPCLIENT_TOKEN); } return res; } /** * Store in JMeter Variables the UserToken so that the SSL context is reused * See <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=57804">Bug 57804</a> * @param localContext {@link HttpContext} */ private void extractClientContextAfterSample(HttpContext localContext) { Object userToken = localContext.getAttribute(HttpClientContext.USER_TOKEN); if (userToken != null) { if (log.isDebugEnabled()) { log.debug("Extracted from HttpContext user token:" + userToken + ", storing it as JMeter variable:" + USER_TOKEN); } // During recording JMeterContextService.getContext().getVariables() is null JMeterVariables jMeterVariables = JMeterContextService.getContext().getVariables(); if (jMeterVariables != null) { jMeterVariables.putObject(USER_TOKEN, userToken); } } } /** * Configure the UserToken so that the SSL context is reused * See <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=57804">Bug 57804</a> * @param localContext {@link HttpContext} */ private void setupClientContextBeforeSample(HttpContext localContext) { Object userToken = null; // During recording JMeterContextService.getContext().getVariables() is null JMeterVariables jMeterVariables = JMeterContextService.getContext().getVariables(); if (jMeterVariables != null) { userToken = jMeterVariables.getObject(USER_TOKEN); } if (userToken != null) { if (log.isDebugEnabled()) { log.debug("Found user token:" + userToken + " as JMeter variable:" + USER_TOKEN + ", storing it in HttpContext"); } localContext.setAttribute(HttpClientContext.USER_TOKEN, userToken); } else { // It would be better to create a ClientSessionManager that would compute this value // for now it can be Thread.currentThread().getName() but must be changed when we would change // the Thread per User model String userId = Thread.currentThread().getName(); if (log.isDebugEnabled()) { log.debug("Storing in HttpContext the user token:" + userId); } localContext.setAttribute(HttpClientContext.USER_TOKEN, userId); } } /** * Calls {@link #sendPostData(HttpPost)} if method is <code>POST</code> and * {@link #sendEntityData(HttpEntityEnclosingRequestBase)} if method is * <code>PUT</code> or <code>PATCH</code> * <p> * Field HTTPSampleResult#queryString of result is modified in the 2 cases * * @param method * String HTTP method * @param result * {@link HTTPSampleResult} * @param httpRequest * {@link HttpRequestBase} * @param localContext * {@link HttpContext} * @throws IOException * when posting data fails due to I/O */ protected void handleMethod(String method, HTTPSampleResult result, HttpRequestBase httpRequest, HttpContext localContext) throws IOException { // Handle the various methods if (httpRequest instanceof HttpPost) { String postBody = sendPostData((HttpPost) httpRequest); result.setQueryString(postBody); } else if (httpRequest instanceof HttpEntityEnclosingRequestBase) { String entityBody = sendEntityData((HttpEntityEnclosingRequestBase) httpRequest); result.setQueryString(entityBody); } } /** * Create HTTPSampleResult filling url, method and SampleLabel. * Monitor field is computed calling isMonitor() * @param url URL * @param method HTTP Method * @return {@link HTTPSampleResult} */ protected HTTPSampleResult createSampleResult(URL url, String method) { HTTPSampleResult res = new HTTPSampleResult(); res.setMonitor(isMonitor()); res.setSampleLabel(url.toString()); // May be replaced later res.setHTTPMethod(method); res.setURL(url); return res; } /** * Execute request either as is or under PrivilegedAction * if a Subject is available for url * @param httpClient the {@link HttpClient} to be used to execute the httpRequest * @param httpRequest the {@link HttpRequest} to be executed * @param localContext th {@link HttpContext} to be used for execution * @param url the target url (will be used to look up a possible subject for the execution) * @return the result of the execution of the httpRequest * @throws IOException * @throws ClientProtocolException */ private HttpResponse executeRequest(final HttpClient httpClient, final HttpRequestBase httpRequest, final HttpContext localContext, final URL url) throws IOException, ClientProtocolException { AuthManager authManager = getAuthManager(); if (authManager != null) { Subject subject = authManager.getSubjectForUrl(url); if (subject != null) { try { return Subject.doAs(subject, new PrivilegedExceptionAction<HttpResponse>() { @Override public HttpResponse run() throws Exception { return httpClient.execute(httpRequest, localContext); } }); } catch (PrivilegedActionException e) { log.error("Can't execute httpRequest with subject:" + subject, e); throw new RuntimeException("Can't execute httpRequest with subject:" + subject, e); } } } return httpClient.execute(httpRequest, localContext); } /** * Holder class for all fields that define an HttpClient instance; * used as the key to the ThreadLocal map of HttpClient instances. */ private static final class HttpClientKey { private final String target; // protocol://[user:pass@]host:[port] private final boolean hasProxy; private final String proxyHost; private final int proxyPort; private final String proxyUser; private final String proxyPass; private final int hashCode; // Always create hash because we will always need it /** * @param url URL Only protocol and url authority are used (protocol://[user:pass@]host:[port]) * @param hasProxy has proxy * @param proxyHost proxy host * @param proxyPort proxy port * @param proxyUser proxy user * @param proxyPass proxy password */ public HttpClientKey(URL url, boolean hasProxy, String proxyHost, int proxyPort, String proxyUser, String proxyPass) { // N.B. need to separate protocol from authority otherwise http://server would match https://erver (<= sic, not typo error) // could use separate fields, but simpler to combine them this.target = url.getProtocol() + "://" + url.getAuthority(); this.hasProxy = hasProxy; this.proxyHost = proxyHost; this.proxyPort = proxyPort; this.proxyUser = proxyUser; this.proxyPass = proxyPass; this.hashCode = getHash(); } private int getHash() { int hash = 17; hash = hash * 31 + (hasProxy ? 1 : 0); if (hasProxy) { hash = hash * 31 + getHash(proxyHost); hash = hash * 31 + proxyPort; hash = hash * 31 + getHash(proxyUser); hash = hash * 31 + getHash(proxyPass); } hash = hash * 31 + target.hashCode(); return hash; } // Allow for null strings private int getHash(String s) { return s == null ? 0 : s.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof HttpClientKey)) { return false; } HttpClientKey other = (HttpClientKey) obj; if (this.hasProxy) { // otherwise proxy String fields may be null return this.hasProxy == other.hasProxy && this.proxyPort == other.proxyPort && this.proxyHost.equals(other.proxyHost) && this.proxyUser.equals(other.proxyUser) && this.proxyPass.equals(other.proxyPass) && this.target.equals(other.target); } // No proxy, so don't check proxy fields return this.hasProxy == other.hasProxy && this.target.equals(other.target); } @Override public int hashCode() { return hashCode; } // For debugging @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(target); if (hasProxy) { sb.append(" via "); sb.append(proxyUser); sb.append("@"); sb.append(proxyHost); sb.append(":"); sb.append(proxyPort); } return sb.toString(); } } private HttpClient setupClient(URL url, SampleResult res) { Map<HttpClientKey, HttpClient> mapHttpClientPerHttpClientKey = HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY .get(); final String host = url.getHost(); String proxyHost = getProxyHost(); int proxyPort = getProxyPortInt(); String proxyPass = getProxyPass(); String proxyUser = getProxyUser(); // static proxy is the globally define proxy eg command line or properties boolean useStaticProxy = isStaticProxy(host); // dynamic proxy is the proxy defined for this sampler boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort); boolean useProxy = useStaticProxy || useDynamicProxy; // if both dynamic and static are used, the dynamic proxy has priority over static if (!useDynamicProxy) { proxyHost = PROXY_HOST; proxyPort = PROXY_PORT; proxyUser = PROXY_USER; proxyPass = PROXY_PASS; } // Lookup key - must agree with all the values used to create the HttpClient. HttpClientKey key = new HttpClientKey(url, useProxy, proxyHost, proxyPort, proxyUser, proxyPass); HttpClient httpClient = null; if (this.testElement.isConcurrentDwn()) { httpClient = (HttpClient) JMeterContextService.getContext().getSamplerContext().get(HTTPCLIENT_TOKEN); } if (httpClient == null) { httpClient = mapHttpClientPerHttpClientKey.get(key); } if (httpClient != null && resetSSLContext && HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(url.getProtocol())) { ((AbstractHttpClient) httpClient).clearRequestInterceptors(); ((AbstractHttpClient) httpClient).clearResponseInterceptors(); httpClient.getConnectionManager().closeIdleConnections(1L, TimeUnit.MICROSECONDS); httpClient = null; JsseSSLManager sslMgr = (JsseSSLManager) SSLManager.getInstance(); sslMgr.resetContext(); resetSSLContext = false; } if (httpClient == null) { // One-time init for this client HttpParams clientParams = new DefaultedHttpParams(new BasicHttpParams(), DEFAULT_HTTP_PARAMS); DnsResolver resolver = this.testElement.getDNSResolver(); if (resolver == null) { resolver = SystemDefaultDnsResolver.INSTANCE; } MeasuringConnectionManager connManager = new MeasuringConnectionManager(createSchemeRegistry(), resolver, TIME_TO_LIVE, VALIDITY_AFTER_INACTIVITY_TIMEOUT); // Modern browsers use more connections per host than the current httpclient default (2) // when using parallel download the httpclient and connection manager are shared by the downloads threads // to be realistic JMeter must set an higher value to DefaultMaxPerRoute if (this.testElement.isConcurrentDwn()) { try { int maxConcurrentDownloads = Integer.parseInt(this.testElement.getConcurrentPool()); connManager.setDefaultMaxPerRoute( Math.max(maxConcurrentDownloads, connManager.getDefaultMaxPerRoute())); } catch (NumberFormatException nfe) { // no need to log -> will be done by the sampler } } httpClient = new DefaultHttpClient(connManager, clientParams) { @Override protected HttpRequestRetryHandler createHttpRequestRetryHandler() { return new DefaultHttpRequestRetryHandler(RETRY_COUNT, false); // set retry count } }; if (IDLE_TIMEOUT > 0) { ((AbstractHttpClient) httpClient).setKeepAliveStrategy(IDLE_STRATEGY); } // see https://issues.apache.org/jira/browse/HTTPCORE-397 ((AbstractHttpClient) httpClient).setReuseStrategy(DefaultClientConnectionReuseStrategy.INSTANCE); ((AbstractHttpClient) httpClient).addResponseInterceptor(RESPONSE_CONTENT_ENCODING); ((AbstractHttpClient) httpClient).addResponseInterceptor(METRICS_SAVER); // HACK ((AbstractHttpClient) httpClient).addRequestInterceptor(METRICS_RESETTER); // Override the default schemes as necessary SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry(); if (SLOW_HTTP != null) { schemeRegistry.register(SLOW_HTTP); } // Set up proxy details if (useProxy) { HttpHost proxy = new HttpHost(proxyHost, proxyPort); clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); if (proxyUser.length() > 0) { ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials( new AuthScope(proxyHost, proxyPort), new NTCredentials(proxyUser, proxyPass, localHost, PROXY_DOMAIN)); } } // Bug 52126 - we do our own cookie handling clientParams.setParameter(ClientPNames.COOKIE_POLICY, CookieSpecs.IGNORE_COOKIES); if (log.isDebugEnabled()) { log.debug("Created new HttpClient: @" + System.identityHashCode(httpClient) + " " + key.toString()); } mapHttpClientPerHttpClientKey.put(key, httpClient); // save the agent for next time round } else { if (log.isDebugEnabled()) { log.debug("Reusing the HttpClient: @" + System.identityHashCode(httpClient) + " " + key.toString()); } } if (this.testElement.isConcurrentDwn()) { JMeterContextService.getContext().getSamplerContext().put(HTTPCLIENT_TOKEN, httpClient); } // TODO - should this be done when the client is created? // If so, then the details need to be added as part of HttpClientKey setConnectionAuthorization(httpClient, url, getAuthManager(), key); return httpClient; } /** * Setup LazySchemeSocketFactory * @see "https://bz.apache.org/bugzilla/show_bug.cgi?id=58099" */ private static SchemeRegistry createSchemeRegistry() { final SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); //$NON-NLS-1$ registry.register(new Scheme("https", 443, new LazySchemeSocketFactory())); //$NON-NLS-1$ return registry; } /** * Setup following elements on httpRequest: * <ul> * <li>ConnRoutePNames.LOCAL_ADDRESS enabling IP-SPOOFING</li> * <li>Socket and connection timeout</li> * <li>Redirect handling</li> * <li>Keep Alive header or Connection Close</li> * <li>Calls setConnectionHeaders to setup headers</li> * <li>Calls setConnectionCookie to setup Cookie</li> * </ul> * * @param url * {@link URL} of the request * @param httpRequest * http request for the request * @param res * sample result to set cookies on * @throws IOException * if hostname/ip to use could not be figured out */ protected void setupRequest(URL url, HttpRequestBase httpRequest, HTTPSampleResult res) throws IOException { HttpParams requestParams = httpRequest.getParams(); // Set up the local address if one exists final InetAddress inetAddr = getIpSourceAddress(); if (inetAddr != null) {// Use special field ip source address (for pseudo 'ip spoofing') requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, inetAddr); } else if (localAddress != null) { requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); } else { // reset in case was set previously requestParams.removeParameter(ConnRoutePNames.LOCAL_ADDRESS); } int rto = getResponseTimeout(); if (rto > 0) { requestParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, rto); } int cto = getConnectTimeout(); if (cto > 0) { requestParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, cto); } requestParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, getAutoRedirects()); // a well-behaved browser is supposed to send 'Connection: close' // with the last request to an HTTP server. Instead, most browsers // leave it to the server to close the connection after their // timeout period. Leave it to the JMeter user to decide. if (getUseKeepAlive()) { httpRequest.setHeader(HTTPConstants.HEADER_CONNECTION, HTTPConstants.KEEP_ALIVE); } else { httpRequest.setHeader(HTTPConstants.HEADER_CONNECTION, HTTPConstants.CONNECTION_CLOSE); } setConnectionHeaders(httpRequest, url, getHeaderManager(), getCacheManager()); String cookies = setConnectionCookie(httpRequest, url, getCookieManager()); if (res != null) { res.setCookies(cookies); } } /** * Set any default request headers to include * * @param request the HttpRequest to be used */ protected void setDefaultRequestHeaders(HttpRequest request) { // Method left empty here, but allows subclasses to override } /** * Gets the ResponseHeaders * * @param response * containing the headers * @param localContext {@link HttpContext} * @return string containing the headers, one per line */ private String getResponseHeaders(HttpResponse response, HttpContext localContext) { StringBuilder headerBuf = new StringBuilder(); headerBuf.append(response.getStatusLine());// header[0] is not the status line... headerBuf.append("\n"); // $NON-NLS-1$ Header[] rh = response.getAllHeaders(); for (Header responseHeader : rh) { writeResponseHeader(headerBuf, responseHeader); } return headerBuf.toString(); } /** * Write responseHeader to headerBuffer * @param headerBuffer {@link StringBuilder} * @param responseHeader {@link Header} */ private void writeResponseHeader(StringBuilder headerBuffer, Header responseHeader) { headerBuffer.append(responseHeader.getName()).append(": ") // $NON-NLS-1$ .append(responseHeader.getValue()).append("\n"); // $NON-NLS-1$ } /** * Extracts all the required cookies for that particular URL request and * sets them in the <code>HttpMethod</code> passed in. * * @param request <code>HttpRequest</code> for the request * @param url <code>URL</code> of the request * @param cookieManager the <code>CookieManager</code> containing all the cookies * @return a String containing the cookie details (for the response) * May be null */ protected String setConnectionCookie(HttpRequest request, URL url, CookieManager cookieManager) { String cookieHeader = null; if (cookieManager != null) { cookieHeader = cookieManager.getCookieHeaderForURL(url); if (cookieHeader != null) { request.setHeader(HTTPConstants.HEADER_COOKIE, cookieHeader); } } return cookieHeader; } /** * Extracts all the required non-cookie headers for that particular URL request and * sets them in the <code>HttpMethod</code> passed in * * @param request * <code>HttpRequest</code> which represents the request * @param url * <code>URL</code> of the URL request * @param headerManager * the <code>HeaderManager</code> containing all the cookies * for this <code>UrlConfig</code> * @param cacheManager the CacheManager (may be null) */ protected void setConnectionHeaders(HttpRequestBase request, URL url, HeaderManager headerManager, CacheManager cacheManager) { if (headerManager != null) { CollectionProperty headers = headerManager.getHeaders(); if (headers != null) { for (JMeterProperty jMeterProperty : headers) { org.apache.jmeter.protocol.http.control.Header header = (org.apache.jmeter.protocol.http.control.Header) jMeterProperty .getObjectValue(); String n = header.getName(); // Don't allow override of Content-Length // TODO - what other headers are not allowed? if (!HTTPConstants.HEADER_CONTENT_LENGTH.equalsIgnoreCase(n)) { String v = header.getValue(); if (HTTPConstants.HEADER_HOST.equalsIgnoreCase(n)) { int port = getPortFromHostHeader(v, url.getPort()); v = v.replaceFirst(":\\d+$", ""); // remove any port specification // $NON-NLS-1$ $NON-NLS-2$ if (port != -1) { if (port == url.getDefaultPort()) { port = -1; // no need to specify the port if it is the default } } request.getParams().setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(v, port)); } else { request.addHeader(n, v); } } } } } if (cacheManager != null) { cacheManager.setHeaders(url, request); } } /** * Get port from the value of the Host header, or return the given * defaultValue * * @param hostHeaderValue * value of the http Host header * @param defaultValue * value to be used, when no port could be extracted from * hostHeaderValue * @return integer representing the port for the host header */ private int getPortFromHostHeader(String hostHeaderValue, int defaultValue) { String[] hostParts = hostHeaderValue.split(":"); if (hostParts.length > 1) { String portString = hostParts[hostParts.length - 1]; if (portString.matches("^\\d+$")) { return Integer.parseInt(portString); } } return defaultValue; } /** * Get all the request headers for the <code>HttpMethod</code> * * @param method * <code>HttpMethod</code> which represents the request * @return the headers as a string */ private String getConnectionHeaders(HttpRequest method) { if (method != null) { // Get all the request headers StringBuilder hdrs = new StringBuilder(100); Header[] requestHeaders = method.getAllHeaders(); for (Header requestHeader : requestHeaders) { // Exclude the COOKIE header, since cookie is reported separately in the sample if (!HTTPConstants.HEADER_COOKIE.equalsIgnoreCase(requestHeader.getName())) { writeResponseHeader(hdrs, requestHeader); } } return hdrs.toString(); } return ""; ////$NON-NLS-1$ } /** * Setup credentials for url AuthScope but keeps Proxy AuthScope credentials * @param client HttpClient * @param url URL * @param authManager {@link AuthManager} * @param key key */ private void setConnectionAuthorization(HttpClient client, URL url, AuthManager authManager, HttpClientKey key) { CredentialsProvider credentialsProvider = ((AbstractHttpClient) client).getCredentialsProvider(); if (authManager != null) { if (authManager.hasAuthForURL(url)) { authManager.setupCredentials(client, url, credentialsProvider, localHost); } else { credentialsProvider.clear(); } } else { Credentials credentials = null; AuthScope authScope = null; if (key.hasProxy && !StringUtils.isEmpty(key.proxyUser)) { authScope = new AuthScope(key.proxyHost, key.proxyPort); credentials = credentialsProvider.getCredentials(authScope); } credentialsProvider.clear(); if (credentials != null) { credentialsProvider.setCredentials(authScope, credentials); } } } // Helper class so we can generate request data without dumping entire file contents private static class ViewableFileBody extends FileBody { private boolean hideFileData; public ViewableFileBody(File file, String mimeType) { super(file, mimeType); hideFileData = false; } @Override public void writeTo(final OutputStream out) throws IOException { if (hideFileData) { out.write("<actual file content, not shown here>".getBytes());// encoding does not really matter here } else { super.writeTo(out); } } } // TODO needs cleaning up /** * * @param post {@link HttpPost} * @return String posted body if computable * @throws IOException if sending the data fails due to I/O */ protected String sendPostData(HttpPost post) throws IOException { // Buffer to hold the post body, except file content StringBuilder postedBody = new StringBuilder(1000); HTTPFileArg[] files = getHTTPFiles(); final String contentEncoding = getContentEncodingOrNull(); final boolean haveContentEncoding = contentEncoding != null; // Check if we should do a multipart/form-data or an // application/x-www-form-urlencoded post request if (getUseMultipartForPost()) { // If a content encoding is specified, we use that as the // encoding of any parameter values Charset charset = null; if (haveContentEncoding) { charset = Charset.forName(contentEncoding); } else { charset = MIME.DEFAULT_CHARSET; } if (log.isDebugEnabled()) { log.debug("Building multipart with:getDoBrowserCompatibleMultipart():" + getDoBrowserCompatibleMultipart() + ", with charset:" + charset + ", haveContentEncoding:" + haveContentEncoding); } // Write the request to our own stream MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setCharset(charset); if (getDoBrowserCompatibleMultipart()) { multipartEntityBuilder.setLaxMode(); } else { multipartEntityBuilder.setStrictMode(); } // Create the parts // Add any parameters for (JMeterProperty jMeterProperty : getArguments()) { HTTPArgument arg = (HTTPArgument) jMeterProperty.getObjectValue(); String parameterName = arg.getName(); if (arg.isSkippable(parameterName)) { continue; } StringBody stringBody = new StringBody(arg.getValue(), ContentType.create("text/plain", charset)); FormBodyPart formPart = FormBodyPartBuilder.create(parameterName, stringBody).build(); multipartEntityBuilder.addPart(formPart); } // Add any files // Cannot retrieve parts once added to the MultiPartEntity, so have to save them here. ViewableFileBody[] fileBodies = new ViewableFileBody[files.length]; for (int i = 0; i < files.length; i++) { HTTPFileArg file = files[i]; File reservedFile = FileServer.getFileServer().getResolvedFile(file.getPath()); fileBodies[i] = new ViewableFileBody(reservedFile, file.getMimeType()); multipartEntityBuilder.addPart(file.getParamName(), fileBodies[i]); } HttpEntity entity = multipartEntityBuilder.build(); post.setEntity(entity); if (entity.isRepeatable()) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (ViewableFileBody fileBody : fileBodies) { fileBody.hideFileData = true; } entity.writeTo(bos); for (ViewableFileBody fileBody : fileBodies) { fileBody.hideFileData = false; } bos.flush(); // We get the posted bytes using the encoding used to create it postedBody.append(new String(bos.toByteArray(), contentEncoding == null ? "US-ASCII" // $NON-NLS-1$ this is the default used by HttpClient : contentEncoding)); bos.close(); } else { postedBody.append("<Multipart was not repeatable, cannot view what was sent>"); // $NON-NLS-1$ } // // Set the content type TODO - needed? // String multiPartContentType = multiPart.getContentType().getValue(); // post.setHeader(HEADER_CONTENT_TYPE, multiPartContentType); } else { // not multipart // Check if the header manager had a content type header // This allows the user to specify his own content-type for a POST request Header contentTypeHeader = post.getFirstHeader(HTTPConstants.HEADER_CONTENT_TYPE); boolean hasContentTypeHeader = contentTypeHeader != null && contentTypeHeader.getValue() != null && contentTypeHeader.getValue().length() > 0; // If there are no arguments, we can send a file as the body of the request // TODO: needs a multiple file upload scenerio if (!hasArguments() && getSendFileAsPostBody()) { // If getSendFileAsPostBody returned true, it's sure that file is not null HTTPFileArg file = files[0]; if (!hasContentTypeHeader) { // Allow the mimetype of the file to control the content type if (file.getMimeType() != null && file.getMimeType().length() > 0) { post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); } else { post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); } } FileEntity fileRequestEntity = new FileEntity(new File(file.getPath()), (ContentType) null);// TODO is null correct? post.setEntity(fileRequestEntity); // We just add placeholder text for file content postedBody.append("<actual file content, not shown here>"); } else { // In a post request which is not multipart, we only support // parameters, no file upload is allowed // If a content encoding is specified, we set it as http parameter, so that // the post body will be encoded in the specified content encoding if (haveContentEncoding) { post.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, contentEncoding); } // If none of the arguments have a name specified, we // just send all the values as the post body if (getSendParameterValuesAsPostBody()) { // Allow the mimetype of the file to control the content type // This is not obvious in GUI if you are not uploading any files, // but just sending the content of nameless parameters // TODO: needs a multiple file upload scenerio if (!hasContentTypeHeader) { HTTPFileArg file = files.length > 0 ? files[0] : null; if (file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, file.getMimeType()); } else { // TODO - is this the correct default? post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); } } // Just append all the parameter values, and use that as the post body StringBuilder postBody = new StringBuilder(); for (JMeterProperty jMeterProperty : getArguments()) { HTTPArgument arg = (HTTPArgument) jMeterProperty.getObjectValue(); // Note: if "Encoded?" is not selected, arg.getEncodedValue is equivalent to arg.getValue if (haveContentEncoding) { postBody.append(arg.getEncodedValue(contentEncoding)); } else { postBody.append(arg.getEncodedValue()); } } // Let StringEntity perform the encoding StringEntity requestEntity = new StringEntity(postBody.toString(), contentEncoding); post.setEntity(requestEntity); postedBody.append(postBody.toString()); } else { // It is a normal post request, with parameter names and values // Set the content type if (!hasContentTypeHeader) { post.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, HTTPConstants.APPLICATION_X_WWW_FORM_URLENCODED); } // Add the parameters PropertyIterator args = getArguments().iterator(); List<NameValuePair> nvps = new ArrayList<>(); String urlContentEncoding = contentEncoding; if (urlContentEncoding == null || urlContentEncoding.length() == 0) { // Use the default encoding for urls urlContentEncoding = EncoderCache.URL_ARGUMENT_ENCODING; } while (args.hasNext()) { HTTPArgument arg = (HTTPArgument) args.next().getObjectValue(); // The HTTPClient always urlencodes both name and value, // so if the argument is already encoded, we have to decode // it before adding it to the post request String parameterName = arg.getName(); if (arg.isSkippable(parameterName)) { continue; } String parameterValue = arg.getValue(); if (!arg.isAlwaysEncoded()) { // The value is already encoded by the user // Must decode the value now, so that when the // httpclient encodes it, we end up with the same value // as the user had entered. parameterName = URLDecoder.decode(parameterName, urlContentEncoding); parameterValue = URLDecoder.decode(parameterValue, urlContentEncoding); } // Add the parameter, httpclient will urlencode it nvps.add(new BasicNameValuePair(parameterName, parameterValue)); } UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nvps, urlContentEncoding); post.setEntity(entity); if (entity.isRepeatable()) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); post.getEntity().writeTo(bos); bos.flush(); // We get the posted bytes using the encoding used to create it if (contentEncoding != null) { postedBody.append(new String(bos.toByteArray(), contentEncoding)); } else { postedBody.append(new String(bos.toByteArray(), SampleResult.DEFAULT_HTTP_ENCODING)); } bos.close(); } else { postedBody.append("<RequestEntity was not repeatable, cannot view what was sent>"); } } } } return postedBody.toString(); } // TODO merge put and post methods as far as possible. // e.g. post checks for multipart form/files, and if not, invokes sendData(HttpEntityEnclosingRequestBase) /** * Creates the entity data to be sent. * <p> * If there is a file entry with a non-empty MIME type we use that to * set the request Content-Type header, otherwise we default to whatever * header is present from a Header Manager. * <p> * If the content charset {@link #getContentEncoding()} is null or empty * we use the HC4 default provided by {@link HTTP#DEF_CONTENT_CHARSET} which is * ISO-8859-1. * * @param entity to be processed, e.g. PUT or PATCH * @return the entity content, may be empty * @throws UnsupportedEncodingException for invalid charset name * @throws IOException cannot really occur for ByteArrayOutputStream methods */ protected String sendEntityData(HttpEntityEnclosingRequestBase entity) throws IOException { // Buffer to hold the entity body StringBuilder entityBody = new StringBuilder(1000); boolean hasEntityBody = false; final HTTPFileArg[] files = getHTTPFiles(); // Allow the mimetype of the file to control the content type // This is not obvious in GUI if you are not uploading any files, // but just sending the content of nameless parameters final HTTPFileArg file = files.length > 0 ? files[0] : null; String contentTypeValue = null; if (file != null && file.getMimeType() != null && file.getMimeType().length() > 0) { contentTypeValue = file.getMimeType(); entity.setHeader(HEADER_CONTENT_TYPE, contentTypeValue); // we provide the MIME type here } // Check for local contentEncoding (charset) override; fall back to default for content body // we do this here rather so we can use the same charset to retrieve the data final String charset = getContentEncoding(HTTP.DEF_CONTENT_CHARSET.name()); // Only create this if we are overriding whatever default there may be // If there are no arguments, we can send a file as the body of the request if (!hasArguments() && getSendFileAsPostBody()) { hasEntityBody = true; // If getSendFileAsPostBody returned true, it's sure that file is not null File reservedFile = FileServer.getFileServer().getResolvedFile(files[0].getPath()); FileEntity fileRequestEntity = new FileEntity(reservedFile); // no need for content-type here entity.setEntity(fileRequestEntity); } // If none of the arguments have a name specified, we // just send all the values as the entity body else if (getSendParameterValuesAsPostBody()) { hasEntityBody = true; // Just append all the parameter values, and use that as the entity body StringBuilder entityBodyContent = new StringBuilder(); for (JMeterProperty jMeterProperty : getArguments()) { HTTPArgument arg = (HTTPArgument) jMeterProperty.getObjectValue(); // Note: if "Encoded?" is not selected, arg.getEncodedValue is equivalent to arg.getValue if (charset != null) { entityBodyContent.append(arg.getEncodedValue(charset)); } else { entityBodyContent.append(arg.getEncodedValue()); } } StringEntity requestEntity = new StringEntity(entityBodyContent.toString(), charset); entity.setEntity(requestEntity); } // Check if we have any content to send for body if (hasEntityBody) { // If the request entity is repeatable, we can send it first to // our own stream, so we can return it final HttpEntity entityEntry = entity.getEntity(); if (entityEntry.isRepeatable()) { entityBody.append("<actual file content, not shown here>"); } else { // this probably cannot happen entityBody.append("<RequestEntity was not repeatable, cannot view what was sent>"); } } return entityBody.toString(); // may be the empty string } /** * * @return the value of {@link #getContentEncoding()}; forced to null if empty */ private String getContentEncodingOrNull() { return getContentEncoding(null); } /** * @param dflt the default to be used * @return the value of {@link #getContentEncoding()}; default if null or empty */ private String getContentEncoding(String dflt) { String ce = getContentEncoding(); if (isNullOrEmptyTrimmed(ce)) { return dflt; } else { return ce; } } private void saveConnectionCookies(HttpResponse method, URL u, CookieManager cookieManager) { if (cookieManager != null) { Header[] hdrs = method.getHeaders(HTTPConstants.HEADER_SET_COOKIE); for (Header hdr : hdrs) { cookieManager.addCookieFromHeader(hdr.getValue(), u); } } } @Override protected void notifyFirstSampleAfterLoopRestart() { log.debug("notifyFirstSampleAfterLoopRestart"); resetSSLContext = !USE_CACHED_SSL_CONTEXT; } @Override protected void threadFinished() { log.debug("Thread Finished"); closeThreadLocalConnections(); } /** * */ private void closeThreadLocalConnections() { // Does not need to be synchronised, as all access is from same thread Map<HttpClientKey, HttpClient> mapHttpClientPerHttpClientKey = HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY .get(); if (mapHttpClientPerHttpClientKey != null) { for (HttpClient cl : mapHttpClientPerHttpClientKey.values()) { ((AbstractHttpClient) cl).clearRequestInterceptors(); ((AbstractHttpClient) cl).clearResponseInterceptors(); ((AbstractHttpClient) cl).close(); cl.getConnectionManager().shutdown(); } mapHttpClientPerHttpClientKey.clear(); } } @Override public boolean interrupt() { HttpUriRequest request = currentRequest; if (request != null) { currentRequest = null; // don't try twice try { request.abort(); } catch (UnsupportedOperationException e) { log.warn("Could not abort pending request", e); } } return request != null; } }