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 (服务器)之间。BS发数据要遵循HTTP协议。SB发数据同样需要遵循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 文件,具体的代码编写如下:

        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 接口编程,就可以获取请求协议当中的数据了。
    • 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 {}

测试结果说明: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. 总结:

  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 getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中Enumeration 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 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接口的常用方法。

7. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!!!