mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 12:05:29 -04:00
XXX: Remove old servlet class.
(cherry picked from commit 2f9768a1d4ad117ef8892cbf10ed57be65ddb953)
This commit is contained in:
parent
d52522fb80
commit
7860bf7e17
@ -1,440 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2008, Harald Kuhr
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.twelvemonkeys.servlet;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.io.FileUtil;
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple proxy servlet implementation. Supports HTTP and HTTPS.
|
|
||||||
* <p>
|
|
||||||
* Note: The servlet is not a true HTTP proxy as described in
|
|
||||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">RFC 2616</a>,
|
|
||||||
* instead it passes on all incoming HTTP requests to the configured remote
|
|
||||||
* server.
|
|
||||||
* Useful for bypassing firewalls or to avoid exposing internal network
|
|
||||||
* infrastructure to external clients.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* At the moment, no caching of content is implemented.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* If the {@code remoteServer} init parameter is not set, the servlet will
|
|
||||||
* respond by sending a {@code 500 Internal Server Error} response to the client.
|
|
||||||
* If the configured remote server is down, or unreachable, the servlet will
|
|
||||||
* respond by sending a {@code 502 Bad Gateway} response to the client.
|
|
||||||
* Otherwise, the response from the remote server will be tunneled unmodified
|
|
||||||
* to the client.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
|
||||||
* @author last modified by $Author: haku $
|
|
||||||
*
|
|
||||||
* @version $Id: ProxyServlet.java#1 $
|
|
||||||
*/
|
|
||||||
public class ProxyServlet extends GenericServlet {
|
|
||||||
|
|
||||||
/** Remote server host name or IP address */
|
|
||||||
protected String remoteServer = null;
|
|
||||||
/** Remote server port */
|
|
||||||
protected int remotePort = 80;
|
|
||||||
/** Remote server "mount" path */
|
|
||||||
protected String remotePath = "";
|
|
||||||
|
|
||||||
private static final String HTTP_REQUEST_HEADER_HOST = "host";
|
|
||||||
private static final String HTTP_RESPONSE_HEADER_SERVER = "server";
|
|
||||||
private static final String MESSAGE_REMOTE_SERVER_NOT_CONFIGURED = "Remote server not configured.";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by {@code init} to set the remote server. Must be a valid host
|
|
||||||
* name or IP address. No default.
|
|
||||||
*
|
|
||||||
* @param pRemoteServer
|
|
||||||
*/
|
|
||||||
public void setRemoteServer(String pRemoteServer) {
|
|
||||||
remoteServer = pRemoteServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by {@code init} to set the remote port. Must be a number.
|
|
||||||
* Default is {@code 80}.
|
|
||||||
*
|
|
||||||
* @param pRemotePort
|
|
||||||
*/
|
|
||||||
public void setRemotePort(String pRemotePort) {
|
|
||||||
try {
|
|
||||||
remotePort = Integer.parseInt(pRemotePort);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
log("RemotePort must be a number!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by {@code init} to set the remote path. May be an empty string
|
|
||||||
* for the root path, or any other valid path on the remote server.
|
|
||||||
* Default is {@code ""}.
|
|
||||||
*
|
|
||||||
* @param pRemotePath
|
|
||||||
*/
|
|
||||||
public void setRemotePath(String pRemotePath) {
|
|
||||||
if (StringUtil.isEmpty(pRemotePath)) {
|
|
||||||
pRemotePath = "";
|
|
||||||
}
|
|
||||||
else if (pRemotePath.charAt(0) != '/') {
|
|
||||||
pRemotePath = "/" + pRemotePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
remotePath = pRemotePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override {@code service} to use HTTP specifics.
|
|
||||||
*
|
|
||||||
* @param pRequest
|
|
||||||
* @param pResponse
|
|
||||||
*
|
|
||||||
* @throws ServletException
|
|
||||||
* @throws IOException
|
|
||||||
*
|
|
||||||
* @see #service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
|
||||||
*/
|
|
||||||
public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
|
|
||||||
service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Services a single request.
|
|
||||||
* Supports HTTP and HTTPS.
|
|
||||||
*
|
|
||||||
* @param pRequest
|
|
||||||
* @param pResponse
|
|
||||||
*
|
|
||||||
* @throws ServletException
|
|
||||||
* @throws IOException
|
|
||||||
*
|
|
||||||
* @see ProxyServlet Class descrition
|
|
||||||
*/
|
|
||||||
protected void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
|
|
||||||
// Sanity check configuration
|
|
||||||
if (remoteServer == null) {
|
|
||||||
log(MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
|
|
||||||
pResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
|
|
||||||
MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpURLConnection remoteConnection = null;
|
|
||||||
try {
|
|
||||||
// Recreate request URI for remote request
|
|
||||||
String requestURI = createRemoteRequestURI(pRequest);
|
|
||||||
URL remoteURL = new URL(pRequest.getScheme(), remoteServer, remotePort, requestURI);
|
|
||||||
|
|
||||||
// Get connection, with method from original request
|
|
||||||
// NOTE: The actual connection is not done before we ask for streams...
|
|
||||||
// NOTE: The HttpURLConnection is supposed to handle multiple
|
|
||||||
// requests to the same server internally
|
|
||||||
String method = pRequest.getMethod();
|
|
||||||
remoteConnection = (HttpURLConnection) remoteURL.openConnection();
|
|
||||||
remoteConnection.setRequestMethod(method);
|
|
||||||
|
|
||||||
// Copy header fields
|
|
||||||
copyHeadersFromClient(pRequest, remoteConnection);
|
|
||||||
|
|
||||||
// Do proxy specifc stuff?
|
|
||||||
// TODO: Read up the specs from RFC 2616 (HTTP) on proxy behaviour
|
|
||||||
// TODO: RFC 2616 says "[a] proxy server MUST NOT establish an HTTP/1.1
|
|
||||||
// persistent connection with an HTTP/1.0 client"
|
|
||||||
|
|
||||||
// Copy message body from client to remote server
|
|
||||||
copyBodyFromClient(pRequest, remoteConnection);
|
|
||||||
|
|
||||||
// Set response status code from remote server to client
|
|
||||||
int responseCode = remoteConnection.getResponseCode();
|
|
||||||
pResponse.setStatus(responseCode);
|
|
||||||
//System.out.println("Response is: " + responseCode + " " + remoteConnection.getResponseMessage());
|
|
||||||
|
|
||||||
// Copy header fields back
|
|
||||||
copyHeadersToClient(remoteConnection, pResponse);
|
|
||||||
|
|
||||||
// More proxy specific stuff?
|
|
||||||
|
|
||||||
// Copy message body from remote server to client
|
|
||||||
copyBodyToClient(remoteConnection, pResponse);
|
|
||||||
}
|
|
||||||
catch (ConnectException e) {
|
|
||||||
// In case we could not connecto to the remote server
|
|
||||||
log("Could not connect to remote server.", e);
|
|
||||||
pResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, e.getMessage());
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
// Disconnect from server
|
|
||||||
// TODO: Should we actually do this?
|
|
||||||
if (remoteConnection != null) {
|
|
||||||
remoteConnection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the message body from the remote server to the client (outgoing
|
|
||||||
* {@code HttpServletResponse}).
|
|
||||||
*
|
|
||||||
* @param pRemoteConnection
|
|
||||||
* @param pResponse
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private void copyBodyToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) throws IOException {
|
|
||||||
InputStream fromRemote = null;
|
|
||||||
OutputStream toClient = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get either input or error stream
|
|
||||||
try {
|
|
||||||
fromRemote = pRemoteConnection.getInputStream();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
// If exception, use errorStream instead
|
|
||||||
fromRemote = pRemoteConnection.getErrorStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
// I guess the stream might be null if there is no response other
|
|
||||||
// than headers (Continue, No Content, etc).
|
|
||||||
if (fromRemote != null) {
|
|
||||||
toClient = pResponse.getOutputStream();
|
|
||||||
FileUtil.copy(fromRemote, toClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (fromRemote != null) {
|
|
||||||
try {
|
|
||||||
fromRemote.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
log("Stream from remote could not be closed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toClient != null) {
|
|
||||||
try {
|
|
||||||
toClient.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
log("Stream to client could not be closed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the message body from the client (incomming
|
|
||||||
* {@code HttpServletRequest}) to the remote server if the request method
|
|
||||||
* is {@code POST} or <tt>PUT<tt>.
|
|
||||||
* Otherwise this method does nothing.
|
|
||||||
*
|
|
||||||
* @param pRequest
|
|
||||||
* @param pRemoteConnection
|
|
||||||
*
|
|
||||||
* @throws java.io.IOException
|
|
||||||
*/
|
|
||||||
private void copyBodyFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) throws IOException {
|
|
||||||
// If this is a POST or PUT, copy message body from client remote server
|
|
||||||
if (!("POST".equals(pRequest.getMethod()) || "PUT".equals(pRequest.getMethod()))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Setting doOutput to true, will make it a POST request (why?)...
|
|
||||||
pRemoteConnection.setDoOutput(true);
|
|
||||||
|
|
||||||
// Get streams and do the copying
|
|
||||||
InputStream fromClient = null;
|
|
||||||
OutputStream toRemote = null;
|
|
||||||
try {
|
|
||||||
fromClient = pRequest.getInputStream();
|
|
||||||
toRemote = pRemoteConnection.getOutputStream();
|
|
||||||
FileUtil.copy(fromClient, toRemote);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (fromClient != null) {
|
|
||||||
try {
|
|
||||||
fromClient.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
log("Stream from client could not be closed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toRemote != null) {
|
|
||||||
try {
|
|
||||||
toRemote.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
log("Stream to remote could not be closed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the remote request URI based on the incoming request.
|
|
||||||
* The URI will include any query strings etc.
|
|
||||||
*
|
|
||||||
* @param pRequest
|
|
||||||
*
|
|
||||||
* @return a {@code String} representing the remote request URI
|
|
||||||
*/
|
|
||||||
private String createRemoteRequestURI(HttpServletRequest pRequest) {
|
|
||||||
StringBuilder requestURI = new StringBuilder(remotePath);
|
|
||||||
requestURI.append(pRequest.getPathInfo());
|
|
||||||
|
|
||||||
if (!StringUtil.isEmpty(pRequest.getQueryString())) {
|
|
||||||
requestURI.append("?");
|
|
||||||
requestURI.append(pRequest.getQueryString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestURI.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies headers from the remote connection back to the client
|
|
||||||
* (the outgoing HttpServletResponse). All headers except the "Server"
|
|
||||||
* header are copied.
|
|
||||||
*
|
|
||||||
* @param pRemoteConnection
|
|
||||||
* @param pResponse
|
|
||||||
*/
|
|
||||||
private void copyHeadersToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) {
|
|
||||||
// NOTE: There is no getHeaderFieldCount method or similar...
|
|
||||||
// Also, the getHeaderFields() method was introduced in J2SE 1.4, and
|
|
||||||
// we want to be 1.2 compatible.
|
|
||||||
// So, just try to loop until there are no more headers.
|
|
||||||
int i = 0;
|
|
||||||
while (true) {
|
|
||||||
String key = pRemoteConnection.getHeaderFieldKey(i);
|
|
||||||
// NOTE: getHeaderField(String) returns only the last value
|
|
||||||
String value = pRemoteConnection.getHeaderField(i);
|
|
||||||
|
|
||||||
// If the key is not null, life is simple, and Sun is shining
|
|
||||||
// However, the default implementations includes the HTTP response
|
|
||||||
// code ("HTTP/1.1 200 Ok" or similar) as a header field with
|
|
||||||
// key "null" (why..?)...
|
|
||||||
// In addition, we want to skip the original "Server" header
|
|
||||||
if (key != null && !HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
|
|
||||||
//System.out.println("client <<<-- remote: " + key + ": " + value);
|
|
||||||
pResponse.setHeader(key, value);
|
|
||||||
}
|
|
||||||
else if (value == null) {
|
|
||||||
// If BOTH key and value is null, there are no more header fields
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1.4+ version below....
|
|
||||||
Map headers = pRemoteConnection.getHeaderFields();
|
|
||||||
for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();) {
|
|
||||||
Map.Entry header = (Map.Entry) iterator.next();
|
|
||||||
|
|
||||||
List values = (List) header.getValue();
|
|
||||||
|
|
||||||
for (Iterator valueIter = values.iterator(); valueIter.hasNext();) {
|
|
||||||
String value = (String) valueIter.next();
|
|
||||||
String key = (String) header.getKey();
|
|
||||||
|
|
||||||
// Skip the server header
|
|
||||||
if (HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
|
|
||||||
key = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The default implementations includes the HTTP response code
|
|
||||||
// ("HTTP/1.1 200 Ok" or similar) as a header field with
|
|
||||||
// key "null" (why..?)...
|
|
||||||
if (key != null) {
|
|
||||||
//System.out.println("client <<<-- remote: " + key + ": " + value);
|
|
||||||
pResponse.setHeader(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies headers from the client (the incoming {@code HttpServletRequest})
|
|
||||||
* to the outgoing connection.
|
|
||||||
* All headers except the "Host" header are copied.
|
|
||||||
*
|
|
||||||
* @param pRequest
|
|
||||||
* @param pRemoteConnection
|
|
||||||
*/
|
|
||||||
private void copyHeadersFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) {
|
|
||||||
Enumeration headerNames = pRequest.getHeaderNames();
|
|
||||||
while (headerNames.hasMoreElements()) {
|
|
||||||
String headerName = (String) headerNames.nextElement();
|
|
||||||
Enumeration headerValues = pRequest.getHeaders(headerName);
|
|
||||||
|
|
||||||
// Skip the "host" header, as we want something else
|
|
||||||
if (HTTP_REQUEST_HEADER_HOST.equalsIgnoreCase(headerName)) {
|
|
||||||
// Skip this header
|
|
||||||
headerName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the the header to the remoteConnection
|
|
||||||
if (headerName != null) {
|
|
||||||
// Convert from multiple line to single line, comma separated, as
|
|
||||||
// there seems to be a shortcoming in the URLConneciton API...
|
|
||||||
StringBuilder headerValue = new StringBuilder();
|
|
||||||
while (headerValues.hasMoreElements()) {
|
|
||||||
String value = (String) headerValues.nextElement();
|
|
||||||
headerValue.append(value);
|
|
||||||
if (headerValues.hasMoreElements()) {
|
|
||||||
headerValue.append(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//System.out.println("client -->>> remote: " + headerName + ": " + headerValue);
|
|
||||||
pRemoteConnection.setRequestProperty(headerName, headerValue.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user