写点什么

Sevlet 规范:HttpServlet 类 和 HttpServletRequest 接口 源码解析

作者:EquatorCoco
  • 2023-07-14
    福建
  • 本文字数:33323 字

    阅读完需:约 109 分钟

1. HTTP 协议解读


什么是协议?


  • 协议实际上是某些人,或者某些组织提前制定好的一套规范,大家都按照这个规范来,这样可以做到沟通无障碍。


  • 协议就是一套规范,就是一套标准。由其他人或其他组织来负责制定的。


  • 我说的话你能听懂,你说的话,我也能听懂,这说明我们之间是有一套规范的,一套协议的,这套协议就是:中国普通话协议。我们都遵守这套协议,我们之间就可以沟通无障碍。但是如果我们遵守不同的协议规范的话,就无法通信了,比如:你说的是阿拉伯语,我说的是汉语,我听不懂你说什么,你也听不同我说什么。你我两者之间无法通信交流。


1.1 什么是 HTTP 协议 ?


  • HTTP 协议:是 W3C 制定的一种超文本传输协议。(通信协议:发送消息的模板提前被制定好。)


  • W3C:https://www.w3.org/万维网联盟组织负责制定标准的:HTTP HTML4.0 HTML5 XML DOM 等规范都是 W3C 制定的。万维网之父:蒂姆·伯纳斯·李


什么是超文本 ?


  • 超文本说的就是:不是普通文本,比如流媒体:声音、视频、图片等。


  • HTTP 协议支持:不但可以传送普通字符串,同样支持传递声音、视频、图片等流媒体信息。


  • 这种协议游走在 B (浏览器)和 S (服务器)之间。B S 发数据要遵循 HTTP 协议。S B 发数据同样需要遵循 HTTP 协议。这样 B 和 S 才能解耦合


什么是解耦合?


这里指定是: B (浏览器) 不 依赖 S(服务器) ,S 也不依赖 B


B/S 表示:B/S 结构的系统(浏览器访问 WEB 服务器的系统)


HTTP 协议的请求 (request) 和 响应(response)


  • 浏览器 向 🔜 WEB 服务器发送数据,叫做:请求(request)


  • WEB 服务器 向 🔜 浏览器发送数据,叫做:响应(response)


HTTP 协议包括:


  • 请求协议浏览器 向🔜 WEB 服务器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。


  • 响应协议 WEB 服务器 向 🔜 浏览器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。


HTTP 协议总结:


HTTP 协议就是提前制定好的一种消息模板。


  • 不管你是哪个品牌的浏览器,都是这么发,同样的不管你是哪个品牌的 WEB 服务器,都是这么发。


  • 火狐浏览器 可以向 Tomcat 发送请求,也可以向 Jetty 服务器发送请求。浏览器不依赖具体的服务器品牌。


  • WEB 服务器也不依赖具体的浏览器品牌。可以是 FF 浏览器,也可以是 Chrome 浏览器,可以是 IE,都行。


1.2 HTTP 请求协议的具体报文


注意:HTTP 请求协议中有多种请求方式。这里我们说的是常用的 get 请求和 post 请求。在该文章的下文有更加详细的说明。


HTTP 的请求协议(B (浏览器)-> S(服务器))


HTTP 的请求协议包括:如下 4 个部分: 请求行,请求头,空白行,请求体


如何查看我们提交的数据报文,在浏览器当中


  1. 我们可以在提交数据的时候按 F12 快捷键查看,或者是右键鼠标浏览器 ,在弹出的窗口当中,选择点击 检查。如下图所示:



  1. 在选择点击检查按钮之后,弹出如下窗口,选择其中的 Network (网络)选项。如下图所示:只要当我们在前端提交了数据,就可以在如下的窗口当中捕获到并显示出相应的报文信息。



  1. 测试如下,看看是否可以捕获到我们的请求报文的信息数据。如下图所示:



  1. 查看我们提交的数据信息:点击我们捕获到的报文信息,再点击 ——> Paload 选项。



如下是:HTTP 请求协议的具体报文:GET 请求


首先编写一个是 Get 请求提交的表单 html 文件,具体的代码编写如下:


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTTP请求登录</title></head><body><h2>get 请求</h2><form action="" method="get">    username: <input type="text" name="username" /> <br>    userpassword: <input type="password" name="userpswd" /> <br>    <input type="submit" value="get" /></form>
<h2>post 请求</h2><form action="" method="post"> username: <input type="text" name="username" /> <br> userpassword: <input type="password" name="userpswd" /> <br> <input type="submit" value="post" /></form></body></html>
复制代码



Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Connection: keep-alive# 下面一行是请求行Host: 127.0.0.1:8080# 下面的是请求头Referer: http://127.0.0.1:8080/servlet08/login.htmlsec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36# 下面是空白行:用于分隔请求头 与 请求体 
# 下面是请求体:向服务器发送的具体数据。username=Hello&userpswd=123
复制代码


如下是另一种的格式下 get 请求的具体报文信息


GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1                            # 请求行Host: localhost:8080                                                                   # 请求头Connection: keep-alivesec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Sec-Fetch-Site: same-originSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentReferer: http://localhost:8080/servlet05/index.htmlAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9 # 空白行   # 请求体
复制代码


如下 post 请求信息



Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Cache-Control: max-age=0Connection: keep-aliveContent-Length: 25Content-Type: application/x-www-form-urlencoded# 请求头Host: 127.0.0.1:8080# 请求行Origin: http://127.0.0.1:8080Referer: http://127.0.0.1:8080/servlet08/login.html?username=Hello&userpswd=123sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
# 空白行
# 请求体
复制代码


如下是另一种的格式下 post 请求的具体报文信息


POST /servlet05/postServlet HTTP/1.1                                                 # 请求行Host: localhost:8080                                                                # 请求头Connection: keep-aliveContent-Length: 25Cache-Control: max-age=0sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Upgrade-Insecure-Requests: 1Origin: http://localhost:8080Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Sec-Fetch-Site: same-originSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentReferer: http://localhost:8080/servlet05/index.htmlAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9  # 空白行                                                            # 请求体  username=lisi&userpwd=123
复制代码


请求行: 由三部分组成: 请求方式,URI ,HTTP 版本号 。


  • 请求行:第一部分:请求方式 (7 种): get(常用的),post(常用的),delete,put,head,options,trace 。


  • 请求行:第二部分: URI


URL : 统一资源定位符。 代表网络中某个资源。注意: 可以通过 URL 定位到该资源,可以直接在浏览器当中输入 URI 访问网络当中的资源)。比如: http://localhost:8080/servlet05/index.html 这是 URL。


