/* * This file is part of the Wayback archival access software * (http://archive-access.sourceforge.net/projects/wayback/). * * Licensed to the Internet Archive (IA) by one or more individual * contributors. * * The IA 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.archive.wayback.util.webapp; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; /** * * Class which allows semi-efficient translation of requests on a specific local * port to a RequestHandler object. * * Mapping within a port is based on the HTTP 1.1 Host field and the first * segment of the requested PATH, that is, whatever is after the context where * the wayback webapp was deployed, and before the first '/'. * * @author brad */ public class PortMapper { private static final Logger LOGGER = Logger.getLogger( PortMapper.class.getName()); private int port = -1; private HashMap<String, RequestHandler> pathMap = null; /** * @param port which this PortMapper is responsible for handling */ public PortMapper(int port) { this.port = port; pathMap = new HashMap<String, RequestHandler>(); } private String hostPathToKey(String host, String firstPath) { StringBuilder sb = null; if((host == null) && (firstPath == null)) { return null; } sb = new StringBuilder(); if(host != null) { sb.append(host); } sb.append("/"); if(firstPath != null) { sb.append(firstPath); } return sb.toString(); } /** * Register the RequestHandler to accept requests for the given host and * port. * @param host the HTTP 1.1 "Host" header which the RequestHandler should * match. If null, the RequestHandler matches any "Host" header value. * @param firstPath the first path of the GET request path which the * RequestHandler should match. This is the first path AFTER the name the * Wayback webapp is deployed under. If null, the RequestHandler matches * all paths. * @param requestHandler The RequestHandler to register. */ public void addRequestHandler(String host, String firstPath, RequestHandler requestHandler) { String key = hostPathToKey(host, firstPath); if (pathMap.containsKey(key)) { LOGGER.warning("Duplicate port:path map for " + port + ":" + key); } else { pathMap.put(key, requestHandler); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Registered requestHandler(port/host/path) (" + port + "/" + host + "/" + firstPath + "): " + key); } } } private String requestToFirstPath(HttpServletRequest request) { String firstPath = null; String requestPath = request.getRequestURI(); String contextPath = request.getContextPath(); if((contextPath.length() > 0) && requestPath.startsWith(contextPath)) { requestPath = requestPath.substring(contextPath.length()); } while(requestPath.startsWith("/")) { requestPath = requestPath.substring(1); } int slashIdx = requestPath.indexOf("/",1); if(slashIdx == -1) { firstPath = requestPath; } else { firstPath = requestPath.substring(0,slashIdx); } return firstPath; } private String requestToHost(HttpServletRequest request) { return request.getServerName(); } /** * Attempts to locate the most strictly matching RequestHandler mapped to * this port. Strictly matching means the lowest number in the following * list: * * 1) request handler matching both HOST and PATH * 2) request handler matching host, registered with an empty PATH * 3) request handler matching path, registered with an empty HOST * 4) request handler registered with empty HOST and PATH * * @param request the HttpServletRequest to be mapped to a RequestHandler * @return the RequestHandlerContext, containing the RequestHandler and the * prefix of the original request path that indicated the RequestHandler, * or null, if no RequestHandler matches. */ public RequestHandlerContext getRequestHandlerContext( HttpServletRequest request) { String host = requestToHost(request); String contextPath = request.getContextPath(); StringBuilder pathPrefix = new StringBuilder(contextPath); // if(contextPath.length() == 0) { pathPrefix.append("/"); // } String firstPath = requestToFirstPath(request); String key = hostPathToKey(host,firstPath); RequestHandler handler = pathMap.get(key); if(handler != null) { LOGGER.fine("Mapped to RequestHandler with " + key); return new RequestHandlerContext(handler, pathPrefix.append(firstPath).toString()); } else { LOGGER.finer("No mapping for " + key); } key = hostPathToKey(host,null); handler = pathMap.get(key); if(handler != null) { LOGGER.fine("Mapped to RequestHandler with " + key); return new RequestHandlerContext(handler,contextPath); } else { LOGGER.finer("No mapping for " + key); } key = hostPathToKey(null,firstPath); handler = pathMap.get(key); if(handler != null) { LOGGER.fine("Mapped to RequestHandler with " + key); return new RequestHandlerContext(handler, pathPrefix.append(firstPath).toString()); } else { LOGGER.finer("No mapping for " + key); } handler = pathMap.get(null); if(handler != null) { LOGGER.fine("Mapped to RequestHandler with null"); return new RequestHandlerContext(handler,contextPath); } // Nothing matching this port:host:path. Try to help get user back on // track. Note this won't help with hostname mismatches at the moment: ArrayList<String> paths = new ArrayList<String>(); for(String tmp : pathMap.keySet()) { // slice off last chunk: int idx = tmp.lastIndexOf('/'); if(idx != -1) { String path = tmp.substring(idx+1); paths.add(path); } } if(paths.size() > 0) { request.setAttribute("AccessPointNames", paths); } return null; } }