/* * 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()); } }