blob: 707ad2a2a315ed9abd6042a8a00e17d1a7815348 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.services.http.proxy;
import flex.messaging.FlexContext;
import flex.messaging.FlexSession;
import flex.messaging.io.MessageIOConstants;
import flex.messaging.log.Log;
import flex.messaging.log.Logger;
import flex.messaging.services.HTTPProxyService;
import flex.messaging.services.http.ExternalProxySettings;
import flex.messaging.services.http.httpclient.FlexGetMethod;
import flex.messaging.services.http.httpclient.FlexPostMethod;
import flex.messaging.util.StringUtils;
import flex.messaging.util.URLEncoder;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.TraceMethod;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
*
* Sends the request to the endpoint, including custom copying of headers and cookies.
*/
public class RequestFilter extends ProxyFilter
{
private static final int CAUGHT_ERROR = 10706;
private static final int UNKNOWN_HOST = 10707;
private static final int INVALID_METHOD = 10719;
private static final String STRING_JSESSIONID = "jsessionid";
/**
* Invoke the filter.
*
* @param context the context
*/
public void invoke(ProxyContext context)
{
setupRequest(context);
copyCookiesToEndpoint(context);
copyHeadersToEndpoint(context);
addCustomHeaders(context);
recordRequestHeaders(context);
sendRequest(context);
if (next != null)
{
next.invoke(context);
}
}
/**
* Setup the request.
*
* @param context the context
*/
protected void setupRequest(ProxyContext context)
{
// set the proxy to send requests through
ExternalProxySettings externalProxy = context.getExternalProxySettings();
if (externalProxy != null)
{
String proxyServer = externalProxy.getProxyServer();
if (proxyServer != null)
{
context.getTarget().getHostConfig().setProxy(proxyServer, externalProxy.getProxyPort());
if (context.getProxyCredentials() != null)
{
context.getHttpClient().getState().setProxyCredentials(ProxyUtil.getDefaultAuthScope(), context.getProxyCredentials());
}
}
}
String method = context.getMethod();
String encodedPath = context.getTarget().getEncodedPath();
if (MessageIOConstants.METHOD_POST.equals(method))
{
FlexPostMethod postMethod = new FlexPostMethod(encodedPath);
context.setHttpMethod(postMethod);
if (context.hasAuthorization())
{
postMethod.setConnectionForced(true);
}
}
else if (ProxyConstants.METHOD_GET.equals(method))
{
FlexGetMethod getMethod = new FlexGetMethod(context.getTarget().getEncodedPath());
context.setHttpMethod(getMethod);
if (context.hasAuthorization())
{
getMethod.setConnectionForced(true);
}
}
else if (ProxyConstants.METHOD_HEAD.equals(method))
{
HeadMethod headMethod = new HeadMethod(encodedPath);
context.setHttpMethod(headMethod);
}
else if (ProxyConstants.METHOD_PUT.equals(method))
{
PutMethod putMethod = new PutMethod(encodedPath);
context.setHttpMethod(putMethod);
}
else if (ProxyConstants.METHOD_OPTIONS.equals(method))
{
OptionsMethod optionsMethod = new OptionsMethod(encodedPath);
context.setHttpMethod(optionsMethod);
}
else if (ProxyConstants.METHOD_DELETE.equals(method))
{
DeleteMethod deleteMethod = new DeleteMethod(encodedPath);
context.setHttpMethod(deleteMethod);
}
else if (ProxyConstants.METHOD_TRACE.equals(method))
{
TraceMethod traceMethod = new TraceMethod(encodedPath);
context.setHttpMethod(traceMethod);
}
else
{
ProxyException pe = new ProxyException(INVALID_METHOD);
pe.setDetails(INVALID_METHOD, "1", new Object[] { method });
throw pe;
}
HttpMethodBase httpMethod = context.getHttpMethod();
if (httpMethod instanceof EntityEnclosingMethod)
{
((EntityEnclosingMethod)httpMethod).setContentChunked(context.getContentChunked());
}
}
/**
* Before calling the endpoint, set up the cookies found in the request.
* @param context the context
*/
public static void copyCookiesToEndpoint(ProxyContext context)
{
HttpServletRequest clientRequest = FlexContext.getHttpRequest();
context.clearRequestCookies();
if (clientRequest != null)
{
javax.servlet.http.Cookie[] cookies = clientRequest.getCookies();
HttpState initState = context.getHttpClient().getState();
if (cookies != null)
{
// Gather up the cookies keyed on the length of the path.
// This is done so that if we have two cookies with the same name,
// we pass the cookie with the longest path first to the endpoint
TreeMap cookieMap = new TreeMap();
for (javax.servlet.http.Cookie cookie : cookies)
{
CookieInfo origCookie = new CookieInfo(cookie.getName(), cookie.getDomain(), cookie.getName(),
cookie.getValue(), cookie.getPath(), cookie.getMaxAge(), null, cookie.getSecure());
CookieInfo newCookie = RequestUtil.createCookie(origCookie, context, context.getTarget().getUrl().getHost(),
context.getTarget().getUrl().getPath());
if (newCookie != null)
{
Integer pathInt = Integer.valueOf(0 - newCookie.path.length());
ArrayList list = (ArrayList) cookieMap.get(pathInt);
if (list == null)
{
list = new ArrayList();
cookieMap.put(pathInt, list);
}
list.add(newCookie);
}
}
// loop through (in order) the cookies we've gathered
for (Object mapValue : cookieMap.values())
{
ArrayList list = (ArrayList) mapValue;
for (Object aList : list)
{
CookieInfo cookieInfo = (CookieInfo) aList;
if (Log.isInfo())
{
String str = "-- Cookie in request: " + cookieInfo;
Log.getLogger(HTTPProxyService.LOG_CATEGORY).debug(str);
}
Cookie cookie = new Cookie(cookieInfo.domain, cookieInfo.name, cookieInfo.value, cookieInfo.path,
cookieInfo.maxAge, cookieInfo.secure);
// If this is a session cookie and we're dealing with local domain, make sure the session
// cookie has the latest session id. This check is needed when the session was invalidated
// and then recreated in this request; we shouldn't be sending the old session id to the endpoint.
if (context.isLocalDomain() && STRING_JSESSIONID.equalsIgnoreCase(cookieInfo.clientName))
{
FlexSession flexSession = FlexContext.getFlexSession();
if (flexSession != null && flexSession.isValid())
{
String sessionId = flexSession.getId();
String cookieValue = cookie.getValue();
if (!cookieValue.contains(sessionId))
{
int colonIndex = cookieValue.indexOf(':');
if (colonIndex != -1)
{
// Websphere changes jsession id to the following format:
// 4 digit cacheId + jsessionId + ":" + cloneId.
ServletContext servletContext = FlexContext.getServletContext();
String serverInfo = servletContext != null ? servletContext.getServerInfo() : null;
boolean isWebSphere = serverInfo != null && serverInfo.contains("WebSphere");
if (isWebSphere)
{
String cacheId = cookieValue.substring(0, 4);
String cloneId = cookieValue.substring(colonIndex);
String wsSessionId = cacheId + sessionId + cloneId;
cookie.setValue(wsSessionId);
}
else
{
cookie.setValue(sessionId);
}
}
else
{
cookie.setValue(sessionId);
}
}
}
}
// finally add the cookie to the current request
initState.addCookie(cookie);
context.addRequestCookie(cookie);
}
}
}
}
}
/**
* Copy HTTP request headers to the endpoint.
* @param context the context
*/
public static void copyHeadersToEndpoint(ProxyContext context)
{
HttpServletRequest clientRequest = FlexContext.getHttpRequest();
if (clientRequest != null)
{
Enumeration headerNames = clientRequest.getHeaderNames();
while (headerNames.hasMoreElements())
{
String headerName = (String)headerNames.nextElement();
if (RequestUtil.ignoreHeader(headerName, context))
{
continue;
}
Enumeration headers = clientRequest.getHeaders(headerName);
while (headers.hasMoreElements())
{
String value = (String)headers.nextElement();
context.getHttpMethod().addRequestHeader(headerName, value);
if (Log.isInfo())
{
Log.getLogger(HTTPProxyService.LOG_CATEGORY).debug("-- Header in request: " + headerName + " : " + value);
}
}
}
}
}
/**
* Add any custom headers.
*
* @param context the context
*/
protected void addCustomHeaders(ProxyContext context)
{
HttpMethodBase httpMethod = context.getHttpMethod();
String contentType = context.getContentType();
if (contentType != null)
{
httpMethod.setRequestHeader(ProxyConstants.HEADER_CONTENT_TYPE, contentType);
}
Map customHeaders = context.getHeaders();
if (customHeaders != null)
{
for (Object entry : customHeaders.entrySet())
{
String name = (String) ((Map.Entry) entry).getKey();
Object value = ((Map.Entry) entry).getValue();
if (value == null)
{
httpMethod.setRequestHeader(name, "");
}
// Single value for the name.
else if (value instanceof String)
{
httpMethod.setRequestHeader(name, (String) value);
}
// Multiple values for the name.
else if (value instanceof List)
{
List valueList = (List) value;
for (Object currentValue : valueList)
{
if (currentValue == null)
{
httpMethod.addRequestHeader(name, "");
}
else
{
httpMethod.addRequestHeader(name, (String) currentValue);
}
}
}
else if (value.getClass().isArray())
{
Object[] valueArray = (Object[]) value;
for (Object currentValue : valueArray)
{
if (currentValue == null)
{
httpMethod.addRequestHeader(name, "");
}
else
{
httpMethod.addRequestHeader(name, (String) currentValue);
}
}
}
}
}
if (context.isSoapRequest())
{
// add the appropriate headers
context.getHttpMethod().setRequestHeader(ProxyConstants.HEADER_CONTENT_TYPE, MessageIOConstants.CONTENT_TYPE_XML);
// get SOAPAction, and if it doesn't exist, create it
String soapAction = null;
Header header = context.getHttpMethod().getRequestHeader(MessageIOConstants.HEADER_SOAP_ACTION);
if (header != null)
{
soapAction = header.getValue();
}
if (soapAction == null)
{
HttpServletRequest clientRequest = FlexContext.getHttpRequest();
if (clientRequest != null)
{
soapAction = clientRequest.getHeader(MessageIOConstants.HEADER_SOAP_ACTION);
}
// SOAPAction has to be quoted per the SOAP 1.1 spec.
if (soapAction != null && !soapAction.startsWith("\"") && !soapAction.endsWith("\""))
{
soapAction = "\"" + soapAction + "\"";
}
// If soapAction happens to still be null at this point, we'll end up not sending
// one, which should generate a fault on the server side which we'll happily convey
// back to the client.
context.getHttpMethod().setRequestHeader(MessageIOConstants.HEADER_SOAP_ACTION, soapAction);
}
}
}
/**
* Record the request headers in the proxy context.
* @param context the context
*/
protected void recordRequestHeaders(ProxyContext context)
{
if (context.getRecordHeaders())
{
Header[] headers = context.getHttpMethod().getRequestHeaders();
if (headers != null)
{
HashMap recordedHeaders = new HashMap();
for (Header header : headers)
{
String headerName = header.getName();
String headerValue = header.getValue();
Object existingHeaderValue = recordedHeaders.get(headerName);
// Value(s) already exist for the header.
if (existingHeaderValue != null)
{
ArrayList headerValues;
// Only a single value exists.
if (existingHeaderValue instanceof String)
{
headerValues = new ArrayList();
headerValues.add(existingHeaderValue);
headerValues.add(headerValue);
recordedHeaders.put(headerName, headerValues);
}
// Multiple values exist.
else if (existingHeaderValue instanceof ArrayList)
{
headerValues = (ArrayList) existingHeaderValue;
headerValues.add(headerValue);
}
}
else
{
recordedHeaders.put(headerName, headerValue);
}
}
context.setRequestHeaders(recordedHeaders);
}
}
}
/**
* Send the request.
*
* @param context the context
*/
protected void sendRequest(ProxyContext context)
{
Target target = context.getTarget();
String method = context.getMethod();
HttpMethod httpMethod = context.getHttpMethod();
final String BEGIN = "-- Begin ";
final String END = "-- End ";
final String REQUEST = " request --";
if (httpMethod instanceof EntityEnclosingMethod)
{
Object data = processBody(context);
Class dataClass = data.getClass();
if (data instanceof String)
{
String requestString = (String)data;
if (Log.isInfo())
{
Logger logger = Log.getLogger(HTTPProxyService.LOG_CATEGORY);
logger.debug(BEGIN + method + REQUEST);
logger.debug(StringUtils.prettifyString(requestString));
logger.debug(END + method + REQUEST);
}
try
{
StringRequestEntity requestEntity = new StringRequestEntity(requestString, null, "UTF-8");
((EntityEnclosingMethod)httpMethod).setRequestEntity(requestEntity);
}
catch (UnsupportedEncodingException ex)
{
ProxyException pe = new ProxyException(CAUGHT_ERROR);
pe.setDetails(CAUGHT_ERROR, "1", new Object[] { ex });
throw pe;
}
}
else if (dataClass.isArray() && Byte.TYPE.equals(dataClass.getComponentType()))
{
byte[] dataBytes = (byte[])data;
ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(dataBytes, context.getContentType());
((EntityEnclosingMethod)httpMethod).setRequestEntity(requestEntity);
}
else if (data instanceof InputStream)
{
InputStreamRequestEntity requestEntity = new InputStreamRequestEntity((InputStream)data, context.getContentType());
((EntityEnclosingMethod)httpMethod).setRequestEntity(requestEntity);
}
//TODO: Support multipart post
//else
//{
//FIXME: Throw exception if unhandled data type
//}
}
else if (httpMethod instanceof GetMethod)
{
Object req = processBody(context);
if (req instanceof String)
{
String requestString = (String)req;
if (Log.isInfo())
{
Logger logger = Log.getLogger(HTTPProxyService.LOG_CATEGORY);
logger.debug(BEGIN + method + REQUEST);
logger.debug(StringUtils.prettifyString(requestString));
logger.debug(END + method + REQUEST);
}
if (!"".equals(requestString))
{
String query = context.getHttpMethod().getQueryString();
if (query != null)
{
query += "&" + requestString;
}
else
{
query = requestString;
}
context.getHttpMethod().setQueryString(query);
}
}
}
context.getHttpClient().setHostConfiguration(target.getHostConfig());
try
{
context.getHttpClient().executeMethod(context.getHttpMethod());
}
catch (UnknownHostException uhex)
{
ProxyException pe = new ProxyException();
pe.setMessage(UNKNOWN_HOST, new Object[] { uhex.getMessage() });
pe.setCode(ProxyException.CODE_SERVER_PROXY_REQUEST_FAILED);
throw pe;
}
catch (Exception ex)
{
// FIXME: JRB - could be more specific by looking for timeout and sending 504 in that case.
// rfc2616 10.5.5 504 - could get more specific if we parse the HttpException
ProxyException pe = new ProxyException(CAUGHT_ERROR);
pe.setDetails(CAUGHT_ERROR, "1", new Object[] { ex.getMessage() });
pe.setCode(ProxyException.CODE_SERVER_PROXY_REQUEST_FAILED);
throw pe;
}
}
/**
* Process the request body and return its content.
*
* @param context the context
* @return the body content
*/
protected Object processBody(ProxyContext context)
{
//FIXME: Should we also send on URL params that were used to contact the message broker servlet?
Object body = context.getBody();
if (body == null)
{
return "";
}
else if (body instanceof String)
{
return body;
}
else if (body instanceof Map)
{
Map params = (Map)body;
StringBuffer postData = new StringBuffer();
boolean formValues = false;
for (Object entry : params.entrySet())
{
String name = (String) ((Map.Entry)entry).getKey();
if (!formValues)
{
formValues = true;
}
else
{
postData.append('&');
}
Object vals = ((Map.Entry)entry).getValue();
if (vals == null)
{
encodeParam(postData, name, "");
}
else if (vals instanceof String)
{
String val = (String) vals;
encodeParam(postData, name, val);
}
else if (vals instanceof List)
{
List valLists = (List) vals;
for (int i = 0; i < valLists.size(); i++)
{
Object o = valLists.get(i);
String val = "";
if (o != null)
val = o.toString();
if (i > 0)
postData.append('&');
encodeParam(postData, name, val);
}
}
else if (vals.getClass().isArray())
{
for (int i = 0; i < Array.getLength(vals); i++)
{
Object o = Array.get(vals, i);
String val = "";
if (o != null)
val = o.toString();
if (i > 0)
postData.append('&');
encodeParam(postData, name, val);
}
}
}
return postData.toString();
}
else if (body.getClass().isArray())
{
return body;
}
else if (body instanceof InputStream)
{
return body;
}
else
{
return body.toString();
}
}
/**
* Encode name=value in to a string buffer.
*
* @param buf buffer
* @param name name
* @param val value
*/
protected void encodeParam(StringBuffer buf, String name, String val)
{
name = URLEncoder.encode(name);
val = URLEncoder.encode(val);
buf.append(name);
buf.append('=');
buf.append(val);
}
}