URI : 统一资源标识符。代表网络中某个资源的名字。注意: URI 是无法定位资源的(就是无法通过直接在浏览器当中输入 URI 访问网络当中的资源)。比如:/servlet05/index.html 这是 URI。


URI 和 URL 什么关系,有什么区别?


URL 是包括了 URI 的。


  • 请求行:第三部分: HTTP 协议版本号


请求头: 包含 : 请求的主机的 IP 地址,主机的端口号,浏览器信息,平台信息,cookie 等信息。


空白行: 空白行是用来区分“请求头”和“请求体”


请求体: 向服务器发送的具体数据。


1.3 HTTP 响应协议的具体报文


HTTP 的响应协议(S(浏览器) --> B(客户端))HTTP 的响应协议包括 4 部分 : 状态行,响应头,空白行,响应体 。


HTTP 响应协议的具体报文:




Accept-Ranges: bytes              Connection: keep-aliveContent-Length: 598Content-Type: text/html             # 响应头Date: Thu, 30 Mar 2023 02:48:49 GMTETag: W/"598-1680144315095"Keep-Alive: timeout=20Last-Modified: Thu, 30 Mar 2023 02:45:15 GMT                                   # 空白行
<!DOCTYPE html> # 响应体:其实就是源代码的信息<html lang="en"><head> <meta charset="UTF-8"> <title>HTTP请求登录</title></head><body><h2>get 请求</h2><form action="" method="get"> username: <input type="text" name="username" /> <br> userpassword: <input type="password" name="userpswd" /> <br> <input type="submit" value="get" /></form>
<h2>post 请求</h2><form action="" method="post"> username: <input type="text" name="username" /> <br> userpassword: <input type="password" name="userpswd" /> <br> <input type="submit" value="post" /></form></body></html>
复制代码


另外一种 post 响应的具体数据报文信息。


HTTP/1.1 200 ok                                    #  状态行Content-Type: text/html;charset=UTF-8              # 响应头Content-Length: 160Date: Mon, 08 Nov 2021 13:19:32 GMTKeep-Alive: timeout=20Connection: keep-alive                                      # 空白行
<!DOCTYPE html> # 响应体:其实就是源代码的信息<html lang="en"><head> <meta charset="UTF-8"> <title>HTTP请求登录</title></head><body><h2>get 请求</h2><form action="" method="get"> username: <input type="text" name="username" /> <br> userpassword: <input type="password" name="userpswd" /> <br> <input type="submit" value="get" /></form>
<h2>post 请求</h2><form action="" method="post"> username: <input type="text" name="username" /> <br> userpassword: <input type="password" name="userpswd" /> <br> <input type="submit" value="post" /></form></body></html>
复制代码


状态行 : 由三部分组成:协议版本号,状态码,状态描述信息。


  • 状态行,第一部分: 协议版本号 (HTTP/1.1)


  • 状态行,第二部分: 状态码(HTTP)协议中规定的响应状态号,不同的响应结果对应不同的号码)。



如下是一些常见的状态码的意义:


  • 200 : 表示请求响应成功,正常结束。


  • 404: 表示访问的资源不存在,通常是因为要么你填写的路径写错了,要么是你路径写对了,但是服务器中对应的 资源并没有启动成。总之 404 错误是基本是前端错误。


  • 405 : 表示前端发送的请求方式与后端接受处理(该请求)的方式不一致时发生的:基本上时如下两种情况:前端是 POST 请求,后端的处理方式按照 get 方式进行处理时,发生 405 前端是 GET 请求,后端的处理方式按照 post 方式进行处理时,发生 405 具体的该文章后面有详细的说明。所以请不要走开。


  • 500 表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的。


  • 总结:以 4 开始的,一般是浏览器端的错误导致的。以 5 开始的,一般是服务器端的错误导致的。


状态行,第三部分: 状态的描述信息:


  • OK 表示正则成结束。


  • not found: 表示资源找不到。


空白行: 用来分隔“响应头”和“响应体”的。


响应体: 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。简单的说就是 html 对应的源代码。


2. GET 请求和 POST 请求有什么区别?


怎么向服务器发送 Get 请求,怎么向服务器发送 Post 请求?


到目前为止,只有一种情况可以发送 post 请求:就是用 form 表单,并且 form 标签当中的 method 的属性值必须为 method = "post" 才行。


其他所有情况一律都是 get 请求:


  • 在浏览器地址栏上直接输入 URL,敲回车,属于 get 请求。


  • 在浏览器上直接点击超链接,属于 get 请求。


  • 使用 form 表单提交数据时,form 标签中没有写 method 属性,默认就是 get


  • 或者使用 form 的时候,form 标签中 method 属性值为:method="get"


  • ....


GET 请求和 POST 请求有什么区别 ?


Get 请求:


  • get 请求发送数据的时候,数据会挂在 URI 的后面,并且在 URI 后面添加一个 "?" ," ? " 后面的就是数据了,这样会导致发送的数据回显到浏览器的地址栏上如下显示的:。(get 请求在 “请求行”上发送数据)。



  • get 请求只能发送普通的字符串数据,并且发送字符串的长度有限制,不同的浏览器限制不同,这个没有明确的规范。并不能发送流媒体信息:比如:图片,声音,视频等等.


  • get 请求无法发送大数据量。


  • get 请求在 W3C 中是这样介绍的:get 请求比较适合从服务器端获取数据。


  • get 请求是安全的,get 请求是安全的,因为:get 请求只是为了从服务器上获取数据,不会对服务器造成威胁,注意:get 请求本身是安全的,你不要用错了,用错了之后,就冤枉 get 请求不安全。这不是 get 请求的问题,而是你使用的问题。比如说:一个注册表单的信息的提交,应该使用的 post 请求,而却使用的是 get 请求,导致提交的信息回显到了地址栏上了。


  • get 请求是支持缓存了。


补充点:


  • 任何一个 get 请求最终的“响应结果”都会被浏览器缓存起来。在浏览器缓存当中:一个 get 请求的路径 a 对应 一个资源。一个 get 请求的路径 b 对应 一个资源。一个 get 请求的路径 c 对应 一个资源。......


  • 实际上,你只要发送 get 请求,浏览器做的第一件事都是先从本地浏览器缓存中找,找不到的时候才会去服务器上获取。这种缓存机制目的是为了提高用户的体验。


  • 有没有这样一个需求:我们不希望 get 请求走缓存,怎么办?怎么避免走缓存?我希望每一次这个 get 请求都去服务器上找资源,我不想从本地浏览器的缓存中取。只要每一次 get 请求的请求路径不同即可。



怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样(缓存的路径也是不一样的),浏览器就不走缓存了。


