package org.ireland.jnetty.dispatch.servlet; /* * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; import javax.servlet.FilterChain; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.ireland.jnetty.config.ConfigException; import org.ireland.jnetty.dispatch.ServletInvocation; import org.ireland.jnetty.dispatch.filterchain.ErrorFilterChain; import org.ireland.jnetty.dispatch.servlet.ServletConfigImpl; import org.ireland.jnetty.dispatch.servlet.ServletManager; import org.ireland.jnetty.dispatch.servlet.ServletMapping; import org.ireland.jnetty.util.http.UrlMap; import org.ireland.jnetty.webapp.WebApp; import org.springframework.util.Assert; /** * Manages dispatching: servlets and filters. :TODO: rename "ServletMapper" TO "ServletMatcher" * * Servlet匹配器 * */ public class ServletMapperForTest { private static final Log log = LogFactory.getLog(ServletMapperForTest.class.getName()); private final WebApp _webApp; private final ServletContext _servletContext; private final ServletManager _servletManager; // <urlPattern, ServletMapping> @Deprecated private UrlMap<ServletMapping> _servletMappings = new UrlMap<ServletMapping>(); // 记录 urlPattern 到 <servlet-mapping>的映射关系(用于URL精确匹配) // 1:ServletMappings for Exact Match <urlPattern,ServletMapping> private Map<String, ServletMapping> _exactServletMappings = new HashMap<String, ServletMapping>(); // 2:ServletMappings for Longest Prefix Match <prefixPattern,ServletMapping>(按pattern长度倒序排列) private SortedMap<String, ServletMapping> _prefixServletMappings = new TreeMap<String, ServletMapping>(new PatternLengthComparator()); // 3:ServletMappings for Extension Match <Extension,ServletMapping> (按web.xml里出现的顺序排列) private LinkedHashMap<String, ServletMapping> _extensionServletMappings = new LinkedHashMap<String, ServletMapping>(); // 4:Default servlet (urlPattern为"/",当无法找到匹配的Servlet或jsp时,则默认匹配的Servlet) private ServletConfigImpl _defaultServlet; // 记录 ServletName 到 urlPattern 之间的映射关系 // Servlet 3.0 maps serletName to urlPattern <serletName,Set<urlPattern>> private Map<String, Set<String>> _urlPatterns = new HashMap<String, Set<String>>(); public ServletMapperForTest(WebApp webApp, ServletContext servletContext, ServletManager servletManager) { /* Assert.notNull(webApp); Assert.notNull(servletContext); Assert.notNull(servletManager);*/ _webApp = webApp; _servletContext = servletContext; _servletManager = servletManager; } // Getter and Setter--------------------------------------------------- /** * Gets the servlet context. */ public WebApp getWebApp() { return _webApp; } /** * Returns the servlet manager. */ public ServletManager getServletManager() { return _servletManager; } // Getter and Setter--------------------------------------------------- /** * Adds a servlet mapping Specification: Servlet-3_1-PFD chapter 12.1 * * 增加 urlPattern + " -> " + ServletMapping 的映射关系 * */ public void addUrlMapping(final String urlPattern, ServletMapping mapping) throws ServletException { try { String servletName = mapping.getServletConfig().getServletName(); /* if (_servletManager.getServlet(servletName) == null) throw new ConfigException( "'{0}' is an unknown servlet-name. servlet-mapping requires that the named servlet be defined in a <servlet> configuration before the <servlet-mapping>.", servletName));*/ if ("/".equals(urlPattern)) // Default servlet { _defaultServlet = mapping.getServletConfig(); } // 添加到精确匹配的分类 _exactServletMappings.put(urlPattern, mapping); // 添加到前缀匹配的分类 if (urlPattern.endsWith("/*")) { _prefixServletMappings.put(urlPattern, mapping); } // 添加到扩展名匹配的分类 if (urlPattern.startsWith("*.")) { _extensionServletMappings.put(urlPattern, mapping); } // Set<String> patterns = _urlPatterns.get(servletName); if (patterns == null) { patterns = new HashSet<String>(); _urlPatterns.put(servletName, patterns); } patterns.add(urlPattern); // log.debug("servlet-mapping " + urlPattern + " -> " + servletName); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw ConfigException.create(e); } } public Set<String> getUrlPatterns(String servletName) { return _urlPatterns.get(servletName); } /** * Sets the default servlet. 4. * 如果前三个规则都没有产生一个servlet匹配,容器将试图为请求资源提供相关的内容。如果应用中定义了一个“default”servlet,它将被使用。许多容器提供了一种隐式的default servlet用于提供内容。 */ public void setDefaultServlet(ServletConfigImpl config) throws ServletException { _defaultServlet = config; } /** * 查找 ServletInvocation 所匹配 的 Servlet,并返回生成的FilterChain * * 用于映射到Servlet的路径是请求对象的请求URL减去上下文和路径参数部分。下面的URL路径映射规则按顺序使用。使用第一个匹配成功的且不会进一步尝试匹配: * * 1. 容器将尝试找到一个请求路径到servlet路径的精确匹配。成功匹配则选择该servlet。 * * 2. 容器将递归地尝试匹配最长路径前缀。这是通过一次一个目录的遍历路径树完成的,使用‘/’字符作为路径分隔符。最长匹配确定选择的servlet。 * * 3. 如果URL最后一部分包含一个扩展名(如 .jsp),servlet容器将视图匹配为扩展名处理请求的Servlet。扩展名定义在最后一部分的最后一个‘.’字符之后。 * * 4. 如果前三个规则都没有产生一个servlet匹配,容器将试图为请求资源提供相关的内容。如果应用中定义了一个“default”servlet,它将被使用。许多容器提供了一种隐式的default servlet用于提供内容。 * * @param invocation * @return * @throws ServletException */ public FilterChain createServletChain(ServletInvocation invocation) throws ServletException { String contextURI = invocation.getContextURI(); String servletName = null; ServletConfigImpl config = null; ArrayList<String> vars = new ArrayList<String>(); // 1-2-3:查找与contextURI最佳匹配的Servlet config = mapServlet(contextURI); // 4:默认的Servlet(urlPattern为"/",当无法找到匹配的Servlet或jsp时,则默认匹配的Servlet) if (config == null && servletName == null) { config = _defaultServlet; vars.add(contextURI); } // 5:无法找到合适的Servlet,返回404 if (config == null && servletName == null) { log.debug(contextURI+" has no default servlet defined"); return new ErrorFilterChain(404); } String servletPath = contextURI; // TODO: how to decide servletPath ? invocation.setServletPath(servletPath); if (servletPath.length() < contextURI.length()) invocation.setPathInfo(contextURI.substring(servletPath.length())); else invocation.setPathInfo(null); invocation.setServletName(servletName); if (log.isDebugEnabled()) { log.debug(_webApp + " map (uri:" + contextURI + " -> " + servletName + ")"); } // 创建FilterChain FilterChain chain; if (config != null) chain = _servletManager.createServletChain(config, invocation); else chain = _servletManager.createServletChain(servletName, invocation); // JSP /* * if (chain instanceof PageFilterChain) { PageFilterChain pageChain = (PageFilterChain) chain; * * chain = PrecompilePageFilterChain.create(invocation, pageChain); } */ return chain; } /** * 查找与contextURI最佳匹配的Servlet Specification: Servlet-3_1-PFD chapter 12.1 * * @param contextURI * @return */ public ServletConfigImpl mapServlet(String contextURI) { // Rule 1 -- Exact Match :查找是否存在URL精确匹配的<servler-mapping> if (_exactServletMappings.size() > 0) { ServletMapping servletMapping = _exactServletMappings.get(contextURI); if (servletMapping != null) return servletMapping.getServletConfig(); } // Rule 2 -- Longest Prefix Match : 查找最长前缀匹配的Servlet,如:/page/today/123 会 匹配 /page/today/*,而非/page/* if (_prefixServletMappings.size() > 0) { String prefixPattern; ServletMapping servletMapping = null; for (Map.Entry<String, ServletMapping> entry : _prefixServletMappings.entrySet()) { prefixPattern = entry.getKey(); if (prefixPatternMatch(contextURI, prefixPattern)) { servletMapping = entry.getValue(); if (servletMapping != null) return servletMapping.getServletConfig(); } } } // Rule 3 -- Extension Match : 像.do,.jsp等基于扩展名的匹配 if (_extensionServletMappings.size() > 0) { String extensionPattern; ServletMapping servletMapping = null; for (Map.Entry<String, ServletMapping> entry : _extensionServletMappings.entrySet()) { extensionPattern = entry.getKey(); if (extensionPatternMatch(contextURI, extensionPattern)) { servletMapping = entry.getValue(); if (servletMapping != null) return servletMapping.getServletConfig(); } } } return null; } @Deprecated public String getServletPattern(String uri) { Object value = null; if (_servletMappings != null) value = _servletMappings.map(uri); if (value != null) return uri; else return null; } /** * Returns the servlet matching patterns. */ public ArrayList<String> getURLPatterns() { ArrayList<String> patterns = _servletMappings.getURLPatterns(); return patterns; } public ServletMapping getServletMapping(String pattern) { return _exactServletMappings.get(pattern); } private void addServlet(String servletName) throws ServletException { if (_servletManager.getServlet(servletName) != null) return; ServletConfigImpl config = _webApp.createNewServletConfig(); try { config.setServletClass(servletName); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ServletException(e); } config.init(); _servletManager.addServlet(config); } public void destroy() { _servletManager.destroy(); } /** * * 按String的长度倒序排列 * * @author KEN * */ private static class PatternLengthComparator implements Comparator<String> { @Override public int compare(String pattern1, String pattern2) { if (pattern1.length() > pattern2.length()) return -1; else if (pattern1.length() == pattern2.length()) return 0; else return 1; } } // util Method--------------------------------------------------------------------------- /** * 前缀匹配 * * @param requestPath * @param prefixPattern * @return */ private static boolean prefixPatternMatch(String requestPath, String prefixPattern) { if (prefixPattern == null) return (false); // Case 2 - Path Match ("/.../*") if (prefixPattern.equals("/*")) return (true); if (prefixPattern.endsWith("/*")) { if (prefixPattern.regionMatches(0, requestPath, 0, prefixPattern.length() - 2)) { if (requestPath.length() == (prefixPattern.length() - 2)) { return (true); } else if ('/' == requestPath.charAt(prefixPattern.length() - 2)) { return (true); } } return (false); } return (false); } /** * * @param requestPath * @param extensionPattern * @return */ private static boolean extensionPatternMatch(String requestPath, String extensionPattern) { if (extensionPattern == null) return (false); // Case 3 - Extension Match if (extensionPattern.startsWith("*.")) { int slash = requestPath.lastIndexOf('/'); int period = requestPath.lastIndexOf('.'); if ((slash >= 0) && (period > slash) && (period != requestPath.length() - 1) && ((requestPath.length() - period) == (extensionPattern.length() - 1))) { return (extensionPattern.regionMatches(2, requestPath, period + 1, extensionPattern.length() - 2)); } } return (false); } }