import
java.io.BufferedReader;
import
java.io.ByteArrayInputStream;
import
java.io.ByteArrayOutputStream;
import
java.io.File;
import
java.io.IOException;
import
java.io.UnsupportedEncodingException;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.Enumeration;
import
java.util.List;
import
java.util.Map;
import
java.util.zip.GZIPInputStream;
import
javax.servlet.ServletConfig;
import
javax.servlet.ServletException;
import
javax.servlet.http.Cookie;
import
javax.servlet.http.HttpServlet;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.apache.commons.fileupload.FileItem;
import
org.apache.commons.fileupload.FileUploadException;
import
org.apache.commons.fileupload.disk.DiskFileItemFactory;
import
org.apache.commons.fileupload.servlet.ServletFileUpload;
import
org.apache.commons.httpclient.Header;
import
org.apache.commons.httpclient.HttpClient;
import
org.apache.commons.httpclient.HttpMethod;
import
org.apache.commons.httpclient.NameValuePair;
import
org.apache.commons.httpclient.methods.GetMethod;
import
org.apache.commons.httpclient.methods.PostMethod;
import
org.apache.commons.httpclient.methods.StringRequestEntity;
import
org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import
org.apache.commons.httpclient.methods.multipart.FilePart;
import
org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import
org.apache.commons.httpclient.methods.multipart.Part;
import
org.apache.commons.httpclient.methods.multipart.StringPart;
/**
*
* Patched to skip "Transfer-Encoding: chunked" headers, avoid double slashes
* in proxied URLs, handle GZip and allow GWT RPC.
*/
public
class
ProxyServlet
extends
HttpServlet {
private
static
final
int
FOUR_KB =
4196
;
/**
* Serialization UID.
*/
private
static
final
long
serialVersionUID = 1L;
/**
* Key for redirect location header.
*/
private
static
final
String STRING_LOCATION_HEADER =
"Location"
;
/**
* Key for content type header.
*/
private
static
final
String STRING_CONTENT_ENGINE_HEADER_NAME =
"Content-Type"
;
/**
* Key for content length header.
*/
private
static
final
String STRING_CONTENT_LENGTH_HEADER_NAME =
"Content-Length"
;
/**
* Key for host header
*/
private
static
final
String STRING_HOST_HEADER_NAME =
"Host"
;
/**
* The directory to use to temporarily store uploaded files
*/
private
static
final
File FILE_UPLOAD_TEMP_DIRECTORY =
new
File(System.getProperty(
"java.io.tmpdir"
));
/**
* The host to which we are proxying requests. Default value is "localhost".
*/
private
String stringProxyHost =
"localhost"
;
/**
* The port on the proxy host to wihch we are proxying requests. Default value is 80.
*/
private
int
intProxyPort =
80
;
/**
* The (optional) path on the proxy host to wihch we are proxying requests. Default value is "".
*/
private
String stringProxyPath =
""
;
/**
* Setting that allows removing the initial path from client. Allows specifying /twitter/* as synonym for twitter.com.
*/
private
boolean
removePrefix;
/**
* The maximum size for uploaded files in bytes. Default value is 5MB.
*/
private
int
intMaxFileUploadSize =
5
*
1024
*
1024
;
private
boolean
isSecure;
private
boolean
followRedirects;
/**
* Initialize the <code>ProxyServlet</code>
* @param servletConfig The Servlet configuration passed in by the servlet container
*/
public
void
init(ServletConfig servletConfig) {
String stringProxyHostNew = servletConfig.getInitParameter(
"proxyHost"
);
if
(stringProxyHostNew ==
null
|| stringProxyHostNew.length() ==
0
) {
throw
new
IllegalArgumentException(
"Proxy host not set, please set init-param 'proxyHost' in web.xml"
);
}
this
.setProxyHost(stringProxyHostNew);
String stringProxyPortNew = servletConfig.getInitParameter(
"proxyPort"
);
if
(stringProxyPortNew !=
null
&& stringProxyPortNew.length() >
0
) {
this
.setProxyPort(Integer.parseInt(stringProxyPortNew));
}
String stringProxyPathNew = servletConfig.getInitParameter(
"proxyPath"
);
if
(stringProxyPathNew !=
null
&& stringProxyPathNew.length() >
0
) {
this
.setProxyPath(stringProxyPathNew);
}
String stringMaxFileUploadSize = servletConfig.getInitParameter(
"maxFileUploadSize"
);
if
(stringMaxFileUploadSize !=
null
&& stringMaxFileUploadSize.length() >
0
) {
this
.setMaxFileUploadSize(Integer.parseInt(stringMaxFileUploadSize));
}
}
/**
* Performs an HTTP GET request
* @param httpServletRequest The {@link HttpServletRequest} object passed
* in by the servlet engine representing the
* client request to be proxied
* @param httpServletResponse The {@link HttpServletResponse} object by which
* we can send a proxied response to the client
*/
public
void
doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws
IOException, ServletException {
String destinationUrl =
this
.getProxyURL(httpServletRequest);
debug(
"GET Request URL: "
+ httpServletRequest.getRequestURL(),
"Destination URL: "
+ destinationUrl);
GetMethod getMethodProxyRequest =
new
GetMethod(destinationUrl);
setProxyRequestHeaders(httpServletRequest, getMethodProxyRequest);
setProxyRequestCookies(httpServletRequest, getMethodProxyRequest);
this
.executeProxyRequest(getMethodProxyRequest, httpServletRequest, httpServletResponse);
}
/**
* Performs an HTTP POST request
* @param httpServletRequest The {@link HttpServletRequest} object passed
* in by the servlet engine representing the
* client request to be proxied
* @param httpServletResponse The {@link HttpServletResponse} object by which
* we can send a proxied response to the client
*/
public
void
doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws
IOException, ServletException {
String contentType = httpServletRequest.getContentType();
String destinationUrl =
this
.getProxyURL(httpServletRequest);
debug(
"POST Request URL: "
+ httpServletRequest.getRequestURL(),
" Content Type: "
+ contentType,
" Destination URL: "
+ destinationUrl);
PostMethod postMethodProxyRequest =
new
PostMethod(destinationUrl);
setProxyRequestHeaders(httpServletRequest, postMethodProxyRequest);
setProxyRequestCookies(httpServletRequest, postMethodProxyRequest);
if
(ServletFileUpload.isMultipartContent(httpServletRequest)) {
this
.handleMultipartPost(postMethodProxyRequest, httpServletRequest);
}
else
{
if
(contentType ==
null
|| PostMethod.FORM_URL_ENCODED_CONTENT_ENGINE.equals(contentType)) {
this
.handleStandardPost(postMethodProxyRequest, httpServletRequest);
}
else
{
this
.handleContentPost(postMethodProxyRequest, httpServletRequest);
}
}
this
.executeProxyRequest(postMethodProxyRequest, httpServletRequest, httpServletResponse);
}
/**
* Sets up the given {@link PostMethod} to send the same multipart POST
* data as was sent in the given {@link HttpServletRequest}
* @param postMethodProxyRequest The {@link PostMethod} that we are
* configuring to send a multipart POST request
* @param httpServletRequest The {@link HttpServletRequest} that contains
* the mutlipart POST data to be sent via the {@link PostMethod}
*/
@SuppressWarnings
(
"unchecked"
)
private
void
handleMultipartPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest)
throws
ServletException {
DiskFileItemFactory diskFileItemFactory =
new
DiskFileItemFactory();
diskFileItemFactory.setSizeThreshold(
this
.getMaxFileUploadSize());
diskFileItemFactory.setRepository(FILE_UPLOAD_TEMP_DIRECTORY);
ServletFileUpload servletFileUpload =
new
ServletFileUpload(diskFileItemFactory);
try
{
List<FileItem> listFileItems = (List<FileItem>) servletFileUpload.parseRequest(httpServletRequest);
List<Part> listParts =
new
ArrayList<Part>();
for
(FileItem fileItemCurrent : listFileItems) {
if
(fileItemCurrent.isFormField()) {
StringPart stringPart =
new
StringPart(
fileItemCurrent.getFieldName(),
fileItemCurrent.getString()
);
listParts.add(stringPart);
}
else
{
FilePart filePart =
new
FilePart(
fileItemCurrent.getFieldName(),
new
ByteArrayPartSource(
fileItemCurrent.getName(),
fileItemCurrent.get()
));
listParts.add(filePart);
}
}
MultipartRequestEntity multipartRequestEntity =
new
MultipartRequestEntity(
listParts.toArray(
new
Part[]{}),
postMethodProxyRequest.getParams());
postMethodProxyRequest.setRequestEntity(multipartRequestEntity);
postMethodProxyRequest.setRequestHeader(STRING_CONTENT_ENGINE_HEADER_NAME, multipartRequestEntity.getContentType());
}
catch
(FileUploadException fileUploadException) {
throw
new
ServletException(fileUploadException);
}
}
/**
* Sets up the given {@link PostMethod} to send the same standard POST
* data as was sent in the given {@link HttpServletRequest}
* @param postMethodProxyRequest The {@link PostMethod} that we are
* configuring to send a standard POST request
* @param httpServletRequest The {@link HttpServletRequest} that contains
* the POST data to be sent via the {@link PostMethod}
*/
@SuppressWarnings
(
"unchecked"
)
private
void
handleStandardPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) {
Map<String, String[]> mapPostParameters = (Map<String, String[]>) httpServletRequest.getParameterMap();
List<NameValuePair> listNameValuePairs =
new
ArrayList<NameValuePair>();
for
(String stringParameterName : mapPostParameters.keySet()) {
String[] stringArrayParameterValues = mapPostParameters.get(stringParameterName);
for
(String stringParamterValue : stringArrayParameterValues) {
NameValuePair nameValuePair =
new
NameValuePair(stringParameterName, stringParamterValue);
listNameValuePairs.add(nameValuePair);
}
}
postMethodProxyRequest.setRequestBody(listNameValuePairs.toArray(
new
NameValuePair[]{}));
}
/**
* Sets up the given {@link PostMethod} to send the same content POST
* data (JSON, XML, etc.) as was sent in the given {@link HttpServletRequest}
* @param postMethodProxyRequest The {@link PostMethod} that we are
* configuring to send a standard POST request
* @param httpServletRequest The {@link HttpServletRequest} that contains
* the POST data to be sent via the {@link PostMethod}
*/
private
void
handleContentPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest)
throws
IOException, ServletException {
StringBuilder content =
new
StringBuilder();
BufferedReader reader = httpServletRequest.getReader();
for
(;;) {
String line = reader.readLine();
if
(line ==
null
)
break
;
content.append(line);
}
String contentType = httpServletRequest.getContentType();
String postContent = content.toString();
if
(contentType.startsWith(
"text/x-gwt-rpc"
)) {
String clientHost = httpServletRequest.getLocalName();
if
(clientHost.equals(
"127.0.0.1"
)) {
clientHost =
"localhost"
;
}
int
clientPort = httpServletRequest.getLocalPort();
String clientUrl = clientHost + ((clientPort !=
80
) ?
":"
+ clientPort :
""
);
String serverUrl = stringProxyHost + ((intProxyPort !=
80
) ?
":"
+ intProxyPort :
""
) + httpServletRequest.getServletPath();
postContent = postContent.replace(clientUrl , serverUrl);
}
String encoding = httpServletRequest.getCharacterEncoding();
debug(
"POST Content Type: "
+ contentType +
" Encoding: "
+ encoding,
"Content: "
+ postContent);
StringRequestEntity entity;
try
{
entity =
new
StringRequestEntity(postContent, contentType, encoding);
}
catch
(UnsupportedEncodingException e) {
throw
new
ServletException(e);
}
postMethodProxyRequest.setRequestEntity(entity);
}
/**
* Executes the {@link HttpMethod} passed in and sends the proxy response
* back to the client via the given {@link HttpServletResponse}
* @param httpMethodProxyRequest An object representing the proxy request to be made
* @param httpServletResponse An object by which we can send the proxied
* response back to the client
* @throws IOException Can be thrown by the {@link HttpClient}.executeMethod
* @throws ServletException Can be thrown to indicate that another error has occurred
*/
private
void
executeProxyRequest(
HttpMethod httpMethodProxyRequest,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws
IOException, ServletException {
HttpClient httpClient =
new
HttpClient();
httpMethodProxyRequest.setFollowRedirects(
false
);
int
intProxyResponseCode = httpClient.executeMethod(httpMethodProxyRequest);
String response = httpMethodProxyRequest.getResponseBodyAsString();
if
(intProxyResponseCode >= HttpServletResponse.SC_MULTIPLE_CHOICES /*
300
*/ && intProxyResponseCode < HttpServletResponse.SC_NOT_MODIFIED /*
304
*/) {
String stringStatusCode = Integer.toString(intProxyResponseCode);
String stringLocation = httpMethodProxyRequest.getResponseHeader(STRING_LOCATION_HEADER).getValue();
if
(stringLocation ==
null
) {
throw
new
ServletException(
"Received status code: "
+ stringStatusCode +
" but no "
+ STRING_LOCATION_HEADER +
" header was found in the response"
);
}
String stringMyHostName = httpServletRequest.getServerName();
if
(httpServletRequest.getServerPort() !=
80
) {
stringMyHostName +=
":"
+ httpServletRequest.getServerPort();
}
stringMyHostName += httpServletRequest.getContextPath();
if
(followRedirects) {
if
(stringLocation.contains(
"jsessionid"
)) {
Cookie cookie =
new
Cookie(
"JSESSIONID"
, stringLocation.substring(stringLocation.indexOf(
"jsessionid="
) +
11
));
cookie.setPath(
"/"
);
httpServletResponse.addCookie(cookie);
}
else
if
(httpMethodProxyRequest.getResponseHeader(
"Set-Cookie"
) !=
null
) {
Header header = httpMethodProxyRequest.getResponseHeader(
"Set-Cookie"
);
String[] cookieDetails = header.getValue().split(
";"
);
String[] nameValue = cookieDetails[
0
].split(
"="
);
Cookie cookie =
new
Cookie(nameValue[
0
], nameValue[
1
]);
cookie.setPath(
"/"
);
httpServletResponse.addCookie(cookie);
}
httpServletResponse.sendRedirect(stringLocation.replace(getProxyHostAndPort() +
this
.getProxyPath(), stringMyHostName));
return
;
}
}
else
if
(intProxyResponseCode == HttpServletResponse.SC_NOT_MODIFIED) {
httpServletResponse.setIntHeader(STRING_CONTENT_LENGTH_HEADER_NAME,
0
);
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return
;
}
httpServletResponse.setStatus(intProxyResponseCode);
Header[] headerArrayResponse = httpMethodProxyRequest.getResponseHeaders();
for
(Header header : headerArrayResponse) {
if
(header.getName().equals(
"Transfer-Encoding"
) && header.getValue().equals(
"chunked"
) ||
header.getName().equals(
"Content-Encoding"
) && header.getValue().equals(
"gzip"
) ||
header.getName().equals(
"WWW-Authenticate"
)) {
}
else
{
httpServletResponse.setHeader(header.getName(), header.getValue());
}
}
List<Header> responseHeaders = Arrays.asList(headerArrayResponse);
if
(isBodyParameterGzipped(responseHeaders)) {
debug(
"GZipped: true"
);
if
(!followRedirects && intProxyResponseCode == HttpServletResponse.SC_MOVED_TEMPORARILY) {
response = httpMethodProxyRequest.getResponseHeader(STRING_LOCATION_HEADER).getValue();
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
intProxyResponseCode = HttpServletResponse.SC_OK;
httpServletResponse.setHeader(STRING_LOCATION_HEADER, response);
}
else
{
response =
new
String(ungzip(httpMethodProxyRequest.getResponseBody()));
}
httpServletResponse.setContentLength(response.length());
}
debug(
"Received status code: "
+ intProxyResponseCode,
"Response: "
+ response);
httpServletResponse.getWriter().write(response);
}
/**
* The response body will be assumed to be gzipped if the GZIP header has been set.
*
* @param responseHeaders of response headers
* @return true if the body is gzipped
*/
private
boolean
isBodyParameterGzipped(List<Header> responseHeaders) {
for
(Header header : responseHeaders) {
if
(header.getValue().equals(
"gzip"
)) {
return
true
;
}
}
return
false
;
}
/**
* A highly performant ungzip implementation. Do not refactor this without taking new timings.
* See ElementTest in ehcache for timings
*
* @param gzipped the gzipped content
* @return an ungzipped byte[]
* @throws java.io.IOException when something bad happens
*/
private
byte
[] ungzip(
final
byte
[] gzipped)
throws
IOException {
final
GZIPInputStream inputStream =
new
GZIPInputStream(
new
ByteArrayInputStream(gzipped));
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream(gzipped.length);
final
byte
[] buffer =
new
byte
[FOUR_KB];
int
bytesRead =
0
;
while
(bytesRead != -
1
) {
bytesRead = inputStream.read(buffer,
0
, FOUR_KB);
if
(bytesRead != -
1
) {
byteArrayOutputStream.write(buffer,
0
, bytesRead);
}
}
byte
[] ungzipped = byteArrayOutputStream.toByteArray();
inputStream.close();
byteArrayOutputStream.close();
return
ungzipped;
}
public
String getServletInfo() {
return
"GWT Proxy Servlet"
;
}
/**
* Retrieves all of the headers from the servlet request and sets them on
* the proxy request
*
* @param httpServletRequest The request object representing the client's
* request to the servlet engine
* @param httpMethodProxyRequest The request that we are about to send to
* the proxy host
*/
@SuppressWarnings
(
"unchecked"
)
private
void
setProxyRequestHeaders(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames();
while
(enumerationOfHeaderNames.hasMoreElements()) {
String stringHeaderName = (String) enumerationOfHeaderNames.nextElement();
if
(stringHeaderName.equalsIgnoreCase(STRING_CONTENT_LENGTH_HEADER_NAME)) {
continue
;
}
Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName);
while
(enumerationOfHeaderValues.hasMoreElements()) {
String stringHeaderValue = (String) enumerationOfHeaderValues.nextElement();
if
(stringHeaderName.equalsIgnoreCase(STRING_HOST_HEADER_NAME)) {
stringHeaderValue = getProxyHostAndPort();
}
Header header =
new
Header(stringHeaderName, stringHeaderValue);
httpMethodProxyRequest.setRequestHeader(header);
}
}
}
/**
* Retrieves all of the cookies from the servlet request and sets them on
* the proxy request
*
* @param httpServletRequest The request object representing the client's
* request to the servlet engine
* @param httpMethodProxyRequest The request that we are about to send to
* the proxy host
*/
@SuppressWarnings
(
"unchecked"
)
private
void
setProxyRequestCookies(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
Cookie[] cookies = httpServletRequest.getCookies();
if
(cookies ==
null
) {
return
;
}
for
(Cookie cookie : cookies) {
cookie.setDomain(stringProxyHost);
cookie.setPath(httpServletRequest.getServletPath());
httpMethodProxyRequest.setRequestHeader(
"Cookie"
, cookie.getName() +
"="
+ cookie.getValue() +
"; Path="
+ cookie.getPath());
}
}
private
String getProxyURL(HttpServletRequest httpServletRequest) {
String stringProxyURL = protocol +
this
.getProxyHostAndPort();
if
(!removePrefix) {
stringProxyURL += httpServletRequest.getServletPath();
}
stringProxyURL +=
"/"
;
String pathInfo = httpServletRequest.getPathInfo();
if
(pathInfo !=
null
&& pathInfo.startsWith(
"/"
)) {
if
(stringProxyURL !=
null
&& stringProxyURL.endsWith(
"/"
)) {
stringProxyURL += pathInfo.substring(
1
);
}
}
else
{
stringProxyURL += httpServletRequest.getPathInfo();
}
if
(httpServletRequest.getQueryString() !=
null
) {
stringProxyURL +=
"?"
+ httpServletRequest.getQueryString();
}
return
stringProxyURL;
}
private
String getProxyHostAndPort() {
if
(
this
.getProxyPort() ==
80
) {
return
this
.getProxyHost();
}
else
{
return
this
.getProxyHost() +
":"
+
this
.getProxyPort();
}
}
protected
String getProxyHost() {
return
this
.stringProxyHost;
}
protected
void
setProxyHost(String stringProxyHostNew) {
this
.stringProxyHost = stringProxyHostNew;
}
protected
int
getProxyPort() {
return
this
.intProxyPort;
}
protected
void
setSecure(
boolean
secure) {
this
.isSecure = secure;
}
protected
void
setFollowRedirects(
boolean
followRedirects) {
this
.followRedirects = followRedirects;
}
protected
void
setProxyPort(
int
intProxyPortNew) {
this
.intProxyPort = intProxyPortNew;
}
protected
String getProxyPath() {
return
this
.stringProxyPath;
}
protected
void
setProxyPath(String stringProxyPathNew) {
this
.stringProxyPath = stringProxyPathNew;
}
protected
void
setRemovePrefix(
boolean
removePrefix) {
this
.removePrefix = removePrefix;
}
protected
int
getMaxFileUploadSize() {
return
this
.intMaxFileUploadSize;
}
protected
void
setMaxFileUploadSize(
int
intMaxFileUploadSizeNew) {
this
.intMaxFileUploadSize = intMaxFileUploadSizeNew;
}
private
void
debug(String ... msg) {
for
(String m : msg) {
System.out.println(
"[DEBUG] "
+ m);
}
}
}