You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@jakarta.apache.org by se...@apache.org on 2010/12/10 03:16:29 UTC
svn commit: r1044199 -
/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
Author: sebb
Date: Fri Dec 10 02:16:29 2010
New Revision: 1044199
URL: http://svn.apache.org/viewvc?rev=1044199&view=rev
Log:
Getting closer; cookies now work.
Still no PUT or POST.
Modified:
jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
Modified: jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java?rev=1044199&r1=1044198&r2=1044199&view=diff
==============================================================================
--- jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java (original)
+++ jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java Fri Dec 10 02:16:29 2010
@@ -18,29 +18,603 @@
package org.apache.jmeter.protocol.http.sampler;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URI;
import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
-import org.apache.commons.lang.NotImplementedException;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.NTCredentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.methods.HttpTrace;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.impl.client.AbstractHttpClient;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.DefaultedHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jmeter.protocol.http.control.AuthManager;
+import org.apache.jmeter.protocol.http.control.Authorization;
+import org.apache.jmeter.protocol.http.control.CacheManager;
+import org.apache.jmeter.protocol.http.control.CookieManager;
+import org.apache.jmeter.protocol.http.control.HeaderManager;
+import org.apache.jmeter.protocol.http.util.SlowHC4SocketFactory;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
/**
* HTTP Sampler using Apache HttpClient 4.x.
+ *
+ * WARNING NOT YET COMPLETE (e.g. does not support PUT/POST yet)
+ * MAY CHANGE
*/
public class HTTPHC4Impl extends HTTPHCAbstractImpl {
- protected HTTPHC4Impl(HTTPSamplerBase testElement) {
- super(testElement);
+ private static final Logger log = LoggingManager.getLoggerForClass();
+
+ private static final long serialVersionUID = 240L;
+
+ private static final ThreadLocal<Map<HttpClientKey, HttpClient>> HTTPCLIENTS =
+ new ThreadLocal<Map<HttpClientKey, HttpClient>>(){
+ @Override
+ protected Map<HttpClientKey, HttpClient> initialValue() {
+ return new HashMap<HttpClientKey, HttpClient>();
+ }
+ };
+
+ // Scheme used for slow sockets. Cannot be set as a default, because must be set on an HttpClient instance.
+ private static final Scheme SLOW_HTTP;
+ private static final Scheme SLOW_HTTPS;
+
+ /*
+ * Create a set of default parameters from the ones initially created.
+ * This allows the defaults to be overridden if necessary from the properties file.
+ */
+ private static final HttpParams DEFAULT_HTTP_PARAMS = new DefaultHttpClient().getParams();
+
+ static {
+ if (CPS_HTTP > 0) {
+ log.info("Setting up HTTP SlowProtocol, cps="+CPS_HTTP);
+ SLOW_HTTP = new Scheme(PROTOCOL_HTTP, DEFAULT_HTTP_PORT, new SlowHC4SocketFactory(CPS_HTTP));
+ } else {
+ SLOW_HTTP = null;
+ }
+ if (CPS_HTTPS > 0) {
+ SLOW_HTTPS = new Scheme(PROTOCOL_HTTPS, DEFAULT_HTTPS_PORT, new SlowHC4SocketFactory(CPS_HTTPS));
+ } else {
+ SLOW_HTTPS = null;
+ }
+ if (localAddress != null){
+ DEFAULT_HTTP_PARAMS.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress);
+ }
+ // Process Apache HttpClient parameters file
+ String file=JMeterUtils.getProperty("hc.parameters.file"); // $NON-NLS-1$
+ if (file != null) {
+ HttpClientDefaultParameters.load(file, DEFAULT_HTTP_PARAMS);
+ }
}
- public boolean interrupt() {
- // TODO Auto-generated method stub
- return false;
+ private volatile HttpUriRequest currentRequest; // Accessed from multiple threads
+
+ protected HTTPHC4Impl(HTTPSamplerBase testElement) {
+ super(testElement);
}
@Override
protected HTTPSampleResult sample(URL url, String method,
boolean areFollowingRedirect, int frameDepth) {
- // TODO Auto-generated method stub
- throw new NotImplementedException();
+
+ // TODO cookie handling
+
+ HTTPSampleResult res = new HTTPSampleResult();
+ res.setMonitor(isMonitor());
+
+ res.setSampleLabel(url.toString()); // May be replaced later
+ res.setHTTPMethod(method);
+ res.setURL(url);
+
+ HttpClient httpClient = setupClient(url);
+
+ HttpRequestBase httpRequest = null;
+ try {
+ URI uri = url.toURI();
+ if (method.equals(POST)) {
+ httpRequest = new HttpPost(uri);
+ } else if (method.equals(PUT)) {
+ httpRequest = new HttpPut(uri);
+ } else if (method.equals(HEAD)) {
+ httpRequest = new HttpHead(uri);
+ } else if (method.equals(TRACE)) {
+ httpRequest = new HttpTrace(uri);
+ } else if (method.equals(OPTIONS)) {
+ httpRequest = new HttpOptions(uri);
+ } else if (method.equals(DELETE)) {
+ httpRequest = new HttpDelete(uri);
+ } else if (method.equals(GET)) {
+ httpRequest = new HttpGet(uri);
+ } else {
+ throw new IllegalArgumentException("Unexpected method: "+method);
+ }
+ setupRequest(url, httpRequest, res); // can throw IOException
+ } catch (Exception e) {
+ res.sampleStart();
+ res.sampleEnd();
+ HTTPSampleResult err = errorResult(e, res);
+ err.setSampleLabel("Error: " + url.toString());
+ return err;
+ }
+
+ HttpContext localContext = new BasicHttpContext();
+
+ res.sampleStart();
+
+ final CacheManager cacheManager = getCacheManager();
+ if (cacheManager != null && GET.equalsIgnoreCase(method)) {
+ if (cacheManager.inCache(url)) {
+ res.sampleEnd();
+ res.setResponseNoContent();
+ res.setSuccessful(true);
+ return res;
+ }
+ }
+
+ try {
+ currentRequest = httpRequest;
+ HttpResponse httpResponse = httpClient.execute(httpRequest, localContext); // perform the sample
+
+ // Needs to be done after execute to pick up all the headers
+ res.setRequestHeaders(getConnectionHeaders((HttpRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST)));
+
+ HttpEntity entity = httpResponse.getEntity();
+ if (entity != null) {
+ InputStream instream = entity.getContent();
+ res.setResponseData(readResponse(res, instream, (int) entity.getContentLength()));
+ Header contentType = entity.getContentType();
+ if (contentType != null){
+ String ct = contentType.getValue();
+ res.setContentType(ct);
+ res.setEncodingAndType(ct);
+ }
+ }
+
+ res.sampleEnd(); // Done with the sampling proper.
+ currentRequest = null;
+
+ // Now collect the results into the HTTPSampleResult:
+ StatusLine statusLine = httpResponse.getStatusLine();
+ int statusCode = statusLine.getStatusCode();
+ res.setResponseCode(Integer.toString(statusCode));
+ res.setResponseMessage(statusLine.getReasonPhrase());
+ res.setSuccessful(isSuccessCode(statusCode));
+
+ res.setResponseHeaders(getResponseHeaders(httpResponse));
+ if (res.isRedirect()) {
+ final Header headerLocation = httpResponse.getLastHeader(HEADER_LOCATION);
+ if (headerLocation == null) { // HTTP protocol violation, but avoids NPE
+ throw new IllegalArgumentException("Missing location header");
+ }
+ res.setRedirectLocation(headerLocation.getValue());
+ }
+
+ // If we redirected automatically, the URL may have changed
+ if (getAutoRedirects()){
+ HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST);
+ HttpHost target = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+ URI redirectURI = req.getURI();
+ if (redirectURI.isAbsolute()){
+ res.setURL(redirectURI.toURL());
+ } else {
+ res.setURL(new URL(new URL(target.toURI()),redirectURI.toString()));
+ }
+ }
+
+ // Store any cookies received in the cookie manager:
+ saveConnectionCookies(httpResponse, res.getURL(), getCookieManager());
+
+ // Save cache information
+ if (cacheManager != null){
+ cacheManager.saveDetails(httpResponse, res);
+ }
+
+ // Follow redirects and download page resources if appropriate:
+ res = resultProcessing(areFollowingRedirect, frameDepth, res);
+
+ } catch (IOException e) {
+ res.sampleEnd();
+ HTTPSampleResult err = errorResult(e, res);
+ err.setSampleLabel("Error: " + url.toString());
+ return err;
+ } finally {
+ currentRequest = null;
+ }
+ return res;
+ }
+
+ /**
+ * Holder class for all fields that define an HttpClient instance;
+ * used as the key to the ThreadLocal map of HttpClient instances.
+ */
+ private static final class HttpClientKey {
+
+ private final URL url;
+ private final boolean hasProxy;
+ private final String proxyHost;
+ private final int proxyPort;
+ private final String proxyUser;
+ private final String proxyPass;
+
+ private final int hashCode; // Always create hash because we will always need it
+
+ public HttpClientKey(URL url, boolean b, String proxyHost,
+ int proxyPort, String proxyUser, String proxyPass) {
+ this.url = url;
+ this.hasProxy = b;
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
+ this.proxyUser = proxyUser;
+ this.proxyPass = proxyPass;
+ this.hashCode = getHash();
+ }
+
+ private int getHash() {
+ int hash = 17;
+ hash = hash*31 + (hasProxy ? 1 : 0);
+ if (hasProxy) {
+ hash = hash*31 + getHash(proxyHost);
+ hash = hash*31 + proxyPort;
+ hash = hash*31 + getHash(proxyUser);
+ hash = hash*31 + getHash(proxyPass);
+ }
+ hash = hash*31 + url.toString().hashCode();
+ return hash;
+ }
+
+ // Allow for null strings
+ private int getHash(String s) {
+ return s == null ? 0 : s.hashCode();
+ }
+
+ @Override
+ public boolean equals (Object obj){
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof HttpClientKey) {
+ return false;
+ }
+ HttpClientKey other = (HttpClientKey) obj;
+ if (this.hasProxy) { // otherwise proxy String fields may be null
+ return
+ this.hasProxy == other.hasProxy &&
+ this.proxyPort == other.proxyPort &&
+ this.proxyHost.equals(other.proxyHost) &&
+ this.proxyUser.equals(other.proxyUser) &&
+ this.proxyPass.equals(other.proxyPass) &&
+ this.url.toString().equals(other.url.toString());
+ }
+ // No proxy, so don't check proxy fields
+ return
+ this.hasProxy == other.hasProxy &&
+ this.url.toString().equals(other.url.toString())
+ ;
+
+ }
+
+ @Override
+ public int hashCode(){
+ return hashCode;
+ }
+ }
+
+ private HttpClient setupClient(URL url) {
+
+ Map<HttpClientKey, HttpClient> map = HTTPCLIENTS.get();
+
+ final String host = url.getHost();
+ final String proxyHost = getProxyHost();
+ final int proxyPort = getProxyPortInt();
+
+ boolean useStaticProxy = isStaticProxy(host);
+ boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort);
+
+ // Lookup key - must agree with all the values used to create the HttpClient.
+ HttpClientKey key = new HttpClientKey(url, (useStaticProxy || useDynamicProxy),
+ useDynamicProxy ? proxyHost : PROXY_HOST,
+ useDynamicProxy ? proxyPort : PROXY_PORT,
+ useDynamicProxy ? getProxyUser() : PROXY_USER,
+ useDynamicProxy ? getProxyPass() : PROXY_PASS);
+
+ HttpClient httpClient = map.get(key);
+
+ if (httpClient == null){
+
+ HttpParams clientParams = new DefaultedHttpParams(new BasicHttpParams(), DEFAULT_HTTP_PARAMS);
+
+ httpClient = new DefaultHttpClient(clientParams);
+
+ if (SLOW_HTTP != null){
+ SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry();
+ schemeRegistry.register(SLOW_HTTP);
+ }
+ if (SLOW_HTTPS != null){
+ SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry();
+ schemeRegistry.register(SLOW_HTTPS);
+ }
+
+ // Set up proxy details
+ if (useDynamicProxy){
+ HttpHost proxy = new HttpHost(proxyHost, proxyPort);
+ clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
+ String proxyUser = getProxyUser();
+ if (proxyUser.length() > 0) {
+ ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials(
+ new AuthScope(proxyHost, proxyPort),
+ new UsernamePasswordCredentials(proxyUser, getProxyPass()));
+ }
+ } else if (useStaticProxy) {
+ HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT);
+ clientParams.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
+ if (PROXY_USER.length() > 0)
+ ((AbstractHttpClient) httpClient).getCredentialsProvider().setCredentials(
+ new AuthScope(PROXY_HOST, PROXY_PORT),
+ new UsernamePasswordCredentials(PROXY_USER, PROXY_PASS));
+ }
+
+ // TODO set up SSL manager etc.
+
+ if (log.isDebugEnabled()) {
+ log.debug("Created new HttpClient: @"+System.identityHashCode(httpClient));
+ }
+
+ map.put(key, httpClient); // save the agent for next time round
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("Reusing the HttpClient: @"+System.identityHashCode(httpClient));
+ }
+ }
+
+ // TODO - should this be done when the client is created?
+ // If so, then the details need to be added as part of HttpClientKey
+ setConnectionAuthorization(httpClient, url, getAuthManager());
+
+ return httpClient;
+ }
+
+ private void setupRequest(URL url, HttpRequestBase httpRequest, HTTPSampleResult res)
+ throws IOException {
+
+ HttpParams requestParams = httpRequest.getParams();
+
+ // Set up the local address if one exists
+ final String ipSource = getIpSource();
+ if (ipSource.length() > 0) {// Use special field ip source address (for pseudo 'ip spoofing')
+ InetAddress inetAddr = InetAddress.getByName(ipSource);
+ requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, inetAddr);
+ } else if (localAddress != null){
+ requestParams.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress);
+ } else { // reset in case was set previously
+ requestParams.removeParameter(ConnRoutePNames.LOCAL_ADDRESS);
+ }
+
+ int rto = getResponseTimeout();
+ if (rto > 0){
+ requestParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, rto);
+ }
+
+ int cto = getConnectTimeout();
+ if (cto > 0){
+ requestParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, cto);
+ }
+
+ requestParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, getAutoRedirects());
+
+ // a well-behaved browser is supposed to send 'Connection: close'
+ // with the last request to an HTTP server. Instead, most browsers
+ // leave it to the server to close the connection after their
+ // timeout period. Leave it to the JMeter user to decide.
+ if (getUseKeepAlive()) {
+ httpRequest.setHeader(HEADER_CONNECTION, KEEP_ALIVE);
+ } else {
+ httpRequest.setHeader(HEADER_CONNECTION, CONNECTION_CLOSE);
+ }
+
+ setConnectionHeaders(httpRequest, url, getHeaderManager(), getCacheManager());
+
+ String cookies = setConnectionCookie(httpRequest, url, getCookieManager());
+
+ if (res != null) {
+ res.setCookies(cookies);
+ }
+
+}
+
+
+ /**
+ * Set any default request headers to include
+ *
+ * @param request the HttpRequest to be used
+ */
+ protected void setDefaultRequestHeaders(HttpRequest request) {
+ // Method left empty here, but allows subclasses to override
+ }
+
+ /**
+ * Gets the ResponseHeaders
+ *
+ * @param response
+ * containing the headers
+ * @return string containing the headers, one per line
+ */
+ private String getResponseHeaders(HttpResponse response) {
+ StringBuilder headerBuf = new StringBuilder();
+ Header[] rh = response.getAllHeaders();
+ headerBuf.append(response.getStatusLine());// header[0] is not the status line...
+ headerBuf.append("\n"); // $NON-NLS-1$
+
+ for (int i = 0; i < rh.length; i++) {
+ headerBuf.append(rh[i].getName());
+ headerBuf.append(": "); // $NON-NLS-1$
+ headerBuf.append(rh[i].getValue());
+ headerBuf.append("\n"); // $NON-NLS-1$
+ }
+ return headerBuf.toString();
+ }
+
+ /**
+ * Extracts all the required cookies for that particular URL request and
+ * sets them in the <code>HttpMethod</code> passed in.
+ *
+ * @param request <code>HttpRequest</code> for the request
+ * @param url <code>URL</code> of the request
+ * @param cookieManager the <code>CookieManager</code> containing all the cookies
+ * @return a String containing the cookie details (for the response)
+ * May be null
+ */
+ private String setConnectionCookie(HttpRequest request, URL url, CookieManager cookieManager) {
+ String cookieHeader = null;
+ if (cookieManager != null) {
+ cookieHeader = cookieManager.getCookieHeaderForURL(url);
+ if (cookieHeader != null) {
+ request.setHeader(HEADER_COOKIE, cookieHeader);
+ }
+ }
+ return cookieHeader;
+ }
+
+ /**
+ * Extracts all the required non-cookie headers for that particular URL request and
+ * sets them in the <code>HttpMethod</code> passed in
+ *
+ * @param request
+ * <code>HttpRequest</code> which represents the request
+ * @param url
+ * <code>URL</code> of the URL request
+ * @param headerManager
+ * the <code>HeaderManager</code> containing all the cookies
+ * for this <code>UrlConfig</code>
+ * @param cacheManager the CacheManager (may be null)
+ */
+ private void setConnectionHeaders(HttpRequestBase request, URL url, HeaderManager headerManager, CacheManager cacheManager) {
+ if (cacheManager != null){
+ cacheManager.setHeaders(url, request);
+ }
+ }
+
+ /**
+ * Get all the request headers for the <code>HttpMethod</code>
+ *
+ * @param method
+ * <code>HttpMethod</code> which represents the request
+ * @return the headers as a string
+ */
+ private String getConnectionHeaders(HttpRequest method) {
+ // Get all the request headers
+ StringBuilder hdrs = new StringBuilder(100);
+ Header[] requestHeaders = method.getAllHeaders();
+ for(int i = 0; i < requestHeaders.length; i++) {
+ // Exclude the COOKIE header, since cookie is reported separately in the sample
+ if(!HEADER_COOKIE.equalsIgnoreCase(requestHeaders[i].getName())) {
+ hdrs.append(requestHeaders[i].getName());
+ hdrs.append(": "); // $NON-NLS-1$
+ hdrs.append(requestHeaders[i].getValue());
+ hdrs.append("\n"); // $NON-NLS-1$
+ }
+ }
+
+ return hdrs.toString();
+ }
+
+ private void setConnectionAuthorization(HttpClient client, URL url, AuthManager authManager) {
+ CredentialsProvider credentialsProvider =
+ ((AbstractHttpClient) client).getCredentialsProvider();
+ if (authManager != null) {
+ Authorization auth = authManager.getAuthForURL(url);
+ if (auth != null) {
+ String username = auth.getUser();
+ String realm = auth.getRealm();
+ String domain = auth.getDomain();
+ if (log.isDebugEnabled()){
+ log.debug(username + " > D="+domain+" R="+realm);
+ }
+ credentialsProvider.setCredentials(
+ new AuthScope(url.getHost(), url.getPort(), realm.length()==0 ? null : realm),
+ new NTCredentials(username, auth.getPass(), localHost, domain));
+ } else {
+ credentialsProvider.clear();
+ }
+ } else {
+ credentialsProvider.clear();
+ }
+ }
+
+ private String sendPostData(){
+ return null;
+ }
+
+ private String sendPutData(){
+ return null;
+ }
+
+ private void saveConnectionCookies(HttpResponse method, URL u, CookieManager cookieManager) {
+ if (cookieManager != null) {
+ Header[] hdrs = method.getHeaders(HEADER_SET_COOKIE);
+ for (Header hdr : hdrs) {
+ cookieManager.addCookieFromHeader(hdr.getValue(),u);
+ }
+ }
+ }
+
+ @Override
+ public void threadFinished() {
+ log.debug("Thread Finished");
+ // Does not need to be synchronised, as all access is from same thread
+ Map<HttpClientKey, HttpClient> map = HTTPCLIENTS.get();
+ if ( map != null ) {
+ for ( HttpClient cl : map.values() ) {
+ cl.getConnectionManager().shutdown();
+ }
+ map.clear();
+ }
+ }
+
+ public boolean interrupt() {
+ HttpUriRequest request = currentRequest;
+ if (request != null) {
+ currentRequest = null;
+ try {
+ request.abort();
+ } catch (UnsupportedOperationException e) {
+ log.warn("Could not abort pending request", e);
+ }
+ }
+ return request != null;
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@jakarta.apache.org
For additional commands, e-mail: notifications-help@jakarta.apache.org