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.FileReader;
0021: import java.io.IOException;
0022: import java.io.PrintStream;
0023: import java.io.UnsupportedEncodingException;
0024: import java.net.MalformedURLException;
0025: import java.net.URI;
0026: import java.net.URISyntaxException;
0027: import java.net.URL;
0028: import java.util.Arrays;
0029: import java.util.Collections;
0030: import java.util.HashMap;
0031: import java.util.Iterator;
0032: import java.util.List;
0033: import java.util.Map;
0034:
0035: import org.apache.commons.io.IOUtils;
0036: import org.apache.jmeter.config.Argument;
0037: import org.apache.jmeter.config.Arguments;
0038: import org.apache.jmeter.engine.event.LoopIterationEvent;
0039: import org.apache.jmeter.protocol.http.control.AuthManager;
0040: import org.apache.jmeter.protocol.http.control.CookieManager;
0041: import org.apache.jmeter.protocol.http.control.HeaderManager;
0042: import org.apache.jmeter.protocol.http.parser.HTMLParseException;
0043: import org.apache.jmeter.protocol.http.parser.HTMLParser;
0044: import org.apache.jmeter.protocol.http.util.EncoderCache;
0045: import org.apache.jmeter.protocol.http.util.HTTPArgument;
0046: import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface;
0047: import org.apache.jmeter.samplers.AbstractSampler;
0048: import org.apache.jmeter.samplers.Entry;
0049: import org.apache.jmeter.samplers.SampleResult;
0050: import org.apache.jmeter.testelement.TestElement;
0051: import org.apache.jmeter.testelement.TestListener;
0052: import org.apache.jmeter.testelement.ThreadListener;
0053: import org.apache.jmeter.testelement.property.BooleanProperty;
0054: import org.apache.jmeter.testelement.property.IntegerProperty;
0055: import org.apache.jmeter.testelement.property.JMeterProperty;
0056: import org.apache.jmeter.testelement.property.PropertyIterator;
0057: import org.apache.jmeter.testelement.property.StringProperty;
0058: import org.apache.jmeter.testelement.property.TestElementProperty;
0059: import org.apache.jmeter.util.JMeterUtils;
0060: import org.apache.jorphan.logging.LoggingManager;
0061: import org.apache.jorphan.util.JOrphanUtils;
0062: import org.apache.log.Logger;
0063: import org.apache.oro.text.MalformedCachePatternException;
0064: import org.apache.oro.text.regex.Pattern;
0065: import org.apache.oro.text.regex.Perl5Matcher;
0066:
0067: /**
0068: * Common constants and methods for HTTP samplers
0069: *
0070: */
0071: public abstract class HTTPSamplerBase extends AbstractSampler implements
0072: TestListener, ThreadListener, HTTPConstantsInterface {
0073:
0074: private static final Logger log = LoggingManager
0075: .getLoggerForClass();
0076:
0077: public final static String ARGUMENTS = "HTTPsampler.Arguments"; // $NON-NLS-1$
0078:
0079: public final static String AUTH_MANAGER = "HTTPSampler.auth_manager"; // $NON-NLS-1$
0080:
0081: public final static String COOKIE_MANAGER = "HTTPSampler.cookie_manager"; // $NON-NLS-1$
0082:
0083: public final static String HEADER_MANAGER = "HTTPSampler.header_manager"; // $NON-NLS-1$
0084:
0085: public final static String MIMETYPE = "HTTPSampler.mimetype"; // $NON-NLS-1$
0086:
0087: public final static String DOMAIN = "HTTPSampler.domain"; // $NON-NLS-1$
0088:
0089: public final static String PORT = "HTTPSampler.port"; // $NON-NLS-1$
0090:
0091: public final static String METHOD = "HTTPSampler.method"; // $NON-NLS-1$
0092:
0093: public final static String CONTENT_ENCODING = "HTTPSampler.contentEncoding"; // $NON-NLS-1$
0094:
0095: public final static String IMPLEMENTATION = "HTTPSampler.implementation"; // $NON-NLS-1$
0096:
0097: public final static String PATH = "HTTPSampler.path"; // $NON-NLS-1$
0098:
0099: public final static String FOLLOW_REDIRECTS = "HTTPSampler.follow_redirects"; // $NON-NLS-1$
0100:
0101: public final static String AUTO_REDIRECTS = "HTTPSampler.auto_redirects"; // $NON-NLS-1$
0102:
0103: public final static String PROTOCOL = "HTTPSampler.protocol"; // $NON-NLS-1$
0104:
0105: private static final String PROTOCOL_FILE = "file"; // $NON-NLS-1$
0106:
0107: private final static String DEFAULT_PROTOCOL = PROTOCOL_HTTP;
0108:
0109: public final static String URL = "HTTPSampler.URL"; // $NON-NLS-1$
0110:
0111: public static final String CLIENT = "HTTPSampler.client"; // $NON-NLS-1$
0112:
0113: public final static String DEFAULT_METHOD = GET; // $NON-NLS-1$
0114: // Supported methods:
0115: private final static String[] METHODS = { DEFAULT_METHOD, // i.e. GET
0116: POST, HEAD, PUT, OPTIONS, TRACE, DELETE, };
0117:
0118: private final static List METHODLIST = Collections
0119: .unmodifiableList(Arrays.asList(METHODS));
0120:
0121: public final static String USE_KEEPALIVE = "HTTPSampler.use_keepalive"; // $NON-NLS-1$
0122:
0123: public final static String FILE_NAME = "HTTPSampler.FILE_NAME"; // $NON-NLS-1$
0124:
0125: public final static String DO_MULTIPART_POST = "HTTPSampler.DO_MULTIPART_POST"; // $NON-NLS-1$
0126:
0127: /* Shown as Parameter Name on the GUI */
0128: public final static String FILE_FIELD = "HTTPSampler.FILE_FIELD"; // $NON-NLS-1$
0129:
0130: // public final static String FILE_DATA = "HTTPSampler.FILE_DATA"; // $NON-NLS-1$
0131:
0132: // public final static String FILE_MIMETYPE = "HTTPSampler.FILE_MIMETYPE"; // $NON-NLS-1$
0133:
0134: public final static String CONTENT_TYPE = "HTTPSampler.CONTENT_TYPE"; // $NON-NLS-1$
0135:
0136: // public final static String NORMAL_FORM = "normal_form"; // $NON-NLS-1$
0137:
0138: // public final static String MULTIPART_FORM = "multipart_form"; // $NON-NLS-1$
0139:
0140: // public final static String ENCODED_PATH= "HTTPSampler.encoded_path";
0141:
0142: // IMAGE_PARSER now really means EMBEDDED_PARSER
0143: public final static String IMAGE_PARSER = "HTTPSampler.image_parser"; // $NON-NLS-1$
0144:
0145: // Embedded URLs must match this RE (if provided)
0146: public final static String EMBEDDED_URL_RE = "HTTPSampler.embedded_url_re"; // $NON-NLS-1$
0147:
0148: public final static String MONITOR = "HTTPSampler.monitor"; // $NON-NLS-1$
0149:
0150: /** A number to indicate that the port has not been set. * */
0151: public static final int UNSPECIFIED_PORT = 0;
0152: public static final String UNSPECIFIED_PORT_AS_STRING = "0"; // $NON-NLS-1$
0153:
0154: protected final static String NON_HTTP_RESPONSE_CODE = "Non HTTP response code";
0155:
0156: protected final static String NON_HTTP_RESPONSE_MESSAGE = "Non HTTP response message";
0157:
0158: private static final String ARG_VAL_SEP = "="; // $NON-NLS-1$
0159:
0160: private static final String QRY_SEP = "&"; // $NON-NLS-1$
0161:
0162: private static final String QRY_PFX = "?"; // $NON-NLS-1$
0163:
0164: protected static final int MAX_REDIRECTS = JMeterUtils
0165: .getPropDefault("httpsampler.max_redirects", 5); // $NON-NLS-1$
0166:
0167: protected static final int MAX_FRAME_DEPTH = JMeterUtils
0168: .getPropDefault("httpsampler.max_frame_depth", 5); // $NON-NLS-1$
0169:
0170: // Derive the mapping of content types to parsers
0171: private static Map parsersForType = new HashMap();
0172: // Not synch, but it is not modified after creation
0173:
0174: private static final String RESPONSE_PARSERS = // list of parsers
0175: JMeterUtils.getProperty("HTTPResponse.parsers");//$NON-NLS-1$
0176:
0177: static {
0178: String[] parsers = JOrphanUtils.split(RESPONSE_PARSERS, " ",
0179: true);// returns empty array for null
0180: for (int i = 0; i < parsers.length; i++) {
0181: final String parser = parsers[i];
0182: String classname = JMeterUtils.getProperty(parser
0183: + ".className");//$NON-NLS-1$
0184: if (classname == null) {
0185: log.info("Cannot find .className property for "
0186: + parser + ", using default");
0187: classname = "";
0188: }
0189: String typelist = JMeterUtils
0190: .getProperty(parser + ".types");//$NON-NLS-1$
0191: if (typelist != null) {
0192: String[] types = JOrphanUtils
0193: .split(typelist, " ", true);
0194: for (int j = 0; j < types.length; j++) {
0195: final String type = types[j];
0196: log.info("Parser for " + type + " is " + classname);
0197: parsersForType.put(type, classname);
0198: }
0199: } else {
0200: log.warn("Cannot find .types property for " + parser);
0201: }
0202: }
0203: if (parsers.length == 0) { // revert to previous behaviour
0204: parsersForType.put("text/html", ""); //$NON-NLS-1$ //$NON-NLS-2$
0205: log
0206: .info("No response parsers defined: text/html only will be scanned for embedded resources");
0207: }
0208: }
0209:
0210: ////////////////////// Variables //////////////////////
0211:
0212: private boolean dynamicPath = false;// Set false if spaces are already encoded
0213:
0214: ////////////////////// Code ///////////////////////////
0215:
0216: public HTTPSamplerBase() {
0217: setArguments(new Arguments());
0218: }
0219:
0220: /**
0221: * The name parameter to be applied to the file
0222: */
0223: public void setFileField(String value) {
0224: setProperty(FILE_FIELD, value);
0225: }
0226:
0227: /**
0228: * The name parameter to be applied to the file
0229: */
0230: public String getFileField() {
0231: return getPropertyAsString(FILE_FIELD);
0232: }
0233:
0234: /**
0235: * The actual name of the file to POST
0236: */
0237: public void setFilename(String value) {
0238: setProperty(FILE_NAME, value);
0239: }
0240:
0241: /**
0242: * The actual name of the file to POST
0243: */
0244: public String getFilename() {
0245: return getPropertyAsString(FILE_NAME);
0246: }
0247:
0248: /**
0249: * Determine if the file should be sent as the entire Post body,
0250: * i.e. without any additional wrapping
0251: *
0252: * @return true if specified file is to be sent as the body,
0253: * i.e. FileField is blank
0254: */
0255: public boolean getSendFileAsPostBody() {
0256: // If no file field is specified, the file is sent as post body
0257: return getFileField().length() == 0
0258: && getFilename().length() > 0;
0259: }
0260:
0261: /**
0262: * Determine if none of the parameters have a name, and if that
0263: * is the case, it means that the parameter values should be sent
0264: * as the post body
0265: *
0266: * @return true if none of the parameters have a name specified
0267: */
0268: public boolean getSendParameterValuesAsPostBody() {
0269: boolean noArgumentsHasName = true;
0270: PropertyIterator args = getArguments().iterator();
0271: while (args.hasNext()) {
0272: HTTPArgument arg = (HTTPArgument) args.next()
0273: .getObjectValue();
0274: if (arg.getName() != null && arg.getName().length() > 0) {
0275: noArgumentsHasName = false;
0276: break;
0277: }
0278: }
0279: return noArgumentsHasName;
0280: }
0281:
0282: /**
0283: * Determine if we should use multipart/form-data or
0284: * application/x-www-form-urlencoded for the post
0285: *
0286: * @return true if multipart/form-data should be used and method is POST
0287: */
0288: public boolean getUseMultipartForPost() {
0289: // We use multipart if we have been told so, or files are present
0290: // and the files should not be send as the post body
0291: if (POST.equals(getMethod())
0292: && (getDoMultipartPost() || (hasUploadableFiles() && !getSendFileAsPostBody()))) {
0293: return true;
0294: } else {
0295: return false;
0296: }
0297: }
0298:
0299: public void setProtocol(String value) {
0300: setProperty(PROTOCOL, value.toLowerCase());
0301: }
0302:
0303: public String getProtocol() {
0304: String protocol = getPropertyAsString(PROTOCOL);
0305: if (protocol == null || protocol.length() == 0) {
0306: return DEFAULT_PROTOCOL;
0307: }
0308: return protocol;
0309: }
0310:
0311: public String getClient() {// TODO should it have a default?
0312: return getPropertyAsString(CLIENT);
0313: }
0314:
0315: public void setClient(String client) {
0316: setProperty(CLIENT, client);
0317: }
0318:
0319: /**
0320: * Sets the Path attribute of the UrlConfig object Also calls parseArguments
0321: * to extract and store any query arguments
0322: *
0323: * @param path
0324: * The new Path value
0325: */
0326: public void setPath(String path) {
0327: // We know that URL arguments should always be encoded in UTF-8 according to spec
0328: setPath(path, EncoderCache.URL_ARGUMENT_ENCODING);
0329: }
0330:
0331: /**
0332: * Sets the Path attribute of the UrlConfig object Also calls parseArguments
0333: * to extract and store any query arguments
0334: *
0335: * @param path
0336: * The new Path value
0337: * @param contentEncoding
0338: * The encoding used for the querystring parameter values
0339: */
0340: public void setPath(String path, String contentEncoding) {
0341: if (GET.equals(getMethod()) || DELETE.equals(getMethod())) {
0342: int index = path.indexOf(QRY_PFX);
0343: if (index > -1) {
0344: setProperty(PATH, path.substring(0, index));
0345: // Parse the arguments in querystring, assuming specified encoding for values
0346: parseArguments(path.substring(index + 1),
0347: contentEncoding);
0348: } else {
0349: setProperty(PATH, path);
0350: }
0351: } else {
0352: setProperty(PATH, path);
0353: }
0354: }
0355:
0356: public String getPath() {
0357: String p = getPropertyAsString(PATH);
0358: if (dynamicPath) {
0359: return encodeSpaces(p);
0360: }
0361: return p;
0362: }
0363:
0364: public void setFollowRedirects(boolean value) {
0365: setProperty(new BooleanProperty(FOLLOW_REDIRECTS, value));
0366: }
0367:
0368: public boolean getFollowRedirects() {
0369: return getPropertyAsBoolean(FOLLOW_REDIRECTS);
0370: }
0371:
0372: public void setAutoRedirects(boolean value) {
0373: setProperty(new BooleanProperty(AUTO_REDIRECTS, value));
0374: }
0375:
0376: public boolean getAutoRedirects() {
0377: return getPropertyAsBoolean(AUTO_REDIRECTS);
0378: }
0379:
0380: public void setMethod(String value) {
0381: setProperty(METHOD, value);
0382: }
0383:
0384: public String getMethod() {
0385: return getPropertyAsString(METHOD);
0386: }
0387:
0388: public void setContentEncoding(String value) {
0389: setProperty(CONTENT_ENCODING, value);
0390: }
0391:
0392: public String getContentEncoding() {
0393: return getPropertyAsString(CONTENT_ENCODING);
0394: }
0395:
0396: public void setUseKeepAlive(boolean value) {
0397: setProperty(new BooleanProperty(USE_KEEPALIVE, value));
0398: }
0399:
0400: public boolean getUseKeepAlive() {
0401: return getPropertyAsBoolean(USE_KEEPALIVE);
0402: }
0403:
0404: public void setDoMultipartPost(boolean value) {
0405: setProperty(new BooleanProperty(DO_MULTIPART_POST, value));
0406: }
0407:
0408: public boolean getDoMultipartPost() {
0409: return getPropertyAsBoolean(DO_MULTIPART_POST, false);
0410: }
0411:
0412: public void setMonitor(String value) {
0413: this .setProperty(MONITOR, value);
0414: }
0415:
0416: public void setMonitor(boolean truth) {
0417: this .setProperty(MONITOR, truth);
0418: }
0419:
0420: public String getMonitor() {
0421: return this .getPropertyAsString(MONITOR);
0422: }
0423:
0424: public boolean isMonitor() {
0425: return this .getPropertyAsBoolean(MONITOR);
0426: }
0427:
0428: public void setImplementation(String value) {
0429: this .setProperty(IMPLEMENTATION, value);
0430: }
0431:
0432: public String getImplementation() {
0433: return this .getPropertyAsString(IMPLEMENTATION);
0434: }
0435:
0436: /**
0437: * Add an argument which has already been encoded
0438: */
0439: public void addEncodedArgument(String name, String value) {
0440: this .addEncodedArgument(name, value, ARG_VAL_SEP);
0441: }
0442:
0443: public void addEncodedArgument(String name, String value,
0444: String metaData, String contentEncoding) {
0445: if (log.isDebugEnabled()) {
0446: log.debug("adding argument: name: " + name + " value: "
0447: + value + " metaData: " + metaData
0448: + " contentEncoding: " + contentEncoding);
0449: }
0450:
0451: HTTPArgument arg = null;
0452: if (contentEncoding != null) {
0453: arg = new HTTPArgument(name, value, metaData, true,
0454: contentEncoding);
0455: } else {
0456: arg = new HTTPArgument(name, value, metaData, true);
0457: }
0458:
0459: // Check if there are any difference between name and value and their encoded name and value
0460: String valueEncoded = null;
0461: if (contentEncoding != null) {
0462: try {
0463: valueEncoded = arg.getEncodedValue(contentEncoding);
0464: } catch (UnsupportedEncodingException e) {
0465: log.warn("Unable to get encoded value using encoding "
0466: + contentEncoding);
0467: valueEncoded = arg.getEncodedValue();
0468: }
0469: } else {
0470: valueEncoded = arg.getEncodedValue();
0471: }
0472: // If there is no difference, we mark it as not needing encoding
0473: if (arg.getName().equals(arg.getEncodedName())
0474: && arg.getValue().equals(valueEncoded)) {
0475: arg.setAlwaysEncoded(false);
0476: }
0477: this .getArguments().addArgument(arg);
0478: }
0479:
0480: public void addEncodedArgument(String name, String value,
0481: String metaData) {
0482: this .addEncodedArgument(name, value, metaData, null);
0483: }
0484:
0485: public void addNonEncodedArgument(String name, String value,
0486: String metadata) {
0487: HTTPArgument arg = new HTTPArgument(name, value, metadata,
0488: false);
0489: arg.setAlwaysEncoded(false);
0490: this .getArguments().addArgument(arg);
0491: }
0492:
0493: public void addArgument(String name, String value) {
0494: this .getArguments().addArgument(new HTTPArgument(name, value));
0495: }
0496:
0497: public void addArgument(String name, String value, String metadata) {
0498: this .getArguments().addArgument(
0499: new HTTPArgument(name, value, metadata));
0500: }
0501:
0502: public boolean hasArguments() {
0503: return getArguments().getArgumentCount() > 0;
0504: }
0505:
0506: public void addTestElement(TestElement el) {
0507: if (el instanceof CookieManager) {
0508: setCookieManager((CookieManager) el);
0509: } else if (el instanceof HeaderManager) {
0510: setHeaderManager((HeaderManager) el);
0511: } else if (el instanceof AuthManager) {
0512: setAuthManager((AuthManager) el);
0513: } else {
0514: super .addTestElement(el);
0515: }
0516: }
0517:
0518: public void setPort(int value) {
0519: setProperty(new IntegerProperty(PORT, value));
0520: }
0521:
0522: public static int getDefaultPort(String protocol, int port) {
0523: if (port == -1) {
0524: return protocol.equalsIgnoreCase(PROTOCOL_HTTP) ? DEFAULT_HTTP_PORT
0525: : protocol.equalsIgnoreCase(PROTOCOL_HTTPS) ? DEFAULT_HTTPS_PORT
0526: : port;
0527: }
0528: return port;
0529: }
0530:
0531: /**
0532: * Tell whether the default port for the specified protocol is used
0533: *
0534: * @return true if the default port number for the protocol is used, false otherwise
0535: */
0536: public boolean isProtocolDefaultPort() {
0537: final int port = getPropertyAsInt(PORT);
0538: final String protocol = getProtocol();
0539: if (port == UNSPECIFIED_PORT
0540: || (PROTOCOL_HTTP.equalsIgnoreCase(protocol) && port == DEFAULT_HTTP_PORT)
0541: || (PROTOCOL_HTTPS.equalsIgnoreCase(protocol) && port == DEFAULT_HTTPS_PORT)) {
0542: return true;
0543: } else {
0544: return false;
0545: }
0546: }
0547:
0548: public int getPort() {
0549: int port = getPropertyAsInt(PORT);
0550: if (port == UNSPECIFIED_PORT) {
0551: String prot = getProtocol();
0552: if (PROTOCOL_HTTPS.equalsIgnoreCase(prot)) {
0553: return DEFAULT_HTTPS_PORT;
0554: }
0555: if (!PROTOCOL_HTTP.equalsIgnoreCase(prot)) {
0556: log.warn("Unexpected protocol: " + prot);
0557: // TODO - should this return something else?
0558: }
0559: return DEFAULT_HTTP_PORT;
0560: }
0561: return port;
0562: }
0563:
0564: public void setDomain(String value) {
0565: setProperty(DOMAIN, value);
0566: }
0567:
0568: public String getDomain() {
0569: return getPropertyAsString(DOMAIN);
0570: }
0571:
0572: public void setArguments(Arguments value) {
0573: setProperty(new TestElementProperty(ARGUMENTS, value));
0574: }
0575:
0576: public Arguments getArguments() {
0577: return (Arguments) getProperty(ARGUMENTS).getObjectValue();
0578: }
0579:
0580: public void setAuthManager(AuthManager value) {
0581: AuthManager mgr = getAuthManager();
0582: if (mgr != null) {
0583: log.warn("Existing Manager " + mgr.getName()
0584: + " superseded by " + value.getName());
0585: }
0586: setProperty(new TestElementProperty(AUTH_MANAGER, value));
0587: }
0588:
0589: public AuthManager getAuthManager() {
0590: return (AuthManager) getProperty(AUTH_MANAGER).getObjectValue();
0591: }
0592:
0593: public void setHeaderManager(HeaderManager value) {
0594: HeaderManager mgr = getHeaderManager();
0595: if (mgr != null) {
0596: log.warn("Existing Manager " + mgr.getName()
0597: + " superseded by " + value.getName());
0598: }
0599: setProperty(new TestElementProperty(HEADER_MANAGER, value));
0600: }
0601:
0602: public HeaderManager getHeaderManager() {
0603: return (HeaderManager) getProperty(HEADER_MANAGER)
0604: .getObjectValue();
0605: }
0606:
0607: public void setCookieManager(CookieManager value) {
0608: CookieManager mgr = getCookieManager();
0609: if (mgr != null) {
0610: log.warn("Existing Manager " + mgr.getName()
0611: + " superseded by " + value.getName());
0612: }
0613: setProperty(new TestElementProperty(COOKIE_MANAGER, value));
0614: }
0615:
0616: public CookieManager getCookieManager() {
0617: return (CookieManager) getProperty(COOKIE_MANAGER)
0618: .getObjectValue();
0619: }
0620:
0621: public void setMimetype(String value) {
0622: setProperty(MIMETYPE, value);
0623: }
0624:
0625: public String getMimetype() {
0626: return getPropertyAsString(MIMETYPE);
0627: }
0628:
0629: public boolean isImageParser() {
0630: return getPropertyAsBoolean(IMAGE_PARSER);
0631: }
0632:
0633: public void setImageParser(boolean parseImages) {
0634: setProperty(new BooleanProperty(IMAGE_PARSER, parseImages));
0635: }
0636:
0637: /**
0638: * Get the regular expression URLs must match.
0639: *
0640: * @return regular expression (or empty) string
0641: */
0642: public String getEmbeddedUrlRE() {
0643: return getPropertyAsString(EMBEDDED_URL_RE, "");
0644: }
0645:
0646: public void setEmbeddedUrlRE(String regex) {
0647: setProperty(new StringProperty(EMBEDDED_URL_RE, regex));
0648: }
0649:
0650: /**
0651: * Obtain a result that will help inform the user that an error has occured
0652: * during sampling, and how long it took to detect the error.
0653: *
0654: * @param e
0655: * Exception representing the error.
0656: * @param res
0657: * SampleResult
0658: * @return a sampling result useful to inform the user about the exception.
0659: */
0660: protected HTTPSampleResult errorResult(Throwable e,
0661: HTTPSampleResult res) {
0662: res.setSampleLabel("Error");
0663: res.setDataType(HTTPSampleResult.TEXT);
0664: ByteArrayOutputStream text = new ByteArrayOutputStream(200);
0665: e.printStackTrace(new PrintStream(text));
0666: res.setResponseData(text.toByteArray());
0667: res.setResponseCode(NON_HTTP_RESPONSE_CODE + ": "
0668: + e.getClass().getName());
0669: res.setResponseMessage(NON_HTTP_RESPONSE_MESSAGE + ": "
0670: + e.getMessage());
0671: res.setSuccessful(false);
0672: res.setMonitor(this .isMonitor());
0673: return res;
0674: }
0675:
0676: /**
0677: * Get the URL, built from its component parts.
0678: *
0679: * @return The URL to be requested by this sampler.
0680: * @throws MalformedURLException
0681: */
0682: public URL getUrl() throws MalformedURLException {
0683: StringBuffer pathAndQuery = new StringBuffer(100);
0684: String path = this .getPath();
0685: if (!path.startsWith("/")) { // $NON-NLS-1$
0686: pathAndQuery.append("/"); // $NON-NLS-1$
0687: }
0688: pathAndQuery.append(path);
0689:
0690: // Add the query string if it is a HTTP GET or DELETE request
0691: if (GET.equals(getMethod()) || DELETE.equals(getMethod())) {
0692: // Get the query string encoded in specified encoding
0693: // If no encoding is specified by user, we will get it
0694: // encoded in UTF-8, which is what the HTTP spec says
0695: String queryString = getQueryString(getContentEncoding());
0696: if (queryString.length() > 0) {
0697: if (path.indexOf(QRY_PFX) > -1) {
0698: pathAndQuery.append(QRY_SEP);
0699: } else {
0700: pathAndQuery.append(QRY_PFX);
0701: }
0702: pathAndQuery.append(queryString);
0703: }
0704: }
0705: // If default port for protocol is used, we do not include port in URL
0706: if (isProtocolDefaultPort()) {
0707: return new URL(getProtocol(), getDomain(), pathAndQuery
0708: .toString());
0709: } else {
0710: return new URL(getProtocol(), getDomain(), getPort(),
0711: pathAndQuery.toString());
0712: }
0713: }
0714:
0715: /**
0716: * Gets the QueryString attribute of the UrlConfig object, using
0717: * UTF-8 to encode the URL
0718: *
0719: * @return the QueryString value
0720: */
0721: public String getQueryString() {
0722: // We use the encoding which should be used according to the HTTP spec, which is UTF-8
0723: return getQueryString(EncoderCache.URL_ARGUMENT_ENCODING);
0724: }
0725:
0726: /**
0727: * Gets the QueryString attribute of the UrlConfig object, using the
0728: * specified encoding to encode the parameter values put into the URL
0729: *
0730: * @param contentEncoding the encoding to use for encoding parameter values
0731: * @return the QueryString value
0732: */
0733: public String getQueryString(String contentEncoding) {
0734: // Check if the sampler has a specified content encoding
0735: if (contentEncoding == null
0736: || contentEncoding.trim().length() == 0) {
0737: // We use the encoding which should be used according to the HTTP spec, which is UTF-8
0738: contentEncoding = EncoderCache.URL_ARGUMENT_ENCODING;
0739: }
0740: StringBuffer buf = new StringBuffer();
0741: PropertyIterator iter = getArguments().iterator();
0742: boolean first = true;
0743: while (iter.hasNext()) {
0744: HTTPArgument item = null;
0745: /*
0746: * N.B. Revision 323346 introduced the ClassCast check, but then used iter.next()
0747: * to fetch the item to be cast, thus skipping the element that did not cast.
0748: * Reverted to work more like the original code, but with the check in place.
0749: * Added a warning message so can track whether it is necessary
0750: */
0751: Object objectValue = iter.next().getObjectValue();
0752: try {
0753: item = (HTTPArgument) objectValue;
0754: } catch (ClassCastException e) {
0755: log.warn("Unexpected argument type: "
0756: + objectValue.getClass().getName());
0757: item = new HTTPArgument((Argument) objectValue);
0758: }
0759: final String encodedName = item.getEncodedName();
0760: if (encodedName.length() == 0) {
0761: continue; // Skip parameters with a blank name (allows use of optional variables in parameter lists)
0762: }
0763: if (!first) {
0764: buf.append(QRY_SEP);
0765: } else {
0766: first = false;
0767: }
0768: buf.append(encodedName);
0769: if (item.getMetaData() == null) {
0770: buf.append(ARG_VAL_SEP);
0771: } else {
0772: buf.append(item.getMetaData());
0773: }
0774:
0775: // Encode the parameter value in the specified content encoding
0776: try {
0777: buf.append(item.getEncodedValue(contentEncoding));
0778: } catch (UnsupportedEncodingException e) {
0779: log
0780: .warn("Unable to encode parameter in encoding "
0781: + contentEncoding
0782: + ", parameter value not included in query string");
0783: }
0784: }
0785: return buf.toString();
0786: }
0787:
0788: // Mark Walsh 2002-08-03, modified to also parse a parameter name value
0789: // string, where string contains only the parameter name and no equal sign.
0790: /**
0791: * This method allows a proxy server to send over the raw text from a
0792: * browser's output stream to be parsed and stored correctly into the
0793: * UrlConfig object.
0794: *
0795: * For each name found, addArgument() is called
0796: *
0797: * @param queryString -
0798: * the query string
0799: * @param contentEncoding -
0800: * the content encoding of the query string. The query string might
0801: * actually be the post body of a http post request.
0802: */
0803: public void parseArguments(String queryString,
0804: String contentEncoding) {
0805: String[] args = JOrphanUtils.split(queryString, QRY_SEP);
0806: for (int i = 0; i < args.length; i++) {
0807: // need to handle four cases:
0808: // - string contains name=value
0809: // - string contains name=
0810: // - string contains name
0811: // - empty string
0812:
0813: String metaData; // records the existance of an equal sign
0814: String name;
0815: String value;
0816: int length = args[i].length();
0817: int endOfNameIndex = args[i].indexOf(ARG_VAL_SEP);
0818: if (endOfNameIndex != -1) {// is there a separator?
0819: // case of name=value, name=
0820: metaData = ARG_VAL_SEP;
0821: name = args[i].substring(0, endOfNameIndex);
0822: value = args[i].substring(endOfNameIndex + 1, length);
0823: } else {
0824: metaData = "";
0825: name = args[i];
0826: value = "";
0827: }
0828: if (name.length() > 0) {
0829: // If we know the encoding, we can decode the argument value,
0830: // to make it easier to read for the user
0831: if (contentEncoding != null) {
0832: addEncodedArgument(name, value, metaData,
0833: contentEncoding);
0834: } else {
0835: // If we do not know the encoding, we just use the encoded value
0836: // The browser has already done the encoding, so save the values as is
0837: addNonEncodedArgument(name, value, metaData);
0838: }
0839: }
0840: }
0841: }
0842:
0843: public void parseArguments(String queryString) {
0844: // We do not know the content encoding of the query string
0845: parseArguments(queryString, null);
0846: }
0847:
0848: public String toString() {
0849: try {
0850: StringBuffer stringBuffer = new StringBuffer();
0851: stringBuffer.append(this .getUrl().toString());
0852: // Append body if it is a post or put
0853: if (POST.equals(getMethod()) || PUT.equals(getMethod())) {
0854: stringBuffer.append("\nQuery Data: ");
0855: stringBuffer.append(getQueryString());
0856: }
0857: return stringBuffer.toString();
0858: } catch (MalformedURLException e) {
0859: return "";
0860: }
0861: }
0862:
0863: /**
0864: * Do a sampling and return its results.
0865: *
0866: * @param e
0867: * <code>Entry</code> to be sampled
0868: * @return results of the sampling
0869: */
0870: public SampleResult sample(Entry e) {
0871: return sample();
0872: }
0873:
0874: /**
0875: * Perform a sample, and return the results
0876: *
0877: * @return results of the sampling
0878: */
0879: public SampleResult sample() {
0880: SampleResult res = null;
0881: try {
0882: if (PROTOCOL_FILE.equalsIgnoreCase(getProtocol())) {
0883: res = fileSample(new URI(PROTOCOL_FILE, getPath(), null));
0884: } else {
0885: res = sample(getUrl(), getMethod(), false, 0);
0886: }
0887: res.setSampleLabel(getName());
0888: return res;
0889: } catch (MalformedURLException e) {
0890: return errorResult(e, new HTTPSampleResult());
0891: } catch (IOException e) {
0892: return errorResult(e, new HTTPSampleResult());
0893: } catch (URISyntaxException e) {
0894: return errorResult(e, new HTTPSampleResult());
0895: }
0896: }
0897:
0898: private HTTPSampleResult fileSample(URI uri) throws IOException {
0899:
0900: String urlStr = uri.toString();
0901:
0902: HTTPSampleResult res = new HTTPSampleResult();
0903: res.setMonitor(isMonitor());
0904: res.setHTTPMethod(GET); // Dummy
0905: res.setURL(new URL(urlStr));
0906: res.setSampleLabel(urlStr);
0907: FileReader reader = null;
0908: res.sampleStart();
0909: try {
0910: byte[] responseData;
0911: StringBuffer ctb = new StringBuffer("text/html"); // $NON-NLS-1$
0912: reader = new FileReader(getPath());
0913: String contentEncoding = getContentEncoding();
0914: if (contentEncoding.length() == 0) {
0915: responseData = IOUtils.toByteArray(reader);
0916: } else {
0917: ctb.append("; charset="); // $NON-NLS-1$
0918: ctb.append(contentEncoding);
0919: responseData = IOUtils.toByteArray(reader,
0920: contentEncoding);
0921: }
0922: res.sampleEnd();
0923: res.setResponseData(responseData);
0924: res.setResponseCodeOK();
0925: res.setResponseMessageOK();
0926: res.setSuccessful(true);
0927: String ct = ctb.toString();
0928: res.setContentType(ct);
0929: res.setEncodingAndType(ct);
0930: } finally {
0931: IOUtils.closeQuietly(reader);
0932: }
0933:
0934: //res.setResponseHeaders("");
0935:
0936: return res;
0937: }
0938:
0939: /**
0940: * Samples the URL passed in and stores the result in
0941: * <code>HTTPSampleResult</code>, following redirects and downloading
0942: * page resources as appropriate.
0943: * <p>
0944: * When getting a redirect target, redirects are not followed and resources
0945: * are not downloaded. The caller will take care of this.
0946: *
0947: * @param u
0948: * URL to sample
0949: * @param method
0950: * HTTP method: GET, POST,...
0951: * @param areFollowingRedirect
0952: * whether we're getting a redirect target
0953: * @param depth
0954: * Depth of this target in the frame structure. Used only to
0955: * prevent infinite recursion.
0956: * @return results of the sampling
0957: */
0958: protected abstract HTTPSampleResult sample(URL u, String method,
0959: boolean areFollowingRedirect, int depth);
0960:
0961: /**
0962: * Download the resources of an HTML page.
0963: * <p>
0964: * If createContainerResult is true, the returned result will contain one
0965: * subsample for each request issued, including the original one that was
0966: * passed in. It will otherwise look exactly like that original one.
0967: * <p>
0968: * If createContainerResult is false, one subsample will be added to the
0969: * provided result for each requests issued.
0970: *
0971: * @param res
0972: * result of the initial request - must contain an HTML response
0973: * @param container
0974: * for storing the results
0975: * @param frameDepth
0976: * Depth of this target in the frame structure. Used only to
0977: * prevent infinite recursion.
0978: * @return "Container" result with one subsample per request issued
0979: */
0980: protected HTTPSampleResult downloadPageResources(
0981: HTTPSampleResult res, HTTPSampleResult container,
0982: int frameDepth) {
0983: Iterator urls = null;
0984: try {
0985: final byte[] responseData = res.getResponseData();
0986: if (responseData.length > 0) { // Bug 39205
0987: String parserName = getParserClass(res);
0988: if (parserName != null) {
0989: final HTMLParser parser = parserName.length() > 0 ? // we have a name
0990: HTMLParser.getParser(parserName)
0991: : HTMLParser.getParser(); // we don't; use the default parser
0992: urls = parser.getEmbeddedResourceURLs(responseData,
0993: res.getURL());
0994: }
0995: }
0996: } catch (HTMLParseException e) {
0997: // Don't break the world just because this failed:
0998: res.addSubResult(errorResult(e, res));
0999: res.setSuccessful(false);
1000: }
1001:
1002: // Iterate through the URLs and download each image:
1003: if (urls != null && urls.hasNext()) {
1004: if (container == null) {
1005: res = new HTTPSampleResult(res);
1006: } else {
1007: res = container;
1008: }
1009:
1010: // Get the URL matcher
1011: String re = getEmbeddedUrlRE();
1012: Perl5Matcher localMatcher = null;
1013: Pattern pattern = null;
1014: if (re.length() > 0) {
1015: try {
1016: pattern = JMeterUtils.getPattern(re);
1017: localMatcher = JMeterUtils.getMatcher();// don't fetch unless pattern compiles
1018: } catch (MalformedCachePatternException e) {
1019: log.warn("Ignoring embedded URL match string: "
1020: + e.getMessage());
1021: }
1022: }
1023: while (urls.hasNext()) {
1024: Object binURL = urls.next();
1025: try {
1026: URL url = (URL) binURL;
1027: if (url == null) {
1028: log
1029: .warn("Null URL detected (should not happen)");
1030: } else {
1031: String urlstr = url.toString();
1032: String urlStrEnc = encodeSpaces(urlstr);
1033: if (!urlstr.equals(urlStrEnc)) {// There were some spaces in the URL
1034: try {
1035: url = new URL(urlStrEnc);
1036: } catch (MalformedURLException e) {
1037: res
1038: .addSubResult(errorResult(
1039: new Exception(
1040: urlStrEnc
1041: + " is not a correct URI"),
1042: res));
1043: res.setSuccessful(false);
1044: continue;
1045: }
1046: }
1047: // I don't think localMatcher can be null here, but check just in case
1048: if (pattern != null
1049: && localMatcher != null
1050: && !localMatcher.matches(urlStrEnc,
1051: pattern)) {
1052: continue; // we have a pattern and the URL does not match, so skip it
1053: }
1054: HTTPSampleResult binRes = sample(url, GET,
1055: false, frameDepth + 1);
1056: res.addSubResult(binRes);
1057: res.setSuccessful(res.isSuccessful()
1058: && binRes.isSuccessful());
1059: }
1060: } catch (ClassCastException e) {
1061: res.addSubResult(errorResult(new Exception(binURL
1062: + " is not a correct URI"), res));
1063: res.setSuccessful(false);
1064: continue;
1065: }
1066: }
1067: }
1068: return res;
1069: }
1070:
1071: /*
1072: * @param res HTTPSampleResult to check
1073: * @return parser class name (may be "") or null if entry does not exist
1074: */
1075: private String getParserClass(HTTPSampleResult res) {
1076: final String ct = res.getMediaType();
1077: return (String) parsersForType.get(ct);
1078: }
1079:
1080: // TODO: make static?
1081: protected String encodeSpaces(String path) {
1082: return JOrphanUtils.replaceAllChars(path, ' ', "%20"); // $NON-NLS-1$
1083: }
1084:
1085: /*
1086: * (non-Javadoc)
1087: *
1088: * @see org.apache.jmeter.testelement.TestListener#testEnded()
1089: */
1090: public void testEnded() {
1091: dynamicPath = false;
1092: }
1093:
1094: /*
1095: * (non-Javadoc)
1096: *
1097: * @see org.apache.jmeter.testelement.TestListener#testEnded(java.lang.String)
1098: */
1099: public void testEnded(String host) {
1100: testEnded();
1101: }
1102:
1103: /*
1104: * (non-Javadoc)
1105: *
1106: * @see org.apache.jmeter.testelement.TestListener#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
1107: */
1108: public void testIterationStart(LoopIterationEvent event) {
1109: }
1110:
1111: /*
1112: * (non-Javadoc)
1113: *
1114: * @see org.apache.jmeter.testelement.TestListener#testStarted()
1115: */
1116: public void testStarted() {
1117: JMeterProperty pathP = getProperty(PATH);
1118: log.debug("path property is a " + pathP.getClass().getName());
1119: log.debug("path beginning value = " + pathP.getStringValue());
1120: if (pathP instanceof StringProperty
1121: && !"".equals(pathP.getStringValue())) {
1122: log.debug("Encoding spaces in path");
1123: pathP.setObjectValue(encodeSpaces(pathP.getStringValue()));
1124: dynamicPath = false;
1125: } else {
1126: log.debug("setting dynamic path to true");
1127: dynamicPath = true;
1128: }
1129: log.debug("path ending value = " + pathP.getStringValue());
1130: }
1131:
1132: /*
1133: * (non-Javadoc)
1134: *
1135: * @see org.apache.jmeter.testelement.TestListener#testStarted(java.lang.String)
1136: */
1137: public void testStarted(String host) {
1138: testStarted();
1139: }
1140:
1141: /*
1142: * (non-Javadoc)
1143: *
1144: * @see java.lang.Object#clone()
1145: */
1146: public Object clone() {
1147: HTTPSamplerBase base = (HTTPSamplerBase) super .clone();
1148: base.dynamicPath = dynamicPath;
1149: return base;
1150: }
1151:
1152: /**
1153: * Iteratively download the redirect targets of a redirect response.
1154: * <p>
1155: * The returned result will contain one subsample for each request issued,
1156: * including the original one that was passed in. It will be an
1157: * HTTPSampleResult that should mostly look as if the final destination of
1158: * the redirect chain had been obtained in a single shot.
1159: *
1160: * @param res
1161: * result of the initial request - must be a redirect response
1162: * @param frameDepth
1163: * Depth of this target in the frame structure. Used only to
1164: * prevent infinite recursion.
1165: * @return "Container" result with one subsample per request issued
1166: */
1167: protected HTTPSampleResult followRedirects(HTTPSampleResult res,
1168: int frameDepth) {
1169: HTTPSampleResult totalRes = new HTTPSampleResult(res);
1170: HTTPSampleResult lastRes = res;
1171:
1172: int redirect;
1173: for (redirect = 0; redirect < MAX_REDIRECTS; redirect++) {
1174: boolean invalidRedirectUrl = false;
1175: // Browsers seem to tolerate Location headers with spaces,
1176: // replacing them automatically with %20. We want to emulate
1177: // this behaviour.
1178: String location = encodeSpaces(lastRes
1179: .getRedirectLocation());
1180: try {
1181: lastRes = sample(new URL(lastRes.getURL(), location),
1182: GET, true, frameDepth);
1183: } catch (MalformedURLException e) {
1184: lastRes = errorResult(e, lastRes);
1185: // The redirect URL we got was not a valid URL
1186: invalidRedirectUrl = true;
1187: }
1188: if (lastRes.getSubResults() != null
1189: && lastRes.getSubResults().length > 0) {
1190: SampleResult[] subs = lastRes.getSubResults();
1191: for (int i = 0; i < subs.length; i++) {
1192: totalRes.addSubResult(subs[i]);
1193: }
1194: } else {
1195: // Only add sample if it is a sample of valid url redirect, i.e. that
1196: // we have actually sampled the URL
1197: if (!invalidRedirectUrl) {
1198: totalRes.addSubResult(lastRes);
1199: }
1200: }
1201:
1202: if (!lastRes.isRedirect()) {
1203: break;
1204: }
1205: }
1206: if (redirect >= MAX_REDIRECTS) {
1207: lastRes = errorResult(new IOException(
1208: "Exceeeded maximum number of redirects: "
1209: + MAX_REDIRECTS), lastRes);
1210: totalRes.addSubResult(lastRes);
1211: }
1212:
1213: // Now populate the any totalRes fields that need to
1214: // come from lastRes:
1215: totalRes.setSampleLabel(totalRes.getSampleLabel() + "->"
1216: + lastRes.getSampleLabel());
1217: // The following three can be discussed: should they be from the
1218: // first request or from the final one? I chose to do it this way
1219: // because that's what browsers do: they show the final URL of the
1220: // redirect chain in the location field.
1221: totalRes.setURL(lastRes.getURL());
1222: totalRes.setHTTPMethod(lastRes.getHTTPMethod());
1223: totalRes.setQueryString(lastRes.getQueryString());
1224: totalRes.setRequestHeaders(lastRes.getRequestHeaders());
1225:
1226: totalRes.setResponseData(lastRes.getResponseData());
1227: totalRes.setResponseCode(lastRes.getResponseCode());
1228: totalRes.setSuccessful(lastRes.isSuccessful());
1229: totalRes.setResponseMessage(lastRes.getResponseMessage());
1230: totalRes.setDataType(lastRes.getDataType());
1231: totalRes.setResponseHeaders(lastRes.getResponseHeaders());
1232: totalRes.setContentType(lastRes.getContentType());
1233: totalRes.setDataEncoding(lastRes.getDataEncoding());
1234: return totalRes;
1235: }
1236:
1237: /**
1238: * Follow redirects and download page resources if appropriate. this works,
1239: * but the container stuff here is what's doing it. followRedirects() is
1240: * actually doing the work to make sure we have only one container to make
1241: * this work more naturally, I think this method - sample() - needs to take
1242: * an HTTPSamplerResult container parameter instead of a
1243: * boolean:areFollowingRedirect.
1244: *
1245: * @param areFollowingRedirect
1246: * @param frameDepth
1247: * @param res
1248: * @return the sample result
1249: */
1250: protected HTTPSampleResult resultProcessing(
1251: boolean areFollowingRedirect, int frameDepth,
1252: HTTPSampleResult res) {
1253: boolean wasRedirected = false;
1254: if (!areFollowingRedirect) {
1255: if (res.isRedirect()) {
1256: log.debug("Location set to - "
1257: + res.getRedirectLocation());
1258:
1259: if (getFollowRedirects()) {
1260: res = followRedirects(res, frameDepth);
1261: areFollowingRedirect = true;
1262: wasRedirected = true;
1263: }
1264: }
1265: }
1266: if (isImageParser()
1267: && (HTTPSampleResult.TEXT).equals(res.getDataType())
1268: && res.isSuccessful()) {
1269: if (frameDepth > MAX_FRAME_DEPTH) {
1270: res
1271: .addSubResult(errorResult(
1272: new Exception(
1273: "Maximum frame/iframe nesting depth exceeded."),
1274: res));
1275: } else {
1276: // If we followed redirects, we already have a container:
1277: if (!areFollowingRedirect) {
1278: HTTPSampleResult container = (HTTPSampleResult) (areFollowingRedirect ? res
1279: .getParent()
1280: : res);
1281:
1282: // Only download page resources if we were not redirected.
1283: // If we were redirected, the page resources have already been
1284: // downloaded for the sample made for the redirected url
1285: if (!wasRedirected) {
1286: res = downloadPageResources(res, container,
1287: frameDepth);
1288: }
1289: }
1290: }
1291: }
1292: return res;
1293: }
1294:
1295: /**
1296: * Determine if the HTTP status code is successful or not
1297: * i.e. in range 200 to 399 inclusive
1298: *
1299: * @return whether in range 200-399 or not
1300: */
1301: protected boolean isSuccessCode(int code) {
1302: return (code >= 200 && code <= 399);
1303: }
1304:
1305: protected static String encodeBackSlashes(String value) {
1306: StringBuffer newValue = new StringBuffer();
1307: for (int i = 0; i < value.length(); i++) {
1308: char charAt = value.charAt(i);
1309: if (charAt == '\\') { // $NON-NLS-1$
1310: newValue.append("\\\\"); // $NON-NLS-1$
1311: } else {
1312: newValue.append(charAt);
1313: }
1314: }
1315: return newValue.toString();
1316: }
1317:
1318: /**
1319: * Method to tell if the request has any files to be uploaded
1320: */
1321: protected boolean hasUploadableFiles() {
1322: return getFilename() != null
1323: && getFilename().trim().length() > 0;
1324: }
1325:
1326: public static String[] getValidMethodsAsArray() {
1327: return (String[]) METHODLIST.toArray(new String[0]);
1328: }
1329:
1330: public static boolean isSecure(String protocol) {
1331: return PROTOCOL_HTTPS.equalsIgnoreCase(protocol);
1332: }
1333:
1334: public static boolean isSecure(URL url) {
1335: return isSecure(url.getProtocol());
1336: }
1337:
1338: // Implement these here, to avoid re-implementing for sub-classes
1339: // (previously these were implemented in all TestElements)
1340: public void threadStarted() {
1341: }
1342:
1343: public void threadFinished() {
1344: }
1345: }
|