Post 请求 :


  • post 请求发送的数据的时候,在请求体当中发送的,不会回显到浏览器的地址栏上,也就是说 Post 发送的数据,在浏览器的地址栏上不能看到的。(post 请求在 “请求体”当中发送数据)。


  • post 请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:图片,视频,声音等。


  • post 请求可以发送大数据量,理论上没有限制的。


  • post 请求在 W3C 是这样说的 : post 请求比较适合向服务器传送数据。


  • post 请求是危险的,因为: post 请求时向服务器提交数据,如果这些数据是通过后门的方式进入到服务器当中,服务器是很危险的。另外 post 是为了提交数据,所以一般情况下拦截请求的时候,大部分选择的是拦截(监听) post 请求。


  • post 是不支持缓存的。因为:(POST 是用来修改服务器端的资源的。)post 请求之后,服务器“响应的结果”不会被浏览器缓存起来。因为这个缓存没有意义。


2.1 GET 请求和 POST 请求如何选择,什么时候使用 GET 请求,什么时候使用 POST 请求 ?


  • 怎么选择 GET 请求和 POST 请求呢?衡量标准是什么呢?你这个请求是想获取服务器端的数据,还是想向服务器发送数据。如果你是想从服务器上获取资源,建议使用 GET 请求,如果你这个请求是为了向服务器提交数据,建议使用 POST 请求。


  • 大部分的 form 表单提交,都是 post 方式,因为 form 表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等。


  • 如果表单中有敏感信息,还是建议适用 post 请求,因为 get 请求会回显敏感信息到浏览器地址栏上。(例如:密码信息)


  • 做文件上传,一定是 post 请求。要传的数据不是普通文本。


  • 其他情况都可以使用 get 请求。


Get 请求与 Post 请求的共性


不管你是 get 请求还是 post 请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:都是 :name=value&name=value&name=value&name=value


其中的 name 表示:以 form 表单为例:form 表单中的 input 标签当中的 name 。


<form>    <input type="text" name="username" > </form>
复制代码


其中的 value 表示的是:同样的以 form 表单为例 :form 表单中的 input 标签当中的 value。


<form>    interest:    smoke<input type="checkbox" name="aihao" value="s"/>    drink <input type="checkbox" name="aihao" value="d"/>    tangtou <input type="checkbox" name="aihao" value="tt"/></form>
复制代码


3. HttpServlet 源码分析




  • HttpServlet 类是专门为 HTTP 协议准备的。比 GenericServlet 更加适合 HTTP 协议下的开发。


  • HttpServlet 在哪个包下?


这是在 Tomcat 10 的基础上:jakarta.servlet.http.HttpServlet,而后面 Tomcat 9 之前的(包括 Tomcat 9 )以内的包是在 : javax.servlet.http.HttpServlet 包下的。


  • 到目前为止我们接触了 servlet 规范中哪些接口 ?。注意: 如下的是基于 Tomcat 10 的基础上的,而 Tomcat 9 之前的包括 9 将如下的 jakarta 替换为 javax 就可以了,具体原因大家可以移步至:


jakarta.servlet.Servlet 核心接口(接口)


jakarta.servlet.ServletConfig Servlet 配置信息接口(接口)


jakarta.servlet.ServletContext Servlet 上下文接口(接口)


jakarta.servlet.ServletRequest Servlet 请求接口(接口)


jakarta.servlet.ServletResponse Servlet 响应接口(接口)


jakarta.servlet.ServletException Servlet 异常(类)


jakarta.servlet.GenericServlet 标准通用的 Servlet 类(抽象类)


  • http 包下都有哪些类和接口呢?jakarta.servlet.http.*


jakarta.servlet.http.HttpServlet (HTTP 协议专用的 Servlet 类,抽象类)


jakarta.servlet.http.HttpServletRequest (HTTP 协议专用的处理请求对象)


jakarta.servlet.http.HttpServletResponse (HTTP 协议专用的处理响应对象)


  • HttpServletRequest 对象中封装了什么信息?


HttpServletRequest 简称为 request 对象。


HttpServletRequest 中封装了请求协议的全部内容。

Tomcat 服务器(WEB 服务器) 将 “请求协议”中的数据全部解析出来,然后将这些数据全部封装到 request 对象当中了。也就是说,我们只要面向 HttpServletRequest 接口编程,就可以获取请求协议当中的数据了。


HttpServletRequest,简称 request对象。


  • HttpServletResponse 对象 是专门用来响应 HTTP 协议到浏览器的。

回忆 Servlet 生命周期 ?


用户第一次请求

Tomcat 服务器通过反射机制,调用无参数构造方法。创建 Servlet 对象。(web.xml 文件中配置的 Servlet 类对应的对象。)

Tomcat 服务器调用 Servlet 对象的 init 方法完成初始化。

Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。


用户第二次请求

Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。


用户第三次请求

Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。


....

Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。


  • 服务器关闭


Tomcat 服务器调用 Servlet 对象的 destroy 方法,做销毁之前的准备工作。


Tomcat 服务器销毁 Servlet 对象。


HttpServlet 源码分析:


public class HelloServlet extends HttpServlet {	// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。	public HelloServlet() {    }        //override 重写 doGet方法    //override 重写 doPost方法}
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { // 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。 public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } // 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init() public void init() throws ServletException { // NOOP by default }}
// HttpServlet模板类。public abstract class HttpServlet extends GenericServlet { // 用户发送第一次请求的时候这个service会执行 // 用户发送第N次请求的时候,这个service方法还是会执行。 // 用户只要发送一次请求,这个service方法就会执行一次。 @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request; HttpServletResponse response;
try { // 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException(lStrings.getString("http.non_http")); } // 调用重载的service方法。 service(request, response); } // 这个service方法的两个参数都是带有Http的。 // 这个service是一个模板方法。 // 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求方式 // 这个请求方式最终可能是:"" // 注意:request.getMethod()方法获取的是请求方式,可能是七种之一: // GET POST PUT DELETE HEAD OPTIONS TRACE String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。 if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } }
} else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp);
} else if (method.equals(METHOD_POST)) { // 如果请求方式是POST请求,则执行doPost方法。 doPost(req, resp);
} else if (method.equals(METHOD_PUT)) { doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) { doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) { doTrace(req,resp);
} else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. //
String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ // 报405错误 String msg = lStrings.getString("http.method_get_not_supported"); sendMethodNotAllowed(req, resp, msg); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 报405错误 String msg = lStrings.getString("http.method_post_not_supported"); sendMethodNotAllowed(req, resp, msg); } }
/*通过以上源代码分析: 假设前端发送的请求是get请求,后端程序员重写的方法是doPost 假设前端发送的请求是post请求,后端程序员重写的方法是doGet 会发生什么呢? 发生405这样的一个错误。 405表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。 通过以上源代码可以知道:只要HttpServlet类中的doGet方法或doPost方法执行了,必然405.
怎么避免405的错误呢? 后端重写了doGet方法,前端一定要发get请求。 后端重写了doPost方法,前端一定要发post请求。 这样可以避免405错误。 这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。 有的人,你会看到为了避免405错误,在Servlet类当中,将doGet和doPost方法都进行了重写。这样,确实可以避免405的发生,但是不建议,405错误还是有用的。该报错的时候就应该让他报错。如果你要是同时重写了doGet和doPost,那还不如你直接重写service方法好了。这样代码还能少写一点。*/
复制代码


