net.sf.j2ep.ProxyFilter.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.j2ep.ProxyFilter.java

Source

/*
 * Copyright 2005 Anders Nyman.
 * 
 * Licensed 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 net.sf.j2ep;

import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.j2ep.factories.MethodNotAllowedException;
import net.sf.j2ep.factories.RequestHandlerFactory;
import net.sf.j2ep.factories.ResponseHandlerFactory;
import net.sf.j2ep.model.AllowedMethodHandler;
import net.sf.j2ep.model.RequestHandler;
import net.sf.j2ep.model.ResponseHandler;
import net.sf.j2ep.model.Server;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A reverse proxy using a set of Rules to identify which resource to proxy.
 * 
 * At first the rule chain is traversed trying to find a matching rule.
 * When the rule is found it is given the option to rewrite the URL.
 * The rewritten URL is then sent to a Server creating a Response Handler
 * that can be used to process the response with streams and headers.
 * 
 * The rules and servers are created dynamically and are specified in the
 * XML data file. This allows the proxy to be easily extended by creating
 * new rules and new servers.
 * 
 * @author Anders Nyman
 */
public class ProxyFilter implements Filter {

    /** 
     * The server chain, will be traversed to find a matching server.
     */
    private ServerChain serverChain;

    /** 
     * Logging element supplied by commons-logging.
     */
    private static Log log;

    /** 
     * The httpclient used to make all connections with, supplied by commons-httpclient.
     */
    private HttpClient httpClient;

    /**
     * Implementation of a reverse-proxy. All request go through here. This is
     * the main class where are handling starts.
     * 
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {

        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        //httpRequest.setCharacterEncoding("UTF-8");
        //httpResponse.setCharacterEncoding("UTF-8");
        Server server = (Server) httpRequest.getAttribute("proxyServer");
        if (server == null) {
            server = serverChain.evaluate(httpRequest);
        }

        if (server == null) {
            filterChain.doFilter(request, response);
        } else {
            String uri = server.getRule().process(getURI(httpRequest));
            String url = request.getScheme() + "://" + server.getDomainName() + server.getPath() + uri;
            log.debug("Connecting to " + url);

            ResponseHandler responseHandler = null;

            try {
                httpRequest = server.preExecute(httpRequest);
                responseHandler = executeRequest(httpRequest, url);
                httpResponse = server.postExecute(httpResponse);

                responseHandler.process(httpResponse);
            } catch (HttpException e) {
                log.error("Problem while connecting to server", e);
                httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                server.setConnectionExceptionRecieved(e);
            } catch (UnknownHostException e) {
                log.error("Could not connection to the host specified", e);
                httpResponse.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
                server.setConnectionExceptionRecieved(e);
            } catch (IOException e) {
                log.error("Problem probably with the input being send, either with a Header or the Stream", e);
                httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } catch (MethodNotAllowedException e) {
                log.error("Incoming method could not be handled", e);
                httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                httpResponse.setHeader("Allow", e.getAllowedMethods());
            } finally {
                if (responseHandler != null) {
                    responseHandler.close();
                }
            }
        }
    }

    /**
     * Will build a URI but including the Query String. That means that it really
     * isn't a URI, but quite near.
     * 
     * @param httpRequest Request to get the URI and query string from
     * @return The URI for this request including the query string
     */
    private String getURI(HttpServletRequest httpRequest) {
        String contextPath = httpRequest.getContextPath();
        String uri = httpRequest.getRequestURI().substring(contextPath.length());
        if (httpRequest.getQueryString() != null) {
            uri += "?" + httpRequest.getQueryString();
        }
        return uri;
    }

    /**
     * Will create the method and execute it. After this the method
     * is sent to a ResponseHandler that is returned.
     * 
     * @param httpRequest Request we are receiving from the client
     * @param url The location we are proxying to
     * @return A ResponseHandler that can be used to write the response
     * @throws MethodNotAllowedException If the method specified by the request isn't handled
     * @throws IOException When there is a problem with the streams
     * @throws HttpException The httpclient can throw HttpExcetion when executing the method
     */
    private ResponseHandler executeRequest(HttpServletRequest httpRequest, String url)
            throws MethodNotAllowedException, IOException, HttpException {
        RequestHandler requestHandler = RequestHandlerFactory.createRequestMethod(httpRequest.getMethod());

        HttpMethod method = requestHandler.process(httpRequest, url);
        method.setFollowRedirects(false);

        /*
         * Why does method.validate() return true when the method has been
         * aborted? I mean, if validate returns true the API says that means
         * that the method is ready to be executed. TODO I don't like doing type
         * casting here, see above.
         */
        if (!((HttpMethodBase) method).isAborted()) {
            httpClient.executeMethod(method);

            if (method.getStatusCode() == 405) {
                Header allow = method.getResponseHeader("allow");
                String value = allow.getValue();
                throw new MethodNotAllowedException("Status code 405 from server",
                        AllowedMethodHandler.processAllowHeader(value));
            }
        }

        return ResponseHandlerFactory.createResponseHandler(method);
    }

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     * 
     * Called upon initialization, Will create the ConfigParser and get the
     * RuleChain back. Will also configure the httpclient.
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        log = LogFactory.getLog(ProxyFilter.class);
        AllowedMethodHandler.setAllowedMethods("OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE");

        httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
        httpClient.getParams().setBooleanParameter(HttpClientParams.USE_EXPECT_CONTINUE, false);
        httpClient.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);

        String data = filterConfig.getInitParameter("dataUrl");
        if (data == null) {
            serverChain = null;
        } else {
            try {
                File dataFile = new File(filterConfig.getServletContext().getRealPath(data));
                ConfigParser parser = new ConfigParser(dataFile);
                serverChain = parser.getServerChain();
            } catch (Exception e) {
                throw new ServletException(e);
            }
        }
    }

    /**
     * @see javax.servlet.Filter#destroy()
     * 
     * Called when this filter is destroyed.
     * Releases the fields.
     */
    public void destroy() {
        log = null;
        httpClient = null;
        serverChain = null;
    }
}