001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jmeter.protocol.http.sampler;
018:
019: import java.io.BufferedInputStream;
020: import java.io.ByteArrayOutputStream;
021: import java.io.FileNotFoundException;
022: import java.io.IOException;
023: import java.io.InputStream;
024:
025: import java.net.BindException;
026: import java.net.HttpURLConnection;
027: import java.net.URL;
028: import java.net.URLConnection;
029:
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Set;
034: import java.util.zip.GZIPInputStream;
035:
036: import org.apache.jmeter.protocol.http.control.AuthManager;
037: import org.apache.jmeter.protocol.http.control.Authorization;
038: import org.apache.jmeter.protocol.http.control.CookieManager;
039: import org.apache.jmeter.protocol.http.control.Header;
040: import org.apache.jmeter.protocol.http.control.HeaderManager;
041:
042: import org.apache.jmeter.samplers.SampleResult;
043: import org.apache.jmeter.testelement.property.CollectionProperty;
044: import org.apache.jmeter.testelement.property.PropertyIterator;
045:
046: import org.apache.jmeter.util.JMeterUtils;
047: import org.apache.jmeter.util.SSLManager;
048:
049: import org.apache.jorphan.logging.LoggingManager;
050:
051: import org.apache.log.Logger;
052:
053: /**
054: * A sampler which understands all the parts necessary to read statistics about
055: * HTTP requests, including cookies and authentication.
056: *
057: */
058: public class HTTPSampler extends HTTPSamplerBase {
059: private static final Logger log = LoggingManager
060: .getLoggerForClass();
061:
062: private static final int MAX_CONN_RETRIES = JMeterUtils
063: .getPropDefault("http.java.sampler.retries" // $NON-NLS-1$
064: , 10); // Maximum connection retries
065:
066: static {
067: log.info("Maximum connection retries = " + MAX_CONN_RETRIES); // $NON-NLS-1$
068: }
069:
070: private static final byte[] NULL_BA = new byte[0];// can share these
071:
072: /** Handles writing of a post request */
073: private transient PostWriter postWriter;
074:
075: /**
076: * Constructor for the HTTPSampler object.
077: *
078: * Consider using HTTPSamplerFactory.newInstance() instead
079: */
080: public HTTPSampler() {
081: }
082:
083: /**
084: * Set request headers in preparation to opening a connection.
085: *
086: * @param conn
087: * <code>URLConnection</code> to set headers on
088: * @exception IOException
089: * if an I/O exception occurs
090: */
091: protected void setPostHeaders(URLConnection conn)
092: throws IOException {
093: postWriter = new PostWriter();
094: postWriter.setHeaders(conn, this );
095: }
096:
097: private void setPutHeaders(URLConnection conn) throws IOException {
098: postWriter = new PutWriter();
099: postWriter.setHeaders(conn, this );
100: }
101:
102: /**
103: * Send POST data from <code>Entry</code> to the open connection.
104: * This also handles sending data for PUT requests
105: *
106: * @param connection
107: * <code>URLConnection</code> where POST data should be sent
108: * @return a String show what was posted. Will not contain actual file upload content
109: * @exception IOException
110: * if an I/O exception occurs
111: */
112: protected String sendPostData(URLConnection connection)
113: throws IOException {
114: return postWriter.sendPostData(connection, this );
115: }
116:
117: private String sendPutData(URLConnection connection)
118: throws IOException {
119: return postWriter.sendPostData(connection, this );
120: }
121:
122: /**
123: * Returns an <code>HttpURLConnection</code> fully ready to attempt
124: * connection. This means it sets the request method (GET or POST), headers,
125: * cookies, and authorization for the URL request.
126: * <p>
127: * The request infos are saved into the sample result if one is provided.
128: *
129: * @param u
130: * <code>URL</code> of the URL request
131: * @param method
132: * GET, POST etc
133: * @param res
134: * sample result to save request infos to
135: * @return <code>HttpURLConnection</code> ready for .connect
136: * @exception IOException
137: * if an I/O Exception occurs
138: */
139: protected HttpURLConnection setupConnection(URL u, String method,
140: HTTPSampleResult res) throws IOException {
141: SSLManager sslmgr = null;
142: if (PROTOCOL_HTTPS.equalsIgnoreCase(u.getProtocol())) {
143: try {
144: sslmgr = SSLManager.getInstance(); // N.B. this needs to be done before opening the connection
145: } catch (Exception e) {
146: log.warn("Problem creating the SSLManager: ", e);
147: }
148: }
149:
150: HttpURLConnection conn = (HttpURLConnection) u.openConnection();
151: // Update follow redirects setting just for this connection
152: conn.setInstanceFollowRedirects(getAutoRedirects());
153:
154: if (PROTOCOL_HTTPS.equalsIgnoreCase(u.getProtocol())) {
155: try {
156: if (null != sslmgr) {
157: sslmgr.setContext(conn); // N.B. must be done after opening connection
158: }
159: } catch (Exception e) {
160: log
161: .warn(
162: "Problem setting the SSLManager for the connection: ",
163: e);
164: }
165: }
166:
167: // a well-bahaved browser is supposed to send 'Connection: close'
168: // with the last request to an HTTP server. Instead, most browsers
169: // leave it to the server to close the connection after their
170: // timeout period. Leave it to the JMeter user to decide.
171: if (getUseKeepAlive()) {
172: conn.setRequestProperty(HEADER_CONNECTION, KEEP_ALIVE);
173: } else {
174: conn
175: .setRequestProperty(HEADER_CONNECTION,
176: CONNECTION_CLOSE);
177: }
178:
179: conn.setRequestMethod(method);
180: setConnectionHeaders(conn, u, getHeaderManager());
181: String cookies = setConnectionCookie(conn, u,
182: getCookieManager());
183:
184: setConnectionAuthorization(conn, u, getAuthManager());
185:
186: if (method.equals(POST)) {
187: setPostHeaders(conn);
188: } else if (method.equals(PUT)) {
189: setPutHeaders(conn);
190: }
191:
192: if (res != null) {
193: res.setURL(u);
194: res.setHTTPMethod(method);
195: res.setRequestHeaders(getConnectionHeaders(conn));
196: res.setCookies(cookies);
197: }
198:
199: return conn;
200: }
201:
202: /**
203: * Reads the response from the URL connection.
204: *
205: * @param conn
206: * URL from which to read response
207: * @return response content
208: * @exception IOException
209: * if an I/O exception occurs
210: */
211: protected byte[] readResponse(HttpURLConnection conn,
212: SampleResult res) throws IOException {
213: byte[] readBuffer = getThreadContext().getReadBuffer();
214: BufferedInputStream in;
215:
216: if ((conn.getContentLength() == 0)
217: && JMeterUtils.getPropDefault(
218: "httpsampler.obey_contentlength", // $NON-NLS-1$
219: false)) {
220: log.info("Content-Length: 0, not reading http-body");
221: res.setResponseHeaders(getResponseHeaders(conn));
222: return NULL_BA;
223: }
224:
225: // works OK even if ContentEncoding is null
226: boolean gzipped = ENCODING_GZIP.equals(conn
227: .getContentEncoding());
228:
229: try {
230: if (gzipped) {
231: in = new BufferedInputStream(new GZIPInputStream(conn
232: .getInputStream()));
233: } else {
234: in = new BufferedInputStream(conn.getInputStream());
235: }
236: } catch (IOException e) {
237: if (!(e.getCause() instanceof FileNotFoundException)) {
238: log.error("readResponse: " + e.toString());
239: Throwable cause = e.getCause();
240: if (cause != null) {
241: log.error("Cause: " + cause);
242: }
243: }
244: // Normal InputStream is not available
245: InputStream errorStream = conn.getErrorStream();
246: if (errorStream == null) {
247: log.info("Error Response Code: "
248: + conn.getResponseCode()
249: + ", Server sent no Errorpage");
250: res.setResponseHeaders(getResponseHeaders(conn));
251: return NULL_BA;
252: } else {
253: log.info("Error Response Code: "
254: + conn.getResponseCode());
255: }
256: if (gzipped) {
257: in = new BufferedInputStream(new GZIPInputStream(
258: errorStream));
259: } else {
260: in = new BufferedInputStream(errorStream);
261: }
262: } catch (Exception e) {
263: log.error("readResponse: " + e.toString());
264: Throwable cause = e.getCause();
265: if (cause != null) {
266: log.error("Cause: " + cause);
267: }
268: in = new BufferedInputStream(conn.getErrorStream());
269: }
270: java.io.ByteArrayOutputStream w = new ByteArrayOutputStream();
271: int x = 0;
272: boolean first = true;
273: while ((x = in.read(readBuffer)) > -1) {
274: if (first) {
275: res.latencyEnd();
276: first = false;
277: }
278: w.write(readBuffer, 0, x);
279: }
280: in.close();
281: w.flush();
282: w.close();
283: return w.toByteArray();
284: }
285:
286: /**
287: * Gets the ResponseHeaders from the URLConnection
288: *
289: * @param conn
290: * connection from which the headers are read
291: * @return string containing the headers, one per line
292: */
293: protected String getResponseHeaders(HttpURLConnection conn) {
294: StringBuffer headerBuf = new StringBuffer();
295: headerBuf.append(conn.getHeaderField(0));// Leave header as is
296: // headerBuf.append(conn.getHeaderField(0).substring(0, 8));
297: // headerBuf.append(" ");
298: // headerBuf.append(conn.getResponseCode());
299: // headerBuf.append(" ");
300: // headerBuf.append(conn.getResponseMessage());
301: headerBuf.append("\n"); //$NON-NLS-1$
302:
303: String hfk;
304: for (int i = 1; (hfk = conn.getHeaderFieldKey(i)) != null; i++) {
305: headerBuf.append(hfk);
306: headerBuf.append(": "); // $NON-NLS-1$
307: headerBuf.append(conn.getHeaderField(i));
308: headerBuf.append("\n"); // $NON-NLS-1$
309: }
310: return headerBuf.toString();
311: }
312:
313: /**
314: * Extracts all the required cookies for that particular URL request and
315: * sets them in the <code>HttpURLConnection</code> passed in.
316: *
317: * @param conn
318: * <code>HttpUrlConnection</code> which represents the URL
319: * request
320: * @param u
321: * <code>URL</code> of the URL request
322: * @param cookieManager
323: * the <code>CookieManager</code> containing all the cookies
324: * for this <code>UrlConfig</code>
325: */
326: private String setConnectionCookie(HttpURLConnection conn, URL u,
327: CookieManager cookieManager) {
328: String cookieHeader = null;
329: if (cookieManager != null) {
330: cookieHeader = cookieManager.getCookieHeaderForURL(u);
331: if (cookieHeader != null) {
332: conn.setRequestProperty(HEADER_COOKIE, cookieHeader);
333: }
334: }
335: return cookieHeader;
336: }
337:
338: /**
339: * Extracts all the required headers for that particular URL request and
340: * sets them in the <code>HttpURLConnection</code> passed in
341: *
342: * @param conn
343: * <code>HttpUrlConnection</code> which represents the URL
344: * request
345: * @param u
346: * <code>URL</code> of the URL request
347: * @param headerManager
348: * the <code>HeaderManager</code> containing all the cookies
349: * for this <code>UrlConfig</code>
350: */
351: private void setConnectionHeaders(HttpURLConnection conn, URL u,
352: HeaderManager headerManager) {
353: // Add all the headers from the HeaderManager
354: if (headerManager != null) {
355: CollectionProperty headers = headerManager.getHeaders();
356: if (headers != null) {
357: PropertyIterator i = headers.iterator();
358: while (i.hasNext()) {
359: Header header = (Header) i.next().getObjectValue();
360: String n = header.getName();
361: String v = header.getValue();
362: conn.addRequestProperty(n, v);
363: }
364: }
365: }
366: }
367:
368: /**
369: * Get all the headers for the <code>HttpURLConnection</code> passed in
370: *
371: * @param conn
372: * <code>HttpUrlConnection</code> which represents the URL
373: * request
374: * @return the headers as a string
375: */
376: private String getConnectionHeaders(HttpURLConnection conn) {
377: // Get all the request properties, which are the headers set on the connection
378: StringBuffer hdrs = new StringBuffer(100);
379: Map requestHeaders = conn.getRequestProperties();
380: Set headerFields = requestHeaders.entrySet();
381: for (Iterator i = headerFields.iterator(); i.hasNext();) {
382: Map.Entry entry = (Map.Entry) i.next();
383: String headerKey = (String) entry.getKey();
384: // Exclude the COOKIE header, since cookie is reported separately in the sample
385: if (!HEADER_COOKIE.equalsIgnoreCase(headerKey)) {
386: List values = (List) entry.getValue();// value is a List of Strings
387: for (int j = 0; j < values.size(); j++) {
388: hdrs.append(headerKey);
389: hdrs.append(": "); // $NON-NLS-1$
390: hdrs.append((String) values.get(j));
391: hdrs.append("\n"); // $NON-NLS-1$
392: }
393: }
394: }
395: return hdrs.toString();
396: }
397:
398: /**
399: * Extracts all the required authorization for that particular URL request
400: * and sets it in the <code>HttpURLConnection</code> passed in.
401: *
402: * @param conn
403: * <code>HttpUrlConnection</code> which represents the URL
404: * request
405: * @param u
406: * <code>URL</code> of the URL request
407: * @param authManager
408: * the <code>AuthManager</code> containing all the cookies for
409: * this <code>UrlConfig</code>
410: */
411: private void setConnectionAuthorization(HttpURLConnection conn,
412: URL u, AuthManager authManager) {
413: if (authManager != null) {
414: Authorization auth = authManager.getAuthForURL(u);
415: if (auth != null) {
416: conn.setRequestProperty(HEADER_AUTHORIZATION, auth
417: .toBasicHeader());
418: }
419: }
420: }
421:
422: /**
423: * Samples the URL passed in and stores the result in
424: * <code>HTTPSampleResult</code>, following redirects and downloading
425: * page resources as appropriate.
426: * <p>
427: * When getting a redirect target, redirects are not followed and resources
428: * are not downloaded. The caller will take care of this.
429: *
430: * @param url
431: * URL to sample
432: * @param method
433: * HTTP method: GET, POST,...
434: * @param areFollowingRedirect
435: * whether we're getting a redirect target
436: * @param frameDepth
437: * Depth of this target in the frame structure. Used only to
438: * prevent infinite recursion.
439: * @return results of the sampling
440: */
441: protected HTTPSampleResult sample(URL url, String method,
442: boolean areFollowingRedirect, int frameDepth) {
443: HttpURLConnection conn = null;
444:
445: String urlStr = url.toString();
446: log.debug("Start : sample " + urlStr);
447:
448: HTTPSampleResult res = new HTTPSampleResult();
449: res.setMonitor(isMonitor());
450:
451: res.setSampleLabel(urlStr);
452: res.sampleStart(); // Count the retries as well in the time
453: try {
454: // Sampling proper - establish the connection and read the response:
455: // Repeatedly try to connect:
456: int retry;
457: // Start with 0 so tries at least once, and retries at most MAX_CONN_RETRIES times
458: for (retry = 0; retry <= MAX_CONN_RETRIES; retry++) {
459: try {
460: conn = setupConnection(url, method, res);
461: // Attempt the connection:
462: conn.connect();
463: break;
464: } catch (BindException e) {
465: if (retry >= MAX_CONN_RETRIES) {
466: log.error("Can't connect", e);
467: throw e;
468: }
469: log.debug("Bind exception, try again");
470: if (conn != null)
471: conn.disconnect();
472: this .setUseKeepAlive(false);
473: continue; // try again
474: } catch (IOException e) {
475: log.debug("Connection failed, giving up");
476: throw e;
477: }
478: }
479: if (retry > MAX_CONN_RETRIES) {
480: // This should never happen, but...
481: throw new BindException();
482: }
483: // Nice, we've got a connection. Finish sending the request:
484: if (method.equals(POST)) {
485: String postBody = sendPostData(conn);
486: res.setQueryString(postBody);
487: } else if (method.equals(PUT)) {
488: String putBody = sendPutData(conn);
489: res.setQueryString(putBody);
490: }
491: // Request sent. Now get the response:
492: byte[] responseData = readResponse(conn, res);
493:
494: res.sampleEnd();
495: // Done with the sampling proper.
496:
497: // Now collect the results into the HTTPSampleResult:
498:
499: res.setResponseData(responseData);
500:
501: int errorLevel = conn.getResponseCode();
502: String respMsg = conn.getResponseMessage();
503: String hdr = conn.getHeaderField(0);
504: if (hdr == null)
505: hdr = "(null)"; // $NON-NLS-1$
506: if (errorLevel == -1) {// Bug 38902 - sometimes -1 seems to be returned unnecessarily
507: if (respMsg != null) {// Bug 41902 - NPE
508: try {
509: errorLevel = Integer.parseInt(respMsg
510: .substring(0, 3));
511: log.warn("ResponseCode==-1; parsed " + respMsg
512: + " as " + errorLevel);
513: } catch (NumberFormatException e) {
514: log.warn("ResponseCode==-1; could not parse "
515: + respMsg + " hdr: " + hdr);
516: }
517: } else {
518: respMsg = hdr; // for result
519: log
520: .warn("ResponseCode==-1 & null ResponseMessage. Header(0)= "
521: + hdr);
522: }
523: }
524: if (errorLevel == -1) {
525: res.setResponseCode("(null)"); // $NON-NLS-1$
526: } else {
527: res.setResponseCode(Integer.toString(errorLevel));
528: }
529: res.setSuccessful(isSuccessCode(errorLevel));
530:
531: if (respMsg == null) {// has been seen in a redirect
532: respMsg = hdr; // use header (if possible) if no message found
533: }
534: res.setResponseMessage(respMsg);
535:
536: String ct = conn.getContentType();
537: if (ct != null) {
538: res.setContentType(ct);// e.g. text/html; charset=ISO-8859-1
539: res.setEncodingAndType(ct);
540: }
541:
542: res.setResponseHeaders(getResponseHeaders(conn));
543: if (res.isRedirect()) {
544: res.setRedirectLocation(conn
545: .getHeaderField(HEADER_LOCATION));
546: }
547:
548: // If we redirected automatically, the URL may have changed
549: if (getAutoRedirects()) {
550: res.setURL(conn.getURL());
551: }
552:
553: // Store any cookies received in the cookie manager:
554: saveConnectionCookies(conn, url, getCookieManager());
555:
556: res = resultProcessing(areFollowingRedirect, frameDepth,
557: res);
558:
559: log.debug("End : sample");
560: return res;
561: } catch (IOException e) {
562: res.sampleEnd();
563: // We don't want to continue using this connection, even if KeepAlive is set
564: if (conn != null) { // May not exist
565: conn.disconnect();
566: }
567: conn = null; // Don't process again
568: return errorResult(e, res);
569: } finally {
570: // calling disconnect doesn't close the connection immediately,
571: // but indicates we're through with it. The JVM should close
572: // it when necessary.
573: disconnect(conn); // Disconnect unless using KeepAlive
574: }
575: }
576:
577: protected void disconnect(HttpURLConnection conn) {
578: if (conn != null) {
579: String connection = conn.getHeaderField(HEADER_CONNECTION);
580: String protocol = conn.getHeaderField(0);
581: if ((connection == null && (protocol == null || !protocol
582: .startsWith(HTTP_1_1)))
583: || (connection != null && connection
584: .equalsIgnoreCase(CONNECTION_CLOSE))) {
585: conn.disconnect();
586: }
587: }
588: }
589:
590: /**
591: * From the <code>HttpURLConnection</code>, store all the "set-cookie"
592: * key-pair values in the cookieManager of the <code>UrlConfig</code>.
593: *
594: * @param conn
595: * <code>HttpUrlConnection</code> which represents the URL
596: * request
597: * @param u
598: * <code>URL</code> of the URL request
599: * @param cookieManager
600: * the <code>CookieManager</code> containing all the cookies
601: * for this <code>UrlConfig</code>
602: */
603: private void saveConnectionCookies(HttpURLConnection conn, URL u,
604: CookieManager cookieManager) {
605: if (cookieManager != null) {
606: for (int i = 1; conn.getHeaderFieldKey(i) != null; i++) {
607: if (conn.getHeaderFieldKey(i).equalsIgnoreCase(
608: HEADER_SET_COOKIE)) {
609: cookieManager.addCookieFromHeader(conn
610: .getHeaderField(i), u);
611: }
612: }
613: }
614: }
615: }
|