3.1 HttpServlet 处理 get 请求和 post 请求 源码分析


上面我们在:HTTP 响应协议的具体报文模块当中提到的 状态行当中的状态码部分中的一个为 405 前端用户提交的 get/post 请求与后端服务器不一致导致的错误。如下测试


当: 前端发送的请求是 get 请求 。后端程序员重写的方法是 doPost


对于前端用户提交数据的 html 代码设计如下:注意:这里我们前端提交的是 get请求。


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTTP请求登录</title></head><body><h2>get 请求</h2><!--action = /项目名 + web.xml当中url的映射的路径--><form action="/servlet08//Test" method="get">    username: <input type="text" name="username"/> <br>    userpassword: <input type="password" name="userpswd"/> <br>    <input type="submit" value="get"/></form></body></html>
复制代码



如下的是对应后端服务器 Servlet 的代码设计。注意了,这里我们 Servlet 服务器端处理的是 doPost 请求的


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置浏览器当中显示的格式 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter();
writer.println("<h1>Hello World<h1>");
}}
复制代码


如下运行结果: 报了 405 错误,原因是:我们前后端的处理请求的不一致。



当:前端发送的请求是 post 请求,后端程序员重写的方法是 doGet 出现的错误


对于前端用户提交数据的 html 代码设计如下:注意:这里我们前端提交的是 post请求。


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTTP请求登录</title></head><body><h2>post 请求</h2><!--action = /项目名 + web.xml当中url的映射的路径--><form action="/servlet08//Test" method="post">    username: <input type="text" name="username"/> <br>    userpassword: <input type="password" name="userpswd"/> <br>    <input type="submit" value="get"/></form></body></html>
复制代码



如下的是对应后端服务器 Servlet 的代码设计。注意了,这里我们 Servlet 服务器端处理的是 doGet 请求的。


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置浏览器当中显示的格式 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter();
writer.println("<h1>Hello World<h1>");
}}
复制代码


如下运行结果: 报了 405 错误,原因是:我们前后端的处理请求的不一致。



通过以上源代码分析:


假设前端发送的请求是 get 请求,后端程序员重写的方法是 doPost

假设前端发送的请求是 post 请求,后端程序员重写的方法是 doGet 会发生什么呢?发生 405 这样的一个错误。405 表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。


为什么为发生 405 错误呢?


我们从 HttpServlet 源码上分析:


// HttpServlet模板类。public abstract class HttpServlet extends GenericServlet {    // 用户发送第一次请求的时候这个service会执行    // 用户发送第N次请求的时候,这个service方法还是会执行。    // 用户只要发送一次请求,这个service方法就会执行一次。    @Override    public void service(ServletRequest req, ServletResponse res)        throws ServletException, IOException {
HttpServletRequest request; HttpServletResponse response;
try { // 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException(lStrings.getString("http.non_http")); } // 调用重载的service方法。 service(request, response); } // 这个service方法的两个参数都是带有Http的。 // 这个service是一个模板方法。 // 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求方式 // 这个请求方式最终可能是:"" // 注意:request.getMethod()方法获取的是请求方式,可能是七种之一: // GET POST PUT DELETE HEAD OPTIONS TRACE String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。 if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } }
} else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp);
} else if (method.equals(METHOD_POST)) { // 如果请求方式是POST请求,则执行doPost方法。 doPost(req, resp);
} else if (method.equals(METHOD_PUT)) { doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) { doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) { doTrace(req,resp);
} else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. //
String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ // 报405错误 String msg = lStrings.getString("http.method_get_not_supported"); sendMethodNotAllowed(req, resp, msg); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 报405错误 String msg = lStrings.getString("http.method_post_not_supported"); sendMethodNotAllowed(req, resp, msg); }
复制代码


通过以上源代码阅读以及运行测试可以知道:只要没有重写对应 HttpServlet 类中的 doGet 方法或 doPost 方法,就会报 405 错误,因为没有重写了 doGet 或 doPost 方法就会执行其中的 HttpServlet 当中编写的 doGet / doPost 方法,而如果执行了的是 HttpServlet 当中 doGet / doPost 方法就会报 405 错误,提示你没有重写 对应的 doGet / doPost 方法。


怎么避免 405 的错误呢?


后端重写了 doGet 方法,前端一定要发 get 请求。后端重写了 doPost 方法,前端一定要发 post 请求。这样可以避免 405 错误。这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。


补充


有的人,你会看到为了避免 405 错误,在 Servlet 类当中,将 doGet 和 doPost 方法都进行了重写。这样,确实可以避免 405 的发生,但是不建议,405 错误还是有用的。该报错的时候就应该让他报错。如果你要是同时重写了 doGet 和 doPost,那还不如你直接重写 service 方法好了。这样代码还能少写一点。


我们编写的 HelloServlet 直接继承 HttpServlet,直接重写 HttpServlet 类中的 service()方法行吗?


  • 可以,只不过你享受不到 405 错误。享受不到 HTTP 协议专属的东西。


一个 Servlet 类的开发步骤:


  • 第一步:编写一个 Servlet 类,直接继承 HttpServlet


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet {}
复制代码


  • 第二步:重写 doGet 方法或者重写 doPost 方法,到底重写谁,javaweb 程序员说了算。注意:前后端请求处理是要保持一致的。


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置浏览器当中显示的格式 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter();
writer.println("<h1>Hello World<h1>");
}}
复制代码


  • 第三步:将 Servlet 类配置到 web.xml 文件当中。


<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"         version="4.0">

<servlet> <servlet-name>HttpServletTest</servlet-name> <servlet-class>com.RainbowSea.servlet.HttpServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>HttpServletTest</servlet-name> <url-pattern>/Test</url-pattern> </servlet-mapping></web-app>
复制代码


  • 第四步:准备前端的页面(form 表单),form 表单中指定请求路径即可


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTTP请求登录</title></head><body><h2>get 请求</h2><!--action = /项目名 + web.xml当中url的映射的路径--><form action="/servlet08//Test" method="get">    username: <input type="text" name="username"/> <br>    userpassword: <input type="password" name="userpswd"/> <br>    <input type="submit" value="get"/></form></body></html>
复制代码


