Package net.redhillsoftware.bonza

Source Code of net.redhillsoftware.bonza.BonzaConfig$Logger

package net.redhillsoftware.bonza;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;

/**
* Hodge-podege mess of the bonza configuration.
*/
public class BonzaConfig {
    private final Map<String, String> m_uri2HostMap;
    private final int m_port;
    private volatile boolean m_logRequests;

    public static BonzaConfig parseOptions(String[] args) {
        BonzaConfig cfg = null;
        try {
            if (args.length == 0) {
                File file = new File(".bonza");
                cfg = parse(file, true);
            } else if (args.length == 1 && isFilename(args[0])) {
                File file = new File(args[0]);
                cfg = parse(file, true);
            } else {
                List<String> list = Arrays.asList(args);
                cfg = parse(list);
            }
        } catch (Exception e) {
            Bonza.err(usage());
            System.exit(1);
        }
        return cfg;
    }

    public BonzaConfig(int port, Map<String, String> uri2HostMap, boolean logRequests) {
        m_port = port;
        m_uri2HostMap = uri2HostMap;
        m_logRequests = logRequests;
    }

    public Map<String, String> getUri2HostMap() {
        return m_uri2HostMap;
    }

    public int getPort() {
        return m_port;
    }

    public String toString() {
        String result = "Listening to port " + m_port;
        for (String key : m_uri2HostMap.keySet()) {
            result += "\n\t" + key + " -> " + m_uri2HostMap.get(key);
        }
        result += m_logRequests ? "\nLogging requests." : "\nNot logging requests";
        return result;
    }

    /**
     * Determine the appropriate url to make for this request and null if no request can be made.
     */
    public String mapRequest(String requestUri) {
        String uri = findMatchingURI(requestUri);
        if (uri == null) {
            return null;
        }
        String routeTo = m_uri2HostMap.get(uri);
        String remapped = requestUri.substring(uri.length());
        String newRequest = routeTo + remapped;

        return newRequest;
    }

    /**
     * Adjust the location header (if possible) so that redirects will pass back through the reverse proxy.
     */
    public String reverseMap(String locationHeader, HttpServletRequest originalRequest) {
        try {
            URL url = new URL(locationHeader);

            String uri2 = findMatchingURI(url.getPath());
            if (uri2 != null) {

                String remappedPath = url.getPath().substring(uri2.length());
                URI newURI = new URI(originalRequest.getScheme(),
                                     originalRequest.getHeader("Host"),
                                     uri2 + remappedPath,
                                     url.getQuery(),
                                     url.getRef());
                locationHeader = newURI.toString();
            }
        } catch (Exception e) {
            err(e.getMessage());
        }
        return locationHeader;
    }

    public Logger requestStart(HttpServletRequest request, String requestURI, String matchedURL) {
        Logger logger = new Logger(request, requestURI, matchedURL);
        return logger;
    }

    private String findMatchingURI(String path) {
        for (String uri : m_uri2HostMap.keySet()) {
            if (path.startsWith(uri)) {
                return uri;
            }
        }
        return null;
    }

