目前微服务大行其道,各种中台应运而生,中台处理部分业务时需要透传请求,这就涉及到对url的处理。一般透传的接口都会保持path路径不变,因此需要从前端的url中提取公共部分。这就需要理解Context Path
、servlet path、path info
,目前servlet提供了对应的方法去方便获取它们。不过这3个到底对应url中的那一段,还是需要好好研究一下,不然很可能得到错误的url。
可以肯定的是 request uri
= Context Path
+ servlet path
+ path info
There is one context per “web application” per Java Virtual Machine. (A “web application” is a collection of servlets and content installed under a specific subset of the server’s URL namespace such as /catalog and possibly installed via a .war file.)
上述是tomcat中ServletContext
类注释的一部分,文中表示Context代表一个app,对应以前一个war包或者一个spring boot开发的一个微服务。
request uri
= Context Path
+ servlet path
+ path info
。url去掉Context Path后,剩余的部分用于匹配servlet。servlet的匹配规则可以通过如下方式进行匹配。
<servlet-mapping>
<servlet-name>ceshiServletservlet-name>
<url-pattern>/ceshi/*url-pattern>
servlet-mapping>
@Bean
public ServletRegistrationBean testdispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet,
"/ceshi/*");
registration.setName("ceshi");
return registration;
}
匹配优先级如下所示:
"/*“
结尾,比如”/ceshi/*"
;其他地方的“*”
不会当做特殊字符处理*.xx
,比如*.jsp
“/”
上述匹配规则,可以直接通过tomcat源码得到印证:org.apache.catalina.mapper.Mapper#addWrapper(org.apache.catalina.mapper.Mapper.ContextVersion, java.lang.String, org.apache.catalina.Wrapper, boolean, boolean)
protected void addWrapper(ContextVersion context, String path,
Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
synchronized (context) {
// 以 /* 结尾
if (path.endsWith("/*")) {
// Wildcard wrapper
String name = path.substring(0, path.length() - 2);
... ...
} else if (path.startsWith("*.")) { // 以 *. 开头
// Extension wrapper
String name = path.substring(2);
... ...
} else if (path.equals("/")) { // 是否为 /,是则表示为默认匹配
// Default wrapper
MappedWrapper newWrapper = new MappedWrapper("", wrapper,
jspWildCard, resourceOnly);
context.defaultWrapper = newWrapper;
} else { // 精确匹配
// Exact wrapper
final String name;
if (path.length() == 0) {
// Special case for the Context Root mapping which is
// treated as an exact match
name = "/";
} else {
name = path;
}
... ...
}
}
}
实际上对于servlet容器来说,他并不关心path info,只要通过url(剔除context-path余下的部分)匹配上servlet,并将请求委托给其处理后,剩余的流程就和servlet容器无关了。因此它本身是用不到path info这部分的。path info只是为了servlet容器方便servlet框架(spring mvc、struts等)做进一步请求转发设计的(不可能为每个业务请求写一个servlet)。
将url(剔除context-path余下的部分)拆分成servlet path、path info也是分上述场景。
匹配的优先级如下:
servlet容器通过url匹配servlet的流程见org.apache.catalina.mapper.Mapper#internalMapWrapper
,
servlet path = 配置的url-pattern
path info = null
因为是精确匹配,所以url(剔除context-path余下的部分)必须等于servlet的url-pattern;因此path info=null。
private final void internalMapExactWrapper
(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {
MappedWrapper wrapper = exactFind(wrappers, path);
if (wrapper != null) {
mappingData.requestPath.setString(wrapper.name);
mappingData.wrapper = wrapper.object;
// 特殊场景
if (path.equals("/")) {
// Special handling for Context Root mapped servlet
mappingData.pathInfo.setString("/");
mappingData.wrapperPath.setString("");
// This seems wrong but it is what the spec says...
mappingData.contextPath.setString("");
mappingData.matchType = MappingMatch.CONTEXT_ROOT;
} else {
// HttpServletRequest#getServletPath方法最终取的是mappingData.wrapperPath
mappingData.wrapperPath.setString(wrapper.name);
mappingData.matchType = MappingMatch.EXACT;
}
}
}
servlet path = 配置的url-pattern去掉结尾的通配符;比如url-pattern为“/ceshi/*”
,那么servlet path = “/ceshi”;
path info = 原始url剔除context-path和servlet path后剩下的部分
源码见org.apache.catalina.mapper.Mapper#internalMapWildcardWrapper
private final void internalMapWildcardWrapper
(MappedWrapper[] wrappers, int nesting, CharChunk path,
MappingData mappingData) {
// path代表去掉context-path后的url
int pathEnd = path.getEnd();
int lastSlash = -1;
int length = -1;
int pos = find(wrappers, path);
if (pos != -1) {
... ...
// wrappers[pos].name就是配置的url-pattern去掉结尾的/\*
length = wrappers[pos].name.length();
if (found) {
mappingData.wrapperPath.setString(wrappers[pos].name);
# 如果path的长度大于wrappers[pos].name的长度,那么url中剩的一节就作为path info。
if (path.getLength() > length) {
# HttpServletRequest#getPathInfo最终来自 mappingData.pathInfo
mappingData.pathInfo.setChars
(path.getBuffer(),
path.getOffset() + length,
path.getLength() - length);
}
mappingData.requestPath.setChars
(path.getBuffer(), path.getOffset(), path.getLength());
mappingData.wrapper = wrappers[pos].object;
mappingData.jspWildCard = wrappers[pos].jspWildCard;
mappingData.matchType = MappingMatch.PATH;
}
}
}
servlet path = 剔除context-path后剩余的url
path info = null
详细见org.apache.catalina.mapper.Mapper#internalMapWrapper
// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
if (contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
// 直接使用剔除context-path后的url作为servlet path,path info为null
mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
mappingData.matchType = MappingMatch.DEFAULT;
}