4. HttpServletRequest 接口详解




  • HttpServletRequest 是一个接口,全限定名称:jakarta.servlet.http.HttpServletRequest这个是基于 Tomcat 10 下的对应的包路径,如果是 Tomcat 9 (包括 9)就是将其中的 jakarta 修改为 javax 就可以了。


  • HttpServletRequest 接口是 Servlet 规范中的一员。


  • HttpServletRequest 接口的父接口:ServletRequest


  • public interface HttpServletRequest extends ServletRequest {}


  • HttpServletRequest 接口的实现类谁写的? HttpServletRequest 对象是谁给创建的?


  • 通过测试:就是通过 request.getClass() 方法获取到该接口的实现类, org.apache.catalina.connector.RequestFacade实现了 HttpServletRequest 接口


  • public class RequestFacade implements HttpServletRequest {}




测试结果说明:Tomcat 服务器(WEB 服务器、WEB 容器)实现了 HttpServletRequest 接口,还是说明了 Tomcat 服务器实现了 Servlet 规范。而对于我们 javaweb 程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可。我们关心的是 HttpServletRequest 接口中有哪些方法,这些方法可以完成什么功能!!!!


4.0.1 HttpServletRequest 对象中都有什么信息?都包装了什么信息 ?


  • HttpServletRequest 对象是 Tomcat 服务 i 其负责创建的,这个对象中封装了如下信息,以及如下 HTTP 的请求协议?

  • 实际上是用户发送请求的时候,遵循了 HTTP 协议,发送的是 遵循了 HTTP 的请求协议的信息内容(name=value&name=value&...),Tomcat 服务器会将客户端遵循 HTTP 协议发送的信息内容获取到,并将其数据全部解析出来,然后 Tomcat 服务器把这些信息封装到 HttpServletRequest 对象当中,传给了我们 Javaweb 程序员。

  • 所以 Javaweb 程序员面向 HttpServletRequest 接口编程,调用其中的方法就可以获取到用户发送的请求信息了。


4.1 获取前端用户提交的数据信息


request 和 response 对象的生命周期?


  • request 对象和 response 对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。

  • 一次请求对应一个 request。

  • 两次请求则对应两个 request。

  • .....


我们后端如何怎么获取前端浏览器用户提交的数据 ?


想要获取到前端浏览器用户提交的数据,使用interface ServletRequest 接口当中抽象方法,也就是HttpServletRequest 实现了 ServletRequest 接口的具体实现类中重写其中的抽象方法。


Map<String,String[]> getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中<前端的name,前端的value>Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值 String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)valueString getParameter(String name)  获取value这个一维数组当中的第一个元素。这个方法最常用。// 以上的4个方法,和获取用户提交的数据有关系// 注意: 这里为什么 value 的值是用String[] 数组存储的。后面有说明。
复制代码


思考:如果是你,前端的 form 表单提交了数据之后,你准备怎么存储这些数据,你准备采用什么样的数据结构去存储这些数据呢?


前端提交的数据格式:username=abc&userpwd=111&aihao=learn&aihao=programme&aihao=playbasketball


注意: 前端表单提交数据的时候,假设提交了 120 这样的“数字”,其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)所以都是用 String字符串。


我会采用 Map 集合来存储:


Map<String,String>    key存储String    value存储String    这种想法对吗?不对。    如果采用以上的数据结构存储会发现key重复的时候value覆盖。比如这里的 name=aihao 是多选框内容,有多个值aihao=s&aihao=d&aihao=tt    key         value    ---------------------    username    abc    userpwd     111    aihao       learn    aihao       programme    aihao       playbasketball    这样是不行的,因为map的key不能重复。Map<String, String[]>    key存储String    value存储String[]  // 将 value 值用 String 数组存储起来。这样就避免了上述的key 重复后 value内容上的覆盖,而没有都存储起来的缺点。    key				value    -------------------------------    username		{"abc"}    userpwd			{"111"}    aihao			{"learn","programme","playbasketball"}
复制代码


举例使用上述四个方法获取前端用户提交的数据


第一步:编写设计好 html 前端显示页面,如下:注意,这里我们前端使用的是 post 请求,后端的 Servlet 就要用 doPost 请求处理,前后端保持一致,不然报 405 错误。


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>user register</title></head><body><h1>user register</h1>
<!--注意:/项目名+/Servlet类的映射路径--><form action="/servlet08//Request" method="post"> username:<input type="text" name="username"/><br> password:<input type="password" name="userpassword"/> <br> interest: 学习:<input type="checkbox" name="aihao" value="learn"/> 编程: <input type="checkbox" name="aihao" value="programme"/> 打篮球: <input type="checkbox" name="aihao" value="playbasketball"/> <br> <input type="submit" value="register"/></form></body></html>
复制代码


第二步: 编写对应处理前端用户提交的请求的 Servlet ,注意前后端请求处理保持一致 : 这里举例使用的是上述四个获取用户请求的其中的一个方法:


Map<String,String[]> getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中<前端的name,前端的value>
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Iterator;import java.util.Map;import java.util.Set;
public class RequestServletTest extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取到前端用户提交的数据信息(name,value),并存储到Map集合当中去。 Map<String, String[]> parameterMap = request.getParameterMap();
// 遍历存储了前端用户提交数据的Map集合 // 获取到 Map 当中所有的 key 值(也就是前端的用户提交的所有的name 值) Set<String> strings = parameterMap.keySet();
// 获取到 set 的迭代器 Iterator<String> iterator = strings.iterator(); while(iterator.hasNext()) { String name = iterator.next(); System.out.print(name +"=");
// 根据 key 获取到对应的 value值 (也就是前端用户提交的value值数据) String[] value = parameterMap.get(name); // 遍历 value 数组 for(String s : value) { System.out.print(s); } System.out.println(); }

}}
复制代码


第三步: 对相关的 Servlet 类配置到对应 webapp 项目的 web.xml 文件中去。


<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"         version="4.0">

<servlet> <servlet-name>RequestServletTest</servlet-name> <servlet-class>com.RainbowSea.servlet.RequestServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>RequestServletTest</servlet-name> <url-pattern>/Request</url-pattern> </servlet-mapping></web-app>
复制代码


第四步: 运行测试。




再举例: 这里的 Serlvelt 获取前端用户提交的数据使用: 如下两个方法


Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值 String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)value
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Enumeration;

public class RequestServletTest extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取到前端用户提交的数据的所有 name 值,并存储到 Enumeration<String[]> 枚举泛型中 Enumeration<String> names = request.getParameterNames();