    private void watchSourceFile(final File file) throws Exception {
        FileAlterationObserver observer = new FileAlterationObserver(file.getAbsoluteFile().getParentFile(), new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getAbsolutePath().equals(file.getAbsolutePath());
            }
        });
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileChange(File file) {
                try {
                    BonzaConfig newCFG = parse(file, false);
                    if (configChanged(newCFG)) {
                        out("\nFair go!!  Configuration change detected!!!");
                        m_uri2HostMap.entrySet().clear();
                        m_uri2HostMap.putAll(newCFG.getUri2HostMap());
                        m_logRequests = newCFG.m_logRequests;
                        out(BonzaConfig.this.toString());
                    }
                    if (newCFG.getPort() != m_port) {
                        err("\nPort changes require a restart!!!");
                    }
                } catch (IOException e) {
                    err("Error parsing " + file.getName());
                } catch (Exception e) {
                    err("Error parsing " + file.getName());
                }
            }

            private boolean configChanged(BonzaConfig newCFG) {
                return !newCFG.getUri2HostMap().equals(m_uri2HostMap) || newCFG.m_logRequests != m_logRequests;
            }
        });
        FileAlterationMonitor monitor = new FileAlterationMonitor(2000);
        monitor.addObserver(observer);
        monitor.start();
    }

    private static BonzaConfig parse(File file, boolean addListener) throws Exception {
        BonzaConfig cfg = parse(FileUtils.readLines(file));
        if (addListener) {
            cfg.watchSourceFile(file);
        }
        return cfg;
    }

    private static BonzaConfig parse(List<String> argsList) {

        List<String> filtered = filterCommentsAndBlankLines(argsList);
        boolean logRequests = true;
        Map<String, String> mapping = new HashMap<String, String>();
        int port = Integer.parseInt(filtered.get(0));
        List<String> proxyExpressions = filtered.subList(1, filtered.size());
        for (String arg : proxyExpressions) {
            if (arg.equals("-quiet")) {
                logRequests = false;
                continue;
            }
            String[] parts = arg.split("=");
            mapping.put(parts[0], parts[1]);
        }
        return new BonzaConfig(port, mapping, logRequests);
    }

    private static List<String> filterCommentsAndBlankLines(List<String> argsList) {
        ArrayList<String> toReturn = new ArrayList<String>(argsList.size());
        for (String arg : argsList) {
            if (arg.indexOf('#') >= 0) {
                arg = arg.substring(0,arg.indexOf('#'));
            }
            arg = arg.trim();
            if (arg.length() == 0) {
                continue;
            }
            toReturn.add(arg);
        }
        return toReturn;
    }

    private static boolean isFilename(String filename) {
        File f = new File(filename);
        return f.exists() && f.canRead();
    }

    private static String usage() {
        String usage = "Usage: bonza [filename |  port [-quiet] prefix1=resource1 [prefix2=resource2]...]" +
                "\n\tIf invoked without arguments, reads configuration from a .bonza file in the same directory." +
                "\n\tIf invoked with a single argument that is a filename, reads configuration from the named file." +
                "\n\tOtherwise reads argumenst from the command line." +
                "\n\n" +
                "Proxy Mapping Expressions: uri_prefix=proxied_resource" +
                "\n\tRoute requests with the given uri prefix to the proxied resource.  " +
                "The uri prefix of the request is replaced with the uri component of the proxied resource." +
                "\n\ne.g. given a mapping expresssion /google=http://google.net/ then a request to /google/blah?q=foo will result in a request to " +
                "http://google.net/blah?q=foo" +
                "\n\n" +
                "File Format:" +
                "\n\tDelimited lines, the first line is the port to bind on with the remaining lines consist of proxy mapping " +
                "expressions.  The # character may be used as a comment character and will result in everything after it being ignored." +
                "\n\tThe system will watch this file for changes and reload the configuration as necessary.  " +
                "\n\tNote - changes to the listening port are not supported under this mechanism." +
                "\n\n" +
                "Logging:" +
                "\n\tThe system logs requests in the following format -" +
                "\n\t\t {timestamp} {source_ip} {method} {uri} -> {proxied_url} > {response_code} in {request_time} ms" +
                "\n\n\tLogging may be disabled by passing -quiet on the command line or in the file.";
        return usage;
    }

    public void out(String msg) {
        System.out.println(msg);
    }

    public void err(String emsg) {
        System.err.println(emsg);
    }

    public class Logger {

        private long m_requestStart;
        private String m_remoteAddress;
        private String m_method;
        private String m_uri;
        private String m_proxiedURL;

        public Logger(HttpServletRequest request, String requestURI, String matchedURL) {
            m_requestStart = System.currentTimeMillis();
            m_remoteAddress = request.getRemoteAddr();
            m_method = request.getMethod();
            m_uri = requestURI;
            m_proxiedURL = matchedURL != null ? matchedURL : "NO_MATCH";
        }

        public void logResponse(int statuscode) {

            long elapsed = System.currentTimeMillis() - m_requestStart;

            String logMessage = MessageFormat.format("{0} {1} {2} {3} -> {4} > {5} in {6} ms.",
                                                     timestampString(),
                                                     m_remoteAddress,
                                                     m_method,
                                                     m_uri,
                                                     m_proxiedURL,
                                                     statuscode,
                                                     elapsed);

            if (m_logRequests) {
                out(logMessage);
            }
        }

        private String timestampString() {
            Date now = new Date(m_requestStart);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
            return sdf.format(now);
        }
    }

}
TOP

Related Classes of net.redhillsoftware.bonza.BonzaConfig$Logger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.