URL、URI、ServletPath、ContextPath、RealPath


从request请求可以获取URL、URI、ServletPath、ContextPath、RealPath,它们之间有何区别?Let’s go!

可能的误区

以SpringBoot配置为例,spring.application.name表示这个Spring应用叫什么,并不代表这个web应用叫什么,决定web应用叫什么的是server.servlet.context-path,也就是我们常说的应用名或者应用前缀或web容器上下文路径。下面这个配置访问应用的所有地址都是以 http://ip:8080/demo 开头,而不是 http://ip:8080/myapp ;而Spring应用名称spring.application.name应用之一便是在SpringCloud微服务框架下服务的标识。

1
2
3
spring.application.name=myapp # Spring应用名称
server.servlet.context-path=/demo # 应用的上下文路径
server.port=8080

实操验证

以SpringBoot环境为例,本地部署,有如下配置:

application. properties

1
2
3
spring.application.name=myapp
server.servlet.context-path=/demo
server.port=8080

一个控制层

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@Controller
public class TestController {
@RequestMapping("/test")
public String m(HttpServletRequest request) {
log.info("URL: {}",request.getRequestURL());
log.info("URI: {}",request.getRequestURI());
log.info("ServletPath: {}",request.getServletPath());
log.info("ContextPath: {}",request.getContextPath());
log.info("RealPath: {}",request.getRealPath("/"));
// ...
}
}

结果如下:

描述
application. properties server.servlet.context-path=/demo
访问链接 http://127.0.0.1:8080/demo/test?name=zs
URL http://127.0.0.1:8080/demo/test
URI /demo/test
ServletPath /test
ContextPath /demo
RealPath C:\Users\WS\AppData\Local\Temp\tomcat-docbase.6884443025746620464.8082\

结合表格结果,加入官方术语简单直白的总结一波:

  • URL:即 Uniform Resource Locator(统一资源定位符),资源在网络中的唯一的地址,格式:协议+主机+端口+路径

  • URI:即 Uniform Resource Identifier(统一资源标识符),用于标识某一互联网资源名称的字符串,格式:路径

  • ServletPath:Servlet路径,简单理解为web应用提供资源的路径,其表现之一在SpringMVC中就是控制层的一个完整资源路径

  • ContextPath:web应用的上下文路径,简单理解为web容器最开始路径,所有的web资源(路径)都要挂在它的下面

  • RealPath:真实路径,简单理解为web资源在服务器的真实物理路径,.HttpServletRequest#getRealPath(String)的方法返回的是web容器所在的真实物理地址(也就是根目录)加上该方法的参数的值的结果,说白了算是字符串拼接,没有验证资源是不是存在。使用场景笔者并不是太了解,可能在某个静态资源定位上比较有用吧,在 Servlet 2.1之后该方法已经过期,替代者如下

    HttpServletRequest#getRealPath

ContextPath斜杠“/”问题

现在我们知道application. properties 中server.servlet.context-path配置的 “/demo” 就是 ContextPath的值了,发散一下,如果我们不配置,那ContextPath的值是多少呢?答案是空字符串(长度为0),如果配置了“/”或“/demo/”或“//”呢?结果分别是空字符串和“/demo”和启动异常,话不多说直接看源码,进对应配置类 org.springframework.boot.autoconfigure.web.ServerProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ServerProperties {
// ...
public void setContextPath(String contextPath) {
this.contextPath = cleanContextPath(contextPath);
}

private String cleanContextPath(String contextPath) {
String candidate = StringUtils.trimWhitespace(contextPath);
if (StringUtils.hasText(candidate) && candidate.endsWith("/")) {
return candidate.substring(0, candidate.length() - 1);
}
return candidate;
}
//...
}

虽然该配置类是SpringBoot下的,我们知道SpringBoot内置tomcat,故而对外提供web容器(tomcat)的配置,最后转换成web容器对应的配置,所以这个配置类的参数规范肯定是要符合tomcat的要求的。

通过源码不难看出,SpringBoot对server.servlet.context-path进行了处理:

  1. 两边去空格
  2. 包含有效字符串的情况下,去除末尾的“/”字符

所以配置“/”或“/demo/”的结果也就解释得通了,而配置两个斜杠“//”的情况下,上述代码得到的结果为“/”,这个配置扔到tomcat肯定是报错了,这也就反映出为什么我们在写@RequestMapping的时候要以“/”开头了,除了协议与主机名之间的分隔之外,一个url不应该以双斜杠“//”出现,大多数情况下多斜杠的url是被允许的,它们只会被当做一个斜杠处理,这是web容器对我们的“宽容”,但开发者不应该乱写,更不能只知其表不知其里。