package com.thinkminimo.golf; /* * Based on ProxyServlet from Jason Edwards. Thanks, man! * http://edwardstx.net/wiki/Wiki.jsp?page=HttpProxyServlet */ import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; 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.PutMethod; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.InputStreamRequestEntity; 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; public class ProxyServlet extends HttpServlet { /** * 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_TYPE_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")); // Proxy host params /** * The host to which we are proxying requests */ private String stringProxyHost; /** * 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 = ""; /** * The (optional) extra query string parameters to add onto any request for proxy. Default value is "". */ private String stringProxyQuery = ""; /** * The maximum size for uploaded files in bytes. Default value is 5MB. */ private int intMaxFileUploadSize = 5 * 1024 * 1024; /** * Initialize the ProxyServlet * @param servletConfig The Servlet configuration passed in by the servlet conatiner */ public void init(ServletConfig servletConfig) { // Get the proxy host 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); // Get the proxy port if specified String stringProxyPortNew = servletConfig.getInitParameter("proxyPort"); if(stringProxyPortNew != null && stringProxyPortNew.length() > 0) { this.setProxyPort(Integer.parseInt(stringProxyPortNew)); } // Get the proxy path if specified String stringProxyPathNew = servletConfig.getInitParameter("proxyPath"); if(stringProxyPathNew != null && stringProxyPathNew.length() > 0) { this.setProxyPath(stringProxyPathNew); } // Get the extra query params if specified String stringProxyQueryNew = servletConfig.getInitParameter("proxyQuery"); if(stringProxyQueryNew != null && stringProxyQueryNew.length() > 0) { this.setProxyQuery(stringProxyQueryNew); } // Get the maximum file upload size if specified 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 { // Create a GET request GetMethod getMethodProxyRequest = new GetMethod(this.getProxyURL(httpServletRequest)); // Forward the request headers setProxyRequestHeaders(httpServletRequest, getMethodProxyRequest); // Execute the proxy request this.executeProxyRequest(getMethodProxyRequest, httpServletRequest, httpServletResponse); } /** * Performs an HTTP DELETE 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 doDelete (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { // Create a DELETE request DeleteMethod delMethodProxyRequest = new DeleteMethod(this.getProxyURL(httpServletRequest)); // Forward the request headers setProxyRequestHeaders(httpServletRequest, delMethodProxyRequest); // Execute the proxy request this.executeProxyRequest(delMethodProxyRequest, httpServletRequest, httpServletResponse); } /** * Performs an HTTP PUT 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 doPut(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { // Create a standard PUT request PutMethod putMethodProxyRequest = new PutMethod(this.getProxyURL(httpServletRequest)); // Forward the request headers setProxyRequestHeaders(httpServletRequest, putMethodProxyRequest); // Check if this is a mulitpart (file upload) PUT if(ServletFileUpload.isMultipartContent(httpServletRequest)) { this.handleMultipartPut(putMethodProxyRequest, httpServletRequest); } else { this.handleStandardPut(putMethodProxyRequest, httpServletRequest); } // Execute the proxy request this.executeProxyRequest(putMethodProxyRequest, httpServletRequest, httpServletResponse); } /** * Sets up the given {@link PutMethod} to send the same multipart PUT * data as was sent in the given {@link HttpServletRequest} * @param putMethodProxyRequest The {@link PutMethod} that we are * configuring to send a multipart PUT request * @param httpServletRequest The {@link HttpServletRequest} that contains * the mutlipart PUT data to be sent via the {@link PutMethod} */ @SuppressWarnings("unchecked") private void handleMultipartPut(PutMethod putMethodProxyRequest, HttpServletRequest httpServletRequest) throws ServletException { // Create a factory for disk-based file items DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); // Set factory constraints diskFileItemFactory.setSizeThreshold(this.getMaxFileUploadSize()); diskFileItemFactory.setRepository(FILE_UPLOAD_TEMP_DIRECTORY); // Create a new file upload handler ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); // Parse the request try { // Get the multipart items as a list List listFileItems = (List) servletFileUpload.parseRequest(httpServletRequest); // Create a list to hold all of the parts List listParts = new ArrayList(); // Iterate the multipart items list for(FileItem fileItemCurrent : listFileItems) { // If the current item is a form field, then create a string part if (fileItemCurrent.isFormField()) { StringPart stringPart = new StringPart( fileItemCurrent.getFieldName(), // The field name fileItemCurrent.getString() // The field value ); // Add the part to the list listParts.add(stringPart); } else { // The item is a file upload, so we create a FilePart FilePart filePart = new FilePart( fileItemCurrent.getFieldName(), // The field name new ByteArrayPartSource( fileItemCurrent.getName(), // The uploaded file name fileItemCurrent.get() // The uploaded file contents ) ); // Add the part to the list listParts.add(filePart); } } MultipartRequestEntity multipartRequestEntity = new MultipartRequestEntity( listParts.toArray(new Part[] {}), putMethodProxyRequest.getParams() ); putMethodProxyRequest.setRequestEntity(multipartRequestEntity); // The current content-type header (received from the client) IS of // type "multipart/form-data", but the content-type header also // contains the chunk boundary string of the chunks. Currently, this // header is using the boundary of the client request, since we // blindly copied all headers from the client request to the proxy // request. However, we are creating a new request with a new chunk // boundary string, so it is necessary that we re-set the // content-type string to reflect the new chunk boundary string putMethodProxyRequest.setRequestHeader(STRING_CONTENT_TYPE_HEADER_NAME, multipartRequestEntity.getContentType()); } catch (FileUploadException fileUploadException) { throw new ServletException(fileUploadException); } } /** * Sets up the given {@link PutMethod} to send the same standard PUT * data as was sent in the given {@link HttpServletRequest} * @param putMethodProxyRequest The {@link PutMethod} that we are * configuring to send a standard PUT request * @param httpServletRequest The {@link HttpServletRequest} that contains * the PUT data to be sent via the {@link PutMethod} */ @SuppressWarnings("unchecked") private void handleStandardPut(PutMethod putMethodProxyRequest, HttpServletRequest httpServletRequest) throws IOException { // Get the client PUT data as a Map Map mapPutParameters = (Map) httpServletRequest.getParameterMap(); // Create a List to hold the NameValuePairs to be passed to the PutMethod List listNameValuePairs = new ArrayList(); // Iterate the parameter names for(String stringParameterName : mapPutParameters.keySet()) { // Iterate the values for each parameter name String[] stringArrayParameterValues = mapPutParameters.get(stringParameterName); for(String stringParamterValue : stringArrayParameterValues) { // Create a NameValuePair and store in list NameValuePair nameValuePair = new NameValuePair(stringParameterName, stringParamterValue); listNameValuePairs.add(nameValuePair); } } // Set the proxy request PUT data putMethodProxyRequest.setRequestEntity(new InputStreamRequestEntity(httpServletRequest.getInputStream())); } /** * 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 { // Create a standard POST request PostMethod postMethodProxyRequest = new PostMethod(this.getProxyURL(httpServletRequest)); // Forward the request headers setProxyRequestHeaders(httpServletRequest, postMethodProxyRequest); // Check if this is a mulitpart (file upload) POST if(ServletFileUpload.isMultipartContent(httpServletRequest)) { this.handleMultipartPost(postMethodProxyRequest, httpServletRequest); } else { this.handleStandardPost(postMethodProxyRequest, httpServletRequest); } // Execute the proxy request 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 { // Create a factory for disk-based file items DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); // Set factory constraints diskFileItemFactory.setSizeThreshold(this.getMaxFileUploadSize()); diskFileItemFactory.setRepository(FILE_UPLOAD_TEMP_DIRECTORY); // Create a new file upload handler ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); // Parse the request try { // Get the multipart items as a list List listFileItems = (List) servletFileUpload.parseRequest(httpServletRequest); // Create a list to hold all of the parts List listParts = new ArrayList(); // Iterate the multipart items list for(FileItem fileItemCurrent : listFileItems) { // If the current item is a form field, then create a string part if (fileItemCurrent.isFormField()) { StringPart stringPart = new StringPart( fileItemCurrent.getFieldName(), // The field name fileItemCurrent.getString() // The field value ); // Add the part to the list listParts.add(stringPart); } else { // The item is a file upload, so we create a FilePart FilePart filePart = new FilePart( fileItemCurrent.getFieldName(), // The field name new ByteArrayPartSource( fileItemCurrent.getName(), // The uploaded file name fileItemCurrent.get() // The uploaded file contents ) ); // Add the part to the list listParts.add(filePart); } } MultipartRequestEntity multipartRequestEntity = new MultipartRequestEntity( listParts.toArray(new Part[] {}), postMethodProxyRequest.getParams() ); postMethodProxyRequest.setRequestEntity(multipartRequestEntity); // The current content-type header (received from the client) IS of // type "multipart/form-data", but the content-type header also // contains the chunk boundary string of the chunks. Currently, this // header is using the boundary of the client request, since we // blindly copied all headers from the client request to the proxy // request. However, we are creating a new request with a new chunk // boundary string, so it is necessary that we re-set the // content-type string to reflect the new chunk boundary string postMethodProxyRequest.setRequestHeader(STRING_CONTENT_TYPE_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) throws IOException { /* // Get the client POST data as a Map Map mapPostParameters = (Map) httpServletRequest.getParameterMap(); // Create a List to hold the NameValuePairs to be passed to the PostMethod List listNameValuePairs = new ArrayList(); // Iterate the parameter names for(String stringParameterName : mapPostParameters.keySet()) { // Iterate the values for each parameter name String[] stringArrayParameterValues = mapPostParameters.get(stringParameterName); for(String stringParamterValue : stringArrayParameterValues) { // Create a NameValuePair and store in list NameValuePair nameValuePair = new NameValuePair(stringParameterName, stringParamterValue); listNameValuePairs.add(nameValuePair); } } // Set the proxy request POST data postMethodProxyRequest.setRequestBody(listNameValuePairs.toArray(new NameValuePair[] { })); */ postMethodProxyRequest.setRequestEntity(new InputStreamRequestEntity(httpServletRequest.getInputStream())); } /** * 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 { // Create a default HttpClient HttpClient httpClient = new HttpClient(); httpMethodProxyRequest.setFollowRedirects(false); // Execute the request int intProxyResponseCode = httpClient.executeMethod(httpMethodProxyRequest); // Check if the proxy response is a redirect // The following code is adapted from org.tigris.noodle.filters.CheckForRedirect // Hooray for open source software 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("Recieved status code: " + stringStatusCode + " but no " + STRING_LOCATION_HEADER + " header was found in the response"); } // Modify the redirect to go to this proxy servlet rather that the proxied host String stringMyHostName = httpServletRequest.getServerName(); if(httpServletRequest.getServerPort() != 80) { stringMyHostName += ":" + httpServletRequest.getServerPort(); } stringMyHostName += httpServletRequest.getContextPath(); httpServletResponse.sendRedirect(stringLocation.replace(getProxyHostAndPort() + this.getProxyPath(), stringMyHostName)); return; } else if(intProxyResponseCode == HttpServletResponse.SC_NOT_MODIFIED) { // 304 needs special handling. See: // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304 // We get a 304 whenever passed an 'If-Modified-Since' // header and the data on disk has not changed; server // responds w/ a 304 saying I'm not going to send the // body because the file has not changed. httpServletResponse.setIntHeader(STRING_CONTENT_LENGTH_HEADER_NAME, 0); httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } // Pass the response code back to the client httpServletResponse.setStatus(intProxyResponseCode); // Pass response headers back to the client Header[] headerArrayResponse = httpMethodProxyRequest.getResponseHeaders(); for(Header header : headerArrayResponse) { httpServletResponse.setHeader(header.getName(), header.getValue()); } // Send the content to the client InputStream inputStreamProxyResponse = httpMethodProxyRequest.getResponseBodyAsStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStreamProxyResponse); OutputStream outputStreamClientResponse = httpServletResponse.getOutputStream(); int intNextByte; while ( ( intNextByte = bufferedInputStream.read() ) != -1 ) { outputStreamClientResponse.write(intNextByte); } } public String getServletInfo() { return "Jason's Proxy Servlet"; } /** * Retreives 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) { // Get an Enumeration of all of the header names sent by the client Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames(); while(enumerationOfHeaderNames.hasMoreElements()) { String stringHeaderName = (String) enumerationOfHeaderNames.nextElement(); if(stringHeaderName.equalsIgnoreCase(STRING_CONTENT_LENGTH_HEADER_NAME)) continue; // As per the Java Servlet API 2.5 documentation: // Some headers, such as Accept-Language can be sent by clients // as several headers each with a different value rather than // sending the header as a comma separated list. // Thus, we get an Enumeration of the header values sent by the client Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName); while(enumerationOfHeaderValues.hasMoreElements()) { String stringHeaderValue = (String) enumerationOfHeaderValues.nextElement(); // In case the proxy host is running multiple virtual servers, // rewrite the Host header to ensure that we get content from // the correct virtual server if(stringHeaderName.equalsIgnoreCase(STRING_HOST_HEADER_NAME)){ stringHeaderValue = getProxyHostAndPort(); } Header header = new Header(stringHeaderName, stringHeaderValue); // Set the same header on the proxy request httpMethodProxyRequest.setRequestHeader(header); } } } // Accessors private String getProxyURL(HttpServletRequest httpServletRequest) { // Set the protocol to HTTP String stringProxyURL = "http://" + this.getProxyHostAndPort(); // Check if we are proxying to a path other that the document root if(!this.getProxyPath().equalsIgnoreCase("")){ stringProxyURL += this.getProxyPath(); } // Handle the path given to the servlet stringProxyURL += httpServletRequest.getRequestURI() .substring(httpServletRequest.getContextPath().length()); System.err.println("PROXY: '"+stringProxyURL+"'"); // Handle the query string String query1 = (httpServletRequest.getQueryString() == null ? "" : httpServletRequest.getQueryString()); String query2 = this.getProxyQuery(); if (query1.length() > 0 && query2.length() > 0) stringProxyURL += "?" + query1 + "&" + query2; else if (query1.length() > 0) stringProxyURL += "?" + query1; else if (query2.length() > 0) stringProxyURL += "?" + query2; return stringProxyURL; } private String getProxyHostAndPort() { if(this.getProxyPort() == 80) { return this.getProxyHost(); } else { return this.getProxyHost() + ":" + this.getProxyPort(); } } private String getProxyHost() { return this.stringProxyHost; } private void setProxyHost(String stringProxyHostNew) { this.stringProxyHost = stringProxyHostNew; } private int getProxyPort() { return this.intProxyPort; } private void setProxyPort(int intProxyPortNew) { this.intProxyPort = intProxyPortNew; } private String getProxyPath() { return this.stringProxyPath; } private void setProxyPath(String stringProxyPathNew) { this.stringProxyPath = stringProxyPathNew; } private String getProxyQuery() { return this.stringProxyQuery; } private void setProxyQuery(String stringProxyQueryNew) { this.stringProxyQuery = stringProxyQueryNew; } private int getMaxFileUploadSize() { return this.intMaxFileUploadSize; } private void setMaxFileUploadSize(int intMaxFileUploadSizeNew) { this.intMaxFileUploadSize = intMaxFileUploadSizeNew; } }