while (names.hasMoreElements()) { // 判断是否还有数据,有返回 true,没有返回 false String name = names.nextElement(); // 获取到其中上述 Enumeration<String> 存储到的元素数据, System.out.print(name + "=");
// 同时向下移动下标 String[] values = request.getParameterValues(name);// 根据 name 值获取到对应的 value值,前端用户提交的。 // 并存储到String[] 字符串数组当中去.
// 遍历数组 for (String v : values) { System.out.print(v); }
System.out.println(); } }}
复制代码



再举例: 这里的 Serlvelt 获取前端用户提交的数据使用: 如下两个方法


String getParameter(String name)  获取value这个一维数组当中的第一个元素。这个方法最常用。String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)value
复制代码


注意: 上述两个方法的,合理使用,当我们的 name 的对应多个 value 值的话,需要使用 getParameterValues() 获取到其中的多个 value 值,而如果这是时候,我们使用的是 getParameter() 方法的话,就仅仅只会获取到其中的第一个 value 值,无法获取到对应 name 后面的 value 值的。


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;

public class RequestServletTest extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 注意:这里直接指明对应的name 值,我们最好去前端编写的html当直接复制其中的name 值 // 不然你可以会手动编写错误,哪怕只有其中的一个字母错误了,该方法都是无法获取到对应 // 前端提交的value数据的。所以为了避免错误,建议直接复制。 String username = request.getParameter("username"); // 因为该name的value值只有一个 System.out.println("username =" + username );
String userpassword = request.getParameter("userpassword"); // 同样的该name值的value也是只有一个 System.out.println("userpassword = " + userpassword);
// 注意:该如下名为 aihaos 的value值是一个多个值的,需要使用数组存储起来。 String[] aihaos = request.getParameterValues("aihao"); System.out.print("aihaos ="); for (String s: aihaos) { System.out.print(s); }

}}
复制代码



直接手动编写 name 值的注意事项


如果我们使用如下方法,其中的参数 name 是我们手动编写的话,存在一个安全隐患,就是:如果我们手动输入的 name 的值,在我们对应前端当中不存在的话(因为当其中的我们手动编写错了,比如说,多/少了字母的话),就无法获取到其中对应的 value 值了。


String getParameter(String name)  获取value这个一维数组当中的第一个元素。这个方法最常用。
复制代码


举例如下:



4.2 请求域对象的详解


HttpServletRequest 对象实际上又称为“请求域”对象。


这里我们回顾一下应用域对象是什么 ?


ServletContext 应用域对象(Servlet 上下文对象)。


什么情况下会考虑向 ServletContext 这个应用域当中绑定数据呢 ?


  • 第一:所有用户的共享数据

  • 第二:这个共享数据量很少

  • 第三:这个共享数据很少进行修改操作,尽可能没有修改操作。

  • 在以上三个条件都满足的情况下,使用这个应用域对象,可以大大提高我们程序执行效率。

  • 实际上向应用域当中绑定数据,就相当于把数据放到了缓存(Cache) 当中,然后用户访问的时候直接从缓存中取,减少 IO 的操作(IO 访问磁盘,其耗费的时间代价十分的大),大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。


你见过哪些缓存技术呢 ?


  • 字符串常量池

  • 整数型常量池[-128~127] ,但凡是在这个范围当中 Integer 对象不再创建新对象,而是直接从这个整数型常量池中获取,大大提升系统性能。

  • 数据库连接池(提前创建好 N 个连接对象,将连接对象放到集合当中,使用连接对象的时候,直接从缓存中拿,省去了连接对象的创建过程,效率提升。)

  • 线程池(Tomcat 服务器就是支持多线程的),所谓的线程池就是提前先创建好 N 个线程对象,将线程对象存储到集合中,然后用户直接去线程池当中获取线程对象,直接拿来用。提升系统性能。)

  • 后期你还会学习更多的缓存技术,例如:redis、mongoDB.....


拉回来,这里我们的主角是 "请求域" 对象:


  • “请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。

  • 一个请求对象 request 对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。

  • 请求域对象也有这三个方法:


void setAttribute(String name, Object obj); // 向请求域当中绑定数据。Object getAttribute(String name); // 从请求域当中根据name获取数据。 注意: 这里的参数是 你向请求域添加数据时,设置的对应数据的,setAttribute(String name ,Object obj)一个 name 值保持一致,尽可以使用复制的方式,防止手动编写错误。导致无法找到,从而为 null 值。void removeAttribute(String name); // 将请求域当中绑定的数据移除
// 以上的操作类似于Map集合的操作。Map<String, Object> map;map.put("name", obj); // 向map集合中放key和valueObject obj = map.get("name"); // 通过map集合的key获取valuemap.remove("name"); // 通过Map集合的key删除key和value这个键值对。
复制代码


请求域和应用域的选用原则 ?


尽量使用小的域对象,因为小的域对象占用的资源较少。


举例: 1.将数据存储到请求域当中,2.从请求域当中取出数据


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.Date;
public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置,在浏览器上响应的格式类型 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter(); Date nowTime = new Date(); // 创建当前时间的 Date 对象
// 1. 将 nowTime 的数据存储(绑定)到请求域当中 request.setAttribute("sysTime",nowTime);
// 2. 取出请求域当中的数据: 这里的name值与上面setAttribute(String name,Object obj) 保持一致。 Object sysTime = request.getAttribute("sysTime");
writer.println(sysTime); // 显示到浏览器页面当中的数据
}}
复制代码


<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"         version="4.0">

<servlet> <servlet-name>AServlet</servlet-name> <servlet-class>com.RainbowSea.servlet.AServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AServlet</servlet-name> <url-pattern>/A</url-pattern> </servlet-mapping></web-app>
复制代码



关于 request 对象中两个非常容易混淆的方法:




// uri?username=zhangsan&userpwd=123&sex=1
String username = request.getParameter("username"); // 获取到的是前端用户提交的数据

// 之前一定是执行过:request.setAttribute("name", new Object())  // 将数据绑定/存储到请求域当中
Object obj = request.getAttribute("name");// 获取到的是绑定到请求域当中的数据

// 以上两个方法的区别是什么?
// 第一个方法:获取的是用户在浏览器上提交的数据。
// 第二个方法:获取的是请求域当中绑定的数据。



4.3 跳转 (两个 Servlet 共享数据)


如果我们想要将两个 Servlet 的请求域当中的数据共享:比如 将 AServlet 类当中的请求域存储的数据,在 BServelt 类当中将其存储到 AServlet 请求域当中的数据取出来。如下



