0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.jmeter.protocol.http.sampler;
0018:
0019: import java.io.ByteArrayOutputStream;
0020: import java.io.File;
0021: import java.io.FileNotFoundException;
0022: import java.io.IOException;
0023: import java.io.InputStream;
0024: import java.io.OutputStream;
0025: import java.net.InetAddress;
0026: import java.net.URL;
0027: import java.net.URLDecoder;
0028: import java.net.UnknownHostException;
0029: import java.util.ArrayList;
0030: import java.util.HashMap;
0031: import java.util.HashSet;
0032: import java.util.Iterator;
0033: import java.util.List;
0034: import java.util.Map;
0035: import java.util.Set;
0036: import java.util.StringTokenizer;
0037: import java.util.zip.GZIPInputStream;
0038:
0039: import org.apache.commons.httpclient.Header;
0040: import org.apache.commons.httpclient.HostConfiguration;
0041: import org.apache.commons.httpclient.HttpClient;
0042: import org.apache.commons.httpclient.HttpMethod;
0043: import org.apache.commons.httpclient.HttpMethodBase;
0044: import org.apache.commons.httpclient.HttpVersion;
0045: import org.apache.commons.httpclient.NTCredentials;
0046: import org.apache.commons.httpclient.ProtocolException;
0047: import org.apache.commons.httpclient.SimpleHttpConnectionManager;
0048: import org.apache.commons.httpclient.auth.AuthScope;
0049: import org.apache.commons.httpclient.cookie.CookiePolicy;
0050: import org.apache.commons.httpclient.methods.DeleteMethod;
0051: import org.apache.commons.httpclient.methods.FileRequestEntity;
0052: import org.apache.commons.httpclient.methods.GetMethod;
0053: import org.apache.commons.httpclient.methods.HeadMethod;
0054: import org.apache.commons.httpclient.methods.OptionsMethod;
0055: import org.apache.commons.httpclient.methods.PostMethod;
0056: import org.apache.commons.httpclient.methods.PutMethod;
0057: import org.apache.commons.httpclient.methods.StringRequestEntity;
0058: import org.apache.commons.httpclient.methods.TraceMethod;
0059: import org.apache.commons.httpclient.methods.multipart.FilePart;
0060: import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
0061: import org.apache.commons.httpclient.methods.multipart.Part;
0062: import org.apache.commons.httpclient.methods.multipart.StringPart;
0063: import org.apache.commons.httpclient.params.DefaultHttpParams;
0064: import org.apache.commons.httpclient.params.HttpMethodParams;
0065: import org.apache.commons.httpclient.params.HttpParams;
0066: import org.apache.commons.httpclient.protocol.Protocol;
0067: import org.apache.jmeter.JMeter;
0068: import org.apache.jmeter.protocol.http.control.AuthManager;
0069: import org.apache.jmeter.protocol.http.control.Authorization;
0070: import org.apache.jmeter.protocol.http.control.CookieManager;
0071: import org.apache.jmeter.protocol.http.control.HeaderManager;
0072: import org.apache.jmeter.protocol.http.util.EncoderCache;
0073: import org.apache.jmeter.protocol.http.util.HTTPArgument;
0074: import org.apache.jmeter.protocol.http.util.LoopbackHttpClientSocketFactory;
0075: import org.apache.jmeter.protocol.http.util.SlowHttpClientSocketFactory;
0076: import org.apache.jmeter.testelement.property.CollectionProperty;
0077: import org.apache.jmeter.testelement.property.PropertyIterator;
0078: import org.apache.jmeter.util.JMeterUtils;
0079: import org.apache.jmeter.util.SSLManager;
0080: import org.apache.jorphan.logging.LoggingManager;
0081: import org.apache.jorphan.util.JOrphanUtils;
0082: import org.apache.log.Logger;
0083:
0084: /**
0085: * A sampler which understands all the parts necessary to read statistics about
0086: * HTTP requests, including cookies and authentication.
0087: *
0088: */
0089: public class HTTPSampler2 extends HTTPSamplerBase {
0090:
0091: private static final Logger log = LoggingManager
0092: .getLoggerForClass();
0093:
0094: private static final String HTTP_AUTHENTICATION_PREEMPTIVE = "http.authentication.preemptive"; // $NON-NLS-1$
0095:
0096: private static final boolean canSetPreEmptive; // OK to set pre-emptive auth?
0097:
0098: static final String PROXY_HOST = System.getProperty(
0099: "http.proxyHost", ""); // $NON-NLS-1$
0100:
0101: private static final String NONPROXY_HOSTS = System.getProperty(
0102: "http.nonProxyHosts", ""); // $NON-NLS-1$
0103:
0104: static final int PROXY_PORT = Integer.parseInt(System.getProperty(
0105: "http.proxyPort", "0")); // $NON-NLS-1$
0106:
0107: // Have proxy details been provided?
0108: private static final boolean PROXY_DEFINED = PROXY_HOST.length() > 0
0109: && PROXY_PORT > 0;
0110:
0111: static final String PROXY_USER = JMeterUtils.getPropDefault(
0112: JMeter.HTTP_PROXY_USER, ""); // $NON-NLS-1$
0113:
0114: static final String PROXY_PASS = JMeterUtils.getPropDefault(
0115: JMeter.HTTP_PROXY_PASS, ""); // $NON-NLS-1$
0116:
0117: private static final String PROXY_DOMAIN = JMeterUtils
0118: .getPropDefault("http.proxyDomain", ""); // $NON-NLS-1$ $NON-NLS-2$
0119:
0120: static final InetAddress localAddress;
0121:
0122: private static final String localHost;
0123:
0124: /*
0125: * Connection is re-used within the thread if possible
0126: */
0127: static final ThreadLocal httpClients = new ThreadLocal();
0128:
0129: private static final Set nonProxyHostFull = new HashSet();// www.apache.org
0130: private static final List nonProxyHostSuffix = new ArrayList();// .apache.org
0131:
0132: private static final int nonProxyHostSuffixSize;
0133:
0134: private static boolean isNonProxy(String host) {
0135: return nonProxyHostFull.contains(host) || isPartialMatch(host);
0136: }
0137:
0138: private static boolean isPartialMatch(String host) {
0139: for (int i = 0; i < nonProxyHostSuffixSize; i++) {
0140: if (host.endsWith((String) nonProxyHostSuffix.get(i)))
0141: return true;
0142: }
0143: return false;
0144: }
0145:
0146: static {
0147: if (NONPROXY_HOSTS.length() > 0) {
0148: StringTokenizer s = new StringTokenizer(NONPROXY_HOSTS, "|");// $NON-NLS-1$
0149: while (s.hasMoreTokens()) {
0150: String t = s.nextToken();
0151: if (t.indexOf("*") == 0) {// e.g. *.apache.org // $NON-NLS-1$
0152: nonProxyHostSuffix.add(t.substring(1));
0153: } else {
0154: nonProxyHostFull.add(t);// e.g. www.apache.org
0155: }
0156: }
0157: }
0158: nonProxyHostSuffixSize = nonProxyHostSuffix.size();
0159:
0160: int cps = JMeterUtils.getPropDefault(
0161: "httpclient.socket.http.cps", 0); // $NON-NLS-1$
0162:
0163: if (cps > 0) {
0164: log.info("Setting up HTTP SlowProtocol, cps=" + cps);
0165: Protocol.registerProtocol(PROTOCOL_HTTP, new Protocol(
0166: PROTOCOL_HTTP,
0167: new SlowHttpClientSocketFactory(cps),
0168: DEFAULT_HTTP_PORT));
0169: }
0170:
0171: // Now done in JsseSSLManager (which needs to register the protocol)
0172: // cps =
0173: // JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$
0174: //
0175: // if (cps > 0) {
0176: // log.info("Setting up HTTPS SlowProtocol, cps="+cps);
0177: // Protocol.registerProtocol(PROTOCOL_HTTPS,
0178: // new Protocol(PROTOCOL_HTTPS,new SlowHttpClientSocketFactory(cps),DEFAULT_HTTPS_PORT));
0179: // }
0180:
0181: InetAddress inet = null;
0182: String localHostOrIP = JMeterUtils.getPropDefault(
0183: "httpclient.localaddress", ""); // $NON-NLS-1$
0184: if (localHostOrIP.length() > 0) {
0185: try {
0186: inet = InetAddress.getByName(localHostOrIP);
0187: log.info("Using localAddress " + inet.getHostAddress());
0188: } catch (UnknownHostException e) {
0189: log.warn(e.getLocalizedMessage());
0190: }
0191: } else {
0192: try {
0193: InetAddress addr = InetAddress.getLocalHost();
0194: // Get hostname
0195: localHostOrIP = addr.getHostName();
0196: } catch (UnknownHostException e) {
0197: log
0198: .warn("Cannot determine localhost name, and httpclient.localaddress was not specified");
0199: }
0200: }
0201: localAddress = inet;
0202: localHost = localHostOrIP;
0203: log.info("Local host = " + localHost);
0204:
0205: // Set default parameters as needed
0206: HttpParams params = DefaultHttpParams.getDefaultParams();
0207:
0208: // Process httpclient parameters file
0209: String file = JMeterUtils
0210: .getProperty("httpclient.parameters.file"); // $NON-NLS-1$
0211: if (file != null) {
0212: HttpClientDefaultParameters.load(file, params);
0213: }
0214:
0215: // If the pre-emptive parameter is undefined, then we cans set it as needed
0216: // otherwise we should do what the user requested.
0217: canSetPreEmptive = params
0218: .getParameter(HTTP_AUTHENTICATION_PREEMPTIVE) == null;
0219:
0220: // Handle old-style JMeter properties
0221: // Default to HTTP version 1.1
0222: String ver = JMeterUtils.getPropDefault("httpclient.version",
0223: "1.1"); // $NON-NLS-1$ $NON-NLS-2$
0224: try {
0225: params.setParameter(HttpMethodParams.PROTOCOL_VERSION,
0226: HttpVersion.parse("HTTP/" + ver));
0227: } catch (ProtocolException e) {
0228: log.warn("Problem setting protocol version "
0229: + e.getLocalizedMessage());
0230: }
0231: String to = JMeterUtils.getProperty("httpclient.timeout");
0232: if (to != null) {
0233: params.setIntParameter(HttpMethodParams.SO_TIMEOUT, Integer
0234: .parseInt(to));
0235: }
0236:
0237: // This must be done last, as must not be overridden
0238: params.setParameter(HttpMethodParams.COOKIE_POLICY,
0239: CookiePolicy.IGNORE_COOKIES);
0240: // We do our own cookie handling
0241:
0242: if (JMeterUtils.getPropDefault("httpclient.loopback", false)) {// $NON-NLS-1$
0243: LoopbackHttpClientSocketFactory.setup();
0244: }
0245: }
0246:
0247: /**
0248: * Constructor for the HTTPSampler2 object.
0249: *
0250: * Consider using HTTPSamplerFactory.newInstance() instead
0251: */
0252: public HTTPSampler2() {
0253: }
0254:
0255: /*
0256: * Send POST data from <code>Entry</code> to the open connection.
0257: *
0258: * @param connection
0259: * <code>URLConnection</code> where POST data should be sent
0260: * @return a String show what was posted. Will not contain actual file upload content
0261: * @exception IOException
0262: * if an I/O exception occurs
0263: */
0264: private String sendPostData(PostMethod post) throws IOException {
0265: // Buffer to hold the post body, expect file content
0266: StringBuffer postedBody = new StringBuffer(1000);
0267:
0268: // Check if we should do a multipart/form-data or an
0269: // application/x-www-form-urlencoded post request
0270: if (getUseMultipartForPost()) {
0271: // If a content encoding is specified, we use that es the
0272: // encoding of any parameter values
0273: String contentEncoding = getContentEncoding();
0274: if (contentEncoding != null
0275: && contentEncoding.length() == 0) {
0276: contentEncoding = null;
0277: }
0278:
0279: // Check how many parts we need, one for each parameter and file
0280: int noParts = getArguments().getArgumentCount();
0281: if (hasUploadableFiles()) {
0282: noParts++;
0283: }
0284:
0285: // Create the parts
0286: Part[] parts = new Part[noParts];
0287: int partNo = 0;
0288: // Add any parameters
0289: PropertyIterator args = getArguments().iterator();
0290: while (args.hasNext()) {
0291: HTTPArgument arg = (HTTPArgument) args.next()
0292: .getObjectValue();
0293: String parameterName = arg.getName();
0294: if (parameterName.length() == 0) {
0295: continue; // Skip parameters with a blank name (allows use of optional variables in parameter lists)
0296: }
0297: parts[partNo++] = new StringPart(arg.getName(), arg
0298: .getValue(), contentEncoding);
0299: }
0300:
0301: // Add any files
0302: if (hasUploadableFiles()) {
0303: File inputFile = new File(getFilename());
0304: // We do not know the char set of the file to be uploaded, so we set it to null
0305: ViewableFilePart filePart = new ViewableFilePart(
0306: getFileField(), inputFile, getMimetype(), null);
0307: filePart.setCharSet(null); // We do not know what the char set of the file is
0308: parts[partNo++] = filePart;
0309: }
0310:
0311: // Set the multipart for the post
0312: MultipartRequestEntity multiPart = new MultipartRequestEntity(
0313: parts, post.getParams());
0314: post.setRequestEntity(multiPart);
0315:
0316: // Set the content type
0317: String multiPartContentType = multiPart.getContentType();
0318: post.setRequestHeader(HEADER_CONTENT_TYPE,
0319: multiPartContentType);
0320:
0321: // If the Multipart is repeatable, we can send it first to
0322: // our own stream, without the actual file content, so we can return it
0323: if (multiPart.isRepeatable()) {
0324: // For all the file multiparts, we must tell it to not include
0325: // the actual file content
0326: for (int i = 0; i < partNo; i++) {
0327: if (parts[i] instanceof ViewableFilePart) {
0328: ((ViewableFilePart) parts[i])
0329: .setHideFileData(true); // .sendMultipartWithoutFileContent(bos);
0330: }
0331: }
0332: // Write the request to our own stream
0333: ByteArrayOutputStream bos = new ByteArrayOutputStream();
0334: multiPart.writeRequest(bos);
0335: bos.flush();
0336: // We get the posted bytes as UTF-8, since java is using UTF-8
0337: postedBody
0338: .append(new String(bos.toByteArray(), "UTF-8")); // $NON-NLS-1$
0339: bos.close();
0340:
0341: // For all the file multiparts, we must revert the hiding of
0342: // the actual file content
0343: for (int i = 0; i < partNo; i++) {
0344: if (parts[i] instanceof ViewableFilePart) {
0345: ((ViewableFilePart) parts[i])
0346: .setHideFileData(false);
0347: }
0348: }
0349: } else {
0350: postedBody
0351: .append("<Multipart was not repeatable, cannot view what was sent>"); // $NON-NLS-1$
0352: }
0353: } else {
0354: // Check if the header manager had a content type header
0355: // This allows the user to specify his own content-type for a POST request
0356: Header contentTypeHeader = post
0357: .getRequestHeader(HEADER_CONTENT_TYPE);
0358: boolean hasContentTypeHeader = contentTypeHeader != null
0359: && contentTypeHeader.getValue() != null
0360: && contentTypeHeader.getValue().length() > 0;
0361:
0362: // If there are no arguments, we can send a file as the body of the request
0363: if (!hasArguments() && getSendFileAsPostBody()) {
0364: if (!hasContentTypeHeader) {
0365: // Allow the mimetype of the file to control the content type
0366: if (getMimetype() != null
0367: && getMimetype().length() > 0) {
0368: post.setRequestHeader(HEADER_CONTENT_TYPE,
0369: getMimetype());
0370: } else {
0371: post.setRequestHeader(HEADER_CONTENT_TYPE,
0372: APPLICATION_X_WWW_FORM_URLENCODED);
0373: }
0374: }
0375:
0376: FileRequestEntity fileRequestEntity = new FileRequestEntity(
0377: new File(getFilename()), null);
0378: post.setRequestEntity(fileRequestEntity);
0379:
0380: // We just add placeholder text for file content
0381: postedBody
0382: .append("<actual file content, not shown here>");
0383: } else {
0384: // In a post request which is not multipart, we only support
0385: // parameters, no file upload is allowed
0386:
0387: // If a content encoding is specified, we set it as http parameter, so that
0388: // the post body will be encoded in the specified content encoding
0389: final String contentEncoding = getContentEncoding();
0390: boolean haveContentEncoding = false;
0391: if (contentEncoding != null
0392: && contentEncoding.trim().length() > 0) {
0393: post.getParams().setContentCharset(contentEncoding);
0394: haveContentEncoding = true;
0395: }
0396:
0397: // If none of the arguments have a name specified, we
0398: // just send all the values as the post body
0399: if (getSendParameterValuesAsPostBody()) {
0400: // Allow the mimetype of the file to control the content type
0401: // This is not obvious in GUI if you are not uploading any files,
0402: // but just sending the content of nameless parameters
0403: if (!hasContentTypeHeader) {
0404: if (getMimetype() != null
0405: && getMimetype().length() > 0) {
0406: post.setRequestHeader(HEADER_CONTENT_TYPE,
0407: getMimetype());
0408: } else {
0409: // TODO - is this the correct default?
0410: post.setRequestHeader(HEADER_CONTENT_TYPE,
0411: APPLICATION_X_WWW_FORM_URLENCODED);
0412: }
0413: }
0414:
0415: // Just append all the non-empty parameter values, and use that as the post body
0416: StringBuffer postBody = new StringBuffer();
0417: PropertyIterator args = getArguments().iterator();
0418: while (args.hasNext()) {
0419: HTTPArgument arg = (HTTPArgument) args.next()
0420: .getObjectValue();
0421: String value;
0422: if (haveContentEncoding) {
0423: value = arg
0424: .getEncodedValue(contentEncoding);
0425: } else {
0426: value = arg.getEncodedValue();
0427: }
0428: postBody.append(value);
0429: }
0430: StringRequestEntity requestEntity = new StringRequestEntity(
0431: postBody.toString(), post.getRequestHeader(
0432: HEADER_CONTENT_TYPE).getValue(),
0433: post.getRequestCharSet());
0434: post.setRequestEntity(requestEntity);
0435: } else {
0436: // It is a normal post request, with parameter names and values
0437:
0438: // Set the content type
0439: if (!hasContentTypeHeader) {
0440: post.setRequestHeader(HEADER_CONTENT_TYPE,
0441: APPLICATION_X_WWW_FORM_URLENCODED);
0442: }
0443: // Add the parameters
0444: PropertyIterator args = getArguments().iterator();
0445: while (args.hasNext()) {
0446: HTTPArgument arg = (HTTPArgument) args.next()
0447: .getObjectValue();
0448: // The HTTPClient always urlencodes both name and value,
0449: // so if the argument is already encoded, we have to decode
0450: // it before adding it to the post request
0451: String parameterName = arg.getName();
0452: if (parameterName.length() == 0) {
0453: continue; // Skip parameters with a blank name (allows use of optional variables in parameter lists)
0454: }
0455: String parameterValue = arg.getValue();
0456: if (!arg.isAlwaysEncoded()) {
0457: // The value is already encoded by the user
0458: // Must decode the value now, so that when the
0459: // httpclient encodes it, we end up with the same value
0460: // as the user had entered.
0461: String urlContentEncoding = contentEncoding;
0462: if (urlContentEncoding == null
0463: || urlContentEncoding.length() == 0) {
0464: // Use the default encoding for urls
0465: urlContentEncoding = EncoderCache.URL_ARGUMENT_ENCODING;
0466: }
0467: parameterName = URLDecoder.decode(
0468: parameterName, urlContentEncoding);
0469: parameterValue = URLDecoder.decode(
0470: parameterValue, urlContentEncoding);
0471: }
0472: // Add the parameter, httpclient will urlencode it
0473: post
0474: .addParameter(parameterName,
0475: parameterValue);
0476: }
0477:
0478: /*
0479: // // Alternative implementation, to make sure that HTTPSampler and HTTPSampler2
0480: // // sends the same post body.
0481: //
0482: // // Only include the content char set in the content-type header if it is not
0483: // // an APPLICATION_X_WWW_FORM_URLENCODED content type
0484: // String contentCharSet = null;
0485: // if(!post.getRequestHeader(HEADER_CONTENT_TYPE).getValue().equals(APPLICATION_X_WWW_FORM_URLENCODED)) {
0486: // contentCharSet = post.getRequestCharSet();
0487: // }
0488: // StringRequestEntity requestEntity = new StringRequestEntity(getQueryString(contentEncoding), post.getRequestHeader(HEADER_CONTENT_TYPE).getValue(), contentCharSet);
0489: // post.setRequestEntity(requestEntity);
0490: */
0491: }
0492:
0493: // If the request entity is repeatable, we can send it first to
0494: // our own stream, so we can return it
0495: if (post.getRequestEntity().isRepeatable()) {
0496: ByteArrayOutputStream bos = new ByteArrayOutputStream();
0497: post.getRequestEntity().writeRequest(bos);
0498: bos.flush();
0499: // We get the posted bytes as UTF-8, since java is using UTF-8
0500: postedBody.append(new String(bos.toByteArray(),
0501: "UTF-8")); // $NON-NLS-1$
0502: bos.close();
0503: } else {
0504: postedBody
0505: .append("<RequestEntity was not repeatable, cannot view what was sent>");
0506: }
0507: }
0508: }
0509: // Set the content length
0510: post.setRequestHeader(HEADER_CONTENT_LENGTH, Long.toString(post
0511: .getRequestEntity().getContentLength()));
0512:
0513: return postedBody.toString();
0514: }
0515:
0516: /**
0517: * Returns an <code>HttpConnection</code> fully ready to attempt
0518: * connection. This means it sets the request method (GET or POST), headers,
0519: * cookies, and authorization for the URL request.
0520: * <p>
0521: * The request infos are saved into the sample result if one is provided.
0522: *
0523: * @param u
0524: * <code>URL</code> of the URL request
0525: * @param httpMethod
0526: * GET/PUT/HEAD etc
0527: * @param res
0528: * sample result to save request infos to
0529: * @return <code>HttpConnection</code> ready for .connect
0530: * @exception IOException
0531: * if an I/O Exception occurs
0532: */
0533: protected HttpClient setupConnection(URL u,
0534: HttpMethodBase httpMethod, HTTPSampleResult res)
0535: throws IOException {
0536:
0537: String urlStr = u.toString();
0538:
0539: org.apache.commons.httpclient.URI uri = new org.apache.commons.httpclient.URI(
0540: urlStr, false);
0541:
0542: String schema = uri.getScheme();
0543: if ((schema == null) || (schema.length() == 0)) {
0544: schema = PROTOCOL_HTTP;
0545: }
0546:
0547: if (PROTOCOL_HTTPS.equalsIgnoreCase(schema)) {
0548: SSLManager.getInstance(); // ensure the manager is initialised
0549: // we don't currently need to do anything further, as this sets the default https protocol
0550: }
0551:
0552: Protocol protocol = Protocol.getProtocol(schema);
0553:
0554: String host = uri.getHost();
0555: int port = uri.getPort();
0556:
0557: /*
0558: * We use the HostConfiguration as the key to retrieve the HttpClient,
0559: * so need to ensure that any items used in its equals/hashcode methods are
0560: * not changed after use, i.e.:
0561: * host, port, protocol, localAddress, proxy
0562: *
0563: */
0564: HostConfiguration hc = new HostConfiguration();
0565: hc.setHost(host, port, protocol); // All needed to ensure re-usablility
0566:
0567: // Set up the local address if one exists
0568: if (localAddress != null) {
0569: hc.setLocalAddress(localAddress);
0570: }
0571:
0572: boolean useProxy = PROXY_DEFINED && !isNonProxy(host);
0573: if (useProxy) {
0574: if (log.isDebugEnabled()) {
0575: log.debug("Setting proxy: " + PROXY_HOST + ":"
0576: + PROXY_PORT);
0577: }
0578: hc.setProxy(PROXY_HOST, PROXY_PORT);
0579: }
0580:
0581: Map map = (Map) httpClients.get();
0582: HttpClient httpClient = (HttpClient) map.get(hc);
0583:
0584: if (httpClient == null) {
0585: httpClient = new HttpClient(
0586: new SimpleHttpConnectionManager());
0587: httpClient.setHostConfiguration(hc);
0588: map.put(hc, httpClient);
0589: // These items don't change, so only need to be done once
0590: if (useProxy) {
0591: if (PROXY_USER.length() > 0) {
0592: httpClient.getState().setProxyCredentials(
0593: new AuthScope(PROXY_HOST, PROXY_PORT, null,
0594: AuthScope.ANY_SCHEME),
0595: new NTCredentials(PROXY_USER, PROXY_PASS,
0596: localHost, PROXY_DOMAIN));
0597: }
0598: }
0599:
0600: }
0601:
0602: // Allow HttpClient to handle the redirects:
0603: httpMethod.setFollowRedirects(getAutoRedirects());
0604:
0605: // a well-behaved browser is supposed to send 'Connection: close'
0606: // with the last request to an HTTP server. Instead, most browsers
0607: // leave it to the server to close the connection after their
0608: // timeout period. Leave it to the JMeter user to decide.
0609: if (getUseKeepAlive()) {
0610: httpMethod.setRequestHeader(HEADER_CONNECTION, KEEP_ALIVE);
0611: } else {
0612: httpMethod.setRequestHeader(HEADER_CONNECTION,
0613: CONNECTION_CLOSE);
0614: }
0615:
0616: setConnectionHeaders(httpMethod, u, getHeaderManager());
0617: String cookies = setConnectionCookie(httpMethod, u,
0618: getCookieManager());
0619:
0620: setConnectionAuthorization(httpClient, u, getAuthManager());
0621:
0622: if (res != null) {
0623: res.setURL(u);
0624: res.setCookies(cookies);
0625: }
0626:
0627: return httpClient;
0628: }
0629:
0630: /**
0631: * Set any default request headers to include
0632: *
0633: * @param httpMethod the HttpMethod used for the request
0634: */
0635: protected void setDefaultRequestHeaders(HttpMethod httpMethod) {
0636: // Method left empty here, but allows subclasses to override
0637: }
0638:
0639: /**
0640: * Gets the ResponseHeaders
0641: *
0642: * @param method
0643: * connection from which the headers are read
0644: * @return string containing the headers, one per line
0645: */
0646: protected String getResponseHeaders(HttpMethod method) {
0647: StringBuffer headerBuf = new StringBuffer();
0648: org.apache.commons.httpclient.Header rh[] = method
0649: .getResponseHeaders();
0650: headerBuf.append(method.getStatusLine());// header[0] is not the status line...
0651: headerBuf.append("\n"); // $NON-NLS-1$
0652:
0653: for (int i = 0; i < rh.length; i++) {
0654: String key = rh[i].getName();
0655: headerBuf.append(key);
0656: headerBuf.append(": "); // $NON-NLS-1$
0657: headerBuf.append(rh[i].getValue());
0658: headerBuf.append("\n"); // $NON-NLS-1$
0659: }
0660: return headerBuf.toString();
0661: }
0662:
0663: /**
0664: * Extracts all the required cookies for that particular URL request and
0665: * sets them in the <code>HttpMethod</code> passed in.
0666: *
0667: * @param method <code>HttpMethod</code> for the request
0668: * @param u <code>URL</code> of the request
0669: * @param cookieManager the <code>CookieManager</code> containing all the cookies
0670: * @return a String containing the cookie details (for the response)
0671: * May be null
0672: */
0673: private String setConnectionCookie(HttpMethod method, URL u,
0674: CookieManager cookieManager) {
0675: String cookieHeader = null;
0676: if (cookieManager != null) {
0677: cookieHeader = cookieManager.getCookieHeaderForURL(u);
0678: if (cookieHeader != null) {
0679: method.setRequestHeader(HEADER_COOKIE, cookieHeader);
0680: }
0681: }
0682: return cookieHeader;
0683: }
0684:
0685: /**
0686: * Extracts all the required non-cookie headers for that particular URL request and
0687: * sets them in the <code>HttpMethod</code> passed in
0688: *
0689: * @param method
0690: * <code>HttpMethod</code> which represents the request
0691: * @param u
0692: * <code>URL</code> of the URL request
0693: * @param headerManager
0694: * the <code>HeaderManager</code> containing all the cookies
0695: * for this <code>UrlConfig</code>
0696: */
0697: private void setConnectionHeaders(HttpMethod method, URL u,
0698: HeaderManager headerManager) {
0699: // Set all the headers from the HeaderManager
0700: if (headerManager != null) {
0701: CollectionProperty headers = headerManager.getHeaders();
0702: if (headers != null) {
0703: PropertyIterator i = headers.iterator();
0704: while (i.hasNext()) {
0705: org.apache.jmeter.protocol.http.control.Header header = (org.apache.jmeter.protocol.http.control.Header) i
0706: .next().getObjectValue();
0707: String n = header.getName();
0708: // Don't allow override of Content-Length
0709: // This helps with SoapSampler hack too
0710: // TODO - what other headers are not allowed?
0711: if (!HEADER_CONTENT_LENGTH.equalsIgnoreCase(n)) {
0712: String v = header.getValue();
0713: method.addRequestHeader(n, v);
0714: }
0715: }
0716: }
0717: }
0718: }
0719:
0720: /**
0721: * Get all the request headers for the <code>HttpMethod</code>
0722: *
0723: * @param method
0724: * <code>HttpMethod</code> which represents the request
0725: * @return the headers as a string
0726: */
0727: private String getConnectionHeaders(HttpMethod method) {
0728: // Get all the request headers
0729: StringBuffer hdrs = new StringBuffer(100);
0730: Header[] requestHeaders = method.getRequestHeaders();
0731: for (int i = 0; i < requestHeaders.length; i++) {
0732: // Exclude the COOKIE header, since cookie is reported separately in the sample
0733: if (!HEADER_COOKIE.equalsIgnoreCase(requestHeaders[i]
0734: .getName())) {
0735: hdrs.append(requestHeaders[i].getName());
0736: hdrs.append(": "); // $NON-NLS-1$
0737: hdrs.append(requestHeaders[i].getValue());
0738: hdrs.append("\n"); // $NON-NLS-1$
0739: }
0740: }
0741:
0742: return hdrs.toString();
0743: }
0744:
0745: /**
0746: * Extracts all the required authorization for that particular URL request
0747: * and sets it in the <code>HttpMethod</code> passed in.
0748: *
0749: * @param client the HttpClient object
0750: *
0751: * @param u
0752: * <code>URL</code> of the URL request
0753: * @param authManager
0754: * the <code>AuthManager</code> containing all the authorisations for
0755: * this <code>UrlConfig</code>
0756: */
0757: private void setConnectionAuthorization(HttpClient client, URL u,
0758: AuthManager authManager) {
0759: HttpParams params = client.getParams();
0760: if (authManager != null) {
0761: Authorization auth = authManager.getAuthForURL(u);
0762: if (auth != null) {
0763: String username = auth.getUser();
0764: String realm = auth.getRealm();
0765: String domain = auth.getDomain();
0766: if (log.isDebugEnabled()) {
0767: log.debug(username + " > D=" + username + " D="
0768: + domain + " R=" + realm);
0769: }
0770: client.getState().setCredentials(
0771: new AuthScope(u.getHost(), u.getPort(), realm
0772: .length() == 0 ? null : realm //"" is not the same as no realm
0773: , AuthScope.ANY_SCHEME),
0774: // NT Includes other types of Credentials
0775: new NTCredentials(username, auth.getPass(),
0776: localHost, domain));
0777: // We have credentials - should we set pre-emptive authentication?
0778: if (canSetPreEmptive) {
0779: log.debug("Setting Pre-emptive authentication");
0780: params.setBooleanParameter(
0781: HTTP_AUTHENTICATION_PREEMPTIVE, true);
0782: }
0783: } else {
0784: client.getState().clearCredentials();
0785: if (canSetPreEmptive) {
0786: params.setBooleanParameter(
0787: HTTP_AUTHENTICATION_PREEMPTIVE, false);
0788: }
0789: }
0790: } else {
0791: client.getState().clearCredentials();
0792: }
0793: }
0794:
0795: /**
0796: * Samples the URL passed in and stores the result in
0797: * <code>HTTPSampleResult</code>, following redirects and downloading
0798: * page resources as appropriate.
0799: * <p>
0800: * When getting a redirect target, redirects are not followed and resources
0801: * are not downloaded. The caller will take care of this.
0802: *
0803: * @param url
0804: * URL to sample
0805: * @param method
0806: * HTTP method: GET, POST,...
0807: * @param areFollowingRedirect
0808: * whether we're getting a redirect target
0809: * @param frameDepth
0810: * Depth of this target in the frame structure. Used only to
0811: * prevent infinite recursion.
0812: * @return results of the sampling
0813: */
0814: protected HTTPSampleResult sample(URL url, String method,
0815: boolean areFollowingRedirect, int frameDepth) {
0816:
0817: String urlStr = url.toString();
0818:
0819: log.debug("Start : sample " + urlStr);
0820: log.debug("method " + method);
0821:
0822: HttpMethodBase httpMethod = null;
0823:
0824: HTTPSampleResult res = new HTTPSampleResult();
0825: res.setMonitor(isMonitor());
0826:
0827: res.setSampleLabel(urlStr); // May be replaced later
0828: res.setHTTPMethod(method);
0829: res.sampleStart(); // Count the retries as well in the time
0830: HttpClient client = null;
0831: InputStream instream = null;
0832: try {
0833: // May generate IllegalArgumentException
0834: if (method.equals(POST)) {
0835: httpMethod = new PostMethod(urlStr);
0836: } else if (method.equals(PUT)) {
0837: httpMethod = new PutMethod(urlStr);
0838: } else if (method.equals(HEAD)) {
0839: httpMethod = new HeadMethod(urlStr);
0840: } else if (method.equals(TRACE)) {
0841: httpMethod = new TraceMethod(urlStr);
0842: } else if (method.equals(OPTIONS)) {
0843: httpMethod = new OptionsMethod(urlStr);
0844: } else if (method.equals(DELETE)) {
0845: httpMethod = new DeleteMethod(urlStr);
0846: } else if (method.equals(GET)) {
0847: httpMethod = new GetMethod(urlStr);
0848: } else {
0849: log.error("Unexpected method (converted to GET): "
0850: + method);
0851: httpMethod = new GetMethod(urlStr);
0852: }
0853:
0854: // Set any default request headers
0855: setDefaultRequestHeaders(httpMethod);
0856: // Setup connection
0857: client = setupConnection(url, httpMethod, res);
0858: // Handle the various methods
0859: if (method.equals(POST)) {
0860: String postBody = sendPostData((PostMethod) httpMethod);
0861: res.setQueryString(postBody);
0862: } else if (method.equals(PUT)) {
0863: String putBody = sendPutData((PutMethod) httpMethod);
0864: res.setQueryString(putBody);
0865: }
0866:
0867: res.setRequestHeaders(getConnectionHeaders(httpMethod));
0868:
0869: int statusCode = client.executeMethod(httpMethod);
0870:
0871: // Request sent. Now get the response:
0872: instream = httpMethod.getResponseBodyAsStream();
0873:
0874: if (instream != null) {// will be null for HEAD
0875:
0876: Header responseHeader = httpMethod
0877: .getResponseHeader(HEADER_CONTENT_ENCODING);
0878: if (responseHeader != null
0879: && ENCODING_GZIP.equals(responseHeader
0880: .getValue())) {
0881: instream = new GZIPInputStream(instream);
0882: }
0883:
0884: //int contentLength = httpMethod.getResponseContentLength();Not visible ...
0885: //TODO size ouststream according to actual content length
0886: ByteArrayOutputStream outstream = new ByteArrayOutputStream(
0887: 4 * 1024);
0888: //contentLength > 0 ? contentLength : DEFAULT_INITIAL_BUFFER_SIZE);
0889: byte[] buffer = new byte[4096];
0890: int len;
0891: boolean first = true;// first response
0892: while ((len = instream.read(buffer)) > 0) {
0893: if (first) { // save the latency
0894: res.latencyEnd();
0895: first = false;
0896: }
0897: outstream.write(buffer, 0, len);
0898: }
0899:
0900: res.setResponseData(outstream.toByteArray());
0901: outstream.close();
0902:
0903: }
0904:
0905: res.sampleEnd();
0906: // Done with the sampling proper.
0907:
0908: // Now collect the results into the HTTPSampleResult:
0909:
0910: res.setSampleLabel(httpMethod.getURI().toString());
0911: // Pick up Actual path (after redirects)
0912:
0913: res.setResponseCode(Integer.toString(statusCode));
0914: res.setSuccessful(isSuccessCode(statusCode));
0915:
0916: res.setResponseMessage(httpMethod.getStatusText());
0917:
0918: String ct = null;
0919: org.apache.commons.httpclient.Header h = httpMethod
0920: .getResponseHeader(HEADER_CONTENT_TYPE);
0921: if (h != null)// Can be missing, e.g. on redirect
0922: {
0923: ct = h.getValue();
0924: res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1
0925: res.setEncodingAndType(ct);
0926: }
0927:
0928: res.setResponseHeaders(getResponseHeaders(httpMethod));
0929: if (res.isRedirect()) {
0930: final Header headerLocation = httpMethod
0931: .getResponseHeader(HEADER_LOCATION);
0932: if (headerLocation == null) { // HTTP protocol violation, but avoids NPE
0933: throw new IllegalArgumentException(
0934: "Missing location header");
0935: }
0936: res.setRedirectLocation(headerLocation.getValue());
0937: }
0938:
0939: // If we redirected automatically, the URL may have changed
0940: if (getAutoRedirects()) {
0941: res.setURL(new URL(httpMethod.getURI().toString()));
0942: }
0943:
0944: // Store any cookies received in the cookie manager:
0945: saveConnectionCookies(httpMethod, res.getURL(),
0946: getCookieManager());
0947:
0948: // Follow redirects and download page resources if appropriate:
0949: res = resultProcessing(areFollowingRedirect, frameDepth,
0950: res);
0951:
0952: log.debug("End : sample");
0953: if (httpMethod != null)
0954: httpMethod.releaseConnection();
0955: return res;
0956: } catch (IllegalArgumentException e)// e.g. some kinds of invalid URL
0957: {
0958: res.sampleEnd();
0959: HTTPSampleResult err = errorResult(e, res);
0960: err.setSampleLabel("Error: " + url.toString());
0961: return err;
0962: } catch (IOException e) {
0963: res.sampleEnd();
0964: HTTPSampleResult err = errorResult(e, res);
0965: err.setSampleLabel("Error: " + url.toString());
0966: return err;
0967: } finally {
0968: JOrphanUtils.closeQuietly(instream);
0969: if (httpMethod != null)
0970: httpMethod.releaseConnection();
0971: }
0972: }
0973:
0974: /**
0975: * Set up the PUT data
0976: */
0977: private String sendPutData(PutMethod put) throws IOException {
0978: // Buffer to hold the put body, except file content
0979: StringBuffer putBody = new StringBuffer(1000);
0980: boolean hasPutBody = false;
0981:
0982: // Check if the header manager had a content type header
0983: // This allows the user to specify his own content-type for a POST request
0984: Header contentTypeHeader = put
0985: .getRequestHeader(HEADER_CONTENT_TYPE);
0986: boolean hasContentTypeHeader = contentTypeHeader != null
0987: && contentTypeHeader.getValue() != null
0988: && contentTypeHeader.getValue().length() > 0;
0989:
0990: // If there are no arguments, we can send a file as the body of the request
0991: if (!hasArguments() && getSendFileAsPostBody()) {
0992: hasPutBody = true;
0993:
0994: FileRequestEntity fileRequestEntity = new FileRequestEntity(
0995: new File(getFilename()), null);
0996: put.setRequestEntity(fileRequestEntity);
0997:
0998: // We just add placeholder text for file content
0999: putBody.append("<actual file content, not shown here>");
1000: }
1001: // If none of the arguments have a name specified, we
1002: // just send all the values as the put body
1003: else if (getSendParameterValuesAsPostBody()) {
1004: hasPutBody = true;
1005:
1006: // If a content encoding is specified, we set it as http parameter, so that
1007: // the post body will be encoded in the specified content encoding
1008: final String contentEncoding = getContentEncoding();
1009: boolean haveContentEncoding = false;
1010: if (contentEncoding != null
1011: && contentEncoding.trim().length() > 0) {
1012: put.getParams().setContentCharset(contentEncoding);
1013: haveContentEncoding = true;
1014: }
1015:
1016: // Just append all the parameter values, and use that as the post body
1017: StringBuffer putBodyContent = new StringBuffer();
1018: PropertyIterator args = getArguments().iterator();
1019: while (args.hasNext()) {
1020: HTTPArgument arg = (HTTPArgument) args.next()
1021: .getObjectValue();
1022: String value = null;
1023: if (haveContentEncoding) {
1024: value = arg.getEncodedValue(contentEncoding);
1025: } else {
1026: value = arg.getEncodedValue();
1027: }
1028: putBodyContent.append(value);
1029: }
1030: String contentTypeValue = null;
1031: if (hasContentTypeHeader) {
1032: contentTypeValue = put.getRequestHeader(
1033: HEADER_CONTENT_TYPE).getValue();
1034: }
1035: StringRequestEntity requestEntity = new StringRequestEntity(
1036: putBodyContent.toString(), contentTypeValue, put
1037: .getRequestCharSet());
1038: put.setRequestEntity(requestEntity);
1039: }
1040: // Check if we have any content to send for body
1041: if (hasPutBody) {
1042: // If the request entity is repeatable, we can send it first to
1043: // our own stream, so we can return it
1044: if (put.getRequestEntity().isRepeatable()) {
1045: ByteArrayOutputStream bos = new ByteArrayOutputStream();
1046: put.getRequestEntity().writeRequest(bos);
1047: bos.flush();
1048: // We get the posted bytes as UTF-8, since java is using UTF-8
1049: putBody.append(new String(bos.toByteArray(), "UTF-8")); // $NON-NLS-1$
1050: bos.close();
1051: } else {
1052: putBody
1053: .append("<RequestEntity was not repeatable, cannot view what was sent>");
1054: }
1055: if (!hasContentTypeHeader) {
1056: // Allow the mimetype of the file to control the content type
1057: // This is not obvious in GUI if you are not uploading any files,
1058: // but just sending the content of nameless parameters
1059: if (getMimetype() != null && getMimetype().length() > 0) {
1060: put.setRequestHeader(HEADER_CONTENT_TYPE,
1061: getMimetype());
1062: }
1063: }
1064: // Set the content length
1065: put.setRequestHeader(HEADER_CONTENT_LENGTH,
1066: Long.toString(put.getRequestEntity()
1067: .getContentLength()));
1068: return putBody.toString();
1069: } else {
1070: return null;
1071: }
1072: }
1073:
1074: /**
1075: * Class extending FilePart, so that we can send placeholder text
1076: * instead of the actual file content
1077: */
1078: private static class ViewableFilePart extends FilePart {
1079: private boolean hideFileData;
1080:
1081: public ViewableFilePart(String name, File file,
1082: String contentType, String charset)
1083: throws FileNotFoundException {
1084: super (name, file, contentType, charset);
1085: this .hideFileData = false;
1086: }
1087:
1088: public void setHideFileData(boolean hideFileData) {
1089: this .hideFileData = hideFileData;
1090: }
1091:
1092: protected void sendData(OutputStream out) throws IOException {
1093: // Check if we should send only placeholder text for the
1094: // file content, or the real file content
1095: if (hideFileData) {
1096: out.write("<actual file content, not shown here>"
1097: .getBytes("UTF-8"));
1098: } else {
1099: super .sendData(out);
1100: }
1101: }
1102: }
1103:
1104: /**
1105: * From the <code>HttpMethod</code>, store all the "set-cookie" key-pair
1106: * values in the cookieManager of the <code>UrlConfig</code>.
1107: *
1108: * @param method
1109: * <code>HttpMethod</code> which represents the request
1110: * @param u
1111: * <code>URL</code> of the URL request
1112: * @param cookieManager
1113: * the <code>CookieManager</code> containing all the cookies
1114: */
1115: protected void saveConnectionCookies(HttpMethod method, URL u,
1116: CookieManager cookieManager) {
1117: if (cookieManager != null) {
1118: Header hdr[] = method.getResponseHeaders(HEADER_SET_COOKIE);
1119: for (int i = 0; i < hdr.length; i++) {
1120: cookieManager.addCookieFromHeader(hdr[i].getValue(), u);
1121: }
1122: }
1123: }
1124:
1125: public void threadStarted() {
1126: log.debug("Thread Started");
1127:
1128: // Does not need to be synchronised, as all access is from same thread
1129: httpClients.set(new HashMap());
1130: }
1131:
1132: public void threadFinished() {
1133: log.debug("Thread Finished");
1134:
1135: // Does not need to be synchronised, as all access is from same thread
1136: Map map = (Map) httpClients.get();
1137:
1138: if (map != null) {
1139: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
1140: Map.Entry entry = (Map.Entry) it.next();
1141: HttpClient cl = (HttpClient) entry.getValue();
1142: cl.getHttpConnectionManager().closeIdleConnections(
1143: -1000);// Closes the connection
1144: }
1145: map.clear();
1146: }
1147: }
1148: }
|