/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.generation; import java.io.IOException; import java.io.InputStream; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.xml.XMLUtils; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.environment.http.HttpEnvironment; import org.apache.cocoon.util.RequestForwardingHttpMethod; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.HttpURL; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.excalibur.xml.sax.SAXParser; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * This is a generic HTTP proxy, designed to handle any possible HTTP method, * but with a particular bias towards WebDAV. As of now it's pretty unstable, but * still it might be a good proof of concept towards a pure HTTP(++) proxy. * * <br>TODO: doesn't handle authentication properly * <br>TODO: doesn't handle (and doubt it'll ever will) HTTP/1.1 keep-alive * * @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a> * @version $Id$ */ public class GenericProxyGenerator extends ServiceableGenerator { /** The real URL to forward requests to */ HttpURL destination; /** The current request */ HttpServletRequest request; /** The current response */ HttpServletResponse response; /** The current request */ String path; SAXParser parser; /** * Compose and get a SAX parser for further use. * * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) */ public void service(ServiceManager manager) throws ServiceException { super.service(manager); this.parser = (SAXParser)manager.lookup(SAXParser.ROLE); } /** * Dispose */ public void dispose() { if ( this.manager != null ) { this.manager.release( this.parser ); this.parser = null; } super.dispose(); } /** * Setup this component by getting the (required) "url" parameter and the * (optional) "path" parameter. If path is not specified, the request URI will * be used and forwarded. * * TODO: handle query string * * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters) */ public void setup( SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { String url = par.getParameter("url", null); request = (HttpServletRequest)objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); response = (HttpServletResponse)objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT); if (url == null) { throw new ProcessingException("Missing the \"url\" parameter"); } path = par.getParameter("path", null); if (path == null) path = request.getRequestURI(); destination = new HttpURL(url); } /** * Get the request data, pass them on to the forwarder and return the result. * * TODO: much better header handling * TODO: handle non XML and bodyless responses (probably needs a smarter Serializer, * since some XML has to go through the pipeline anyway. * * @see org.apache.cocoon.generation.Generator#generate() */ public void generate() throws IOException, SAXException, ProcessingException { RequestForwardingHttpMethod method = new RequestForwardingHttpMethod(request, destination); // Build the forwarded connection HttpConnection conn = new HttpConnection(destination.getHost(), destination.getPort()); HttpState state = new HttpState(); state.setCredentials(null, destination.getHost(), new UsernamePasswordCredentials(destination.getUser(), destination.getPassword())); method.setPath(path); // Execute the method method.execute(state, conn); // Send the output to the client: set the status code... response.setStatus(method.getStatusCode()); // ... retrieve the headers from the origin server and pass them on Header[] methodHeaders = method.getResponseHeaders(); for (int i = 0; i < methodHeaders.length; i++) { // there is more than one DAV header if (methodHeaders[i].getName().equals("DAV")) { response.addHeader(methodHeaders[i].getName(), methodHeaders[i].getValue()); } else if (methodHeaders[i].getName().equals("Content-Length")) { // drop the original Content-Length header. Don't ask me why but there // it's always one byte off } else { response.setHeader(methodHeaders[i].getName(), methodHeaders[i].getValue()); } } // no HTTP keepalives here... response.setHeader("Connection", "close"); // Parse the XML, if any if (method.getResponseHeader("Content-Type").getValue().startsWith("text/xml")) { InputStream stream = method.getResponseBodyAsStream(); parser.parse(new InputSource(stream), this.contentHandler, this.lexicalHandler); } else { // Just send a dummy XML this.contentHandler.startDocument(); this.contentHandler.startElement("", "no-xml-content", "no-xml-content", XMLUtils.EMPTY_ATTRIBUTES); this.contentHandler.endElement("", "no-xml-content", "no-xml-content"); this.contentHandler.endDocument(); } // again, no keepalive here. conn.close(); } }