观察,思考如下代码:是否可以实现 两个 Servlet 类的数据共享


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.Date;
public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置,在浏览器上响应的格式类型 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter(); Date nowTime = new Date(); // 创建当前时间的 Date 对象
// 将 nowTime 的数据存储(绑定)到请求域当中 request.setAttribute("sysTime",nowTime);
// 这样做可以吗? // 在AServlet当中new 一个BServlet对象,然后调用BServlet 对象的doGet()方法,把request 对象传过去 // 因为我们数据是存储该 request 请求域当中的。所以我们将该数据传给 BServlet ,让BServlet // 将其中的请求域当中的数据取出来,这么做可以吗 ? BServlet bServlet = new BServlet(); bServlet.doGet(request,response);

}}
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置,在浏览器上响应的格式类型 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter();
// 取出请求域当中的数据: 这里的name值与上面setAttribute(String name,Object obj) 保持一致。 Object sysTime = request.getAttribute("sysTime");
writer.println("sysTime = " + sysTime); // 显示到浏览器页面当中的数据



}}
复制代码


如下:运行结果



注意:

注意: 上述这个代码虽然可以实现功能,但是到那时 Servlet 对象不能自己由程序员来 new,因为自己 new 的 Servlet 的对象的生命周期不受 Tomcat 服务器的管理。如果不是被 Tomcat 管理了就无法实现合理的关闭销毁对应的 Servlet 的资源了。


哪要如何合理的将两个 Servlet 数据共享呢 ?*


可以,使用转发机制。


  • 将数据放到 ServletContext 应用域当中,当然是可以的,但是应用域范围太大,占用资源太多。不建议使用。

  • 可以将数据放到 request 域当中,然后 AServlet 转发到 BServlet,保证 AServlet 和 BServlet 在同一次请求当中,这样就可以做到两个 Servlet,或者多个 Servlet 共享同一份数据。

  • 转发(一次请求的核心方法)


// 第一步:获取请求转发器对象// 注意:转发的时候,路径的写法要注意,转发的路径以“/”开始,不加项目名。/ 后接 对应转发的Servelt 在web.xml配置文件当中 uRl 映射路径即可。RequestDispatcher dispatcher = request.getRequestDispatcher("/B");// 第二步:调用转发器的forward方法完成跳转/转发dispatcher.forward(request,response);
// 第一步和第二步代码可以联合在一起。request.getRequestDispatcher("/B").forward(request,response);
复制代码


举例:转发机制,将 AServlett 类当中的信息转发到 BServlet 当中去


package com.RainbowSea.servlet;
import javax.servlet.RequestDispatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Date;
public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置,在浏览器上响应的格式类型 Date nowTime = new Date(); // 创建当前时间的 Date 对象
// 将 nowTime 的数据存储(绑定)到请求域当中 request.setAttribute("sysTime",nowTime);
// 第一步: 获取到转发对象,注意:/ 开始,不家项目名 , / + 对应跳转的 Servlet 当中的 web.xml 当中的url映射的路径 RequestDispatcher requestDispatcher = request.getRequestDispatcher("/B");
// 第二步: 调用转发器的forward方法完成跳转/转发 requestDispatcher.forward(request,response);



}}
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置,在浏览器上响应的格式类型 response.setContentType("text/html;charSet=utf-8"); PrintWriter writer = response.getWriter();
// 取出请求域当中的数据: 这里的name值与上面setAttribute(String name,Object obj) 保持一致。 Object sysTime = request.getAttribute("sysTime");
writer.println("sysTime = " + sysTime); // 显示到浏览器页面当中的数据 }}
复制代码




转发的下一个资源必须是一个 Servlet 吗 ?


不一定,只要是 Tomcat 服务器当中的合法资源,都是可以转发的。例如:html....


举例:转发一个 html 文件


注意: 如果对应的不是 Servlet ,默认是从项目的中的 web 目录开始的,如果是转发 web 的目录下的子目录的话,需要指定对应的子目录的文件。



package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 转发的下一个资源不一定是Servlet 资源, // 只要是Tomcat服务器当中合法的资源,都是可以转发的,例如: html... // 注意:转发的时候,路径的写法要注意,转发的路径以 “/” 开始,不加项目名 // 默认是从项目的中的web目录开始的,如果是转发web的目录下的子目录的话,需要指定对应的子目录 // 如下是含有子目录的 / 表示 web目录 request.getRequestDispatcher("/test/test.html").forward(request,response);
}}
复制代码



4.4 post 请求 request 乱码问题 ?


如下:如果我们在前端 post 请求中提交中文字符串信息,当我们在后端 Servlet 接受的时候,会存在一个乱码问题 ?但是英文不会存在这个现象。如下:所示


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTTP请求登录</title></head><body><h2>Post 请求</h2><!--action = /项目名 + web.xml当中url的映射的路径--><form action="/servlet08//Test" method="post">    username: <input type="text" name="username"/> <br>    userpassword: <input type="password" name="userpswd"/> <br>    <input type="submit" value="post"/></form></body></html>
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
public class TestServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); System.out.println("username = " + username);
String userpswd = request.getParameter("userpswd"); System.out.println("userpswd = " + userpswd);
}}
复制代码



如下我们:使用中文,提交数据出现,乱码。



解决方案:


在显示获取前端 post 请求时,执行如下代码就可以解决 post 乱码问题? 注意: 仅仅只能解决 Post 请求的乱码问题,不能解决 get 请求的乱码问题。


// post请求在请求体中提交数据。// 设置请求体的字符集。(显然这个方法是处理POST请求的乱码问题。这种方式并不能解决get请求的乱码问题。)// Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。// Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?执行以下代码。request.setCharacterEncoding("UTF-8");
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
public class TestServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// post请求在请求体中提交数据。 // 设置请求体的字符集。(显然这个方法是处理POST请求的乱码问题。这种方式并不能解决get请求的乱码问题。) // Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。 // Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?执行以下代码。 request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username"); System.out.println("username = " + username);
String userpswd = request.getParameter("userpswd"); System.out.println("userpswd = " + userpswd);
}}
复制代码



4.5 response 响应到浏览器中的 乱码问题 ?


如果我们想在 Servlet 直接向前端页面中响应中文信息,会存在乱码问题。如下:


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置在页面当显示的格式类型 response.setContentType("text/html"); PrintWriter writer = response.getWriter(); writer.println("<h1> 你好世界 <h1>");
}}
复制代码




解决方案:


// 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码?可以在响应操作之前,// 先执行如下代码,设置在页面当显示的格式,以及字符集编码response.setContentType("text/html;charset=UTF-8");// 在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了。以上代码就不需要设置UTF-8了。
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码? response.setContentType("text/html;charset=UTF-8");// 在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了。以上代码就不需要设置UTF-8了。 PrintWriter writer = response.getWriter(); writer.println("<h1> 你好 世界 <h1>"); }}
复制代码



