001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2007 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.lib.web.micro.base;
028:
029: import java.io.InputStream;
030: import java.io.IOException;
031: import java.io.OutputStream;
032: import java.util.Collections;
033: import java.util.Enumeration;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.Map;
037:
038: import javax.servlet.Servlet;
039: import javax.servlet.ServletConfig;
040: import javax.servlet.ServletException;
041: import javax.servlet.ServletInputStream;
042: import javax.servlet.ServletOutputStream;
043: import javax.servlet.ServletRequest;
044: import javax.servlet.ServletResponse;
045: import javax.servlet.http.HttpServletResponse;
046: import javax.servlet.http.HttpServletRequest;
047:
048: /**
049: * A utility class to tunnel servlet requests through a client
050: * {@link Connection}.
051: * <p>
052: * Typical usage is:<pre>
053: * Map metaData = {@link ServletTunnel#extractMetaData}(req);
054: * Connection con = {@link ClientFactory#connect}(uri, metaData);
055: * {@link ServletTunnel#tunnel}(req, res, con);
056: * </pre>
057: */
058: public final class ServletTunnel {
059:
060: private ServletTunnel() {
061: }
062:
063: /**
064: * Extract the non-header metadata from the servlet request.
065: * <p>
066: * We use this to tunnel this information to the remote server, otherwise
067: * it won't know our client's IP address and other non-header info.
068: */
069: public static Map extractMetaData(HttpServletRequest req) {
070: Map ret = new HashMap();
071: ret.put("serverURL", req.getScheme() + "://"
072: + req.getServerName() + ":" + req.getServerPort()
073: + req.getServletPath());
074: ret.put("contextPath", req.getContextPath());
075: ret.put("clientAddr", req.getRemoteAddr());
076: ret.put("clientHost", req.getRemoteHost());
077: ret = Collections.unmodifiableMap(ret);
078: return ret;
079: }
080:
081: /**
082: * Tunnel a servlet call through a remote connection.
083: */
084: public static void tunnel(HttpServletRequest req,
085: HttpServletResponse res, Connection con)
086: throws ServletException, IOException {
087:
088: // forward header and post data
089: AnnotatedOutputStream out = con.getOutputStream();
090: {
091: boolean is_post = "post".equalsIgnoreCase(req.getMethod());
092:
093: // write request line:
094: String queryString = req.getQueryString();
095: out.print(req.getMethod() + " " + req.getRequestURI());
096: if (queryString != null) {
097: out.print("?" + queryString);
098: }
099: out.println(" " + req.getProtocol());
100:
101: // get posted parameters "name=value" line
102: String post_params = null;
103: if (is_post) {
104: StringBuffer buf = new StringBuffer();
105: Map m = req.getParameterMap();
106: for (Iterator iter = m.entrySet().iterator(); iter
107: .hasNext();) {
108: Map.Entry me = (Map.Entry) iter.next();
109: String name = (String) me.getKey();
110: String[] values = (String[]) me.getValue();
111: for (int i = 0; i < values.length; i++) {
112: if (buf.length() > 0) {
113: buf.append("&");
114: }
115: buf.append(name).append("=").append(values[i]);
116: }
117: }
118: post_params = buf.toString();
119: }
120:
121: // write headers:
122: if (is_post) {
123: out.println("Content-Length: " + post_params.length());
124: }
125: for (Enumeration en = req.getHeaderNames(); en
126: .hasMoreElements();) {
127: String name = (String) en.nextElement();
128: if (is_post && "Content-Length".equals(name)) {
129: continue;
130: }
131: for (Enumeration e2 = req.getHeaders(name); e2
132: .hasMoreElements();) {
133: String value = (String) e2.nextElement();
134: out.println(name + ": " + value);
135: }
136: }
137: out.println();
138:
139: // write post data:
140: if (is_post) {
141: out.print(post_params);
142: } else {
143: int r_contentLength = req.getContentLength();
144: InputStream r_in = req.getInputStream();
145: if (r_contentLength >= 0) {
146: pipeTo(r_in, out, r_contentLength);
147: } else {
148: // FIXME according to the HTTP spec this seems like an error, but we
149: // may be missing some valid cases (e.g. chunked transfer data)
150: }
151: }
152:
153: // send data
154: out.flush();
155:
156: // we're done writing & flushing, but we can't close the stream yet,
157: // otherwise a socket-based connection will complain.
158: out.done();
159: }
160:
161: // read from our pipe
162: InputStream in = con.getInputStream();
163:
164: // read status, e.g.:
165: // HTTP/1.0 200 OK
166: String status = readLine(in);
167: if (status == null) {
168: throw new RuntimeException("Missing status");
169: }
170:
171: // read headers
172: String location = null;
173: int contentLength = -1;
174: while (true) {
175: String s = readLine(in);
176: if (s == null)
177: break;
178: if (s.length() == 0)
179: break;
180: int sep = s.indexOf(':');
181: if (sep <= 0) {
182: throw new RuntimeException("Invalid header: " + s);
183: }
184: String name = s.substring(0, sep).trim();
185: String value = s.substring(sep + 1).trim();
186: res.addHeader(name, value);
187: if ("Content-Length".equals(name)) {
188: contentLength = Integer.parseInt(value);
189: res.setContentLength(contentLength);
190: } else if ("Content-Type".equals(name)) {
191: res.setContentType(value);
192: } else if ("Location".equals(name)) {
193: location = value;
194: }
195: }
196:
197: // set status
198: {
199: int sc_sep = status.indexOf(' ');
200: int sm_sep = status.indexOf(' ', sc_sep + 1);
201: int sc = Integer.parseInt(status.substring(sc_sep + 1,
202: sm_sep).trim());
203: if (sc < 300 || sc == HttpServletResponse.SC_NOT_MODIFIED) {
204: // okay
205: res.setStatus(sc);
206: } else if (sc < 400) {
207: // redirect? Must read "Location" header.
208: if (location == null) {
209: throw new RuntimeException(
210: "Expecting a \"Location\" header for \""
211: + status + "\"");
212: }
213: res.sendRedirect(location);
214: } else {
215: // error
216: //
217: // use "setStatus" instead of "sendError" -- see bug 1259
218: //String sm = status.substring(sm_sep+1).trim();
219: res.setStatus(sc);
220: }
221: }
222:
223: // read data
224: if (location == null) {
225: ServletOutputStream r_out = res.getOutputStream();
226: pipeTo(in, r_out, contentLength);
227: }
228:
229: // done
230: in.close();
231: out.close();
232: con.close();
233: }
234:
235: private static void pipeTo(final InputStream is, OutputStream out,
236: int contentLength) throws IOException {
237: // we use an "annotated" stream to preserve the "out.flush()" requests.
238: AnnotatedInputStream ais = AnnotatedInputStream
239: .toAnnotatedInputStream(is);
240: if (contentLength >= 0) {
241: byte[] buf = new byte[Math.max(2048, contentLength)];
242: for (int i = 0; i < contentLength;) {
243: int count = ais.read2(buf, 0, Math.max(buf.length,
244: (contentLength - i)));
245: if (count == AnnotatedInputStream.NOOP) {
246: continue;
247: }
248: if (count == AnnotatedInputStream.FLUSH) {
249: out.flush();
250: continue;
251: }
252: if (count < 0)
253: break;
254: out.write(buf, 0, count);
255: i += count;
256: }
257: } else {
258: byte[] buf = new byte[2048];
259: while (true) {
260: int count = ais.read2(buf);
261: if (count == AnnotatedInputStream.NOOP) {
262: continue;
263: }
264: if (count == AnnotatedInputStream.FLUSH) {
265: out.flush();
266: continue;
267: }
268: if (count < 0)
269: break;
270: out.write(buf, 0, count);
271: }
272: }
273: }
274:
275: private static String readLine(InputStream in) throws IOException {
276: StringBuffer buf = new StringBuffer();
277: while (true) {
278: int b = in.read();
279: if (b < 0)
280: break;
281: if (b == '\r')
282: b = in.read();
283: if (b == '\n')
284: break;
285: buf.append((char) b);
286: }
287: return buf.toString().trim();
288: }
289: }
|