/*
* ProxyHandler.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 1999-2009 Sun Microsystems, Inc.
*
* Sun Public License Notice
*
* 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 included as the file "license.terms",
* and also available at http://www.sun.com/
*
* The Original Code is from:
* Brazil project web application toolkit release 2.3.
* The Initial Developer of the Original Code is: cstevens.
* Portions created by cstevens are Copyright (C) Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): cstevens, drach, suhler.
*
* Version: 2.9
* Created by cstevens on 99/09/15
* Last modified by suhler on 09/04/03 13:25:53
*
* Version Histories:
*
* 2.9 09/04/03-13:25:53 (suhler)
* set proxyUrl property
*
* 2.8 09/04/03-09:54:04 (suhler)
* add isTransparent option
*
* 2.7 08/03/17-09:44:51 (suhler)
* use factory methods
*
* 2.6 06/11/08-10:27:34 (suhler)
* fixed stupid type, docs, and added rewriteHeaders
* ,
* .
* .
*
* 2.5 05/07/12-10:37:33 (suhler)
* add "https" support
*
* 2.4 05/06/08-10:53:03 (suhler)
* add "proxylog" option to print http headers
*
* 2.3 04/11/30-15:19:40 (suhler)
* fixed sccs version string
*
* 2.2 03/07/26-16:55:07 (drach)
* Move return out of finally clause to eliminate warning about finally clause not able
* to complete normally.
*
* 2.1 02/10/01-16:37:09 (suhler)
* version change
*
* 1.22 02/07/24-10:47:23 (suhler)
* doc updates
*
* 1.21 01/08/03-18:22:17 (suhler)
* remove training ws from classnames before trying to instantiate
*
* 1.20 00/12/11-13:31:25 (suhler)
* add class=props for automatic property extraction
*
* 1.19 00/10/31-10:20:10 (suhler)
* doc fixes
*
* 1.18 00/04/12-15:56:08 (cstevens)
* imports
*
* 1.17 00/03/29-16:16:40 (cstevens)
* Code to check if proxy server should be used had been removed.
*
* 1.16 00/03/10-17:09:43 (cstevens)
* Removing unused member variables:
*
* 1.15 00/02/25-11:00:15 (suhler)
* - added docs
* - changed useProxy to do only simple initializations
*
* 1.14 99/11/17-10:26:48 (suhler)
* Merged changes between child workspace "/home/suhler/brazil/naws" and
* parent workspace "/net/mack.eng/export/ws/brazil/naws".
*
* 1.13 99/11/16-19:08:29 (cstevens)
* wildcard imports.
*
* 1.12.1.2 99/11/01-11:57:01 (suhler)
* fixed imports
*
* 1.12.1.1 99/10/27-13:20:52 (suhler)
* Merged changes between child workspace "/home/suhler/brazil/naws" and
* parent workspace "/net/mack.eng/export/ws/brazil/naws".
*
* 1.11.1.1 99/10/27-13:15:21 (suhler)
*
* 1.12 99/10/26-18:13:09 (cstevens)
* Eliminate public methods Server.initHandler() and Server.initObject().
*
* 1.11 99/10/14-12:46:44 (cstevens)
* Remove Server.initFields()
*
* 1.10 99/10/11-11:49:10 (cstevens)
* Server.initFields has been banished.
*
* 1.9 99/10/08-16:43:23 (cstevens)
* Move HTTP/1.0 vs. HTTP/1.1 logic for sending HTTP responses into Request.
* RemoteHandler correctly handles "Via" header, both ingoing and outgoing.
* Move logic for removing point-to-point headers into the HttpRequest as a
* static method.
*
* 1.8 99/10/07-13:00:03 (cstevens)
* javadoc lint
*
* 1.7 99/10/04-16:02:48 (cstevens)
* Documentation for LexML and StringMap.
*
* 1.6 99/10/01-13:06:04 (cstevens)
* getting better with server.initHandler()
*
* 1.5 99/10/01-11:26:50 (cstevens)
* Change logging to show prefix of Handler generating the log message.
*
* 1.4 99/09/30-14:11:26 (cstevens)
* Better error message if couldn't connect because machine wasn't listening
* on specified port, rather than saying "unknown host".
*
* 1.3 99/09/30-12:10:12 (cstevens)
* better logging
*
* 1.2 99/09/29-16:07:03 (cstevens)
* Rewrite RemoteHandler (proxy) to use HttpRequest.
*
* 1.2 99/09/15-13:31:24 (Codemgr)
* SunPro Code Manager data about conflicts, renames, etc...
* Name history : 2 1 proxy/ProxyHandler.java
* Name history : 1 0 proxy/RemoteHandler.java
*
* 1.1 99/09/15-13:31:23 (cstevens)
* date and time created 99/09/15 13:31:23 by cstevens
*
*/
package sunlabs.brazil.proxy;
import sunlabs.brazil.server.Handler;
import sunlabs.brazil.server.Request;
import sunlabs.brazil.server.Server;
import sunlabs.brazil.util.Format;
import sunlabs.brazil.util.http.HttpInputStream;
import sunlabs.brazil.util.http.HttpRequest;
import sunlabs.brazil.util.http.MimeHeaders;
/**/
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.net.ConnectException;
import java.util.Properties;
/**
* Handler for implementing a web proxy.
* By default, this is a dumb proxy. It can be combined with other
* handlers to generate side effects, such as content rewriting.
*
* Properties:
*
* - useproxy
- The name of the SocketFactory class to use for
* this handler. If additional properties are required to set up
* the SocketFactory, it should be configured as a handler instead.
* This is here for convenience only.
*
- auth
- The value of the proxy-authenticate header (if any) sent to the upstream proxy
*
- proxyHost
- If specified, the name of the upstream proxy
*
- proxyPort
- The up stream proxys port, if a proxyHost is specified (defaults to 80)
*
- proxylog
- If set all http headers will be logged to the console. This
*
- transparent
- If true, then transparent mode is enabled: The client
* can make a non-proxy http request, and the http "host" header is used
* to derive the proxy host.
* ma
* is for debugging.
*
- proxyUrl
- This property is set to the fully qualified URL that was
* used to retrieve the URL, for the benefit of any downstream handlers.
*
*
* @author Stephen Uhler
* @version 2.9
*/
public class ProxyHandler
implements Handler
{
public static final String PROXY_HOST = "proxyHost";
public static final String PROXY_PORT = "proxyPort";
public static final String AUTH = "auth";
public static final String USE_PROXY = "useproxy";
String via;
/**
* The proxy server.
*/
public String proxyHost;
/**
* The proxy server's port. Default is 80.
*/
public int proxyPort = 80;
/**
* The string to send as the value for the "Proxy-Authorization"
* HTTP header (if needed).
*/
public String auth;
boolean shouldLog; // if true, log all headers
UseProxy proxyTester;
boolean isTransparent; // run in transparent mode - use http host header
String prefix; // our properties prefix
/**
* Do one-time setup.
* get and process the properties file options, and make sure
*/
public boolean
init(Server server, String prefix)
{
String str;
Properties props = server.props;
this.prefix = prefix;
proxyHost = props.getProperty(prefix + PROXY_HOST);
str = props.getProperty(prefix + PROXY_PORT);
try {
proxyPort = Integer.decode(str).intValue();
} catch (Exception e) {};
auth = props.getProperty(prefix + AUTH);
shouldLog = Format.isTrue(props.getProperty(prefix + "proxylog"));
isTransparent = Format.isTrue(props.getProperty(prefix+ "transparent"));
/*
* Set a proxy. If more sophisicated initialization is required than newinstance(),
* set up the proxy in a separate handler
*/
String useproxy = props.getProperty(prefix + USE_PROXY);
if (useproxy != null) {
try {
Class type = Class.forName(useproxy.trim());
proxyTester = (UseProxy) type.newInstance();
} catch (Exception e) {
server.log(Server.LOG_WARNING, prefix,
"Proxy installation error : " + e);
}
}
if (proxyTester == null) {
proxyTester = new UseProxy() {
public boolean useProxy(String host, int port) { return true; }
};
}
via = " " + server.hostName + ":" + server.listen.getLocalPort()
+ " (" + server.name + ")";
return true;
}
/**
* @see Handler#respond
*/
public boolean
respond(Request client)
throws IOException
{
String url = client.url;
if (isTransparent && url.startsWith("/")) {
String host = client.headers.get("Host", "localhost");
url = client.getProtocol() + "://" + host + url;
System.out.println("Setting url to: " + url);
} else if (url.startsWith("http:") == false &&
url.startsWith("https:") == false) {
return false;
}
client.props.put(prefix + "proxyUrl", url);
if ((client.query != null) && (client.query.length() > 0)) {
url += "?" + client.query;
}
MimeHeaders clientHeaders = client.headers;
int count = client.server.requestCount;
if (shouldLog) {
System.err.println(dumpHeaders(count, client, clientHeaders, true));
}
/*
* "Proxy-Connection" may be used (instead of just "Connection")
* to keep alive a connection between a client and this proxy.
*/
String pc = clientHeaders.get("Proxy-Connection");
if (pc != null) {
client.connectionHeader = "Proxy-Connection";
client.keepAlive = pc.equalsIgnoreCase("Keep-Alive");
}
HttpRequest.removePointToPointHeaders(clientHeaders, false);
HttpRequest target = HttpRequest.getRequest(url);
try {
MimeHeaders targetHeaders = target.requestHeaders;
target.setMethod(client.method);
clientHeaders.copyTo(targetHeaders);
/* targetHeaders.add("Via", client.protocol + via);*/
/*
* We might need to authenticate to a target proxy.
*/
if ((proxyHost != null)
&& proxyTester.useProxy(target.host, target.port)) {
target.setProxy(proxyHost, proxyPort);
if (auth != null) {
targetHeaders.add("Proxy-Authorization", auth);
}
}
if (client.postData != null) {
OutputStream out = target.getOutputStream();
out.write(client.postData);
out.close();
}
target.connect();
targetHeaders = target.responseHeaders;
rewriteHeaders(targetHeaders);
if (shouldLog) {
dumpHeaders(count, client, targetHeaders, false);
System.err.println(" " + target.status + "\n" +
dumpHeaders(count, client, targetHeaders, false));
}
HttpRequest.removePointToPointHeaders(targetHeaders, true);
clientHeaders = client.responseHeaders;
targetHeaders.copyTo(clientHeaders);
try {
clientHeaders.add("Via",
target.status.substring(0, 8) + via);
} catch (StringIndexOutOfBoundsException e) {
clientHeaders.add("Via", via);
}
client.sendResponse(target.getInputStream(),
target.getContentLength(), null, target.getResponseCode());
} catch (InterruptedIOException e) {
/*
* Read timeout while reading from the remote side. We use a
* read timeout in case the target never responds.
*/
client.sendError(408, "Timeout / No response");
} catch (EOFException e) {
client.sendError(500, "No response");
} catch (UnknownHostException e) {
client.sendError(500, "Unknown host");
} catch (ConnectException e) {
client.sendError(500, "Connection refused");
} catch (IOException e) {
/*
* An IOException will happen if we can't communicate with the
* target or the client. Rather than attempting to discriminate,
* just send an error message to the client, and let the send
* fail if the client was the one that was in error.
*/
String msg = "Error from proxy";
if (e.getMessage() != null) {
msg += ": " + e.getMessage();
}
client.sendError(500, msg);
} finally {
target.close();
}
return true;
}
/**
* Allow sub-classes to rewrite any or all of the target
* headers, if needed.
*/
protected MimeHeaders rewriteHeaders(MimeHeaders responseHeaders) {
return responseHeaders;
}
/**
* Dump the headers on stderr
*/
public static String
dumpHeaders(int count, Request request, MimeHeaders headers, boolean sent) {
String prompt;
StringBuffer sb = new StringBuffer();
String label = " " + count;
label = label.substring(label.length()-4);
if (sent) {
prompt = label + "> ";
sb.append(prompt).append(request.toString()).append("\n");
} else {
prompt = label + "< ";
}
for (int i = 0; i < headers.size(); i++) {
sb.append(prompt).append(headers.getKey(i));
sb.append(": ").append(headers.get(i)).append("\n");
}
return(sb.toString());
}
}