4.6 get 请求的 request 乱码问题 ?


如果我们使用 get 请求中,处理中文乱码问题。


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTTP请求登录</title></head><body><h2>get 请求</h2><!--action = /项目名 + web.xml当中url的映射的路径--><form action="/servlet08//Test" method="get">    username: <input type="text" name="username"/> <br>    userpassword: <input type="password" name="userpswd"/> <br>    <input type="submit" value="get"/></form></body></html>
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); System.out.println("username = " + username);
String userpswd = request.getParameter("userpswd"); System.out.println("userpswd = " + userpswd); }}
复制代码




解决方案:


首先找我们安装的 Tomcat 的安装目录,在 conf 目录下,找到一个名为 server.xml 的文件名,如下



打开该 server.xml 文件,找到其中的如下这一段代码



    <Connector port="8080" protocol="HTTP/1.1"               connectionTimeout="20000"               redirectPort="8443" />
复制代码


在该 server.xml 配置文件当中的上述对应的一段代码上,多添加上一个字符集设置 URIEncoding="UTF-8" 注意 />结尾就可以了。


    <Connector port="8080" protocol="HTTP/1.1"               connectionTimeout="20000"               redirectPort="8443"                URIEncoding="UTF-8" />
复制代码


// get请求乱码问题怎么解决?// get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的。// get请求乱码怎么解决// 方案:修改CATALINA_HOME/conf/server.xml配置文件<Connector URIEncoding="UTF-8" />// 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了。
复制代码



4.7 HttpServletRequest 接口的其他常用方法


// 获取客户端的IP地址String remoteAddr = request.getRemoteAddr();。    // 获取应用的根路径String contextPath = request.getContextPath();
// 获取请求方式String method = request.getMethod();
// 获取请求的URIString uri = request.getRequestURI();
// 获取 servlet pathString servletPath = request.getServletPath();
复制代码


举例上述方法的使用:



<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"         version="4.0">
<servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>com.RainbowSea.servlet.TestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/Test</url-pattern> </servlet-mapping></web-app>
复制代码


package com.RainbowSea.servlet;
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取 客户端的IP地址 String ip = request.getRemoteAddr(); System.out.println("客户端的ip地址: " + ip);
// 获取用户的请求方式 String method = request.getMethod(); System.out.println("用户的请求方式: " + method);
// 获取webapp的根路径 String contextPath = request.getContextPath(); System.out.println("webapp的根路径: " + contextPath);
// 获取请求的URI String requestURI = request.getRequestURI(); System.out.println("请求的URI:" + requestURI);
// 获取 Servlet path 路径 String servletPath = request.getServletPath(); System.out.println("Servlet 的路径: " + servletPath);


}}
复制代码



5. 补充:


在 WEB-INF 目录下新建了一个文件:welcome.html


打开浏览器访问:http://localhost:8080/servlet07/WEB-INF/welcome.html出现了 404 错误。

  • 注意:放在 WEB-INF 目录下的资源是受保护的。在浏览器上不能够通过路径直接访问。所以像 HTML、CSS、JS、image 等静态资源一定要放到 WEB-INF 目录之外。

  • 如果非要访问的话,也有方法,详细内容请关注我,后续为您更新。


6. 总结:


  1. HTTP 的请求协议包括:如下 4 个部分: 请求行,请求头,空白行,请求体

  2. HTTP 的响应协议(S(浏览器) --> B(客户端))HTTP 的响应协议包括 4 部分 : 状态行,响应头,空白行,响应体 。

  3. GET 请求和 POST 请求有什么区别 ?

  4. 不管你是 get 请求还是 post 请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:都是 :name=value&name=value&name=value&name=value

  5. GET 请求和 POST 请求如何选择,什么时候使用 GET 请求,什么时候使用 POST 请求 ?

  6. HttpServlet 类是专门为 HTTP 协议准备的。比 GenericServlet 更加适合 HTTP 协议下的开发。

  7. HttpServlet 在哪个包下?这是在 Tomcat 10 的基础上:jakarta.servlet.http.HttpServlet,而后面 Tomcat 9 之前的(包括 Tomcat 9 )以内的包是在 : javax.servlet.http.HttpServlet包下的。

  8. 只要没有重写对应 HttpServlet 类中的 doGet 方法或 doPost 方法,就会报 405 错误,因为没有重写了 doGet 或 doPost 方法就会执行其中的 HttpServlet 当中编写的 doGet / doPost 方法,而如果执行了的是 HttpServlet 当中 doGet / doPost 方法就会报 405 错误,提示你没有重写 对应的 doGet / doPost 方法。怎么避免 405 的错误呢?

    后端重写了 doGet 方法,前端一定要发 get 请求。后端重写了 doPost 方法,前端一定要发 post 请求。这样可以避免 405 错误。这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。

  9. request 对象和 response 对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。一次请求对应一个 request。两次请求则对应两个 request。

  10. 后端如何获取前端浏览器用户提交的数据的常用方法。


Map<String,String[]> getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中<前端的name,前端的value>Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值 String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)valueString getParameter(String name)  获取value这个一维数组当中的第一个元素。这个方法最常用。// 以上的4个方法,和获取用户提交的数据有关系// 注意: 这里为什么 value 的值是用String[] 数组存储的。后面有说明。
复制代码


  1. HttpServletRequest 对象又称为:"请求域" 请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。一个请求对象 request 对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。如下是“请求域”的增删改操作的方法。


void setAttribute(String name, Object obj); // 向请求域当中绑定数据。Object getAttribute(String name); // 从请求域当中根据name获取数据。 注意: 这里的参数是 你向请求域添加数据时,设置的对应数据的,setAttribute(String name ,Object obj)一个 name 值保持一致,尽可以使用复制的方式,防止手动编写错误。导致无法找到,从而为 null 值。void removeAttribute(String name); // 将请求域当中绑定的数据移除
// 以上的操作类似于Map集合的操作。Map<String, Object> map;map.put("name", obj); // 向map集合中放key和valueObject obj = map.get("name"); // 通过map集合的key获取valuemap.remove("name"); // 通过Map集合的key删除key和value这个键值对。
复制代码
  1. 跳转 (两个 Servlet 共享数据)

  2. Post 请求以及 get 请求,response 响应的三者的中文乱码问题。

  3. HttpServletRequest 接口的常用方法。


文章转载自:Rainbow-Sea

原文链接:https://www.cnblogs.com/TheMagicalRainbowSea/p/17285259.html

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析_前端_EquatorCoco_InfoQ写作社区