/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.http;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import com.slamd.asn1.ASN1Element;
import com.unboundid.util.Base64;
/**
* This class defines a means of encapsulating an HTTP request to send to a
* remote server. The request may use either the GET or POST method.
*
*
* @author Neil A. Wilson
*/
public class HTTPRequest
{
/**
* The HTTP method for GET requests.
*/
public static final String HTTP_METHOD_GET = "GET";
/**
* The HTTP method for POST requests.
*/
public static final String HTTP_METHOD_POST = "POST";
// The list of parameter names that should be used for this request.
private ArrayList<String> parameterNames;
// The list of parameter values that should be used for this request.
private ArrayList<String> parameterValues;
// Indicates whether this is a GET or POST request.
private boolean isGet;
// The mapping of the header information for this request.
private LinkedHashMap<String,String> headerMap;
// The request body.
private String body;
// The base URL for this request.
URL baseURL;
/**
* Creates a new HTTP request with the provided method and base URL. It will
* not have any request headers or parameters.
*
* @param isGet Indicates whether this request should use the HTTP GET
* method (if not, POST will be used).
* @param baseURL The base URL to use for the request.
*/
public HTTPRequest(boolean isGet, URL baseURL)
{
this.isGet = isGet;
this.baseURL = baseURL;
if ((baseURL.getPath() == null) || (baseURL.getPath().length() == 0))
{
try
{
baseURL = new URL(baseURL.toExternalForm() + '/');
} catch (Exception e) {}
}
headerMap = new LinkedHashMap<String,String>();
parameterNames = new ArrayList<String>();
parameterValues = new ArrayList<String>();
}
/**
* Indicates whether this is an HTTP GET request.
*
* @return <CODE>true</CODE> if this represents a GET request, or
* <CODE>false</CODE> if it represents a POST request.
*/
public boolean isGet()
{
return isGet;
}
/**
* Retrieves the HTTP method associated with this request.
*
* @return The HTTP method associated with this request.
*/
public String getRequestMethod()
{
if (isGet)
{
return HTTP_METHOD_GET;
}
else
{
return HTTP_METHOD_POST;
}
}
/**
* Retrieves the base URL for this request.
*
* @return The base URL for this request.
*/
public URL getBaseURL()
{
return baseURL;
}
/**
* Retrieves the value of the requested header for this request.
*
* @param name The name of the header to retrieve.
*
* @return The value of the requested header for this request, or
* <CODE>null</CODE> if no such header has been defined.
*/
public String getHeader(String name)
{
return headerMap.get(name);
}
/**
* Retrieves the map containing information about the headers for this
* request.
*
* @return The map containing information about the headers for this request.
*/
public LinkedHashMap<String,String> getHeaderMap()
{
return headerMap;
}
/**
* Adds the provided name and value to the set of headers for this request.
* If a header already exists with the given name, it will be replaced with
* the provided value.
*
* @param name The name of the header to add to the request.
* @param value The value of the header to add.
*/
public void setHeader(String name, String value)
{
headerMap.put(name, value);
}
/**
* Removes the header with the specified name from this request.
*
* @param name The name of the header to remove from this request.
*/
public void removeHeader(String name)
{
headerMap.remove(name);
}
/**
* Removes all header information for this request.
*/
public void clearHeaders()
{
headerMap.clear();
}
/**
* Retrieves an array list whose string elements are the names of the
* parameters associated with this request.
*
* @return An array list whose string elements are the names of the
* parameters associated with this request.
*/
public ArrayList getParameterNameList()
{
return parameterNames;
}
/**
* Retrieves the names of the parameters for this request.
*
* @return The names of the parameters for this request.
*/
public String[] getParameterNames()
{
String[] nameArray = new String[parameterNames.size()];
parameterNames.toArray(nameArray);
return nameArray;
}
/**
* Retrieves an array list whose string elements are the values of the
* parameters associated with this request. The order of the values will
* correspond to the order in which the names are provided.
*
* @return An array list whose string elements are the values of the
* parameters associated with this request.
*/
public ArrayList getParameterValueList()
{
return parameterValues;
}
/**
* Retrieves the values of the parameters for this request. The order of the
* values will correspond to the order in which the names are provided.
*
* @return The values of the parameters for this request.
*/
public String[] getParameterValues()
{
String[] valueArray = new String[parameterValues.size()];
parameterValues.toArray(valueArray);
return valueArray;
}
/**
* Retrieves the value of the parameter with the specified name. If the
* given parameter has multiple values, then the first value will be returned.
* If there is no such parameter for this entry, then <CODE>null</CODE> will
* be returned.
*
* @param parameterName The name of the parameter for which to retrieve the
* value.
*
* @return The value for the requested parameter.
*/
public String getParameterValue(String parameterName)
{
for (int i=0; i < parameterNames.size(); i++)
{
if (parameterNames.get(i).equalsIgnoreCase(parameterName))
{
return parameterValues.get(i);
}
}
return null;
}
/**
* Retrieves the set of values for the parameter with the specified name. If
* there is no such parameter for this entry, then an empty array will be
* returned.
*
* @param parameterName The name of the parameter for which to retrieve the
* set of values.
*
* @return The set of values for the requested parameter.
*/
public String[] getParameterValues(String parameterName)
{
ArrayList<String> valueList = new ArrayList<String>();
for (int i=0; i < parameterNames.size(); i++)
{
if (parameterNames.get(i).equalsIgnoreCase(parameterName))
{
valueList.add(parameterValues.get(i));
}
}
String[] valueArray = new String[valueList.size()];
valueList.toArray(valueArray);
return valueArray;
}
/**
* Adds the specified parameter to this request. If a parameter already
* exists with the given name, then another value will be added.
*
* @param name The name of the value to add to this request.
* @param value The value of the parameter to add to this request.
*/
public void addParameter(String name, String value)
{
parameterNames.add(name);
parameterValues.add(value);
}
/**
* Adds the provided parameter to this request with a value that is already
* properly URL-encoded.
*
* @param name The name to use for the parameter.
* @param value The pre-encoded value to for the parameter.
*
* @throws UnsupportedEncodingException If a problem occurs while attempting
* to decode the value as UTF-8.
*/
public void addEncodedParameter(String name, String value)
throws UnsupportedEncodingException
{
// This is not very efficient, since we're decoding the value now only to
// re-encode it later, but this method should not be used very frequently
// anyway so we can take the hit to avoid having to write a lot of code to
// make it more efficient.
parameterNames.add(name);
parameterValues.add(URLDecoder.decode(value, "UTF-8"));
}
/**
* Adds the specified parameter to this request. If a parameter already
* exists with the given name, then another value will be added.
*
* @param name The name of the value to add to this request.
* @param values The set of value for the parameter to add to this request.
*/
public void addParameter(String name, String[] values)
{
for (int i=0; ((values != null) && (i < values.length)); i++)
{
parameterNames.add(name);
parameterValues.add(values[i]);
}
}
/**
* Replaces any existing values for the specified parameter in this request
* with the provided value. If no values exist with the specified name, a new
* parameter will be added.
*
* @param name The name of the parameter for which to replace any
* existing values.
* @param newValue The new value to use in place of any existing value(s).
*/
public void replaceParameter(String name, String newValue)
{
for (int i=parameterNames.size()-1; i >= 0; i--)
{
if (parameterNames.get(i).equalsIgnoreCase(name))
{
parameterNames.remove(i);
parameterValues.remove(i);
}
}
parameterNames.add(name);
parameterValues.add(newValue);
}
/**
* Replaces any existing values for the specified parameter in this request
* with the provided set of values. If no values exist with the specified
* name, a new parameter will be added.
*
* @param name The name of the parameter for which to replace any
* existing values.
* @param newValues The set of values to use in place of any existing
* value(s).
*/
public void replaceParameter(String name, String[] newValues)
{
for (int i=parameterNames.size()-1; i >= 0; i--)
{
if (parameterNames.get(i).equalsIgnoreCase(name))
{
parameterNames.remove(i);
parameterValues.remove(i);
}
}
for (int i=0; ((newValues != null) && (i < newValues.length)); i++)
{
parameterNames.add(name);
parameterValues.add(newValues[i]);
}
}
/**
* Removes all values for the parameter with the given name. If no values for
* the specified parameter exist in the entry, then no action will be
* performed.
*
* @param name The name of the header for which to remove any values from
* the request.
*/
public void removeParameter(String name)
{
for (int i=parameterNames.size()-1; i >= 0; i--)
{
if (parameterNames.get(i).equalsIgnoreCase(name))
{
parameterNames.remove(i);
parameterValues.remove(i);
}
}
}
/**
* Removes the parameter with the specified name and value from this request.
* If the specified parameter does not exist with the given value, then no
* action will be performed.
*
* @param name The name of the parameter from which to remove the specified
* value.
* @param value The value of the parameter to remove.
*/
public void removeParameter(String name, String value)
{
for (int i=parameterNames.size()-1; i >= 0; i--)
{
if (parameterNames.get(i).equalsIgnoreCase(name) &&
parameterValues.get(i).equalsIgnoreCase(value))
{
parameterNames.remove(i);
parameterValues.remove(i);
return;
}
}
}
/**
* Removes all parameter information from this request.
*/
public void removeAllParameters()
{
parameterNames.clear();
parameterValues.clear();
}
/**
* Retrieves the body for this request. This will only be used for POST
* operations, and only if no parameters have been provided. It will only be
* available if a body has been provided using the {@link #setBody} method.
*
* @return The body to use for this request.
*/
public String getBody()
{
return body;
}
/**
* Sets the request body. This is only available for POST operations, and if
* any parameters have been provided, then the body will be ignored.
*
* @param body The body to use for the request.
*/
public void setBody(String body)
{
this.body = body;
}
/**
* Creates a string that is suitable for sending to an HTTP server or proxy.
*
* @param client The client that will be sending the request.
*
* @return The string for sending to the HTTP server or proxy.
*/
public String generateHTTPRequest(HTTPClient client)
{
StringBuilder buffer = new StringBuilder();
StringBuilder paramBuffer = null;
String baseURLStr;
if (client.proxyHost == null)
{
baseURLStr = baseURL.getFile();
}
else
{
baseURLStr = baseURL.toExternalForm();
}
if (isGet)
{
buffer.append("GET ");
buffer.append(baseURLStr);
if (! parameterNames.isEmpty())
{
if (baseURL.getQuery() == null)
{
buffer.append('?');
buffer.append(parameterNames.get(0));
buffer.append('=');
buffer.append(encodeValue(parameterValues.get(0)));
for (int i=1; i < parameterNames.size(); i++)
{
buffer.append('&');
buffer.append(parameterNames.get(i));
buffer.append('=');
buffer.append(encodeValue(parameterValues.get(i)));
}
}
else
{
for (int i=0; i < parameterNames.size(); i++)
{
buffer.append('&');
buffer.append(parameterNames.get(i));
buffer.append('=');
buffer.append(encodeValue(parameterValues.get(i)));
}
}
}
buffer.append(" HTTP/1.1\r\n");
}
else
{
buffer.append("POST ");
buffer.append(baseURLStr);
buffer.append(" HTTP/1.1\r\n");
if (! parameterNames.isEmpty())
{
String name = parameterNames.get(0);
if ((name == null) || (name.length() == 0))
{
paramBuffer = new StringBuilder();
paramBuffer.append(parameterValues.get(0));
}
else
{
paramBuffer = new StringBuilder();
paramBuffer.append(parameterNames.get(0));
paramBuffer.append('=');
paramBuffer.append(encodeValue(parameterValues.get(0)));
}
for (int i=1; i < parameterNames.size(); i++)
{
name = parameterNames.get(i);
if ((name == null) || (name.length() == 0))
{
paramBuffer.append('&');
paramBuffer.append(parameterValues.get(i));
}
else
{
paramBuffer.append('&');
paramBuffer.append(parameterNames.get(i));
paramBuffer.append('=');
paramBuffer.append(encodeValue(parameterValues.get(i)));
}
}
}
}
buffer.append("Host: ");
buffer.append(baseURL.getHost());
if (baseURL.getPort() > 0)
{
buffer.append(':');
buffer.append(baseURL.getPort());
}
buffer.append("\r\n");
buffer.append("Connection: ");
if (client.useKeepAlive)
{
buffer.append("Keep-Alive\r\n");
}
else
{
buffer.append("Close\r\n");
}
if (client.enableGZIP)
{
buffer.append("Accept-Encoding: gzip\r\n");
}
if (paramBuffer != null)
{
buffer.append("Content-Length: ");
buffer.append(paramBuffer.length());
buffer.append("\r\n");
}
else if (body != null)
{
buffer.append("Content-Length: ");
buffer.append(body.length());
buffer.append("\r\n");
}
boolean hasContentType = false;
Iterator iterator = client.commonHeaderMap.keySet().iterator();
while (iterator.hasNext())
{
String headerName = (String) iterator.next();
if (headerName.equalsIgnoreCase("content-type"))
{
hasContentType = true;
}
if (headerMap.get(headerName) == null)
{
buffer.append(headerName);
buffer.append(": ");
buffer.append(client.commonHeaderMap.get(headerName));
buffer.append("\r\n");
}
}
iterator = headerMap.keySet().iterator();
while (iterator.hasNext())
{
String headerName = (String) iterator.next();
if (headerName.equalsIgnoreCase("content-type"))
{
hasContentType = true;
}
buffer.append(headerName);
buffer.append(": ");
buffer.append(headerMap.get(headerName));
buffer.append("\r\n");
}
// If this is a POST request and the user didn't provide a content type,
// then automatically include a default content type.
if ((! isGet) && (! hasContentType))
{
buffer.append("Content-Type: application/x-www-form-urlencoded\r\n");
}
if ((client.proxyAuthID != null) && (client.proxyAuthPW != null))
{
String authStr = client.proxyAuthID + ':' + client.proxyAuthPW;
buffer.append(HTTPClient.PROXY_AUTH_HEADER_PREFIX);
buffer.append(Base64.encode(ASN1Element.getBytes(authStr)));
buffer.append("\r\n");
}
if ((client.authID != null) && (client.authPW != null))
{
String authStr = client.authID + ':' + client.authPW;
buffer.append(HTTPClient.AUTH_HEADER_PREFIX);
buffer.append(Base64.encode(ASN1Element.getBytes(authStr)));
buffer.append("\r\n");
}
HTTPCookie[] cookies = client.getCookies(baseURL);
if (cookies.length > 0)
{
buffer.append("Cookie: ");
buffer.append(cookies[0].getName());
buffer.append('=');
buffer.append(cookies[0].getValue());
for (int i=1; i < cookies.length; i++)
{
buffer.append("; ");
buffer.append(cookies[i].getName());
buffer.append('=');
buffer.append(cookies[i].getValue());
}
buffer.append("\r\n");
}
buffer.append("\r\n");
if (paramBuffer != null)
{
buffer.append(paramBuffer);
}
else
{
buffer.append(body);
}
return buffer.toString();
}
/**
* Encodes the provided value in a form suitable for including in an HTTP
* request. Any unsafe characters will be escaped.
*
* @param parameterValue The value to be encoded.
*
* @return The properly encoded value.
*/
public static String encodeValue(String parameterValue)
{
try
{
return URLEncoder.encode(parameterValue, "UTF-8");
}
catch (UnsupportedEncodingException uee)
{
// This should never happen.
return parameterValue;
}
}
/**
* Creates a new HTTP request that is a copy of this request, optionally using
* a different base URL. This is particularly useful when following redirects
* generated in response to POST requests.
*
* @return The generated HTTP request.
*
* @param baseURL The base URL to use for the new request. If this is null
* then the base URL from this request will be used.
*/
public HTTPRequest clone(URL baseURL)
{
HTTPRequest newRequest;
if (baseURL == null)
{
newRequest = new HTTPRequest(isGet, this.baseURL);
}
else
{
newRequest = new HTTPRequest(isGet, baseURL);
}
newRequest.parameterNames.addAll(parameterNames);
newRequest.parameterValues.addAll(parameterValues);
newRequest.headerMap = new LinkedHashMap<String,String>(headerMap);
return newRequest;
}
}