Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析
每博一文案
命运总是不如人愿,但往往是在无数的痛苦总,在重重的矛盾和艰辛中,才是人成熟起来。你,为这瞬间的辉煌,忍耐了多少暗淡无光的日月,你会死亡,但你也会证明生命有多强大。一次邂逅,一次目光的交融,就是永远的合二为一,就是与上帝的契约;总是风暴雷电,也无法分解这种心灵的粘结。直到在哪远离故乡的地方发生过那场刻苦铭心的感谢悲剧后,他才理解了人活在世界上有多少幸福,又有多少苦难!生活不能等待别人来安排,要自己去争取和奋斗;而不论其结果是喜和悲,但可以慰籍,你总不枉在这世界上活了一场,有了这样的认识,你就会珍重生活,而不会玩世不恭;同时也会给人自身注入一种强大的内在力量...... —————— 《平凡的世界》路遥
@
目录
- Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析
- 每博一文案
- 1. HTTP协议解读
- 1.1 什么是HTTP协议 ?
- 1.2 HTTP请求协议的具体报文
- 1.3 HTTP响应协议的具体报文
- 2. GET请求和POST请求有什么区别?
- 2.1 GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求 ?
- 3. HttpServlet源码分析
- 3.1 HttpServlet 处理 get 请求和 post 请求 源码分析
- 4. HttpServletRequest接口详解
- 4.0.1 HttpServletRequest对象中都有什么信息?都包装了什么信息 ?
- 4.1 获取前端用户提交的数据信息
- 4.2 请求域对象的详解
- 4.3 跳转 (两个Servlet 共享数据)
- 4.4 post 请求 request 乱码问题 ?
- 4.5 response 响应到浏览器中的 乱码问题 ?
- 4.6 get 请求的 request 乱码问题 ?
- 4.7 HttpServletRequest接口的其他常用方法
- 5. 补充:
- 6. 总结:
- 7. 最后:
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 个部分: 请求行,请求头,空白行,请求体。
如何查看我们提交的数据报文,在浏览器当中
- 我们可以在提交数据的时候按 F12 快捷键查看,或者是右键鼠标浏览器 ,在弹出的窗口当中,选择点击 检查。如下图所示:
- 在选择点击检查按钮之后,弹出如下窗口,选择其中的 Network (网络)选项。如下图所示:只要当我们在前端提交了数据,就可以在如下的窗口当中捕获到并显示出相应的报文信息。
- 测试如下,看看是否可以捕获到我们的请求报文的信息数据。如下图所示:
- 查看我们提交的数据信息:点击我们捕获到的报文信息,再点击 ——> Paload 选项。
如下是:HTTP请求协议的具体报文:GET请求
首先编写一个是 Get 请求提交的表单 html 文件,具体的代码编写如下:
HTTP请求登录 get 请求
username:
userpassword:
post 请求
username:
userpassword:
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 # 空白行 # 响应体:其实就是源代码的信息 HTTP请求登录 get 请求
username:
userpassword:
post 请求
username:
userpassword:
另外一种 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 # 空白行 # 响应体:其实就是源代码的信息 HTTP请求登录 get 请求
username:
userpassword:
post 请求
username:
userpassword:
状态行 : 由三部分组成:协议版本号,状态码,状态描述信息。
- 状态行,第一部分: 协议版本号 (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请求的请求路径不同即可。
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897898
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897899
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=系统毫秒数
- 怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样(缓存的路径也是不一样的),浏览器就不走缓存了。
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 。
其中的 value 表示的是:同样的以 form 表单为例 :form 表单中的 input 标签当中的 value。
interest: smoke drink tangtou
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
就可以了,具体原因大家可以移步至:javaEE Web(Tomcat)深度理解 和 Servlet的本质_ChinaRainbowSea的博客-CSDN博客 中。- 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 接口编程,就可以获取请求协议当中的数据了。
- Tomcat 服务器(WEB 服务器) 将 “请求协议”中的数据全部解析出来,然后将这些数据全部封装到
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
请求。
HTTP请求登录 get 请求
username:
userpassword:
如下的是对应后端服务器 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("Hello World"); }}
如下运行结果: 报了 405 错误,原因是:我们前后端的处理请求的不一致。
当:前端发送的请求是post请求,后端程序员重写的方法是doGet 出现的错误
对于前端用户提交数据的html 代码设计如下:注意:这里我们前端提交的是 post
请求。
HTTP请求登录 post 请求
username:
userpassword:
如下的是对应后端服务器 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("Hello World"); }}
如下运行结果: 报了 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("Hello World"); }}
- 第三步:将Servlet类配置到web.xml文件当中。
HttpServletTest com.RainbowSea.servlet.HttpServletTest HttpServletTest /Test
- 第四步:准备前端的页面(form表单),form表单中指定请求路径即可
HTTP请求登录 get 请求
username:
userpassword:
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 {}
- 通过测试:就是通过 request.getClass() 方法获取到该接口的实现类,
测试结果说明: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 getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中Enumeration 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 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 key存储String value存储String[] // 将 value 值用 String 数组存储起来。这样就避免了上述的key 重复后 value内容上的覆盖,而没有都存储起来的缺点。 keyvalue ------------------------------- username{"abc"} userpwd{"111"} aihao{"learn","programme","playbasketball"}
举例使用上述四个方法获取前端用户提交的数据
第一步:编写设计好 html 前端显示页面,如下:注意,这里我们前端使用的是 post 请求,后端的Servlet 就要用 doPost 请求处理,前后端保持一致,不然报 405错误。
user register user register
username:
password:
interest: 学习: 编程: 打篮球:
第二步: 编写对应处理前端用户提交的请求的 Servlet ,注意前后端请求处理保持一致 : 这里举例使用的是上述四个获取用户请求的其中的一个方法:
Map getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中
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 parameterMap = request.getParameterMap(); // 遍历存储了前端用户提交数据的Map集合 // 获取到 Map 当中所有的 key 值(也就是前端的用户提交的所有的name 值) Set strings = parameterMap.keySet(); // 获取到 set 的迭代器 Iterator 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
文件中去。
RequestServletTest com.RainbowSea.servlet.RequestServletTest RequestServletTest /Request
第四步: 运行测试。
再举例: 这里的 Serlvelt 获取前端用户提交的数据使用: 如下两个方法
Enumeration 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 枚举泛型中 Enumeration names = request.getParameterNames(); while (names.hasMoreElements()) { // 判断是否还有数据,有返回 true,没有返回 false String name = names.nextElement(); // 获取到其中上述 Enumeration 存储到的元素数据, 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访问磁盘,其耗费的时间代价十分的大),大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。
- 关于 ServletContext 具体的使用大家可以移步至: 解读 Servlet 源码:GenericServlet,ServletConfig,ServletContext_ChinaRainbowSea的博客-CSDN博客
你见过哪些缓存技术呢 ?
- 字符串常量池
- 整数型常量池
[-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 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); // 显示到浏览器页面当中的数据 }}
AServlet com.RainbowSea.servlet.AServlet AServlet /A
关于 request对象中两个非常容易混淆的方法:
// uri?username=zhangsan&userpwd=123&sex=1String 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 接受的时候,会存在一个乱码问题 ?但是英文不会存在这个现象。如下:所示
HTTP请求登录 Post 请求
username:
userpassword:
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(" 你好世界 "); }}
解决方案:
// 在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(" 你好 世界 "); }}
4.6 get 请求的 request 乱码问题 ?
如果我们使用 get 请求中,处理中文乱码问题。
HTTP请求登录 get 请求
username:
userpassword:
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 文件,找到其中的如下这一段代码
在该 server.xml 配置文件当中的上述对应的一段代码上,多添加上一个字符集设置 URIEncoding="UTF-8"
注意 />
结尾就可以了。
// get请求乱码问题怎么解决?// get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的。// get请求乱码怎么解决// 方案:修改CATALINA_HOME/conf/server.xml配置文件// 注意:从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();
举例上述方法的使用:
TestServlet com.RainbowSea.servlet.TestServlet TestServlet /Test
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. 总结:
HTTP的请求协议包括:如下 4 个部分: 请求行,请求头,空白行,请求体。
HTTP的响应协议(S(浏览器) –> B(客户端))HTTP的响应协议包括4部分 : 状态行,响应头,空白行,响应体 。
GET请求和POST请求有什么区别 ?
不管你是get请求还是post请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:都是 :
name=value&name=value&name=value&name=value
GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求 ?
HttpServlet类是专门为 HTTP协议准备的。比 GenericServlet更加适合 HTTP协议下的开发。
HttpServlet在哪个包下?这是在 Tomcat 10 的基础上:
jakarta.servlet.http.HttpServlet
,而后面 Tomcat 9 之前的(包括Tomcat 9 )以内的包是在 :javax.servlet.http.HttpServlet
包下的。只要没有重写对应 HttpServlet类中的doGet方法或doPost方法,就会报 405 错误,因为没有重写了doGet 或 doPost 方法就会执行其中的 HttpServlet 当中编写的 doGet / doPost 方法,而如果执行了的是 HttpServlet 当中 doGet / doPost 方法就会报 405 错误,提示你没有重写 对应的 doGet / doPost 方法。怎么避免405的错误呢?
后端重写了doGet方法,前端一定要发get请求。后端重写了doPost方法,前端一定要发post请求。这样可以避免405错误。这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
request对象和response对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。一次请求对应一个request。两次请求则对应两个request。
后端如何获取前端浏览器用户提交的数据的常用方法。
Map getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中Enumeration getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值 String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)valueString getParameter(String name) 获取value这个一维数组当中的第一个元素。这个方法最常用。// 以上的4个方法,和获取用户提交的数据有关系// 注意: 这里为什么 value 的值是用String[] 数组存储的。后面有说明。
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 map;map.put("name", obj); // 向map集合中放key和valueObject obj = map.get("name"); // 通过map集合的key获取valuemap.remove("name"); // 通过Map集合的key删除key和value这个键值对。
- 跳转 (两个Servlet 共享数据)
- Post 请求以及get请求,response 响应的三者的中文乱码问题。
- HttpServletRequest接口的常用方法。
7. 最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!!!