001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.generation;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.Map;
022:
023: import javax.servlet.http.HttpServletRequest;
024: import javax.servlet.http.HttpServletResponse;
025:
026: import org.apache.avalon.framework.parameters.Parameters;
027: import org.apache.avalon.framework.service.ServiceException;
028: import org.apache.avalon.framework.service.ServiceManager;
029: import org.apache.cocoon.ProcessingException;
030: import org.apache.cocoon.xml.XMLUtils;
031: import org.apache.cocoon.environment.SourceResolver;
032: import org.apache.cocoon.environment.http.HttpEnvironment;
033: import org.apache.cocoon.util.RequestForwardingHttpMethod;
034: import org.apache.commons.httpclient.Header;
035: import org.apache.commons.httpclient.HttpConnection;
036: import org.apache.commons.httpclient.HttpState;
037: import org.apache.commons.httpclient.HttpURL;
038: import org.apache.commons.httpclient.UsernamePasswordCredentials;
039: import org.apache.excalibur.xml.sax.SAXParser;
040: import org.xml.sax.InputSource;
041: import org.xml.sax.SAXException;
042:
043: /**
044: * This is a generic HTTP proxy, designed to handle any possible HTTP method,
045: * but with a particular bias towards WebDAV. As of now it's pretty unstable, but
046: * still it might be a good proof of concept towards a pure HTTP(++) proxy.
047: *
048: * <br>TODO: doesn't handle authentication properly
049: * <br>TODO: doesn't handle (and doubt it'll ever will) HTTP/1.1 keep-alive
050: *
051: * @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a>
052: * @version $Id: GenericProxyGenerator.java 433543 2006-08-22 06:22:54Z crossley $
053: */
054: public class GenericProxyGenerator extends ServiceableGenerator {
055:
056: /** The real URL to forward requests to */
057: HttpURL destination;
058: /** The current request */
059: HttpServletRequest request;
060: /** The current response */
061: HttpServletResponse response;
062: /** The current request */
063: String path;
064: SAXParser parser;
065:
066: /**
067: * Compose and get a SAX parser for further use.
068: *
069: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
070: */
071: public void service(ServiceManager manager) throws ServiceException {
072: super .service(manager);
073: this .parser = (SAXParser) manager.lookup(SAXParser.ROLE);
074: }
075:
076: /**
077: * Dispose
078: */
079: public void dispose() {
080: if (this .manager != null) {
081: this .manager.release(this .parser);
082: this .parser = null;
083: }
084: super .dispose();
085: }
086:
087: /**
088: * Setup this component by getting the (required) "url" parameter and the
089: * (optional) "path" parameter. If path is not specified, the request URI will
090: * be used and forwarded.
091: *
092: * TODO: handle query string
093: *
094: * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)
095: */
096: public void setup(SourceResolver resolver, Map objectModel,
097: String src, Parameters par) throws ProcessingException,
098: SAXException, IOException {
099: String url = par.getParameter("url", null);
100: request = (HttpServletRequest) objectModel
101: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
102: response = (HttpServletResponse) objectModel
103: .get(HttpEnvironment.HTTP_RESPONSE_OBJECT);
104:
105: if (url == null) {
106: throw new ProcessingException(
107: "Missing the \"url\" parameter");
108: }
109: path = par.getParameter("path", null);
110: if (path == null)
111: path = request.getRequestURI();
112: destination = new HttpURL(url);
113:
114: }
115:
116: /**
117: * Get the request data, pass them on to the forwarder and return the result.
118: *
119: * TODO: much better header handling
120: * TODO: handle non XML and bodyless responses (probably needs a smarter Serializer,
121: * since some XML has to go through the pipeline anyway.
122: *
123: * @see org.apache.cocoon.generation.Generator#generate()
124: */
125: public void generate() throws IOException, SAXException,
126: ProcessingException {
127: RequestForwardingHttpMethod method = new RequestForwardingHttpMethod(
128: request, destination);
129:
130: // Build the forwarded connection
131: HttpConnection conn = new HttpConnection(destination.getHost(),
132: destination.getPort());
133: HttpState state = new HttpState();
134: state.setCredentials(null, destination.getHost(),
135: new UsernamePasswordCredentials(destination.getUser(),
136: destination.getPassword()));
137: method.setPath(path);
138:
139: // Execute the method
140: method.execute(state, conn);
141:
142: // Send the output to the client: set the status code...
143: response.setStatus(method.getStatusCode());
144:
145: // ... retrieve the headers from the origin server and pass them on
146: Header[] methodHeaders = method.getResponseHeaders();
147: for (int i = 0; i < methodHeaders.length; i++) {
148: // there is more than one DAV header
149: if (methodHeaders[i].getName().equals("DAV")) {
150: response.addHeader(methodHeaders[i].getName(),
151: methodHeaders[i].getValue());
152: } else if (methodHeaders[i].getName().equals(
153: "Content-Length")) {
154: // drop the original Content-Length header. Don't ask me why but there
155: // it's always one byte off
156: } else {
157: response.setHeader(methodHeaders[i].getName(),
158: methodHeaders[i].getValue());
159: }
160: }
161:
162: // no HTTP keepalives here...
163: response.setHeader("Connection", "close");
164:
165: // Parse the XML, if any
166: if (method.getResponseHeader("Content-Type").getValue()
167: .startsWith("text/xml")) {
168: InputStream stream = method.getResponseBodyAsStream();
169: parser.parse(new InputSource(stream), this .contentHandler,
170: this .lexicalHandler);
171: } else {
172: // Just send a dummy XML
173: this .contentHandler.startDocument();
174: this .contentHandler.startElement("", "no-xml-content",
175: "no-xml-content", XMLUtils.EMPTY_ATTRIBUTES);
176: this .contentHandler.endElement("", "no-xml-content",
177: "no-xml-content");
178: this .contentHandler.endDocument();
179: }
180:
181: // again, no keepalive here.
182: conn.close();
183: }
184:
185: }
|