国庆节10月1日
--


HTTP 协议的第三篇——Tomcat 把请求报文解析并封装成 HttpServletRequest 之后怎么把它用起来,逐条讲清 getParameter、getRequestURI、getRequestURL、getHeader、getMethod、getQueryString 六个方法的写法与差别(重点辨析只差一个字母的 URI 与 URL),并附本机实测的七行真实输出

这是 HTTP 协议这一块的第 2 篇。32 篇里看的是报文长什么样(请求行、请求头、请求体怎么写),这一篇(PPT 第 28~31 页)接着解决后面那半句——在 SpringBoot 的代码里,怎么把这些数据拿出来用。结论先说:不需要自己解析,Web 服务器已经把它整理成了一个对象,我们要做的只是把这个对象”要”过来。
PPT 第 28 页是本节的目录页:HTTP 协议这块下面分了两组——「请求数据格式 → 请求数据获取」和「响应数据格式 → 响应数据设置」。上一篇讲的是第一组的”格式”,这一篇讲第一组的”获取”。
PPT 第 29 页把”获取”的原理说得非常直白:
Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。
拆成三步看:
HttpServletRequest(请求对象);
GET /brand/findAll?name=OPPO&status=1 HTTP/1.1),第二行起是请求头(Accept、Accept-Encoding、Accept-Language、Host: localhost:8080、User-Agent: Mozilla/5.0 … Chrome/…)。这一整段就是 Tomcat 要解析的对象,本篇六个方法不过是各取其中一段:getRequestURI() 取 /brand/findAll、getQueryString() 取 name=OPPO&status=1、getHeader("Host") 取 localhost:8080、getHeader("User-Agent") 取最后那一长串。注意这段报文里没有 Content-Type、Content-Length、Cookie——因为它是 GET 请求,压根没有请求体“程序员不必直接对协议进行操作”这句话的实际含义是:你永远不需要自己写字符串去切分那段报文。像 getParameter("name") 这种调用,背后就是 Tomcat 在解析请求行/请求体时顺手存进对象里的结果。
PPT 第 30 页在 HelloController 里加了一个 /request 方法,把六个方法一次演示完(下面是 PPT 的代码,补上了类上的注解与 import):
1import jakarta.servlet.http.HttpServletRequest;2
3@RestController4public class HelloController {5
6 @RequestMapping("/request")7 public String request(HttpServletRequest request) {8 // 1.获取请求参数name,age9 String name = request.getParameter("name"); // Tom10 // 2.获取请求路径uri 和 url11 String uri = request.getRequestURI(); // /request12 String url = request.getRequestURL().toString(); // http://localhost:8080/request13 // 3.获取请求头 User-Agent14 String userAgent = request.getHeader("User-Agent"); // Mozilla/5.0 (Windows NT 10.0; Win64; x64)15 // 4.获取请求方式16 String method = request.getMethod(); // GET17 // 5.获取请求的查询字符串18 String queryString = request.getQueryString(); // name=Tomcat&age=1019 return "request success";20 }21}要留意的地方:
HttpServletRequest 参数,也可以在前面再放别的参数;jakarta.servlet.http 包(Tomcat 10 之后 Servlet 规范从 javax.* 换成了 jakarta.*,见31 篇里起步依赖带进来的 Tomcat 版本);getRequestURL() 返回的不是 String 而是 StringBuffer,所以 PPT 里多写了一步 .toString()。PPT 第 30 页的注释里给了每个方法的预期结果,把它和报文对齐就是这样:
| 方法 | 取的是报文里的哪一块 | PPT 注释里的示例值 |
|---|---|---|
getParameter("name") | 请求行里问号后面的请求参数(?name=Tomcat&age=10) | Tom |
getParameter("age") | 同上,按参数名取 | 10 |
getRequestURI() | 请求行的资源路径(不含协议、主机、端口) | /request |
getRequestURL() | 请求行的资源路径 + 协议 + 主机 + 端口(完整地址) | http://localhost:8080/request |
getHeader("User-Agent") | 请求头里名为 User-Agent 的那一行 | Mozilla/5.0 (Windows NT 10.0; Win64; x64) |
getMethod() | 请求行的请求方式 | GET |
getQueryString() | 问号后面那串原样的参数字符串 | name=Tomcat&age=10 |
一句话总结每个方法的口径:要值用 getParameter,要路径用 getRequestURI/getRequestURL,要请求头用 getHeader,要方式用 getMethod,要原始查询串用 getQueryString。
取请求参数其实还有更省事的写法:课程30 篇里的 public String hello(String name) 就是把参数名直接写成方法参数,SpringMVC 会自动按名字去请求参数里找同名值塞进来。差别在于:直接写参数只能拿到你要的那一两个值,而拿到 HttpServletRequest 对象等于拿到了整个请求(路径、请求头、请求方式都能顺手取)。
光看注释还不够,六个方法到底返回什么,本机在 Spring Boot 3.2.8(内嵌 Tomcat 10.1.26 / JDK 17)里跑了一遍。请求用的是带两个参数的地址:
本机实测——请求 curl -s "http://localhost:8080/request?name=Tomcat&age=10",服务端把这六个方法的结果各打印/返回一行:
1getParameter(name) = Tomcat2getParameter(age) = 103getRequestURI() = /request4getRequestURL() = http://localhost:8080/request5getHeader(User-Agent) = curl/8.15.06getMethod() = GET7getQueryString() = name=Tomcat&age=10七行输出(六类方法,getParameter 取了两个参数所以是两行)里有两处特别值得记:
getRequestURI() 只有路径,getRequestURL() 是完整地址——见下一节;getHeader("User-Agent") 拿到的是 curl/8.15.0,而 PPT 注释里写的是 Mozilla/5.0 (Windows NT 10.0; Win64; x64)。两者都没错:这个值就是”谁在访问”的自述,用命令行工具 curl 访问就是 curl/8.15.0,用浏览器访问才是一长串 Mozilla/5.0 … Chrome/…(正是上图报文里那一行的样子)。这两个方法名只差一个字母,实测结果却差了一大截,是这一节最容易错的地方:
getRequestURI() | getRequestURL() | |
|---|---|---|
| 全称 | URI(Uniform Resource Identifier,统一资源标识符) | URL(Uniform Resource Locator,统一资源定位符) |
| 实测返回 | /request | http://localhost:8080/request |
| 含哪些部分 | 只有资源路径 | 协议 http:// + 主机 localhost + 端口 8080 + 资源路径 |
| 返回类型 | String | StringBuffer(要 .toString()) |
| 什么时候用 | 只关心”请求的是哪个接口/页面” | 需要拼一个完整地址的时候(比如做重定向、回显) |
记法:URL 多了”定位”(Locator)这三部分——协议、主机、端口,所以它才是能在浏览器里直接打开的完整地址。二者都来自请求行的 GET /request HTTP/1.1 这一段,只是 URL 把 Host 请求头里的主机信息也一起拼上了。
| PPT 的问题 | 答案 |
|---|---|
| HTTP请求数据需要程序员自己解析吗? | 不需要,web 服务器负责对 HTTP 请求数据进行解析,并封装为了请求对象(Tomcat 解析的是请求行、请求头、请求体,程序员只面对解析好的对象) |
| 如何获取请求数据? | HttpServletRequest 对象里面封装了所有的请求信息——把它写在 Controller 方法的参数里,服务器就会传进来,六个方法各取一部分 |
| 问题 | 答案 |
|---|---|
| 请求数据是谁解析的? | Web 服务器(Tomcat)解析 HTTP 请求数据,并封装成请求对象,调用 Controller 方法时作为方法参数传进来,程序员不用直接操作协议 |
| 请求对象叫什么、怎么拿到? | HttpServletRequest(jakarta.servlet.http 包);写在 Controller 方法的参数里即可,不是自己 new 的 |
| 取请求参数用什么? | request.getParameter("参数名"),返回 String;请求参数就是问号后面 key=value&key2=value2 里的值 |
| 取请求路径用什么? | getRequestURI() 只有路径(实测 /request);getRequestURL() 是完整地址(实测 http://localhost:8080/request),返回 StringBuffer 要 .toString() |
| 取请求头用什么? | getHeader("头名"),如 getHeader("User-Agent")(实测 curl 访问是 curl/8.15.0,浏览器访问是 Mozilla/5.0 …) |
| 取请求方式和原始查询串用什么? | getMethod()(实测 GET);getQueryString()(实测 name=Tomcat&age=10,原样不动) |
HttpServletRequest(jakarta.servlet.http 包);写在 Controller 方法的参数里就行,它由服务器创建并注入,不是自己 new 出来的getParameter("名") 取请求参数、getRequestURI() 取资源路径、getRequestURL() 取完整地址、getHeader("名") 取请求头、getMethod() 取请求方式、getQueryString() 取问号后面的原始查询串getRequestURI() 与 getRequestURL() 的区别:URI 只有路径(本机实测 /request),URL 是含协议 + 主机 + 端口 + 路径的完整地址(实测 http://localhost:8080/request);另外 getRequestURL() 返回的是 StringBuffer,要 .toString()http://localhost:8080/request?name=Tomcat&age=10):getParameter(name) = Tomcat、getParameter(age) = 10、getRequestURI() = /request、getRequestURL() = http://localhost:8080/request、getHeader(User-Agent) = curl/8.15.0、getMethod() = GET、getQueryString() = name=Tomcat&age=10User-Agent 为什么两次不一样:它就是”谁在访问”的自述——curl 访问是 curl/8.15.0,浏览器访问是一长串 Mozilla/5.0 … Chrome/…,两者都对,取决于客户端getParameter 与 getQueryString 的分工:前者按参数名取值(拿到的只是值,如 Tomcat),后者把问号后面那一整段原样返回(如 name=Tomcat&age=10);参数存在请求体里时(POST),getParameter 照样能取到,但 getQueryString 就只反映地址栏上那段public String hello(String name)),SpringMVC 会自动按名绑定——省事,但只能拿到声明的那几个值,比不上直接拿到整个请求对象HttpServletRequest 对象里封装了所有的请求信息getParameter 2-1 写一个能收到问候对象的接口
需求:浏览器访问 /hello2?name=Tomcat 时,服务器在控制台打印出这次来访者的名字,并给浏览器返回一句简单的问候语(比如”Hello Tomcat”)。要求名字是从请求里取出来的、不能写死。
(练习文件 test_33_获取请求数据.java 里给了写作区。)
一级 · 思路:地址上 ?name=Tomcat 就是一个请求参数,取参数得先有”请求对象”;这个对象不用自己造,声明成方法的参数服务器就会送进来
二级 · 方法:HttpServletRequest(jakarta.servlet.http 包)+ 它的 getParameter("name");方法返回值由 @RestController + @RequestMapping 直接写进响应体
三级 · 骨架:@RequestMapping("____") public String hello2(____ request){ String name = request.____("____"); System.out.println(____); return ____; }
1package com.itheima;2
3import jakarta.servlet.http.HttpServletRequest;4import org.springframework.web.bind.annotation.RequestMapping;5import org.springframework.web.bind.annotation.RestController;6
7@RestController8public class HelloController {9
10 @RequestMapping("/hello2")11 public String hello2(HttpServletRequest request) {12 // 从请求参数里按名字取出 name(地址 ?name=Tomcat)13 String name = request.getParameter("name");14 // 控制台打印来访者名字15 System.out.println("HelloController ... hello2 : " + name);16 // 把问候写回浏览器17 return "Hello " + name;18 }19}访问 http://localhost:8080/hello2?name=Tomcat,浏览器显示 Hello Tomcat,IDEA 控制台打印 HelloController ... hello2 : Tomcat。
想省掉请求对象也行——把 String name 直接写成方法参数(课程30 篇的写法),SpringMVC 会自动把同名的请求参数绑定进来。
2-2 做一个”请求信息回显”接口
需求:访问 /requestInfo 时,把这次请求的三个信息拼成三行文字返回给浏览器——① 请求的是哪个资源路径;② 这个请求的完整地址;③ 用的是哪种请求方式。要求三行各占一行,且值都从请求里取。
(练习文件 test_33_请求信息回显.java 里给了写作区。)
一级 · 思路:三个信息都在请求行里,分别对应”只要路径""要完整地址""要方法名”三个取值方法;拼字符串时每写完一段补一个换行
二级 · 方法:getRequestURI() / getRequestURL().toString() / getMethod(),用 StringBuilder 拼接,换行用 "\n"
三级 · 骨架:StringBuilder sb = new StringBuilder(); sb.append("请求路径 = ").append(request.____()); sb.append("\n"); …… return sb.toString();
1package com.itheima;2
3import jakarta.servlet.http.HttpServletRequest;4import org.springframework.web.bind.annotation.RequestMapping;5import org.springframework.web.bind.annotation.RestController;6
7@RestController8public class RequestInfoController {9
10 @RequestMapping("/requestInfo")11 public String requestInfo(HttpServletRequest request) {12 StringBuilder sb = new StringBuilder();13 sb.append("请求路径 = ").append(request.getRequestURI()).append("\n");14 // getRequestURL() 返回的是 StringBuffer,要 toString()15 sb.append("完整地址 = ").append(request.getRequestURL().toString()).append("\n");16 sb.append("请求方式 = ").append(request.getMethod());17 return sb.toString();18 }19}访问 http://localhost:8080/requestInfo,浏览器(返回的是 text/plain,浏览器按原文显示、保留换行)里是三行,本机实测同样的取法结果是 请求路径 = /request、完整地址 = http://localhost:8080/request、请求方式 = GET。
2-3 把来访者的身份记到控制台
需求:随意写一个能访问到的接口,在里面把来访客户端的身份信息和它访问的主机名取出来打印到控制台,并观察同一个地址分别用浏览器和用 curl 访问时,这两行输出有什么不同。
做完在文件末尾的注释里回答:这两行分别取的是报文里的哪一部分?为什么同一个接口两次访问拿到的值不一样?
(练习文件 test_33_读取请求头.java 里给了写作区。)
一级 · 思路:客户端身份和主机名都不是地址上的参数,而是报文第二行开始的那些”键:值”——要用”按头名取请求头”的方法
二级 · 方法:request.getHeader("User-Agent")(客户端身份)、request.getHeader("Host")(主机名,浏览器访问时是 localhost:8080)
三级 · 骨架:String ua = request.getHeader("____"); String host = request.getHeader("____"); System.out.println("User-Agent = " + ua); System.out.println("Host = " + host);
1package com.itheima;2
3import jakarta.servlet.http.HttpServletRequest;4import org.springframework.web.bind.annotation.RequestMapping;5import org.springframework.web.bind.annotation.RestController;6
7@RestController8public class HeaderController {9
10 @RequestMapping("/header")11 public String header(HttpServletRequest request) {12 String userAgent = request.getHeader("User-Agent");13 String host = request.getHeader("Host");14 System.out.println("User-Agent = " + userAgent);15 System.out.println("Host = " + host);16 return "ok";17 }18}两个问题:
① 两行取的都是请求头(request header),不是请求行里的参数——所以要用 getHeader("头名"),而不是 getParameter;
② 因为请求头是客户端自己填的:请求头里的 User-Agent 就是”我是谁”的自述,本机实测用 curl 访问拿到的是 curl/8.15.0,用浏览器访问拿到的是 Mozilla/5.0 (Windows NT 10.0; Win64; x64) … Chrome/…;两台不同的客户端访问同一个地址,值自然不一样(Host 也会因为访问用的地址不同而变,如 localhost:8080 与 127.0.0.1:8080)。
3-1 照课程做一个”请求信息体检”接口(把六类数据一次取全) 把这一篇的六个方法串成一个接口做一遍,做完它你就把”从请求里取数据”这件事打通了。
/request,方法参数里声明请求对象(这就是本篇的核心:让服务器把封装好的请求对象送进来);name、age;User-Agent 和请求方式;http://localhost:8080/request?name=Tomcat&age=10,把返回的七行结果抄到练习文件末尾的注释里;test_33_综合_请求信息体检.java 里按这 9 步给了写作区。)涉及知识点
| 知识点 | 在这里的应用 |
|---|---|
| Tomcat 解析 + 封装 | 方法参数里的 HttpServletRequest 不是自己 new 的 |
@RestController / @RequestMapping | 标类、标方法,把返回值直接写进响应体 |
| 六个取值方法 | getParameter ×2、getRequestURI、getRequestURL、getHeader、getMethod、getQueryString |
| URI 与 URL 的区别 | 一个只有路径、一个是完整地址(含协议/主机/端口) |
| 请求头 | User-Agent 随客户端变化(curl 与浏览器不同) |
一级 · 思路:先”拿到请求对象”,再”从它身上一个个取”——取参数、取路径、取头、取方式,最后把七行拼成一段字符串返回;第 9 步的两个问题回去看本篇开头 PPT 第 29 页那句原话
二级 · 方法:@RestController + @RequestMapping("/request");参数写 HttpServletRequest request;拼串用 StringBuilder(append 一串 .append("\n")),注意 getRequestURL() 要 .toString()
三级 · 骨架:String name = request.____("name"); String uri = request.____(); String url = request.____().toString(); String ua = request.____("User-Agent"); String method = request.____(); String qs = request.____();
3-1
1~7. 代码(本机实验室里用的就是这个结构,HttpLabController):
1package com.itheima;2
3import jakarta.servlet.http.HttpServletRequest;4import org.springframework.web.bind.annotation.RequestMapping;5import org.springframework.web.bind.annotation.RestController;6
7@RestController8public class RequestLabController {9
10 @RequestMapping("/request")11 public String request(HttpServletRequest request) {12 // 1.请求参数13 String name = request.getParameter("name");14 String age = request.getParameter("age");15 // 2.路径 uri 与完整地址 url16 String uri = request.getRequestURI();17 String url = request.getRequestURL().toString();18 // 3.请求头19 String userAgent = request.getHeader("User-Agent");20 // 4.请求方式21 String method = request.getMethod();22 // 5.查询字符串23 String queryString = request.getQueryString();24
25 StringBuilder sb = new StringBuilder();26 sb.append("getParameter(name) = ").append(name).append("\n");27 sb.append("getParameter(age) = ").append(age).append("\n");28 sb.append("getRequestURI() = ").append(uri).append("\n");29 sb.append("getRequestURL() = ").append(url).append("\n");30 sb.append("getHeader(User-Agent) = ").append(userAgent).append("\n");31 sb.append("getMethod() = ").append(method).append("\n");32 sb.append("getQueryString() = ").append(queryString);33 return sb.toString();34 }35}http://localhost:8080/request?name=Tomcat&age=10 的本机实测输出:
1getParameter(name) = Tomcat2getParameter(age) = 103getRequestURI() = /request4getRequestURL() = http://localhost:8080/request5getHeader(User-Agent) = curl/8.15.06getMethod() = GET7getQueryString() = name=Tomcat&age=10User-Agent 那一行取决于用什么访问:curl 是 curl/8.15.0,浏览器是一长串 Mozilla/5.0 … Chrome/…。)uri = /request(只有资源路径,来自请求行的 GET /request HTTP/1.1 那一段);url = http://localhost:8080/request(完整地址,多了协议、主机、端口)。名字只差一个字母,URL 才是能在浏览器里直接打开的地址;另外 getRequestURL() 返回 StringBuffer,所以代码里多写了 .toString();
② 请求数据由 Web 服务器(Tomcat)解析并封装成 HttpServletRequest 请求对象,在调用 Controller 方法时作为方法参数传给该方法——所以方法里一行解析代码都不用写(PPT 第 29 页原话,也是 PPT 第 31 页问答的答案)。如果你喜欢,那么欢迎来到我的世界!
了解更多暂未播放



"所有的相遇都是久别重逢。"—— 未知


