001: /*
002: * GenericProxyHandler.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1998-2001 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: suhler.
018: * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, rinaldo, suhler.
022: *
023: * Version: 1.29
024: * Created by suhler on 98/09/14
025: * Last modified by suhler on 01/01/16 14:25:54
026: */
027:
028: package sunlabs.brazil.handler;
029:
030: import java.io.ByteArrayOutputStream;
031: import java.io.IOException;
032: import java.io.InterruptedIOException;
033: import java.io.OutputStream;
034: import java.net.ConnectException;
035: import java.net.UnknownHostException;
036: import sunlabs.brazil.server.Handler;
037: import sunlabs.brazil.server.Request;
038: import sunlabs.brazil.server.Server;
039: import sunlabs.brazil.util.http.HttpRequest;
040: import sunlabs.brazil.util.http.MimeHeaders;
041:
042: /**
043: * Handler for implementing a virtual web site.
044: * This causes another web site to "appear" inside our document root.
045: * This classes is intended to be sub-classed, so some of the methods
046: * in this implementation don't do too much.
047: *
048: * All of the appropriate links in HTML documents on the virtual
049: * site are rewritten, so they appear to be local references.
050: * This can be used on a firewall in conjunction with
051: * {@link AclSwitchHandler}
052: * to provide authenticated acces to selected web sites.
053: * <p>
054: * Properties:
055: * <dl class=props>
056: * <dt>prefix <dd>URL prefix must match
057: * <dt>host <dd>name of host site to proxy to.
058: * <dt>port <dd>Host port to proxy to (defaults to 80).
059: * <dt>proxyHost<dd>Which proxy host to use (if any)
060: * to contact "host".
061: * <dt>proxyPort<dd>The proxy's port (defaults to 80)
062: * </dl>
063: *
064: * @author Stephen Uhler
065: * @version 1.29, 01/01/16
066: */
067:
068: public class GenericProxyHandler implements Handler {
069: // protected Server server;
070: protected String prefix;
071:
072: protected String host; // The host containing the actual page
073: protected int port; // The port for above (defaults to 80)
074: protected String proxyHost; // The proxy server (if any)
075: protected int proxyPort; // The proxy server's port (defaults to 80)
076: protected String urlPrefix; // The url prefix that triggers this handler
077: protected String requestPrefix; // The host/port prefix
078:
079: /**
080: * Handler configuration property <b>prefix</b>.
081: * Only URL's that begin with this string are considered by this handler.
082: * The default is (/).
083: */
084: public static final String PREFIX = "prefix"; // URL prefix for proxy
085: /**
086: * Handler configuration property <b>host</b>.
087: * The actual host site to appear on our site (required)
088: */
089: public static final String HOST = "host"; // The host to proxy these requests to
090: /**
091: * Handler configuration property <b>port</b>.
092: * The actual port on the host site (defaults to 80).
093: */
094: public static final String PORT = "port"; // The host port to proxy these requests to
095: /**
096: * Handler configuration property <b>proxyHost</b>.
097: * The name of a proxy to use (if any) to get to the <b>host</b>.
098: */
099: public static final String PROXY_HOST = "proxyHost"; // The proxy server (if any)
100: /**
101: * Handler configuration property <b>proxyPort</b>.
102: * The proxy port to use to get to the <b>host</b>. defaults to 80.
103: */
104: public static final String PROXY_PORT = "proxyPort"; // The proxy server port (if any)
105: public static final String NL = "\r\n"; // line terminator
106:
107: /**
108: * Do one-time setup.
109: * get and process the handler properties.
110: * we can contact the server identified by the <b>host</b> parameter.
111: */
112:
113: public boolean init(Server server, String prefix) {
114: // this.server = server;
115: this .prefix = prefix;
116:
117: if (server.logLevel > server.LOG_DIAGNOSTIC) {
118: MapPage.log = true;
119: }
120:
121: /* XXX
122: * Need to add tag map entries here for a specific host
123: */
124:
125: host = server.props.getProperty(prefix + HOST);
126: if (host == null) {
127: server.log(Server.LOG_WARNING, prefix,
128: "no host to proxy to");
129: return false;
130: }
131:
132: urlPrefix = server.props.getProperty(prefix + PREFIX, "/");
133: if (urlPrefix.indexOf('/') != 0) {
134: urlPrefix = "/" + urlPrefix;
135: }
136: if (!urlPrefix.endsWith("/")) {
137: urlPrefix += "/";
138: }
139:
140: port = 80;
141: try {
142: String str = server.props.getProperty(prefix + PORT);
143: port = Integer.decode(str).intValue();
144: } catch (Exception e) {
145: }
146:
147: proxyHost = server.props.getProperty(prefix + PROXY_HOST);
148: proxyPort = 80;
149: try {
150: String str = server.props.getProperty(prefix + PROXY_PORT);
151: proxyPort = Integer.decode(str).intValue();
152: } catch (Exception e) {
153: }
154:
155: if (port == 80) {
156: requestPrefix = "http://" + host;
157: } else {
158: requestPrefix = "http://" + host + ":" + port;
159: }
160: return true;
161: }
162:
163: /**
164: * If this is one of "our" url's, fetch the document from
165: * the destination server, and return it as if it was local.
166: */
167:
168: public boolean respond(Request request) throws IOException {
169: if (!isMine(request)) {
170: return false;
171: }
172:
173: String url = request.url.substring(urlPrefix.length());
174: if (!url.startsWith("/")) {
175: url = "/" + url;
176: }
177:
178: if (!request.query.equals("")) {
179: url += "?" + request.query;
180: }
181:
182: HttpRequest target = new HttpRequest(requestPrefix + url);
183: // System.out.println("Fetching: " + requestPrefix + url);
184: if (proxyHost != null) {
185: target.setProxy(proxyHost, proxyPort);
186: }
187: target.setMethod(request.method);
188:
189: HttpRequest.removePointToPointHeaders(request.headers, false);
190: request.headers.remove("if-modified-since"); // wrong spot XXX
191: request.headers.copyTo(target.requestHeaders);
192:
193: /* XXX This doesn't belong here! - the proxy shoud do it */
194: target.requestHeaders.put("host", host);
195:
196: boolean code = true;
197: try {
198: if (request.postData != null) {
199: OutputStream out = target.getOutputStream();
200: out.write(request.postData);
201: out.close();
202: }
203:
204: // Connect to target and read the response headers
205:
206: target.connect();
207: // System.out.println("Got headers: " + target.responseHeaders);
208: HttpRequest.removePointToPointHeaders(
209: target.responseHeaders, true);
210: target.responseHeaders.copyTo(request.responseHeaders);
211:
212: // Now filter the output, writing the header and content if true
213:
214: request.log(Server.LOG_DIAGNOSTIC, " Response headers: "
215: + target.responseHeaders);
216: if (shouldFilter(request.responseHeaders)) {
217:
218: ByteArrayOutputStream out = new ByteArrayOutputStream();
219: target.getInputStream().copyTo(out);
220:
221: request.log(Server.LOG_DIAGNOSTIC,
222: " parsing/modifying " + out.size() + " bytes");
223: byte[] content = modifyContent(request, out
224: .toByteArray());
225:
226: if (content == null) { // This is wrong!!
227: request.log(Server.LOG_DIAGNOSTIC,
228: " null content, returning false");
229: code = false;
230: } else {
231: request.sendResponse(content, null);
232: }
233: } else {
234: request.log(Server.LOG_DIAGNOSTIC,
235: "Delivering normal content");
236: request.sendResponse(target.getInputStream(), target
237: .getContentLength(), null, target
238: .getResponseCode());
239: }
240: } catch (InterruptedIOException e) {
241: /*
242: * Read timeout while reading from the remote side. We use a
243: * read timeout in case the target never responds.
244: */
245: request.sendError(408, "Timeout / No response");
246: } catch (UnknownHostException e) {
247: request.sendError(503, urlPrefix + " Not reachable");
248: } catch (ConnectException e) {
249: request.sendError(500, "Connection refused");
250: } catch (IOException e) {
251: request.sendError(500, "Error retrieving response: " + e);
252: e.printStackTrace();
253: } finally {
254: target.close();
255: // System.out.println("Finally (proxy): " + code);
256: }
257: return code;
258: }
259:
260: /**
261: * See if the content needs to be filtered.
262: * Return "true" if "modifyContent" should be called
263: * @param headers Vector of mime headers for data to proxy
264: */
265:
266: protected boolean shouldFilter(MimeHeaders headers) {
267: String type = headers.get("Content-Type");
268: // System.out.println("Modify?? " + type);
269: return (headers.get("location") != null || (type != null && type
270: .indexOf("text/html") >= 0));
271: }
272:
273: /**
274: * See if this is one of my requests.
275: * This method can be overridden to do more sophisticated mappings.
276: * @param request The standard request object
277: */
278:
279: public boolean isMine(Request request) {
280: return request.url.startsWith(urlPrefix);
281: }
282:
283: /**
284: * Rewrite the links in an html file so they resolve correctly
285: * in proxy mode.
286: *
287: * @param request The original request to this "proxy"
288: * @param headers The vector of mime headers for the proxy request
289: * @return true if the headers and content should be sent to the client, false otherwise
290: * Modifies "headers" as a side effect
291: */
292:
293: public byte[] modifyContent(Request request, byte[] content) {
294: MapPage mapper = new MapPage(urlPrefix);
295: addMap(mapper);
296: byte[] result;
297: result = mapper.convertHtml(new String(content)).getBytes();
298:
299: /*
300: * Now fix the content length
301: */
302:
303: request.responseHeaders.put("Content-Length", result.length);
304:
305: /*
306: * Rewrite the location header, if any
307: */
308:
309: String location = request.responseHeaders.get("location");
310: if (location != null) {
311: request.setStatus(302); // XXX doesn't belong here, should copy
312: // targets status
313: String fixed = mapper.convertString(location);
314: if (fixed != null) {
315: String newLocation = request.serverUrl() + fixed;
316: System.out.println("Mapping location: " + location
317: + "->" + newLocation);
318: request.responseHeaders.put("Location", newLocation);
319: }
320: }
321:
322: /*
323: * Fix the domain specifiers on set-cookie requests
324: */
325:
326: String cookie = request.responseHeaders.get("set-cookie");
327: if (cookie != null) {
328: System.out.println("Need to map: " + cookie);
329: }
330:
331: return result;
332: }
333:
334: /**
335: * for subclassing
336: */
337:
338: public void addMap(MapPage mapper) {
339: }
340: }
|