请求路径映射
请求路径映射
参考博客:
https://www.jianshu.com/p/817f5f83d916
基本定义
RequestURI:请求路径
返回 HTTP 协议报文的第一行中的路径部分中,从协议名称(不包含)开始到查询参数(不包含)的部分。
HTTP 协议报文的第一行 | RequestURI | 备注 |
---|---|---|
POST /some/path.html HTTP/1.1 | /some/path.html | |
GET http://foo.bar/a.html HTTP/1.0 | /a.html | foo.bar 是域名 |
HEAD /xyz?a=b HTTP/1.1 | /xyz | |
如果排除 URL 参数中的 ; 的影响 |
||
RequestURI = ContextPath + ServletPath + PathInfo |
ContextPath:上下文路径,
返回 RequestURI 中表示请求的上下文的部分,即对应的 ServletContext 关联的路径前缀。若该上下文是处于服务器基地址的默认上下文,则这个路径是空串,这种情况请参考 Tomcat 的 Context 配置文档 Apache Tomcat 8 Configuration Reference (8.5.82) - The Context Container 中的 Naming 一节。否则,如果这个上下文不处在服务器的根,则这个路径会以 /
开头但不会以 /
结尾。
servlet 容器(比如 Tomcat)可能通过多个上下文路径匹配一个实际的应用上下文。在这种情况下,该方法将返回当前请求使用的实际上下文路径,它可能与 ServletContext#getContextPath
方法返回的路径不同。应该将 ServletContext#getContextPath
返回的上下文路径视为应用程序的首选上下文路径。
ServletPath:Servlet 路径
返回这个请求的 URL 中调用(匹配)servlet 的那一部分。该路径一般以“/”字符开头,包含 servlet 名称或到 servlet 的路径,但不包含任何额外的路径信息(比如 ContextPath 上下文路径)或查询字符串(query parameter,以 ?
开头以 &
分割的参数)。
当请求与 /*
或空串两种模式匹配时是 ServletPath 都是空串。
PathInfo
PathInfo 定义为请求路径中既不是 Context Path 也不是 Servlet Path 的部分,它要么是 null,这是没有额外路径的情况下,要么是以/开头的字符串。
Web 应用服务器中的路径映射
在收到客户端的请求后,由 Web 容器(比如 Tomcat)来决定向哪个 Web 应用(Web Application)转发该请求。所选择的 Web 应用一定有与请求 URL 从起始处开始相匹配的最长的 ContextPath
。URL 中匹配的部分就是映射到 servlet 时的 ContextPath。
映射到 servlet 时使用的路径是请求对象中的请求 URL 除去 ContextPath 和查询参数路径参数(query parameter,以 ?
开头以 &
分割的参数)。即 ServletPath
下面的 URL 路径映射规则需要按顺序使用,第一个匹配后便不再尝试其他匹配:
- 容器尝试查找请求路径与 servlet 的精确匹配;
- 容器会递归地尝试匹配最长路径前缀。以
/
为分隔符,在路径树中一次步进一个目录。最长的匹配会最终胜出;/*
或者/**
路径的匹配在这一层生效。 - 如果 URL 路径中的最后一段包含扩展名(如.jsp),那么容器会尝试匹配能处理扩展名的 servlet。扩展名定义为最后一段中最后的点号
.
之后的部分; - 如果前三个规则没有成功匹配,容器会尝试去提供请求路径对应的资源(Tomcat 提供的默认的 Servlet 的功能),许多容器都提供了隐式的默认 servlet。如果应用自己定义了默认 servlet,则它会被使用。
/
的匹配在这一层生效。映射路径为
/
的就是默认的 Servlet,而不是指只某个特定类名的 Servlet,为什么这么说呢,因为根据第二条匹配规则,/
可以跟任何路径匹配,但是它最短,所以只能最后匹配,所以它是默认的兜底的 Servlet。
Web.xml - Servlet 的映射匹配路径声明
Web 应用部署描述符(Deployment Descriptor,即 Web.xml)使用如下规则定义映射:
- 以
/
开头并以/*
结尾的字符串用于路径映射; - 以
*.
前缀开头的字符串用于扩展名映射; - 空串 "" 是特殊的模式,精确地映射到应用上下文的根,举例来说,对来自
http://host:port/<context-root>/
的请求,PathInfo 是/
,ServletPath 和 ContextPath 都是空串 ""; - 只包含
/
的字符串表明此 Servlet 是此应用的默认 servlet,这种情况下 ServletPath 是请求 URI 减去 ContextPath,PathInfo 是 null; - 其他字符串只会精确匹配。
实践
在 Web.xml 中
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
访问 /SpringMVC_AnnotationConfig/aaa
,结果为:
分析:
根据 Web 应用服务器中的路径映射,/SpringMVC_AnnotationConfig
的 Web 应用能与 /SpringMVC_AnnotationConfig/aaa
最长匹配,匹配的部分 /SpringMVC_AnnotationConfig
即是 ContextPath;映射到 servlet 时使用的路径是 /SpringMVC_AnnotationConfig/aaa
减去 /SpringMVC_AnnotationConfig
,所以 /aaa
会用于 Servlet 映射路径匹配,ServletPath
一节,与 /*
匹配时,ServletPath 为 "";
根据等式规则可得 PathInfo 为 /aaa
。
此时将 <url-pattern
中的 /*
改成 /
,
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
访问 /SpringMVC_AnnotationConfig/aaa
,结果为:
跟之前不一样,分析:
根据 Web 应用服务器中的路径映射,/SpringMVC_AnnotationConfig
的 Web 应用能与 /SpringMVC_AnnotationConfig/aaa
最长匹配,匹配的部分 /SpringMVC_AnnotationConfig
即是 ContextPath;映射到 servlet 时使用的路径是 /SpringMVC_AnnotationConfig/aaa
减去 /SpringMVC_AnnotationConfig
,所以 /aaa
会用于 Servlet 映射路径匹配,但是此时用户只声明了一个映射路径为 /
的 Servlet(默认的 Servlet),根据 Web 应用服务器中的路径映射的第四条规则,匹配到此 Servlet,因此 ServletPath 为 /aaa
,PathInfo 为 null。
那什么情况下 ServletPath 和 PathInfo 都有值呢?设置 Servlet 的映射路径为 /Spring/*
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/Spring/*</url-pattern>
</servlet-mapping>
访问 /SpringMVC_AnnotationConfig/Spring/aaa
,结果为:
可以看到 ServletPath 为 /Spring
,PathInfo 为 /aaa
。分析如下:
根据 Web 应用服务器中的路径映射,/SpringMVC_AnnotationConfig
的 Web 应用能与 /SpringMVC_AnnotationConfig/Spring/aaa
最长匹配,匹配的部分 /SpringMVC_AnnotationConfig
即是 ContextPath;映射到 servlet 时使用的路径是 /SpringMVC_AnnotationConfig/Spring/aaa
减去 /SpringMVC_AnnotationConfig
,所以 /Spring/aaa
会用于 Servlet 映射路径匹配,但是此时用户只声明了一个映射路径为 /Spring/*
的 Servlet,根据 Web 应用服务器中的路径映射的第 2 条规则,匹配到此 Servlet,同时因为第二节路径是通过 /*
匹配的,而通过 /*
匹配的最终都为空字符串(ServletPath 小节提到过),因此,ServletPath 为 /Spring
,剩下的就是 PathInfo,所以 PathInfo 为 /aaa
。
从这个例子中,我们也能看出 PathInfo 这个概念的意义,这个概念就是为了存放
/*
到底匹配了什么。
实际应用
在 web 过滤器和 spring 拦截器中
/
表示会过滤所有的路径请求 比如/s/hello/a1
,但是不会拦截 JSP 页面 比如/s/hello/hello.jsp
因为根据由 Web 应用服务器中的路径映射规则,
/
的匹配在文件拓展名匹配(比如.jsp
)的后面/*
表示会过滤所有的路径和 JSP 页面请求,即/s/hello/a1
和/s/hello/hello.jsp
都会走拦截器或过滤器 但是不能过滤子文件的请求因为根据由 Web 应用服务器中的路径映射规则,
/*
的匹配在文件拓展名匹配(比如.jsp
)的前面/**
表示过滤所有路径和 JSP 页面请求,包括子文件的请求
举个最简单的例子,当我们的视图时转发视图的时候,其实还是要在进行一次 Servlet 的调用,即还需要进行 Servlet 的路径映射,此时,如果目标 Sevlet 的访问路径包含了 jsp,那么DispatcherServlet
配置成/
和/*
的区别就会体现出来,配置成/
的话,会被 Tomcat 默认的 JSP 视图解析器org.apache.jasper.servlet.JspServlet
解析,但是配置成/*
的话就会被DispatcherServlet
拦截,来让 Spring 来解析,实际上 Tomcat 也只默认给了一个 JSP 视图解析器,/
和/*
的区别也只在解析 JSP 的时候体现出来,大部分的时候,我们都不用 JSP 了,转发的时候,目标视图的路径是以.vm
或者.html
结尾时,DispatcherServlet
不管是配置成/
和/*
,最终都还是DispatcherServlet
来解析。但是为了兼容性,最好还是将DispatcherServlet
的映射路径设置为/
,而不是/*
。
TODO
这个内容,在之前也学到过 《Servlet3.1-Specification.docx》
汇总过来。
《Servlet3.1-Specification.docx》
小节中的请求路径元素小节中的信息弄过来。