001: /**
002: * Copyright 2003 Sun Microsystems, Inc. All
003: * rights reserved. Use of this product is subject
004: * to license terms. Federal Acquisitions:
005: * Commercial Software -- Government Users
006: * Subject to Standard License Terms and
007: * Conditions.
008: *
009: * Sun, Sun Microsystems, the Sun logo, and Sun ONE
010: * are trademarks or registered trademarks of Sun Microsystems,
011: * Inc. in the United States and other countries.
012: */package com.sun.portal.wsrp.consumer.resourceproxy;
013:
014: import javax.servlet.ServletException;
015: import javax.servlet.http.HttpServlet;
016: import javax.servlet.http.HttpServletRequest;
017: import javax.servlet.http.HttpServletResponse;
018: import java.io.IOException;
019: import java.net.URL;
020: import java.net.URLConnection;
021: import java.io.OutputStream;
022: import java.io.InputStream;
023: import java.io.InputStreamReader;
024: import java.io.PrintWriter;
025: import java.io.BufferedReader;
026: import java.util.Enumeration;
027: import java.util.Set;
028: import java.util.HashSet;
029: import java.util.logging.Level;
030: import java.util.logging.Logger;
031:
032: import com.sun.portal.wsrp.consumer.common.WSRPConsumerRewriter;
033: import com.sun.portal.log.common.PortalLogger;
034:
035: /**
036: * The ResourceProxyServlet is design to service calls to resources on
037: * the producer side.
038: * <P>
039: * It acts very much as a HTTP proxy.
040: * <P>
041: * It also contains a set of hooks design to add functionality and simplify
042: * the incremental integration with other products. These hooks provide
043: * means for pre forwarding and post forwarding
044: * of the request.
045: * <P>
046: * When forwarding the incoming request to the backend URL it filters out the
047: * following HTTP headers: host, connection, cookie.
048: * <P>
049: * When forwarding back the response from the backend URL to the caller response
050: * it filters out the following HTTP header: set-cookie.
051: * <P>
052: * If session managment is desired with the incoming requests and backend calls,
053: * it should be implemented in DispatcherHooks.
054: * <P>
055: *
056: * @author <A HREF="mailto:alejandro.abdelnur@sun.com">Alejandro Abdelnur</A>
057: *
058: */
059: public class ResourceProxyServlet extends HttpServlet {
060:
061: // Sets of headers that have to be ignored in the proxying process.
062: // When adding header names to be ignored add them always in lower case,
063: // all in-traffic headers are converted to lower case before being check
064: // for black listing.
065: //
066: private static final Set DO_NOT_FORWARD_HEADERS = new HashSet();
067: private static final Set DO_NOT_FORWARD_BACK_HEADERS = new HashSet();
068: private static final Set MIMES_TO_REWRITE = new HashSet();
069:
070: private static Logger logger = PortalLogger
071: .getLogger(ResourceProxyServlet.class);
072:
073: {
074: DO_NOT_FORWARD_HEADERS.add("host");
075: DO_NOT_FORWARD_HEADERS.add("connection");
076: DO_NOT_FORWARD_HEADERS.add("cookie");
077: DO_NOT_FORWARD_HEADERS.add("content-length");
078:
079: DO_NOT_FORWARD_BACK_HEADERS.add("content-length");
080: DO_NOT_FORWARD_BACK_HEADERS.add("set-cookie");
081: DO_NOT_FORWARD_BACK_HEADERS.add("date");
082: DO_NOT_FORWARD_BACK_HEADERS.add("server");
083: DO_NOT_FORWARD_BACK_HEADERS.add("transfer-encoding");
084:
085: MIMES_TO_REWRITE.add("text/html");
086: MIMES_TO_REWRITE.add("text/vnd.wap.wml");
087: MIMES_TO_REWRITE.add("text/xml");
088:
089: }
090:
091: /**
092: * Initializes the Servlet.
093: *
094: */
095: public void init() throws ServletException {
096: }
097:
098: private String getResourceURL(HttpServletRequest req) {
099: return req
100: .getParameter(WSRPConsumerRewriter.PROXY_RESOURCE_URL);
101: }
102:
103: private boolean hasResourceRewritingOn(HttpServletRequest req) {
104: String sResourceRewrite = req
105: .getParameter(WSRPConsumerRewriter.PROXY_RESOURCE_REWRITE);
106: return (sResourceRewrite != null) ? sResourceRewrite
107: .equalsIgnoreCase("true") : false;
108: }
109:
110: private String getCookieJarKey(HttpServletRequest req) {
111: return req.getParameter(WSRPConsumerRewriter.PROXY_COOKIE_KEY);
112: }
113:
114: private String getPortalServerURLPrefix(HttpServletRequest req) {
115: return req
116: .getParameter(WSRPConsumerRewriter.PROXY_PORTAL_SERVER_URL);
117: }
118:
119: private String getNamespacePrefix(HttpServletRequest req) {
120: return req.getParameter(WSRPConsumerRewriter.PROXY_NAMESPACE);
121: }
122:
123: /**
124: * It processes GET and POST requests, proxing them to the encoded resource URL doing
125: * rewriting in the response if indicated.
126: * <P>
127: *
128: * @param req the servlet's request.
129: *
130: * @param res the servlet's response.
131: *
132: * @exception ServletException thrown if there was a problem processing the request.
133: *
134: * @exception IOException thrown if there was a problem processing the IO for the request.
135: *
136: */
137: public void service(HttpServletRequest req, HttpServletResponse res)
138: throws ServletException, IOException {
139: String resourceURL = getResourceURL(req);
140: boolean resourceRewrite = hasResourceRewritingOn(req);
141: String cookieJarKey = getCookieJarKey(req);
142:
143: if (resourceURL == null) {
144: res.sendError(res.SC_NOT_FOUND);
145: } else {
146: try {
147: String requestQueryString = req.getQueryString();
148: if (logger.isLoggable(Level.FINEST)) {
149: String[] param = { "ResourceURL", resourceURL };
150: logger.log(Level.FINEST, "PSWS_CSPWCRP0001", param);
151: param[0] = "ResourceRewrite";
152: param[1] = "" + resourceRewrite;
153: logger.log(Level.FINEST, "PSWS_CSPWCRP0001", param);
154: param[0] = "cookieJarKey";
155: param[1] = cookieJarKey;
156: logger.log(Level.FINEST, "PSWS_CSPWCRP0001", param);
157: }
158:
159: URL url = new URL(resourceURL);
160: URLConnection conn = url.openConnection();
161:
162: ConnectionHandler ch = new CookiesConnectionHandler(
163: req, cookieJarKey);
164: ch.preConnection(conn);
165:
166: conn.setDoInput(true);
167: conn.setUseCaches(false);
168:
169: forwardRequest(req, res, conn);
170: forwardBackResponse(req, res, conn, resourceRewrite);
171:
172: ch.postConnection(conn);
173: } catch (IOException ex) {
174: if (logger.isLoggable(Level.SEVERE))
175: logger.log(Level.SEVERE, "PSWS_CSPWCRP0002", ex);
176: throw ex;
177: } catch (ServletException ex) {
178: if (logger.isLoggable(Level.SEVERE))
179: logger.log(Level.SEVERE, "PSWS_CSPWCRP0002", ex);
180: throw ex;
181: }
182:
183: }
184: }
185:
186: /**
187: * Forwards the Servlet's request to the backend URL.
188: * <P>
189: * <B>NOTE</B>: We should investigate about using chuncks (byte[]) instead byte by byte.
190: * <P>
191: *
192: * @param req the servlet's request.
193: *
194: * @param res the servlet's response.
195: *
196: * @param conn the URLConnection for the backend URL.
197: *
198: * @exception ServletException thrown if there was a problem processing the request.
199: *
200: * @exception IOException thrown if there was a problem processing the IO for the request.
201: *
202: */
203: protected void forwardRequest(HttpServletRequest req,
204: HttpServletResponse res, URLConnection conn)
205: throws IOException, ServletException {
206: try {
207:
208: setRequestHeadersIntoConnection(req, conn);
209:
210: if (req.getMethod().equals("POST")) {
211: conn.setDoOutput(true);
212:
213: OutputStream output = conn.getOutputStream();
214: int count = req.getContentLength();
215: InputStream input = req.getInputStream();
216: int c;
217:
218: while ((count-- > 0) && ((c = input.read()) >= 0)) {
219: output.write(c);
220: }
221: output.close();
222: }
223: } catch (Exception ex) {
224: if (logger.isLoggable(Level.SEVERE))
225: logger.log(Level.SEVERE, "PSWS_CSPWCRP0003", ex);
226: res.sendError(res.SC_BAD_REQUEST);
227: }
228: }
229:
230: /**
231: * Copies the Servlet's request heards to the URLConnection of the backend URL.
232: * <P>
233: * It skips the following headers: host, connection, cookie.
234: * <P>
235: *
236: * @param req the servlet's request.
237: *
238: * @param conn the URLConnection for the backend URL.
239: *
240: * @exception ServletException thrown if there was a problem processing the request.
241: *
242: * @exception IOException thrown if there was a problem processing the IO for the request.
243: *
244: */
245: protected void setRequestHeadersIntoConnection(
246: HttpServletRequest req, URLConnection conn)
247: throws IOException, ServletException {
248: Enumeration e = req.getHeaderNames();
249: while (e.hasMoreElements()) {
250: String name = (String) e.nextElement();
251: String value = req.getHeader(name);
252:
253: // Filters the headers that are not supposed to be forwarded
254: if (!DO_NOT_FORWARD_HEADERS.contains(name.toLowerCase())) {
255: conn.setRequestProperty(name, value);
256:
257: }
258: }
259:
260: }
261:
262: private static final int BUFFER_SIZE = 4096;
263:
264: /*
265: * Forwards the backend response back to the Servlet's response, doing rewriting if indicated.
266: * <P>
267: *
268: * @param req the servlet's request.
269: *
270: * @param res the servlet's response.
271: *
272: * @conn the URLConnection for the backend URL.
273: *
274: * @exception ServletException thrown if there was a problem processing the request.
275: *
276: * @exception IOException thrown if there was a problem processing the IO for the request.
277: *
278: */
279: protected void forwardBackResponse(HttpServletRequest req,
280: HttpServletResponse res, URLConnection conn,
281: boolean resourceRewrite) throws IOException,
282: ServletException {
283: try {
284:
285: setConnectionHeadersIntoResponse(conn, res);
286:
287: String responseMime = conn.getContentType();
288: if (responseMime == null) {
289: resourceRewrite = false;
290: } else {
291: if (responseMime.indexOf(";") > -1) {
292: responseMime = responseMime.substring(0,
293: responseMime.indexOf(";") - 1);
294: }
295: resourceRewrite = resourceRewrite
296: && MIMES_TO_REWRITE.contains(responseMime);
297: }
298:
299: if (logger.isLoggable(Level.FINEST)) {
300: String[] param = { "Resource rewriting",
301: "" + resourceRewrite };
302: logger.log(Level.FINEST, "PSWS_CSPWCRP0001", param);
303: }
304:
305: if (resourceRewrite) {
306: InputStream input = conn.getInputStream();
307: BufferedReader bReader = new BufferedReader(
308: new InputStreamReader(input));
309: char[] buffer = new char[BUFFER_SIZE];
310: StringBuffer sBuffer = new StringBuffer(BUFFER_SIZE);
311: int charsRead = bReader.read(buffer, 0, BUFFER_SIZE);
312: while (charsRead > -1) {
313: sBuffer.append(buffer, 0, charsRead);
314: charsRead = bReader.read(buffer, 0, BUFFER_SIZE);
315: }
316: bReader.close();
317: WSRPConsumerRewriter rewriter = new WSRPConsumerRewriter(
318: getPortalServerURLPrefix(req),
319: getCookieJarKey(req), getNamespacePrefix(req));
320: String rewrittenMarkup = rewriter.rewrite(sBuffer
321: .toString(), req);
322: PrintWriter pWriter = res.getWriter();
323: pWriter.write(rewrittenMarkup);
324: pWriter.close();
325: } else {
326: InputStream input = conn.getInputStream();
327: OutputStream output = res.getOutputStream();
328: byte[] buffer = new byte[BUFFER_SIZE * 2];
329: int bytesRead = input.read(buffer, 0, BUFFER_SIZE * 2);
330: while (bytesRead > -1) {
331: output.write(buffer, 0, bytesRead);
332: bytesRead = input.read(buffer, 0, BUFFER_SIZE * 2);
333: }
334: input.close();
335: output.flush();
336: }
337: } catch (Throwable ex) {
338: if (logger.isLoggable(Level.SEVERE))
339: logger.log(Level.SEVERE, "", ex);
340: res.sendError(res.SC_BAD_REQUEST);
341: }
342: }
343:
344: /**
345: * Copies the backend URL URLConnection response headers back to the Servlet's response.
346: * <P>
347: * It skips the following headers: set-cookie.
348: * <P>
349: *
350: * @param conn the URLConnection for the backend URL.
351: *
352: * @param res the servlet's response.
353: *
354: * @exception ServletException thrown if there was a problem processing the response.
355: *
356: * @exception IOException thrown if there was a problem processing the IO for the response.
357: *
358: */
359: protected void setConnectionHeadersIntoResponse(URLConnection conn,
360: HttpServletResponse res) throws IOException,
361: ServletException {
362: int i = 1;
363: /*
364: // special handling of content length, if not done the servlet container
365: // treats the output as with unknown length even if the content-length
366: // header is there.
367: int length = conn.getContentLength();
368: if (length>-1) {
369: res.setContentLength(length);
370: }
371: */
372: String name = conn.getHeaderFieldKey(i);
373:
374: while (name != null) {
375: String value = conn.getHeaderField(i);
376:
377: // Filters the headers that are not supposed to be forwarded back
378: if (!DO_NOT_FORWARD_BACK_HEADERS.contains(name
379: .toLowerCase())) {
380: res.addHeader(name, value);
381:
382: }
383: i++;
384: name = conn.getHeaderFieldKey(i);
385: }
386:
387: // add to make sure nothing gets cached.
388: res.addHeader("Cache-control",
389: "no-store, no-cache, must-revalidate");
390: res.addHeader("Pragma", "no-cache");
391: res.addHeader("Expires", "Sat, 1 Jan 2000 00:00:00 GMT");
392:
393: }
394:
395: }
|