当前位置:   article > 正文

初阶JavaEE(13)(安装、配置:Smart Tomcat;访问出错怎么办?Servlet初识、调试、运行;HttpServlet:HttpServlet;HttpServletResponse)_smart tomcat 报错context path cannot be empty

smart tomcat 报错context path cannot be empty

接上次博客:JavaEE初阶(12)HTTPS(“加密“ 、对称加密、非对称加密、所谓证书、Tomcat内容介绍和下载安装、部署静态页面、初识Servlet、第一个 Servlet 程序)-CSDN博客

目录

插件

安装 Smart Tomcat 插件

 配置Smart Tomcat插件

访问出错怎么办?

HTTP 404

HTTP 405错误

HTTP 500错误

出现"空白页面"

出现"无法访问此网站"

Servlet初识与调试小结

Servlet 运行原理

Tomcat的定位与HTTP请求处理流程

​编辑 Tomcat 的伪代码

Tomcat 处理请求流程 

 Servlet 的 service 方法的实现

Servlet API 详解

HttpServlet

核心方法 

Servlet的生命周期

HttpServlet: 处理 GET 请求

HTML文件:testMethod.html

部署与访问 Servlet

通过Fiddler抓包观察

解决乱码问题

处理POST请求

重新部署程序

发送POST请求 

其他HTTP方法

HttpServletRequest 

HttpServletRequest的主要功能

HttpServletRequest 接口核心方法

Tomcat通过Socket API解析HTTP请求

过程概述

细节

Jackson库

ObjectMapper 类

readValue 方法

writeValueAsString 方法

在服务器端处理JSON请求和响应

HttpServletResponse

概述

HttpServletResponse 对象的主要功能

1. 设置响应头信息

2. 获取输出流

3. 设置响应内容

4. 处理重定向

5. 处理错误响应

6. 缓存控制

7. 添加响应头部信息

 核心方法:

Tomcat 处理 HttpServletResponse

总结

HttpServletResponse

概述

HttpServletResponse 对象的主要功能

1. 设置响应头信息

2. 获取输出流

3. 设置响应内容

4. 处理重定向

5. 处理错误响应

6. 缓存控制

7. 添加响应头部信息

 核心方法:

设置一个错误页面:

通过setHeader给响应中设置一些特殊的header,比如,可以设置refresh,让浏览器每秒钟刷新一次页面:

构造一个重定向响应

Tomcat 处理 HttpServletResponse

总结


于是聪明的程序猿们又想出了新的解决办法,借助IDEA的插件,把Tomcat集成到IDEA中,此时就可以通过IDEA一键式的重新打包部署了。

插件是什么?

IDEA这样的程序已经功能已经非常强大了,但是即使如此也无法保证能面面俱到。

我们一方面希望IDEA功能能够多多益善,一方面又担心IDEA过于臃肿,于是引入了插件的机制来解决上述问题。

插件

插件(Plugin)是一种软件组件或模块,它可以被添加到已有的软件应用程序中,以扩展应用程序的功能或提供额外的特性。插件通常用于自定义、增强或改进现有的软件,使用户能够根据其需求扩展软件的功能。

插件通常具有以下特征:

  1. 可扩展性: 插件系统设计用于容纳新的功能。通过添加插件,用户可以根据需求自定义和扩展软件的功能,而无需修改软件的核心代码。

  2. 模块化: 插件是独立的模块,通常以独立的文件或库的形式存在。这使得插件可以被开发和维护独立于主应用程序。

  3. 功能增强: 插件通常用于添加新功能、工具、工作流或特性。例如,浏览器插件可以添加广告拦截、密码管理、翻译等功能。

  4. 定制性: 插件允许用户根据他们的需求自定义软件。这使得应用程序更具灵活性,能够满足不同用户的不同需求。

  5. 独立更新: 插件通常可以独立更新,而不必更新整个应用程序。这有助于保持软件的稳定性和安全性。

  6. 扩展性: 插件系统通常提供了一种扩展应用程序的标准方法,允许开发人员创建新的插件以满足用户需求。

插件是一种增加、扩展或改进软件应用程序功能的灵活方式,它使用户能够根据需要自定义其使用体验,可以在各种软件中找到,包括文本编辑器、集成开发环境(IDE)、Web浏览器、图形设计工具、音视频播放器等。例如,浏览器插件可以添加广告拦截功能,IDE插件可以增加新的编程语言支持,文本编辑器插件可以提供额外的编辑功能,等等。

综上,为了让Tomcat能够集成进来,我们就需要安装一个插件。

安装 Smart Tomcat 插件

  1. 进入设置:
    • 路径: 菜单 -> 文件 -> Settings
  2. 插件市场:
    • 选择 "Plugins"
    • 跳转到 "Marketplace"
    • 在搜索栏中输入 "tomcat"
    • 在搜索结果中找到 "Smart Tomcat" 并点击 "Install"
    • 注意: 安装插件需要联网
  3. 重启IDEA:
    • 插件安装完成后,IDEA会提示重启以完成安装

你可以看到我这里已经安装好了Smart Tomcat插件,你们就需要去右边的插件市场找,然后下载。 

当然,值得注意的一点就是,因为这个下载网站也是在国外,所以在国内访问可能会出现下载停滞的情况,这个时候你可以选择去手动下载安装:

 

下载之后你会得到一个JAR包,把这个包拖到idea里面它就会开始进行安装了。 

像是IDEA的专业版也内置了插件Tomcat Server,也能起到类似的效果,但是还是更推荐Smart Tomcat,因为用起来更简单,而且社区版专业版都支持。

 配置Smart Tomcat插件

  1. 添加配置:
    • 在IDEA右上角点击 "Add Configuration"
  2. 选择Smart Tomcat:
    • 在配置界面的左侧选择 "Smart Tomcat"
  3. 配置详细信息:
    • Name: 为此配置提供一个名字(例如:"MyTomcatDeployment")
    • Tomcat Server: 选择本地的Tomcat安装目录
    • Context Path: 默认是项目名称。这将影响后续访问URL的路径
    • 其他默认设置通常无需更改
  4. 启动Tomcat:
    • 点击 "OK" 保存配置
    • 在IDEA右上角点击绿色三角符号。这将触发编译、部署和启动Tomcat的过程
    • Tomcat的启动日志会在IDEA的控制台中显示,解决了之前可能存在的日志乱码问题
  5. 访问Web应用:

我这里是已经创建好了的,但是还是用图片给个参考: 

 

 上面的Context path 很重要,决定了 后续你在浏览器中访问这个Servlet的时候第一级路径怎么写。没有使用Smart Tomcat的时候是直接写作WAR包的名字的(目录名),但是使用Smart Tomcat,Context path 就需要你手动配置,不配置默认就是项目名字。

最后点一下就可以跑起来: 

点击运行按钮之后,Tomcat就跑起来了,Tomcat的日志就显示在idea的控制台上了: 

与此同时,乱码问题也消失了。

但是,一般会显示:

这个提示说明程序退出了(Tomcat退出了),说明Tomcat没有启动成功。

报错信息说的非常清楚: 

这是一个非常常见的报错,说明我们当前要绑定的端口已经被别人占用了。

Tomcat启动要绑定两个端口:8080(业务端口),8005(管理端口)。

那么被谁占用着?

被我们刚刚通过控制台的方式启动的Tomcat占用了!

关掉后重新运行:

成功!提示启动时间768ms。 

但是不要点这个链接:

回想一下我们之前访问程序的时候是通过context path和servlet path 两级目录访问的,但是这里只有一级目录结构。

所以我们自己补充完整:

同样的: 

这里的localhost是一个域名,往往被解析成 127.0.0.1。 

通常情况下,"localhost" 和 "127.0.0.1" 是等价的,它们都用于本地主机(即你的计算机)的循环地址。循环地址是一种特殊的IP地址,用于在同一台计算机上进行网络通信。在绝大多数情况下,它们可以互换使用,用于本地计算机上的服务访问和测试。

然而,有一些情况下它们可能不等价:

  1. 主机名解析问题:在某些情况下,操作系统或网络配置可能会影响主机名的解析。在这种情况下,"localhost" 可能无法正确解析为本地回环地址。而直接使用 "127.0.0.1" 通常可以确保使用正确的地址。

  2. IPv6支持:"localhost" 可以解析为IPv4地址(通常是 "127.0.0.1"),但也可以解析为IPv6地址(通常是 "::1")。某些应用程序可能会优先选择IPv6,而另一些则可能出于各种原因更喜欢IPv4。

在绝大多数情况下,无论你使用 "localhost" 还是 "127.0.0.1",都不会有问题。然而,如果你在特定的情况下遇到问题,可以尝试使用 "127.0.0.1" 作为备选方案,以确保本地主机的访问。

 现在我们可以修改一下重新运行:

哎???等会儿?汉字乱码了??? 

乱码意味着有多个环节对于编码方式的理解不一致,比如构造数据按照UTF8,但是解析数据却是按照GBK,这就很容易出现乱码。

所以你需要梳理清楚程序运行过程中哪些环节涉及到对于编码的处理;还得明白每个环节都编码都是什么。

构造数据:此时“你好”这两个字是直接在idea的代码编辑器中构造的,这个编码方式就看idea的代码编辑器是什么编码:

 解析数据:浏览器的默认的解析方式是跟随系统的,Windows 10 简体中文版使用的字符集是GBK。

一般来说我们选择把GBK改成UTF-8。

所以我们需要在HTTP的响应报文中显示的告诉浏览器,返回的body字符集是什么:

  1. @Override
  2. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  3. resp.setContentType("text/html;charset=utf8");
  4. // 这个打印是显示在 tomcat 的控制台上 (服务器端)
  5. System.out.println("hello java");
  6. // 把内容显示到页面上. 把 "hello world" 字符串作为 http 响应的 body 部分.
  7. // 这个部分就会被浏览器显示到页面上.S
  8. resp.getWriter().write("hello java");
  9. }

此时,在HTTP响应报文的header中就会有Content-Type,这里就描述了body的数据格式以及编码方式。

再次运行:

访问出错怎么办?

HTTP 404

出现404错误表示用户访问的资源不存在,通常是由于URL路径写错或与服务器中的资源不匹配导致的。

以下是一些可能出现的错误情况以及对应的处理方法:

  1. 错误实例1: 少写了 Context Path

    • 尝试通过 /hello 访问服务器时出现错误
    • 解决方法:添加正确的Context Path到URL
    • 例如:/YourContextPath/hello
  2. 错误实例2: 少写了 Servlet Path

    • 尝试通过 /ServletHelloWorld 访问服务器时出现错误
    • 解决方法:确保在URL中包含正确的Servlet Path
    • 例如:/YourContextPath/ServletHelloWorld
  3. 错误实例3: Servlet Path写错

    • Servlet Path在代码中的路径和URL中的路径不匹配
    • 解决方法:
      • 修改@WebServlet注解的路径,确保与URL中的路径匹配
      • 重新启动Tomcat服务器
  4. 错误实例4: web.xml写错

    • web.xml中的内容有误,导致Tomcat服务器无法正确映射请求
    • 解决方法:
      • 清除web.xml中的内容,或修复其中的错误
      • 重新启动Tomcat服务器
  5. 查看Tomcat启动错误

    • 如果在启动Tomcat时出现错误提示,可通过浏览器访问URL以查看具体的错误信息
    • 根据错误信息修复代码或配置问题
    • 重新启动Tomcat服务器

HTTP 405错误

HTTP 405表示对应的HTTP请求方法没有被实现。这通常是由于没有正确实现HTTP请求方法,比如没有实现doGet方法。

错误示例: 没有实现 doGet 方法

  • 问题描述:没有在Servlet中实现doGet方法法。
  • 解决方法:确保在Servlet中实现正确的HTTP请求方法,如doGet。
  • 重启Tomcat服务器,以应用更改。

同时,如果你忘了删除之前代码中自动添加的super.doGet( ),也会出现405,此时的响应的格式已经不是合法的HTTP了,导致浏览器出错。 

HTTP请求方法

HTTP请求方法是定义在HTTP协议中的动作。常见的HTTP请求方法包括:

  • GET: 用于获取资源。
  • POST: 用于提交数据。
  • PUT: 用于更新资源。
  • DELETE: 用于删除资源。
  • HEAD: 与GET相同,但只获取响应头而不获取响应体。
  • 等等。

HTTP 500错误

HTTP 500错误通常是由Servlet代码中的异常引起的。这个错误会在服务器端抛出异常,通常是由错误的代码引起的。

服务器的内部错误会在日志或者响应中带有异常信息,你可以很容易的找到问题。

响应中还会非常清楚的告诉你里面出现了什么异常、在什么位置……

  1. @WebServlet("/hello")
  2. public class HelloServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. String s = null;
  6. resp.getWriter().write(s.length());
  7. }
  8. }

  • 问题描述:在代码中出现了空指针异常。
  • 解决方法:仔细检查代码,查找异常的具体原因,并修复它。在异常信息中,通常会包含出错的代码行号。
  • 重启Tomcat服务器,以应用更改。

不一定是空指针异常,任何一个异常如果没有很好的catch住,都有可能出现上述问题。 

异常处理

在Servlet中,异常处理非常重要。开发人员应该学会如何捕获和处理异常,以提高应用程序的稳定性。可以使用try-catch块来捕获异常,然后采取适当的措施来处理它们,例如记录错误信息或返回适当的错误响应。

出现"空白页面"

这种情况通常是由于Servlet代码中的响应操作不正确导致的,通常是由于没有正确的响应内容。

这个时候就需要检查服务器是否真的返回了带有正文的响应报文。浏览器显示的内容就是HTTP的响应的正文。

  1. @WebServlet("/hello")
  2. public class HelloServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. System.out.println("hello");
  6. }
  7. }

  • 问题描述:Servlet中去掉了resp.getWriter().write()操作,导致没有响应内容。
  • 解决方法:确保Servlet提供正确的响应内容,可以使用resp.getWriter().write()等方法来输出内容。
  • 重启服务器,以应用更改。

HTTP响应

HTTP响应通常包括响应头和响应体。响应头包含有关响应的信息,如状态码和内容类型。响应体包含实际的响应内容。在Servlet中,你可以使用resp.getWriter().write()或resp.getOutputStream().write()来设置响应体。

出现"无法访问此网站"

这通常是因为Tomcat启动失败导致的。可能你把Tomcat关闭了或者你的Servlet路径设置不正确。在这种情况下,Tomcat在启动时就会报错。

错误示例

  • 问题描述:如果检查之后发现Tomcat确实在正常工作,可能是IP地址或者端口号错误、Servlet Path设置不正确,导致Tomcat启动失败。
  • 解决方法:检查Tomcat启动日志,查找关键的错误提示,通常会显示有关Servlet路径的错误。
  • 修复Servlet路径设置,重新启动Tomcat服务器。

拓展知识点: Tomcat启动问题

Tomcat启动问题可能与多种因素有关,包括端口冲突、配置错误等。查看Tomcat启动日志,理解其中的错误信息,通常可以帮助解决启动问题。

Servlet初识与调试小结


学习Servlet是Web开发的基础,但这一过程中经常会遇到各种问题。正确地编写Servlet代码只是挑战的一部分,更关键的是学会如何有效地调试和排查问题。

  1. 程序猿的调试思维:

    • 调试与医生诊断疾病相似,都需要准确地找到问题所在并解决。
    • 经验丰富的开发者与新手的区别通常不仅仅在于代码编写能力,更重要的是调试的效率。有时候新手可能花费数天甚至更长时间来定位一个问题,而有经验的开发者可能在短时间内就找到了问题的根源。
    • 开发不仅是一份技术工作,更多地需要对问题的敏锐洞察和有效的解决方法。
  2. HTTP协议的重要性:

    • 了解HTTP协议对于调试Web应用是非常有帮助的。知道各种状态码的含义,可以更快地确定问题的范围。
      • 4xx状态码:通常表示客户端错误。例如,404表示找不到资源,这时需要检查URL、Context Path和Servlet Path是否正确。
      • 5xx状态码:表示服务器端错误。在这种情况下,我们需要查看服务器的错误日志和页面上的错误提示。
  3. 常见的问题及其解决方法:

    • 如果连接失败,可能是Tomcat没有正确启动。这时,我们需要检查Tomcat的日志,看是否有错误提示。
    • 如果出现空白页面,我们可以使用抓包工具(如Wireshark或浏览器开发者工具)来观察HTTP请求和响应的交互过程。
  4. 日志的重要性:

    • 观察日志是识别和解决问题的关键。Tomcat的日志中包含了大量的信息,我们需要学会如何从中快速地找到有关的提示。
    • 与其盲目地尝试各种可能的解决方案,不如花一些时间仔细阅读和分析日志,这样可以更准确、更快速地找到问题所在。

总之,Servlet的学习和调试是一个系统的过程,需要结合HTTP知识、日志分析和实践经验。只有不断地遇到问题、解决问题,才能真正掌握Servlet和Web开发的技能。

 以上,我们主要是介绍了Servlet 的 hello world 的写法,也就是使用Smart Tomcat和几种常见的问题。接下来我么还要来学习Servlet API里面的详细情况。

我们主要介绍三个类,掌握了这三个类,就已经可以完成Servlet的大部分开发了:

1、HttpServlet ; 2、HttpServletRequest ; 3、HttpServletResponse。

Servlet 运行原理

HttpServlet写一个Servlet代码,往往都是要继承这个类,重写里面的方法。但是:

在 Servlet 的代码中我们并没有写 main 方法, 那么对应的 doGet 代码是如何被调用的呢? 响应又是如何返回给浏览器的?

就拿我们刚刚写的那个代码为例,我们来描述Servlet处理HTTP请求的一个简化流程,这有助于理解Servlet的工作原理。

  1. 通过TCP Socket读取请求:

    • 首先,Tomcat作为HTTP服务器,使用TCP Socket接收来自客户端浏览器的HTTP请求。HTTP是基于TCP的协议,因此通过Socket来传输HTTP请求数据。
  2. 解析请求:

    • Tomcat需要解析接收到的HTTP请求。这包括解析请求行、请求头、请求体等,以便理解客户端的请求意图以及请求中包含的信息。解析后的请求数据可以被封装成一个HttpServletRequest对象。
  3. 构造响应对象:

    • 在处理请求之前,Tomcat会创建一个空的HttpServletResponse对象,用于构建HTTP响应。这个响应对象包含了HTTP响应的相关信息,如状态码、响应头和响应体。
  4. 创建Servlet实例:

    • 根据请求的URL,Tomcat确定要使用哪个Servlet来处理请求。Tomcat会创建该Servlet的实例,例如,使用new HelloServlet()来创建HelloServlet的实例。也就是说,Tomcat能够自动new出来一个我们写的子类。
  5. 调用Servlet的方法:

    • 一旦Servlet实例被创建,Tomcat会调用Servlet的方法,例如,s.doGet(req, resp),这是根据请求方法(GET、POST等)来确定的。在这个阶段,我们的Servlet的业务逻辑被执行,它可以访问HttpServletRequest对象来获取请求信息,然后使用HttpServletResponse对象构建响应(也就相当于我们的代码被“嵌入”进了Tomcat内部运行了,相当于我们自定义了Tomcat的行为)。
  6. 返回响应给浏览器:

    • Servlet在处理请求后,已经将响应数据设置在HttpServletResponse对象中,Tomcat将这个响应数据转换为HTTP响应字符串,并通过TCP Socket发送回客户端浏览器。浏览器接收响应后,根据响应内容渲染页面或执行其他操作。

以上这个流程展示了Servlet如何接受HTTP请求、处理请求并生成HTTP响应。Servlet充当了处理请求的桥梁,完成了99%的通用的、公共的工作,我们作为开发者,可以在Servlet中编写业务逻辑以实现各种Web应用程序,只用补充好1%的专有化的自定义的工作,所以这种模型的好处是可以将处理请求的逻辑模块化,以简化Web应用程序的开发和维护。

再详细一点分析:

Servlet是基于Java的服务器端技术,其运行原理涉及到Servlet容器(如Tomcat)的工作方式:

  1. Servlet容器:Servlet容器是一个Web服务器或应用服务器的一部分,负责托管和管理Servlet。最常见的Servlet容器之一是Apache Tomcat。

  2. Servlet的部署:在Web应用程序中,Servlet通常以Java类的形式编写,并部署到Servlet容器中。Servlet容器根据应用程序的配置将Servlet与特定的URL映射关联。

  3. 请求到达Servlet容器:当用户在浏览器中输入URL并按下回车,或者通过其他方式向Web服务器发出HTTP请求时,该请求将到达Servlet容器。

  4. URL到Servlet的映射:Servlet容器根据URL映射表,将请求映射到相应的Servlet。这通常是通过Web应用程序的部署描述符(web.xml文件)或Servlet注解(@WebServlet)来配置的。

  5. 调用Servlet的service方法:一旦Servlet容器确定了要处理请求的Servlet,它会创建一个Servlet实例(如果没有已创建的实例),然后调用Servlet的service方法。

  6. service方法:service方法接收HTTP请求对象(HttpServletRequest)和HTTP响应对象(HttpServletResponse),并根据请求类型(GET、POST等)调用适当的方法,如doGet或doPost。

  7. 处理请求:在doGet或doPost方法中,Servlet可以执行业务逻辑,生成响应内容,与数据库交互等。Servlet可以访问请求参数、会话信息和应用程序范围的数据。

  8. 生成响应:Servlet生成HTTP响应,将内容写入HttpServletResponse对象,这通常包括HTML、JSON、XML等数据。Servlet还可以设置响应头、状态码和Cookie等信息。

  9. 响应返回给浏览器:Servlet容器将生成的HTTP响应返回给客户端浏览器,浏览器根据响应内容渲染页面或执行其他操作。

总结来说,Servlet的运行原理是基于Servlet容器的,它接收HTTP请求,根据URL映射到相应的Servlet,调用service方法,处理请求并生成响应,最终将响应返回给浏览器。Servlet容器处理了与HTTP请求和响应相关的许多底层细节,使开发者能够专注于业务逻辑的编写。

作为初学者,为了更好的理解相关内容,我们可以来看看两个简单的代码:

例1:接收表单数据并返回响应:

考虑一个简单的Servlet示例,用户在浏览器中填写一个表单,然后点击提交按钮,Servlet接收表单数据并返回响应。

  1. @WebServlet("/form")
  2. public class FormServlet extends HttpServlet {
  3. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  4. // 从请求中获取表单参数
  5. String name = request.getParameter("name");
  6. String email = request.getParameter("email");
  7. // 构造响应
  8. response.setContentType("text/html");
  9. PrintWriter out = response.getWriter();
  10. out.println("<html><body>");
  11. out.println("<h2>Hello, " + name + "!</h2>");
  12. out.println("<p>Your email is: " + email + "</p>");
  13. out.println("</body></html>");
  14. }
  15. }

例2:重定向到另一个页面 :

在这个示例中,Servlet接收一个HTTP请求,然后将请求重定向到另一个页面。 

  1. @WebServlet("/redirect")
  2. public class RedirectServlet extends HttpServlet {
  3. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  4. // 设置响应状态码和重定向URL
  5. response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); // 302状态码
  6. response.setHeader("Location", "https://www.example.com/redirected-page");
  7. }
  8. }

在这两个示例中,Servlet通过HttpServletRequest接收请求信息,然后使用HttpServletResponse构造响应。第一个示例接收表单数据,将其嵌入HTML响应中。第二个示例将HTTP请求重定向到另一个URL。这些简单的示例说明了Servlet如何与HTTP请求和响应交互。

Tomcat的定位与HTTP请求处理流程

Tomcat是一个开源的Servlet容器,用于托管基于Java的web应用程序。它同时也具备了一个基础的HTTP服务器功能,可以接收HTTP请求并处理返回相应的HTTP响应。下面是Tomcat如何处理HTTP请求的详细流程:

  1. 接收请求:

    • 当用户在浏览器中输入URL,浏览器会创建一个HTTP请求。
    • 该HTTP请求通过网络协议栈被封装成二进制bit流,并经过物理层转化为光信号或电信号。
    • 经过Internet上的多个网络设备,这些信号最终到达目标服务器。
    • 服务器接收到这些信号,并通过其网络协议栈对其进行解码,还原为HTTP请求。
    • 根据服务器上配置的端口号,该HTTP请求被发送到Tomcat进程。
    • Tomcat接收到HTTP请求后,首先根据Context Path确定webapp,接着通过Servlet Path确定要处理该请求的具体Servlet类。
    • 最后,Tomcat根据HTTP请求方法(GET/POST等)决定调用Servlet的哪一个方法(例如doGet或doPost)。
  2. 处理请求并生成响应:

    • 一旦请求被映射到特定的Servlet,Tomcat就会调用该Servlet的相应方法(如doGet或doPost)来处理请求。
    • 开发者在Servlet中的代码会执行,处理请求,并根据请求内容在HttpServletResponse对象中设置响应信息,如状态码、响应头和响应体等。
  3. 返回响应:

    • 一旦Servlet处理完成,Tomcat会从HttpServletResponse对象中取得响应数据,并将其转换为符合HTTP协议的字符串格式。
    • 该HTTP响应再次经过服务器的网络协议栈被封装为二进制bit流,然后转换为光信号或电信号发送回客户端。
    • 经过Internet上的一系列网络设备,这些信号最终到达用户的计算机。
    • 用户计算机上的网络协议栈解码这些信号,还原为HTTP响应,并传递给浏览器。
    • 浏览器解析HTTP响应,并根据响应内容渲染页面或执行其他操作。

整个流程涉及多个协议、硬件和软件组件的协同工作。Tomcat在此过程中起到核心的角色,不仅作为HTTP服务器接收请求,还作为Servlet容器处理请求并生成响应。

 Tomcat 的伪代码

下面的代码通过 "伪代码" 的形式描述了 Tomcat 初始化/处理请求这两部分核心逻辑。

所谓 "伪代码" 并不是一些语法严谨、功能完备的代码, 只是通过这种形式来大概表达某种逻辑。

  1. class Tomcat {
  2. // 用来存储所有的 Servlet 对象
  3. private List<Servlet> instanceList = new ArrayList<>();
  4. public void start() {
  5. // 根据约定,读取 WEB-INF/web.xml 配置文件;
  6. // 并解析被 @WebServlet 注解修饰的类
  7. // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
  8. Class<Servlet>[] allServletClasses = ...;
  9. // 这里要做的的是实例化出所有的 Servlet 对象出来;
  10. for (Class<Servlet> cls : allServletClasses) {
  11. // 这里是利用 java 中的反射特性做的
  12. // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
  13. // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
  14. // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
  15. Servlet ins = cls.newInstance();
  16. instanceList.add(ins);
  17. }
  18. // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
  19. for (Servlet ins : instanceList) {
  20. ins.init();
  21. }
  22. // 利用我们之前学过的知识,启动一个 HTTP 服务器
  23. // 并用线程池的方式分别处理每一个 Request
  24. ServerSocket serverSocket = new ServerSocket(8080);
  25. // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
  26. ExecuteService pool = Executors.newFixedThreadPool(100);
  27. while (true) {
  28. Socket socket = ServerSocket.accept();
  29. // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
  30. pool.execute(new Runnable() {
  31. doHttpRequest(socket);
  32. });
  33. }
  34. // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
  35. for (Servlet ins : instanceList) {
  36. ins.destroy();
  37. }
  38. }
  39. public static void main(String[] args) {
  40. new Tomcat().start();
  41. }
  42. }
  1. Tomcat初始化流程:

    • 在start方法中,Tomcat首先读取WEB-INF/web.xml配置文件,并解析被@WebServlet注解修饰的类,以获得所有Servlet类。
    • Servlet类的加载通常会涉及自定义类加载器,以确保类字节码正确加载。
    • 创建Servlet实例,将其存储在instanceList中。
    • 调用每个Servlet对象的init方法,这个方法在Servlet的生命周期中只会被调用一次,用于初始化Servlet。
    • 启动HTTP服务器并使用线程池的方式处理每个HTTP请求。Tomcat会监听端口8080,接受传入的Socket连接,并为每个请求创建一个线程来处理。
  2. 处理HTTP请求:

    • 对于每个传入的Socket连接,Tomcat创建一个独立的线程来执行doHttpRequest方法,其中socket表示HTTP请求的Socket连接。
    • 在doHttpRequest方法中,Tomcat可以从Socket中读取HTTP请求内容,解析请求行、请求头、请求体等,以了解客户端的请求。
    • 然后,Tomcat选择要处理请求的Servlet对象,通常根据请求的URL映射到合适的Servlet。
    • 最后,Tomcat调用匹配的Servlet的doGet或doPost方法来处理请求,并生成HTTP响应。
  3. Tomcat关闭:

    • 当Tomcat关闭或终止时,它迭代instanceList中的每个Servlet对象,并调用它们的destroy方法以执行清理操作。这个方法在Servlet的生命周期中只会被调用一次。

这个伪代码概括了Tomcat的工作原理,尤其是如何初始化Servlet、处理HTTP请求以及关闭Tomcat。这有助于我们更好地理解Servlet容器的内部工作。

当然,实际的Tomcat代码要比伪代码复杂得多,这个示例只是提供了一个清晰的概念。

里面的一些帮助你理解、你需要了解的概念: 

  1. Tomcat的main方法:

    • Tomcat代码中内置了main方法,当启动Tomcat时,执行main方法是从哪里开始的。这个方法负责Tomcat的启动和初始化。
  2. @WebServlet注解:

    • 类被@WebServlet注解修饰后,Tomcat在启动时会扫描并获取这些类,以便管理它们。这允许Tomcat动态地知道哪些类可以处理HTTP请求。
  3. 反射机制:

    • Tomcat使用反射机制来创建被@WebServlet注解修饰的类的实例。这允许Tomcat在运行时实例化Servlet类。
  4. 初始化和销毁:

    • 在Servlet生命周期中,Tomcat在创建实例后会调用Servlet的init方法进行初始化。这是一个适合执行一次性设置的方法。同样,在销毁Servlet实例之前,Tomcat会调用destroy方法以执行清理工作。开发者可以重写这些方法以执行自定义初始化和清理操作。
  5. Socket API:

    • Tomcat内部使用Socket API来进行网络通信。它监听指定的端口并接受传入的HTTP请求,然后使用Socket来处理通信。
  6. 多线程处理HTTP请求:

    • 为了同时处理多个HTTP请求,Tomcat采用多线程的方式。对于每个传入的HTTP请求,Tomcat会创建一个独立的线程来处理,以提高性能和并发性。因此,Servlet必须能够在多线程环境下安全地处理请求,开发者需要确保Servlet的线程安全性。

Tomcat 处理请求流程 

  1. class Tomcat {
  2. void doHttpRequest(Socket socket) {
  3. // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
  4. HttpServletRequest req = HttpServletRequest.parse(socket);
  5. HttpServletRequest resp = HttpServletRequest.build(socket);
  6. // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内
  7. // 直接使用我们学习过的 IO 进行内容输出
  8. if (file.exists()) {
  9. // 返回静态内容
  10. return;
  11. }
  12. // 走到这里的逻辑都是动态内容了
  13. // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
  14. // 最终找到要处理本次请求的 Servlet 对象
  15. Servlet ins = findInstance(req.getURL());
  16. // 调用 Servlet 对象的 service 方法
  17. // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
  18. try {
  19. ins.service(req, resp);
  20. } catch (Exception e) {
  21. // 返回 500 页面,表示服务器内部错误
  22. }
  23. }
  24. }

总结: 

  1. HTTP请求解析: Tomcat从Socket中读取到的HTTP请求是一个字符串,它按照HTTP协议的格式进行解析,最终转换成一个HttpServletRequest对象,包含了请求的详细信息。

  2. 静态资源和动态资源: Tomcat会根据URL中的路径判断请求是针对静态资源还是动态资源。如果是静态资源(如HTML、CSS、JavaScript文件),Tomcat直接找到对应文件并将文件内容通过Socket返回。如果是动态资源,才会执行到Servlet的相关逻辑。

  3. URL路径解析: Tomcat使用URL中的Context Path和Servlet Path来确定要调用哪个Servlet实例的、service方法。Context Path通常表示Web应用程序的根路径,Servlet Path则表示与Servlet相关的路径。

  4. Servlet的调用: 通过service方法,Tomcat会进一步调用到我们编写的doGet或doPost等方法,具体取决于HTTP请求的方法(GET、POST等)。这些方法包含了我们自己编写的处理逻辑,用于生成HTTP响应。

  5. 异常处理: 如果在Servlet的处理过程中发生异常,Tomcat可以捕获并返回HTTP 500错误页面,表示服务器内部错误。​​​​​​

这个伪代码提供了Tomcat内部处理HTTP请求的简化视图,帮助理解Tomcat如何管理请求,路由到正确的Servlet,并生成响应。

 Servlet 的 service 方法的实现

  1. class Servlet {
  2. public void service(HttpServletRequest req, HttpServletResponse resp) {
  3. String method = req.getMethod();
  4. if (method.equals("GET")) {
  5. doGet(req, resp);
  6. } else if (method.equals("POST")) {
  7. doPost(req, resp);
  8. } else if (method.equals("PUT")) {
  9. doPut(req, resp);
  10. } else if (method.equals("DELETE")) {
  11. doDelete(req, resp);
  12. }
  13. ......
  14. }
  15. }

总结: 

  1. Servlet的service方:

    • service方法根据当前HTTP请求的方法(GET、POST、PUT、DELETE等)来确定应该调用哪个doXXX方法。
  2. 多态机制:

    • 通过使用多态机制,Servlet的service方法在运行时可以动态地调用与请求方法对应的doXXX方法。这是Java中面向对象编程的一个关键特性,也是Servlet框架的核心。
  3. 继承关系:

    • Servlet是一个抽象类,HttpServlet是Servlet的子类,而自己编写的Servlet类(如HelloServlet)通常是HttpServlet的子类。
    • 通过继承,自定义的Servlet类继承了HttpServlet的行为,包括service方法,但可以重写doXXX方法以执行特定的请求处理逻辑。
  4. 多态的体现:

    • 在Tomcat启动阶段,Tomcat已经创建了Servlet实例(例如HelloServlet),并将其放入Servlet数组中。然后,根据请求的URL获取到了相应的Servlet实例,但是使用了父类引用(Servlet ins)。
    • 最终,通过ins.doGet()这样的代码调用doGet方法时,由于"父类引用指向子类对象",触发了多态机制,从而执行了自定义的doGet方法,这个方法在HelloServlet中实现。
    • 等价代码:
      1. Servlet ins = new HelloServlet();
      2. ins.doGet(req, resp);
       

我们需要理解Servlet中的继承和多态机制,以及如何根据HTTP请求方法动态调用适当的方法,实现特定的请求处理逻辑。多态是面向对象编程的重要概念,Servlet框架充分利用了这一特性来处理各种HTTP请求。

Servlet API 详解

HttpServlet

HTTPServlet是Java EE的一部分,它扩展了通用Servlet类,专门用于处理HTTP请求和响应。在编写Servlet代码时,继承HttpServlet是非常常见的做法,因为它提供了处理HTTP请求的基本结构和方法。

我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet, 并重写其中的某些方法。

核心方法 

这几个方法都是可以在子类中重写的,在子类重写这些方法之后,这些方法也都是不需要我们手动调用的,都是Tomcat会在合适的时机自行调用:

  1. init

    • 调用时机:在HttpServlet实例化之后,第一次创建Servlet对象时调用,仅在Servlet的生命周期内调用一次。
    • 作用:通常用于执行一次性的初始化任务,例如加载配置信息或建立数据库连接。
  2. destroy

    • 调用时机:在Servlet不再使用,即Servlet的生命周期结束时调用,仅在Servlet销毁前调用一次。
    • 作用:通常用于执行清理工作,例如关闭数据库连接或释放资源
  3. service

    • 调用时机:每次收到HTTP请求时都会调用,用于处理请求分派到适当的HTTP方法处理方法。
    • 作用:根据HTTP请求方法(GET、POST、PUT、DELETE等)来选择调用适当的doXXX方法,以处理具体的请求。
  4. doGet

    • 调用时机:由service方法调用,处理HTTP GET请求时调用。
    • 作用:用于处理GET请求,获取请求参数、处理数据并生成响应。
  5. doPost

    • 调用时机:由service方法调用,处理HTTP POST请求时调用。
    • 作用:用于处理POST请求,通常用于提交表单数据或进行其他数据修改操作。
  6. 其他HTTP方法的处理

    • 除了doGet和doPost,还可以重写其他HTTP方法处理方法,如doPut、doDelete、doOptions等。这些方法由service方法根据请求方法调用。
    • 作用:用于处理与相应HTTP方法相关的操作,例如在doPut方法中处理HTTP PUT请求,或在doDelete方法中处理HTTP DELETE请求。

这些方法提供了灵活性,使我们能够根据不同的HTTP请求方法执行特定的逻辑。通过适当重写这些方法,Servlet可以有效地处理各种类型的HTTP请求。

这三个方法(init、destroy、service)与Servlet的生命周期密切相关,它们在不同阶段调用,用于管理Servlet的生命周期。

Servlet的生命周期通常如下:

  1. Servlet容器(如Tomcat)启动时,会实例化Servlet对象。
  2. 在实例化过程中,调用Servlet的init方法进行初始化。
  3. Servlet容器可以在Servlet的生命周期内多次调用service方法,以处理不同的HTTP请求。
  4. 当Servlet容器关闭或卸载Servlet应用程序时,会调用Servlet的destroy方法来执行清理工作。

当然,上述三个方法在实际开发中很少会被用到,一般都是出现在面试题里。imit还算是比较有用,但是service就一般会被 doGet/doPost替代,至于destroy,就处在一个更尴尬的位置上,这个方法大概率是执行不到的:

当一个Servlet不用了,说明Tomcat要关闭了。关闭有两种方式:

1、直接干掉Tomcat进程(比如直接再任务管理器中结束任务或者直接点“×”)。此时是完全来不及调用destroy的;

2、通过8005管理端口,给Tomcat发送一个“停机”的指令,这个时候是能够执行destr的。

但是实际开发中我们基本上用到的都是第一种方式。

至于为什么service就一般会被替代?

如果你没有重写service方法,Servlet容器会使用HttpServlet父类自己的service方法来进行请求分发。这个service方法会先解析出请求的HTTP方法,然后根据方法类型分别调用doGet、doPost、doPut、doDelete、doOptions等方法中的一个,以处理请求。这是根据HTTP方法的条件判断来分发请求的。

举个例子,如果客户端发起了一个GET请求,service方法会调用doGet方法来处理该请求;如果客户端发起了一个POST请求,service方法会调用doPost方法来处理该请求,以此类推。

但是一般情况下,开发者通常会重写这些方法中的一个或多个,以便在特定HTTP方法下执行他们自己的逻辑。例如,可以在doGet方法中处理GET请求,而在doPost方法中处理POST请求。这样,Servlet就可以根据请求方法来执行不同的操作。

Servlet的生命周期

Servlet的生命周期是Servlet实例从被创建到销毁的整个过程,它经历了一系列的阶段,包括初始化、处理请求和销毁。以下是Servlet的典型生命周期:

  1. 实例化(Instantiation):当Servlet容器(如Tomcat)启动或在首次接收到与Servlet相关的请求时,会创建一个Servlet实例。这是Servlet生命周期的第一阶段。在这个阶段,Servlet容器会调用Servlet的构造函数来创建Servlet对象。

  2. 初始化(Initialization):在Servlet实例化后,Servlet容器会调用Servlet的init(ServletConfig)方法,用于执行一次性的初始化工作,例如加载配置信息、建立数据库连接等。init方法只在Servlet的生命周期内调用一次。

  3. 请求处理(Request Handling):一旦Servlet实例初始化完成,Servlet容器会开始处理HTTP请求。每次接收到与该Servlet关联的请求,都会调用service(ServletRequest, ServletResponse)方法。service方法根据请求的HTTP方法(GET、POST、PUT、DELETE等)分派到适当的doXXX方法(如doGet、doPost等)来处理请求。service方法在Servlet的整个生命周期中可能会被多次调用,每次处理不同的请求。

  4. 销毁(Destruction):当Servlet容器关闭或卸载Servlet应用程序时,会调用Servlet的destroy()方法。在destroy方法中,可以执行清理工作,如关闭数据库连接或释放资源。与init方法一样,destroy方法只在Servlet的生命周期内调用一次。

  5. 卸载(Unloading):在Servlet应用程序被卸载或Servlet容器关闭时,Servlet实例会被销毁,即Servlet对象被垃圾回收。这标志着Servlet的生命周期的结束。

Servlet的生命周期是由Servlet容器管理的,通过编写init、service和destroy等方法来定义Servlet的行为。Servlet容器负责在适当的时机调用这些方法,以便正确处理HTTP请求并在不需要时执行清理工作。Servlet生命周期的管理和调用由Servlet容器自动完成,我们只需关注编写相应的方法以控制Servlet的行为。

HttpServlet: 处理 GET 请求

创建一个名为MethodServlet.java的Servlet类,创建并实现doGet方法以处理GET请求。

  1. @WebServlet("/method")
  2. public class MethodServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. resp.getWriter().write("GET response");
  6. }
  7. }

HTML文件:testMethod.html
  • 创建一个HTML文件名为testMethod.html,并将其放置在webapp目录中,内容如下:
    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <title>GET请求示例</title>
    5. </head>
    6. <body>
    7. <button onclick="sendGet()">发送 GET 请求</button>
    8. </body>
    9. <script>
    10. function sendGet() {
    11. ajax({
    12. method: 'GET',
    13. url: 'method',
    14. callback: function (body, status) {
    15. console.log(body);
    16. }
    17. });
    18. }
    19. // 把之前封装的 ajax 函数拷贝过来
    20. function ajax(args) {
    21. var xhr = new XMLHttpRequest();
    22. xhr.onreadystatechange = function () {
    23. if (xhr.readyState == 4) {
    24. args.callback(xhr.responseText, xhr.status);
    25. }
    26. }
    27. xhr.open(args.method, args.url);
    28. if (args.contentType) {
    29. xhr.setRequestHeader('Content-type', args.contentType);
    30. }
    31. if (args.body) {
    32. xhr.send(args.body);
    33. } else {
    34. xhr.send();
    35. }
    36. }
    37. </script>
    38. </html>

部署与访问 Servlet
通过Fiddler抓包观察
  • 当浏览器中输入URL之后,浏览器会向服务器发送一个HTTP GET请求。
  • 当点击"发送 GET 请求"按钮,浏览器通过Ajax向服务器再次发送一个HTTP GET请求。
解决乱码问题
  • 中文编码方式:UTF-8是最常见的编码方式,确保能正确显示中文字符。
  • 在Servlet响应代码中,如果包含中文字符,应明确指定字符编码方式。
  • 通过
    resp.setContentType("text/html; charset=utf-8");
    在代码中明确指定UTF-8编码方式。
  • 这将确保浏览器能够正确解析中文字符,避免出现乱码问题。

处理POST请求

在MethodServlet.java中,新增doPost方法,用于处理POST请求:

  1. package org.example;
  2. import javax.servlet.annotation.WebServlet;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. @WebServlet("/method")
  8. public class MethodServlet extends HttpServlet{
  9. @Override
  10. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
  11. ServletException, IOException {
  12. resp.setContentType("text/html; charset=utf-8");
  13. System.out.println("doPost");
  14. resp.getWriter().write("doPost");
  15. }
  16. }

Servlet使用@WebServlet注解来指定URL模式,这里是/method,表示当请求的URL匹配/method时,该Servlet会处理该请求。

Servlet的doPost方法用于处理POST请求。在这个示例中,它首先在控制台中输出"doPost",然后将字符串"doPost"写入HTTP响应。

我们还是需要注意路径唯一问题:

首先,一定不可以把这个注解忘记写,其次,注解里面的参数务必以“/”开头,否则会报以下错误:

最后,你应该确保一个项目中多个servlet指定的路径是不重复的, 否则:

(看到日志出现异常调用栈的情况,就需要赶紧看看自己是不是哪里写错了)

 

 在testMethod.html中,使用Postman,新增一个按钮以及相应的点击事件处理函数,用于发送POST请求:

  1. <button onclick="sendPost()">发送 POST 请求</button>
  2. <script>
  3. function sendPost() {
  4. ajax({
  5. method: 'POST',
  6. url: 'method',
  7. callback: function (body, status) {
  8. console.log(body);
  9. }
  10. });
  11. }
  12. </script>

我们需要借助Postman这个软件来实现前端和我们写的后端之间的交互,理解前端和后端之间的交互的对应关系是一个很重要的事情,也是我们进行web开发的入门难点。

我们先来了解一下Postman是什么:

Postman 是一款流行的前端开发工具,主要用于测试和调试 RESTful API(Representational State Transfer Application Programming Interface)和其他网络服务。它提供了强大的功能,用于创建、发送、测试和管理 HTTP 请求,并查看和分析响应。

下面是一些 Postman 的主要特点和用途:

  1. HTTP 请求发送与管理: Postman 允许用户轻松创建各种类型的 HTTP 请求,包括 GET、POST、PUT、DELETE 等,以与不同的后端服务进行通信。用户可以定义请求头、请求体、参数、认证信息等。

  2. 集合与环境: Postman 允许将相关请求组织成集合(Collection),并在集合中创建不同的环境(Environment)来管理不同的配置。这在测试和调试多个环境(如开发、测试、生产)中的 API 时非常有用。

  3. 自动化测试: Postman 支持创建测试脚本,以验证 API 响应是否符合预期。这使得可以自动运行测试套件来检查 API 的可靠性和性能。

  4. 变量和脚本: 用户可以使用 Postman 变量系统来动态生成请求和响应数据,以便进行更高级的测试和模拟不同情况。Postman 支持 JavaScript 脚本,可用于自定义测试逻辑和处理响应数据。

  5. 监控: Postman 提供监控功能,可用于定期运行集合中的请求,以监测 API 的性能和可用性。用户可以设置警报和通知以及生成报告。

  6. 分享与协作: Postman 支持分享集合和环境,以便团队成员协作测试和调试 API。用户还可以导出和导入 Postman 集合以便分享或备份。

  7. 集成: Postman 可与其他工具和服务集成,如版本控制系统、CI/CD 工具、API 文档生成器等,以便更好地支持整个 API 开发生命周期。

总的来说,Postman 是前端开发中不可或缺的工具,它使开发者能够更轻松地测试、调试和管理 RESTful API,提高了开发效率并有助于确保 API 的质量和可靠性。它提供了一个直观的界面,使得即使非开发人员也能够轻松地使用它来与 API 进行交互。

重新部署程序
  1. 重新部署Servlet程序。
  2. 访问testMethod.html的URL:http://127.0.0.1:8080/ServletHelloWorld/testMethod.html。
发送POST请求 
  1. 单击"发送 POST 请求"按钮。
  2. 在浏览器控制台中查看POST响应结果。

这里的doPost是第二行代码打印的:

与此同时,服务器这里也显示了一个doPost:

第一行代码打印的:

其他HTTP方法

类似的方式可以用于验证doPut、doDelete等方法。

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/method")
  9. public class MethodServlet extends HttpServlet{
  10. @Override
  11. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  12. System.out.println("doPost");
  13. resp.getWriter().write("doPost");
  14. }
  15. @Override
  16. protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  17. System.out.println("doPut");
  18. resp.getWriter().write("doPut");
  19. }
  20. @Override
  21. protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  22. System.out.println("doDelete");
  23. resp.getWriter().write("doDelete");
  24. }
  25. }

但是不知道你会不会觉得每次都要重启一下服务器太麻烦了?有没有办法能够做到不重启就自动生效?

emmmm……

 确实有,热加载,具体的我们会在以后学习spring中介绍。

热加载(Hot Loading)是一种软件开发和部署技术,它允许在应用程序运行时动态更新或替换代码、配置或资源,而无需停止或重新启动应用程序。这种技术的主要目标是减少系统的停机时间,提高开发、测试和维护的效率。

热加载的核心思想是将新的代码或资源注入到运行中的应用程序,以使更改立即生效,而不需要重新启动应用程序。

HttpServletRequest 

HttpServletRequest是Java Servlet API的一部分,用于在Web应用程序中处理HTTP请求。它提供了关于客户端请求的信息,包括请求参数、请求头、请求方法和其他相关数据。

HttpServletRequest的主要功能

  1. 请求方法 (HTTP Method):getMethod()方法用于获取HTTP请求的方法,例如GET、POST、PUT、DELETE等。

  2. 请求参数 (Query Parameters):通过getParameter(String name)方法可以获取请求中包含的参数值,这些参数通常来自于URL查询字符串或表单数据。

  3. 请求头 (Request Headers):getHeader(String name)方法允许我们检索HTTP请求的各种头部信息,如User-Agent、Content-Type等。

  4. Session管理:getSession()方法可用于获取与请求关联的会话对象,以便在不同请求之间存储和检索数据。

  5. 请求URI和URL:getRequestURI()和getRequestURL()方法用于获取请求的URI和URL。

  6. Cookie处理:getCookies()方法用于获取请求中的Cookie。

  7. 请求属性 (Request Attributes):允许开发人员将数据附加到请求,以便在请求处理期间共享。

HttpServletRequest 接口核心方法

  1. String getProtocol()

    • 描述:返回请求协议的名称和版本。
    • getProtocol() 方法用于获取客户端发起请求时所使用的协议的名称和版本。通常情况下,它将返回字符串形式的协议名称和版本号,例如 "HTTP/1.1"。

    • HTTP协议的版本号通常以 "HTTP/1.0" 或 "HTTP/1.1" 的形式出现,这些版本号代表了HTTP协议的不同版本。不同的协议版本可能具有不同的特性和规范。

    • 例如,getProtocol() 方法的返回值可能是 "HTTP/1.1",表明客户端使用的是HTTP 1.1协议来发送请求。这对于服务器端了解客户端所使用的协议版本非常重要,因为不同的协议版本可能会导致不同的行为和特性要求。

      这个方法通常用于了解请求的协议信息,以便在服务器端根据不同的协议版本来进行适当的处理和响应生成。

  2. String getMethod()

    • 描述:返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
    • getMethod() 方法用于获取客户端发起的HTTP请求所使用的HTTP方法的名称。HTTP方法定义了客户端请求的操作类型,例如获取资源、提交数据、删除资源等。

    • 以下是一些常见的HTTP方法及其含义:

      GET: 用于获取指定资源的信息。通常用于读取数据。
      POST: 用于向服务器提交数据,通常用于在服务器上创建新资源。
      PUT: 用于更新或替换服务器上的资源。
      DELETE: 用于删除服务器上的资源。
      HEAD: 类似于GET,但只返回资源的头部信息,用于检查资源的元数据。
      OPTIONS: 用于获取服务器支持的HTTP方法和其他选项。
      PATCH: 用于部分更新资源。
      getMethod() 方法返回的是请求的HTTP方法的名称,例如 "GET"、"POST" 或 "PUT"。服务器端通常会根据不同的HTTP方法来执行相应的处理逻辑,以满足客户端的请求。

    • 例如,如果getMethod() 返回 "GET",那么服务器通常会执行一个用于读取资源的操作,而如果返回 "POST",则服务器可能会执行一个用于创建新资源的操作。这有助于服务器端根据客户端请求的不同操作类型来采取适当的行为。

  3. String getRequestURI()

    • 描述:从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。
    • getRequestURI() 方法用于获取客户端发起的HTTP请求的URL的一部分。具体来说,它返回的是从协议名称直到HTTP请求的第一行的查询字符串部分的URL。

    • HTTP请求的URL通常包括以下部分:

      协议名称(例如,http://或https://)
      主机名(例如,www.example.com)
      端口号(如果不是标准HTTP端口80或HTTPS端口443)
      路径(表示资源在服务器上的位置,例如,/path/to/resource)
      查询字符串(可选,包含请求参数)
      getRequestURI() 返回的是URL中的第4和第5部分,即路径和查询字符串。它不包括协议名称、主机名或端口号。

    • 例如,如果客户端发送的请求URL是 "http://www.example.com:8080/myapp/resource?param=value",那么getRequestURI() 将返回 "/myapp/resource?param=value"。这可以让服务器端更容易地识别请求的资源路径和任何查询参数,以便进行适当的处理。

  4. String getContextPath()

    • 描述:返回指示请求上下文的请求 URI 部分。
    • getContextPath() 方法用于获取请求的上下文路径(Context Path)的部分。上下文路径是Web应用程序在Web服务器中的根路径,它通常指示了应用程序的部署位置。

    • 具体来说,getContextPath() 返回的是请求URI中指示上下文路径的部分,该部分通常位于URL的开头。这部分路径用于将请求路由到正确的Web应用程序。通常,上下文路径以斜杠("/")开头,但不以斜杠结尾。

    • 例如,如果您的Web应用程序部署在服务器上的路径是 "/myapp",并且客户端发送的请求URL是 "http://www.example.com:8080/myapp/resource",那么`getContextPath()` 将返回 "/myapp",这指示请求的上下文路径是 "/myapp"。

    • 使用getContextPath() 可以在多个Web应用程序部署在同一Web服务器上时,帮助服务器正确路由请求到相应的应用程序。这对于构建多个独立的Web应用程序非常有用,以便它们可以在同一服务器上同时运行而不互相干扰。

  5. String getQueryString()

    • 描述:返回包含在路径后的请求 URL 中的查询字符串(键值对)。
    • getQueryString() 方法用于获取HTTP请求URL中包含在路径后的查询字符串部分。查询字符串通常用于传递请求参数给服务器,这些参数可以用于定制请求的行为或提供额外的信息。

    • 查询字符串通常以问号(?)开头,然后包含一个或多个参数对,每个参数对由参数名和参数值组成,参数之间以和号(&)分隔。例如,以下是一个包含查询字符串的URL示例:

      http://www.example.com/myapp/resource?param1=value1&param2=value2

      在这个示例中,查询字符串是 param1=value1&param2=value2。

    • getQueryString() 方法将返回整个查询字符串部分,包括问号。在上面的示例中,它将返回 "param1=value1&param2=value2"。这使得服务器能够轻松地解析并提取请求中的各个参数和它们的值,以便进行适当的处理。

    • Query String本质上是键值对,我们一般都是要根据Key获取vlaue,很少Query String作为一个整体来使用,所以这个方法其实用到的地方不多。后面的6~8(特别是7)的方法则是对Query String 进行相关操作,比较常用。

  6. Enumeration<String> getParameterNames()

    • 描述:返回一个String对象的枚举,包含在该请求中包含的参数的名称。
    • getParameterNames() 方法用于获取请求中包含的所有参数的名称,并将它们作为String对象的枚举返回。这个方法允许我们遍历请求中的所有参数名称,以便进一步处理它们。

    • 一般情况下,HTTP请求可以包含一些查询参数或表单参数,这些参数具有名称和相应的值。通过调用getParameterNames() 方法,我们可以获取请求中的所有参数名称,并进一步使用getParameter(String name) 方法来获取每个参数的值。

      1. Enumeration<String> parameterNames = request.getParameterNames();
      2. while (parameterNames.hasMoreElements()) {
      3. String paramName = parameterNames.nextElement();
      4. String paramValue = request.getParameter(paramName);
      5. // 处理参数名和参数值
      6. }

      在这个示例中,我们获取了请求中所有的参数名称,并使用循环遍历它们。在循环内部,我们使用getParameter(String name) 方法来获取每个参数的值,然后可以进一步处理这些参数值。这对于处理客户端提交的数据非常有用,例如表单数据或查询参数。

    • Enumeration<String> 是Java中的一个接口,它用于遍历一组元素,通常是一组对象或值。在Servlet开发中,Enumeration<String> 常用于遍历一组字符串,例如HTTP请求头的名称、请求参数的名称等。

      主要特点和用途:

      只读遍历:Enumeration 接口提供了只读遍历元素的功能,意味着您只能使用它来访问元素,而不能对元素进行添加、删除或修改操作。这适用于一些不可修改的数据集合。

      遍历方式:Enumeration 接口提供了两个主要的方法来进行遍历:hasMoreElements() 和 nextElement()。通常,您可以使用 hasMoreElements() 来检查是否还有更多的元素可以遍历,然后使用 nextElement() 来获取下一个元素的值。

      泛型类型:在Servlet API中,Enumeration<String> 表示了一个包含字符串的枚举,这意味着它用于遍历一组字符串值,例如请求头的名称或请求参数的名称。

      常见用途:在Servlet开发中,Enumeration<String> 常用于获取HTTP请求中的头部信息、参数名称等,以便在服务器端处理请求。

      以下是一个示例用法,遍历请求头的名称:

      1. Enumeration<String> headerNames = request.getHeaderNames();
      2. while (headerNames.hasMoreElements()) {
      3. String headerName = headerNames.nextElement();
      4. // 在此处理每个请求头的名称
      5. }


      在这个示例中,headerNames 是一个 Enumeration<String>,它用于遍历请求对象 request 中的所有请求头的名称。通过循环,我们可以依次获取每个请求头的名称并进行处理。

      总的来说,Enumeration<String> 是一种用于遍历一组字符串元素的接口,它在Servlet开发中经常用于遍历HTTP请求的相关信息,如请求头、请求参数等。

  7. String getParameter(String name)

    • 描述:以字符串形式返回请求参数的值,或者如果参数不存在则返回null。
    • getParameter(String name) 方法用于从HTTP请求中获取具有指定名称的请求参数的值。请求参数通常是客户端在URL查询字符串或HTTP请求体中发送给服务器的数据。此方法返回一个字符串,其中包含指定名称的参数的值,如果请求中不存在该参数,则返回 null。

      例如,如果客户端通过以下URL发送请求:

      http://www.example.com/servlet?username=johndoe&age=30
      
    • 我们可以使用 getParameter("username") 来获取 "username" 参数的值,使用 getParameter("age") 来获取 "age" 参数的值。如果参数不存在,例如 getParameter("email"),那么将返回 null。

      1. String username = request.getParameter("username");
      2. String age = request.getParameter("age");
      3. String email = request.getParameter("email");
      4. if (username != null) {
      5. // 处理用户名
      6. }
      7. if (age != null) {
      8. // 处理年龄
      9. }
      10. if (email == null) {
      11. // 参数"email" 不存在,可以进行相应处理
      12. }


      在示例中,我们使用 getParameter 方法从请求中获取了不同的参数,并根据需要进行处理。如果参数不存在,我们可以根据返回的 null 值进行适当的处理。

      这个方法在Servlet开发中非常常见,用于处理从客户端传递的各种数据,例如表单数据、查询参数等。通过检查参数是否为 null,我们可以确定参数是否存在,以便执行相应的逻辑。

  8. String[] getParameterValues(String name)

    • 描述:返回一个字符串对象的数组,包含所有给定的请求参数的值(key存在重复的情况,一个key有多个值),如果参数不存在则返回null。
    • getParameterValues(String name) 方法用于从HTTP请求中获取具有指定名称的请求参数的值,并将这些值存储在字符串数组中。请求参数通常是客户端在URL查询字符串或HTTP请求体中发送给服务器的数据。

    • 这个方法返回一个包含所有给定参数的值的字符串数组。如果请求中不存在具有指定名称的参数,那么它将返回 null。

      例如,如果客户端通过以下URL发送请求:

      http://www.example.com/servlet?colors=red&colors=green&colors=blue
      

      我们可以使用 getParameterValues("colors") 来获取名为 "colors" 的参数的所有值,这些值将存储在字符串数组中。在这个例子中,getParameterValues("colors") 将返回一个包含 "red"、"green" 和 "blue" 的字符串数组。

      1. String[] colors = request.getParameterValues("colors");
      2. if (colors != null) {
      3. for (String color : colors) {
      4. // 处理每个颜色值
      5. }
      6. } else {
      7. // 参数"colors" 不存在,可以进行相应处理
      8. }

      在示例中,我们使用 getParameterValues 方法来获取名为 "colors" 的参数的所有值,并将这些值存储在字符串数组 colors 中。然后,我们可以遍历该数组以处理每个颜色值。如果参数不存在,colors 将为 null。

      这个方法在处理来自客户端的多值参数时非常有用,例如复选框或多选下拉列表中的选项,它们可以具有相同的名称但不同的值。通过使用 getParameterValues 方法,您可以轻松地获取和处理多个值的参数。

  9. Enumeration<String> getHeaderNames()

    • 描述:返回一个枚举,包含在该请求中包含的所有的头名。HTTP的请求header部分也是键值对,也需要根据key获取value。
    • getHeaderNames() 方法用于获取HTTP请求中包含的所有请求头的名称,并将这些名称作为 Enumeration<String> 返回。HTTP请求头通常包括诸如"User-Agent"、"Host"、"Accept"等标头,它们提供了关于请求的额外信息。

      以下是示例代码,演示如何使用 getHeaderNames() 方法遍历请求中的所有头名称:

      1. Enumeration<String> headerNames = request.getHeaderNames();
      2. while (headerNames.hasMoreElements()) {
      3. String headerName = headerNames.nextElement();
      4. // 处理每个请求头的名称
      5. }

      在这个示例中,我们通过调用 getHeaderNames() 方法获取一个 Enumeration<String>,然后使用 while 循环遍历枚举中的每个请求头名称。对于每个请求头名称,都可以进行进一步的处理或分析以满足我们的需求。

    • 这种方法非常有用,特别是当我们需要访问HTTP请求的各种头信息时,例如了解客户端的User-Agent、处理自定义请求头、或对不同请求头进行条件检查等。通过遍历头名称,就可以检查和处理请求中包含的各种信息。

  10. String getHeader(String name)

    • 描述:以字符串形式返回指定的请求头的值。
    • getHeader(String name) 方法用于获取HTTP请求中指定请求头的值。HTTP请求头是客户端发送给服务器的元数据,包含了有关请求的各种信息,如User-Agent、Content-Type、Host等。

    • 这个方法接受请求头的名称作为参数,然后返回该请求头的值。如果指定名称的请求头不存在,该方法将返回 null。

      1. String userAgent = request.getHeader("User-Agent");
      2. String host = request.getHeader("Host");
      3. String contentType = request.getHeader("Content-Type");


      在上述示例中,我们使用 getHeader 方法获取了不同请求头的值。例如,request.getHeader("User-Agent") 返回了User-Agent请求头的值,它通常包含了客户端浏览器或应用程序的信息。类似地,我们可以获取其他请求头的值,如Host、Content-Type等。

    • 这个方法在Servlet开发中常用于访问和分析HTTP请求的头部信息,以便在服务器端根据请求头的内容和值来执行相应的逻辑和处理。

  11. String getCharacterEncoding()

    • 描述:返回请求主体中使用的字符编码的名称。
    • 这个方法,包括后面的两个,都是对getHeader进行了简单的封装。
    • getCharacterEncoding() 方法用于获取HTTP请求的主体(即请求体)中所使用的字符编码的名称。请求主体通常用于传输POST请求中的数据,例如通过HTML表单提交的数据,JSON数据等。字符编码指定了数据的字符集,它影响着如何解析和处理请求主体中的文本数据。

    • 这个方法返回一个字符串,表示请求主体的字符编码的名称。通常,它返回的是字符编码的标准名称,如 "UTF-8"、"ISO-8859-1" 等。

      String encoding = request.getCharacterEncoding();
      

      在示例中,我们使用 getCharacterEncoding() 方法获取了请求主体中所使用的字符编码的名称。这对于处理请求主体中包含的文本数据非常重要,因为它告诉服务器如何正确解析和处理这些数据。如果字符编码不是默认的UTF-8,服务器可能需要显式设置适当的字符编码以确保正确处理文本数据。

    • 在实际开发中,通常需要根据请求的内容类型和字符编码来确定如何解析和处理请求主体中的数据。如果我们知道请求的字符编码,就可以使用它来正确处理文本数据。如果返回值为 null,则意味着字符编码未指定,我们可以考虑使用默认的字符编码或根据其他信息来确定字符编码。

  12. String getContentType()

    • 描述:返回请求主体的 MIME 类型,如果不知道类型则返回null。
    • getContentType() 方法用于获取HTTP请求主体(即请求体)的MIME类型。MIME类型是一种标识数据类型的方式,它告诉服务器如何正确解析和处理请求主体中的数据。通常,MIME类型指定了数据的格式,例如文本、图像、音频、视频等。

    • 这个方法返回一个字符串,表示请求主体的MIME类型。如果请求没有提供MIME类型信息,或者类型未知,则该方法将返回 null。

      String contentType = request.getContentType();
      

      在示例中,我们使用 getContentType() 方法获取了请求主体的MIME类型。根据返回的MIME类型,服务器可以采取适当的处理措施。例如,如果MIME类型是 "application/json",则服务器可能会将请求主体解析为JSON格式的数据。如果MIME类型是 "text/html",则服务器可能会将请求主体解析为HTML文档。

    • 在实际开发中,了解请求的MIME类型对于正确处理请求主体中的数据非常重要。它有助于服务器确定如何解析和处理数据以满足客户端请求。如果返回值为 null,则意味着请求中没有提供MIME类型信息,服务器可能需要根据其他信息来确定如何处理请求主体的数据。

  13. int getContentLength()

    • 描述:以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回-1。
    • getContentLength() 方法用于获取HTTP请求主体(请求体)的长度,以字节为单位。请求主体通常包含POST请求中的数据,例如通过HTML表单提交的数据、JSON数据等。这个方法返回请求主体的长度,如果长度未知,则返回 -1。

      int contentLength = request.getContentLength();
      

      在示例中,我们使用 getContentLength() 方法获取了请求主体的长度,以字节为单位。这个长度信息对于服务器来说是非常重要的,因为它告诉服务器请求主体的大小,以便适当地解析和处理数据。

    • 如果返回值为 -1,则表示请求主体的长度未知。这种情况通常发生在请求主体没有明确的长度标头时,或者在使用分块传输编码(chunked transfer encoding)时。在这种情况下,服务器可能需要采取适当的策略来处理请求主体,而不依赖于明确的长度信息。

    • 通常,如果我们需要处理请求主体中的数据,我们可以使用 getInputStream() 方法来获取一个输入流,然后从输入流中读取数据。这样,即使不知道请求主体的长度,您也可以逐步读取数据。

  14. InputStream getInputStream()

    • 描述:用于读取请求的 body 内容。返回一个InputStream对象(流对象),读取这个流对象就能得到整个请求的body。通过这些方法可以获取请求中的各个方面的信息。注意:请求对象是服务器收到的内容,不应该修改。因此上面的方法也都只是 "读" 方法,而不是 "写" 方法。
    • 使用String对象不太合适,可能过长,而是用流对象就允许我们不必一次性处理完数据,我们有更大的自由度去选择是一次处理一点、还是一次全部处理完……并且流对象也能够更好的兼容二进制数据。
    • getInputStream() 方法用于获取HTTP请求的主体(请求体)的输入流,通过这个输入流可以读取请求主体中的数据。请求主体通常包含POST请求中的数据,例如通过HTML表单提交的数据、JSON数据等。

      InputStream inputStream = request.getInputStream();
      

      在示例中,我们使用 getInputStream() 方法获取了请求主体的输入流。通过这个输入流,我们可以逐步读取请求主体中的数据。这对于处理来自客户端的大量或未知长度的数据非常有用。

    • 需要注意的是,HTTP请求对象是服务器收到的内容,不应该被修改。因此,getInputStream() 方法和其他获取请求信息的方法都是"读"方法,用于获取请求的各个方面的信息,而不用于修改请求内容。

    • 在实际开发中,我们可以使用getInputStream() 方法来处理来自客户端的数据,例如通过处理表单提交的数据或处理REST API请求中的JSON数据。根据请求的内容类型和长度,我们可以使用输入流逐步处理和解析数据,然后根据需要进行相应的处理和响应生成。

上述这些方法允许开发人员访问HTTP请求的各个部分,包括请求头、请求参数、请求方法、URI等。HttpServletRequest对象是Servlet开发中的关键组件,用于处理客户端的HTTP请求并生成响应。

上述核心方法中涉及到URI和URL的概念,我们需要对它们进行一些区分: 

URI(Uniform Resource Identifier)和URL(Uniform Resource Locator)是与Web资源定位和标识相关的两个重要概念,它们都能描述资源的唯一性,在许多情况下都可以混合使用,把URL作为URI。但它们具有不同的含义:

  1. URI(Uniform Resource Identifier-唯一资源标识符):

    • 定义: URI 是一种字符串,用于唯一标识或定位某个资源,这个资源可以是文档、图像、文件,或者是 Web 上的任何其他东西。
    • 分类: URI 包括两种主要类型 - URL 和 URN。
    • 示例: https://www.example.com/index.html 是一个URI,它标识了一个Web页面。
  2. URL(Uniform Resource Locator-唯一资源定位符/地址符):

    • 定义: URL 是 URI 的一种,它不仅标识资源的位置,还提供了如何访问该资源的方式,包括协议(如HTTP)、主机名、端口号、路径等。
    • 示例: https://www.example.com/index.html 是一个URL,它标识了一个Web页面的位置以及如何通过HTTP访问它。
  3. URN(Uniform Resource Name):

    • 定义: URN 也是 URI 的一种,它的目的是为资源分配一个永久的、独特的名称,与资源的位置或访问方式无关。
    • 示例: urn:isbn:0451450523 是一个URN,它标识了一本书的ISBN号,而不涉及其位置或获取方式。

URI是一个通用的概念,用于唯一标识和定位资源,而URL是一种具体的URI类型,它提供了资源的位置和访问方式。URN是另一种URI类型,用于提供资源的永久名称。我们在日常使用中,经常把"URL"和"URI"混合使用,因为URL是URI的一种具体形式,不做太严格的区分。

你有没有发现?上述介绍的方法都是get方法(读方法),没有set方法(没有“写”方法)?这是为什么?

这是因为当前拿到的HttpServletRequest,数据都是来自于客户端的,这些数据内容已经确认了,我么不能进行修改,这也体现了“正式框架的做出的限制”。

HttpServletRequest接口中的方法通常是用于获取来自客户端的请求信息,而不提供直接修改请求的方法。这是因为HTTP请求在客户端发出后,其内容通常不应该在服务器端进行修改,以确保数据的完整性和安全性。Servlet容器负责解析和处理请求,但不鼓励或允许直接修改请求内容,因为这可能会引发安全问题或导致不可预测的行为。

如果开发人员需要对请求进行修改,通常是通过创建一个新的HttpServletRequest对象,然后构造一个包含所需修改的请求内容的新请求。这种方式可以确保不会破坏原始请求的完整性,同时允许开发人员构建自定义请求以满足特定需求。

总之,HttpServletRequest的主要目的是提供一种访问和读取客户端请求信息的方法,以便在Servlet中处理请求和生成响应。对于修改请求内容,开发人员通常需要采用其他方式,如构建新的请求对象。这有助于确保Web应用程序的数据完整性和安全性。

 现在我们可以来运用一下这些方法:

Tomcat通过Socket API解析HTTP请求

Tomcat是一个流行的Java Servlet容器,用于处理和执行Java Web应用程序。当Tomcat通过Socket API读取HTTP请求字符串并解析为HttpServletRequest对象时,它遵循HTTP协议的规范。

过程概述

  1. Socket连接建立:Tomcat通过Socket API建立与客户端的TCP连接。

  2. 读取HTTP请求字符串:Tomcat从Socket连接中读取原始HTTP请求字符串。这个字符串包括请求行、请求头、请求体等信息。

  3. HTTP请求解析:Tomcat根据HTTP协议的规范解析HTTP请求字符串,将其转换为一个HttpServletRequest对象。

  4. HttpServletRequest对象:该对象包含了从HTTP请求中提取的所有信息,如请求方法、请求参数、请求头等。

  5. Servlet处理:HttpServletRequest对象被传递给相应的Servlet,以便处理HTTP请求。

细节

  • Tomcat使用Socket API建立TCP连接,监听HTTP请求的端口(通常是80或8080)。
  • 一旦连接建立,Tomcat通过Socket读取客户端发送的HTTP请求数据。
  • Tomcat解析HTTP请求字符串以获得请求行、请求头和请求体。它将请求行中的HTTP方法、请求URI等信息提取出来。
  • Tomcat还解析请求头,以获取各种信息,如请求的Content-Type、Cookie等。
  • 使用这些信息,Tomcat构建一个HttpServletRequest对象,该对象包含有关请求的所有数据。
  • HttpServletRequest对象然后被传递给适当的Servlet,以便处理请求。

总的来说,HttpServletRequest是一个核心的Java Servlet API类,用于处理HTTP请求。当Tomcat通过Socket API解析HTTP请求字符串时,它将这些请求转换为HttpServletRequest对象,以便Servlet能够方便地处理请求并生成响应。这个过程是Web应用程序的基础,它允许开发人员创建动态的、交互式的Web应用程序。

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. import java.io.PrintWriter;
  9. import java.util.Enumeration;
  10. @WebServlet("/show")
  11. public class ShowRequestServlet extends HttpServlet {
  12. @Override
  13. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  14. //调用是个上述API,把得到的结果构造成一个字符串,统一返回给客户端
  15. // 创建一个StringBuilder来构建HTML响应
  16. StringBuilder stringBuilder = new StringBuilder();
  17. // 使用HttpServletRequest方法获取请求信息
  18. stringBuilder.append("<h1>HttpServletRequest示例</h1>");
  19. //在HTML中,\n不能表示换行,我们使用<br>标签来表示换行
  20. stringBuilder.append("请求协议: " + req.getProtocol() + "<br>");
  21. stringBuilder.append("请求方法: " + req.getMethod() + "<br>");
  22. stringBuilder.append("请求URI: " + req.getRequestURI() + "<br>");
  23. stringBuilder.append("上下文路径: " + req.getContextPath() + "<br>");
  24. stringBuilder.append("查询字符串: " + req.getQueryString() + "<br>");
  25. // 获取所有的请求头
  26. stringBuilder.append("<h2>请求头:</h2>");
  27. Enumeration<String> headerNames = req.getHeaderNames();
  28. while (headerNames.hasMoreElements()) {
  29. String key = headerNames.nextElement();
  30. String value = req.getHeader(key);
  31. stringBuilder.append(key + ": " + value + "<br>");
  32. }
  33. // 设置响应内容类型
  34. resp.setContentType("text/html;charset=UTF-8");
  35. // 获取响应的输出流并输出HTML响应
  36. PrintWriter out = resp.getWriter();
  37. out.println("<html>");
  38. out.println("<head><title>HttpServletRequest示例</title></head>");
  39. out.println("<body>");
  40. //把上述内容整体返回到客户端这边
  41. out.println(stringBuilder.toString());
  42. out.println("</body>");
  43. out.println("</html>");
  44. }
  45. }

也可以通过浏览器得到验证: 

如果我们再添上字符串:

在服务器这边获取到请求重点参数(Query String,键值对,都是程序猿自定义的,实际开发中被广泛使用)。

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/getParameter")
  9. public class GetParameterServlet extends HttpServlet {
  10. @Override
  11. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  12. // 此处约定,请求中给定的query string 是形如:uesrname=zhangsan&password=123
  13. // 上述query string 就会被Tomcat自动解析成Map结构
  14. // getParameter就是在查询Map<String, String>里的内容
  15. // 手动记录请求参数信息
  16. String username = req.getParameter("username");
  17. String password = req.getParameter("password");
  18. //拿到这两个内容之后我们就可以进行一些其他的处理
  19. System.out.println("username = "+username);
  20. System.out.println("password = "+password);
  21. resp.getWriter().write("OK");
  22. }
  23. }

请求中没有加上QUERY STRING,得到的结果就是null。

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/getParameter")
  9. public class GetParameterServlet extends HttpServlet {
  10. @Override
  11. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  12. // 此处约定,请求中给定的query string 是形如:uesrname=zhangsan&password=123
  13. // 上述query string 就会被Tomcat自动解析成Map结构
  14. // getParameter就是在查询Map<String, String>里的内容
  15. String username = req.getParameter("username");
  16. String password = req.getParameter("password");
  17. // 这里可以使用获取到的参数进行其他操作,例如验证用户身份
  18. if (username != null && password != null) {
  19. if (username.equals("zhangsan") && password.equals("123")) {
  20. // 用户名和密码正确
  21. resp.getWriter().write("登录成功!");
  22. } else {
  23. // 用户名或密码不正确
  24. resp.getWriter().write("用户名或密码不正确!");
  25. }
  26. } else {
  27. // 未提供用户名或密码
  28. resp.getWriter().write("请输入用户名和密码!");
  29. }
  30. }
  31. }

如果我们输入的有中文该怎么办?这样的写法并不科学,这种做法存在风险。

此处涉及到urlencoded概念。

URL编码是一种将特殊字符转换为百分号编码的方式,以便它们可以在URL中正确传递。

在HTTP请求中,中文字符通常会被转换为UTF-8编码,然后使用百分号编码表示。例如,中文字符"你好"可以被编码为"%E4%BD%A0%E5%A5%BD"。这可以通过编程语言或工具库的URL编码函数来实现。

对于不同编程语言和工具,有不同的方法来进行URL编码。

总之我们最好要进行urlencoded转码。不进行,有些服务器无法正确处理(虽然这里好像可以……)。

 

我们复制粘贴编码内容,encode(加密)之后的结果发到服务器这边,服务器也能自动进行urldecoded。 

到了这里,也许你会疑惑,一个URL中的Query String中的key,value,到底是谁负责定义的?到底要怎么定义?

在一个URL中的Query String中的key和value通常是由开发团队中的程序员或工程师来定义的,而不是由产品经理来定义。这是因为Query String中的参数通常是与具体的应用程序或网站功能有关的,而程序员通常更了解如何在代码中使用这些参数。

通常情况下,定义Query String参数的过程可以如下:

  1. 产品经理提出需求:产品经理根据用户需求和应用功能的目标,提出需要在URL中包含的查询参数的概念。产品经理通常以产品需求文档(MRD)的形式将这些需求详细描述。

  2. 需求评审会:产品经理与开发、测试、运维团队的技术人员共同召开需求评审会议,以评估需求的合理性、可实现性以及时间排期。在这个过程中,技术团队可以提供建议和反馈,以确保需求的可行性。

  3. 前后端接口约定:在需求确定后,前端和后端开发团队需要协商和约定如何处理查询字符串中的键和值。这包括定义要发送的查询参数、参数的名称、数据类型、值的格式、错误处理方式等。这通常在技术讨论会议中讨论和确定。

  4. 文档和沟通:前后端接口约定的细节应该被记录并与相关团队成员共享,通常以文档的形式。这可以是API文档、开发文档或邮件通知,以确保每个人了解如何处理查询字符串中的参数。文档包括参数的名称、类型、用途、取值范围等详细信息。这些文档可以在整个团队中共享,以便参考和使用。

  5. 前端和后端开发:前端和后端团队可以根据协议约定开始各自的开发工作。前端工程师将根据规定的参数名称和格式构建请求,而后端工程师将负责解析这些请求并响应相应的数据。

  6. 实施和测试:程序员将这些参数添加到代码中,并确保它们按照规范进行使用。测试团队也会验证参数的正确性和功能。

  7. 更新文档:如果参数发生变化或有新的参数被添加,开发团队会更新文档,以反映这些变化。

总之,Query String中的key和value通常是由开发团队定义的,需要确保团队内部有明确的文档和协作流程,以确保参数的一致性和正确性。产品经理的角色通常是提供业务需求和功能规划,而参数的具体定义和实现通常是由开发团队来负责。

 

上面我们一直在用 Query String 来进行HTTP的请求:

 查询字符串(Query String): 这种方式通常用于HTTP GET请求,参数附加在URL的查询字符串中。虽然不是POST请求的主要方式,但仍然可以在POST请求中使用查询字符串。这种方式适合发送简单的键值对参数。

URL: http://www.example.com/resource?key1=value1&key2=value2

除了通过Query String来传递参数外,我们还可以使用HTTP请求的消息体(body)来传递参数,通常用于HTTP POST请求。

在使用POST请求时,通常将参数放置在请求的消息体中,并使用Content-Type头部来指示参数的编码方式,例如:

  • application/x-www-form-urlencoded:用于传递表单数据,参数以键值对的形式编码。
  • application/json:用于传递JSON格式的数据。
  • multipart/

上面的包括 Query String 的几种方法本质上是等价的,都是把键值对数据交给服务器。 根据我们的应用程序和数据的性质,可以选择使用其中的一种或多种方式。通常,使用表单数据和JSON数据是最常见的,而查询字符串通常用于GET请求。不同的数据格式适用于不同的数据传输需求和服务器端处理逻辑。

下面我们来具体了解一下使用表单数据和JSON数据传递参数的方式:

1、直接通过form表单(body的格式就是Query String 的格式)

表单数据(Content-Type:application/x-www-form-urlencoded): 这种方式通常用于通过HTML表单提交数据。数据以键值对的形式发送,类似于查询字符串(Query String)的格式。这是HTTP POST请求的默认Content-Type类型,因此如果我们不显式指定Content-Type,通常会使用该格式。

  1. Content-Type: application/x-www-form-urlencoded
  2. body: key1=value1&key2=value2
  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. @WebServlet("/postParameter")
  8. public class PostParameterServlet extends HelloServlet {
  9. @Override
  10. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
  11. //约定,前端构造形如这样的请求:
  12. //POST /postParameter
  13. //Content-Type:x-www-form-urlencoded
  14. //uesrname=zhangsan&password=123
  15. //就需要在后端代码中把body中的值拿到
  16. //获取值的方法仍然是getParameter()
  17. String username = req.getParameter("username");
  18. String password = req.getParameter("password");
  19. System.out.println("username = "+username);
  20. System.out.println("password = "+password);
  21. resp.getWriter().write("ok");
  22. }
  23. }

 

此时我们就通过这个postParameter把前端传过来的数据拿到了: 

2、直接使用json(body的格式就是json)

JSON数据(Content-Type:application.json): 这种方式适用于发送结构化数据,通常用于RESTful API等。数据以JSON格式发送,可以包含复杂的数据结构,如嵌套对象和数组。这种方式更适合传递复杂的数据。

  1. Content-Type: application/json
  2. body: {"key1": "value1", "key2": "value2"}

上述Query String和form表单的方法,都是Servlet天然支持的。

但是这种json的方法是Servlet天然不支持,我们还要引入额外的第三方库。

此处为了针对JSON的格式的数据进行解析和构造(json本质也是键值对,但是规则和form表单截然不同,解析方式自然改变。由于json支持“嵌套”——“嵌套任意层,某个key的value也可以是另一个json”,因此自己手写解析json的代码并不容易),还需要引入JSON的库。 

手动解析 JSON 数据可以是复杂的任务,因为我们需要考虑如何处理嵌套结构、递归解析、处理各种数据类型等。为了简化 JSON 解析,几乎所有编程语言都提供了库和工具,以便开发者可以方便地解析和生成 JSON 数据。这些库通常将 JSON 解析为编程语言中的对象或数据结构,使开发者可以轻松地处理 JSON 数据。里面通常提供了直观的方式来访问和操作 JSON 数据,无需手动编写解析代码。

我们先来引入“Jackson”,因为它是spring官方推荐的库,也被spring集成起来了。

我们打开中央仓库,搜索一下,然后将Maven复制粘贴到 pom.xml:

 

Jackson库

Jackson是一个强大的Java库,用于处理JSON数据。它提供了ObjectMapper类,可以将JSON字符串映射为Java对象,以及将Java对象转换为JSON字符串。在网络传输中,特别是在服务器端,通常需要处理JSON数据,以便在Java代码中进行各种逻辑操作。

ObjectMapper 类

ObjectMapper是Jackson库的核心类,它用于执行JSON数据与Java对象之间的映射。有两个主要方法,分别是readValue和writeValueAsString。

它们一个把json字符串映射成一个Java对象,一个把Java对象映射成json字符串。

因为在网络传输中我们使用json字符串,而Java代码中各种逻辑则是需要Java对象。

站在服务器的角度,收到的请求就是一个json字符串,所以它需要把这个json字符串先映射成一个Java对象,再进行一系列的业务逻辑处理。

处理完了之后可能还需要把得到的Java对象映射回json字符串,并且通过响应来返回。

readValue 方法

readValue方法用于将JSON字符串映射为Java对象。它的常见用法如下:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. MyData myData = objectMapper.readValue(jsonString, MyData.class);

其中,jsonString是包含JSON数据的字符串,MyData是目标Java类。readValue方法将JSON数据解析并映射到MyData类的实例中。

writeValueAsString 方法

writeValueAsString方法用于将Java对象转换为JSON字符串。它的常见用法如下:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. String jsonString = objectMapper.writeValueAsString(myData);

其中,myData是一个MyData类的实例,jsonString将包含myData对象的JSON表示。 

在服务器端处理JSON请求和响应

刚刚我们说:站在服务器的角度,通常会收到JSON格式的请求,需要将其映射为Java对象,然后执行各种业务逻辑操作。处理完后,还可能需要将Java对象转换为JSON字符串,并通过HTTP响应返回给客户端。

以下是处理JSON请求和响应的一般流程:

  1. 接收JSON请求:在服务器端,通过HTTP请求接收JSON数据。这通常涉及到HTTP POST请求或其他HTTP方法,具体取决于您的应用程序设计。

  2. 映射为Java对象:使用ObjectMapper的readValue方法将JSON数据映射为Java对象。这使您能够在Java代码中轻松处理请求数据。

  3. 执行业务逻辑:对映射得到的Java对象执行所需的业务逻辑。这可以包括验证数据、处理请求、查询数据库等操作。

  4. 将Java对象转换为JSON:使用ObjectMapper的writeValueAsString方法将处理后的Java对象转换为JSON字符串,以便进行响应。

  5. 通过HTTP响应返回JSON:通过HTTP响应将JSON字符串返回给客户端。这通常涉及设置适当的HTTP响应头,以及将JSON字符串作为响应的主体内容。

这个流程允许服务器端与客户端之间进行JSON数据的双向通信,使得处理和交换数据更加灵活和方便。 Jackson的ObjectMapper类是实现这一目标的重要工具。


 接下来我们会通过具体的例子来深入了解json实现的请求和响应:

  1. package org.example;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.annotation.WebServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. class Requset{
  9. public String username;
  10. public String password;
  11. }
  12. @WebServlet("/json")
  13. public class JsonParameterServlet extends HelloServlet{
  14. @Override
  15. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
  16. // 此处约定请求的格式如下
  17. //POST /json
  18. //Content-Type:application/json
  19. //
  20. //{
  21. // username="zhangsan"
  22. // password="234"
  23. //}
  24. //此处约定响应的格式(也按照json来组织)
  25. //{
  26. // ok:true
  27. //}
  28. //把请求的body按照json格式解析成Java对象
  29. ObjectMapper objectMapper = new ObjectMapper();
  30. Requset requset = objectMapper.readValue(req.getInputStream(),Requset.class);
  31. }
  32. }

当处理来自客户端的JSON请求时,核心工作涉及将JSON数据映射到Java对象以进行后续操作。以下是关于核心映射过程和逻辑的解释:

  1. ObjectMapper初始化: 首先,我们创建一个ObjectMapper对象,这是Jackson库的一部分。ObjectMapper用于处理JSON数据的序列化和反序列化。在JsonParameterServlet的构造函数中,我们创建了一个ObjectMapper对象。

  2. 获取HTTP请求的输入流: 在doGet方法中,我们使用req.getInputStream()获取HTTP请求的输入流。这个输入流包含了HTTP请求的请求体body内容,即JSON字符串。这一步是为了获取请求体中的JSON数据,它将作为输入提供给ObjectMapper进行解析。

  3. JSON数据映射到Java对象: 接下来,调用objectMapper.readValue方法,将请求体中的JSON数据映射到Java对象。这个方法接受两个参数:

    • req.getInputStream(): 这是包含JSON数据的输入流,它是objectMapper.readValue方法需要解析的数据源。
    • Request.class: 这是我们要将JSON数据映射成的目标Java类,即Request类。

    readValue方法的工作如下:

    • 它通过反射API创建一个Request类的实例。
    • 它分析JSON数据,查找JSON对象中的键值对。
    • 它将找到的键值对映射到Request对象的属性。例如,如果JSON数据包含username和password字段,readValue方法会将它们映射到Request对象的username和password属性上,当前Request.class就知道了Request有两个属性,分别是username和password。它就拿着username去刚才的Map里查询,查到的结果叫做“zhangsan”,于是赋值给username,它就拿着password去刚才的Map里查询,查到的结果叫做“123”,于是赋值给password,最终就得到了一个完整的Request对象。
  4. 处理Java对象: 一旦JSON数据成功映射到Request对象,我们就可以使用Request对象执行进一步的业务逻辑。我们可以访问Request对象的属性,验证数据,进行处理,以及执行其他操作。

  5. 构造响应: 最后,我们可以根据我们的业务逻辑构造响应。这通常涉及创建一个新的Java对象,将其转换为JSON字符串(使用ObjectMapper的writeValueAsString方法),然后将JSON字符串作为响应返回给客户端。

还需要注意一点,这里的: 

  1. class Requset{
  2. public String username;
  3. public String password;
  4. }

里面如果写作“private”,你务必要提供对应的getter和setter,否则Jackson只会处理public属性。 

总之,JsonParameterServlet允许我们接收JSON格式的数据,并将其映射为Java对象,以便在服务器端进行进一步处理。这是一种非常有用的技术,适用于处理JSON数据的RESTful API和其他类似的场景。 Jackson库的ObjectMapper类是实现这种映射的关键工具。 

但是反射API属于非常规操作,除非万不得已,开发中不要随便使用反射。

什么是类对象?

当使用 类名.class 获取一个类的 Class 对象时,我们实际上在进行 Java 反射(Reflection)。Java 反射是一种强大的机制,允许我们在运行时检查、分析和操作类的属性、方法、构造函数等元数据信息。以下是有关 类名.class 的详细介绍:

  1. 什么是 Class 对象: 在 Java 中,每个类都有一个与之关联的 Class 对象,这个对象包含了有关类的元数据信息,例如类的名称、字段、方法、构造函数、修饰符等。Class 对象是 Java 反射的核心,它允许你在运行时获取类的信息并与之交互。

  2. 我们写的Java代码都要被javac编译成.class文件(二进制文件) 。当Java进程启动,就会读取.class文件,把这些二进制内容读到内存中并进行解析(过程叫做“类加载”)。类加载完毕之后,就会在内存中得到类对象。类对象中也就包含了.class文件中的所有信息。所以类对象就相当于是一个类的图纸,后续我们要构造这个类的实例都是基于类对象进行的。

  3. 获取 Class 对象: 有三种主要的方式来获取一个类的 Class 对象:

    • 使用 类名.class:这是最简单的方式,通过直接使用类名后跟 .class 获取 Class 对象。
    • 使用 Object.getClass():对于已经创建的对象,可以使用 getClass() 方法获取它的 Class 对象。
    • 使用 Class.forName(String className):通过类的全限定名(包括包名)来获取 Class 对象。
  4. 使用 Class 对象: 一旦你获得了一个类的 Class 对象,你可以执行各种操作,如:

    • 实例化对象:使用 newInstance() 方法创建类的实例。
    • 获取类的成员:使用 getFields()getDeclaredFields()getMethods()getDeclaredMethods() 等方法获取字段和方法。
    • 调用方法:使用 Method 对象来调用类的方法。
    • 访问字段:使用 Field 对象来访问和修改类的字段。
    • 创建实例:使用 Constructor 对象来创建类的实例。
  5. 用途: Java 反射主要用于以下情况:

    • 在运行时检查和操作类的元数据。
    • 编写通用框架和工具,可以处理未知的类。
    • 实现插件系统,动态加载和卸载类。
    • 在依赖注入(DI)框架中,实现对象的自动装配。
    • 创建动态代理对象,实现 AOP(面向切面编程)等。
  6. 注意事项: Java 反射是一项强大的功能,但也需要小心使用,因为它可以绕过编译时类型检查,可能会导致运行时错误。另外,反射的性能开销通常较高,因此应谨慎使用,尤其在性能敏感的应用中。

Java 的反射机制允许我们在运行时动态地获取和操作类的元数据,为编写灵活的、通用的代码提供了有力的工具。但要谨慎使用,确保遵循最佳实践,以避免潜在的问题和性能瓶颈。

  1. package org.example;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.annotation.WebServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. import java.net.ResponseCache;
  9. import java.util.Optional;
  10. class Requset{
  11. //如果要写作private
  12. public String username;
  13. public String password;
  14. }
  15. class Response{
  16. public boolean ok;
  17. }
  18. @WebServlet("/json")
  19. public class JsonParameterServlet extends HelloServlet{
  20. @Override
  21. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
  22. // 此处约定请求的格式如下
  23. //POST /json
  24. //Content-Type:application/json
  25. //
  26. //{
  27. // username="zhangsan"
  28. // password="234"
  29. //}
  30. //此处约定响应的格式(也按照json来组织)
  31. //{
  32. // ok:true
  33. //}
  34. //把请求的body按照json格式解析成Java对象
  35. ObjectMapper objectMapper = new ObjectMapper();
  36. Requset requset = objectMapper.readValue(req.getInputStream(),Requset.class);
  37. System.out.println("username = "+requset.username);
  38. System.out.println("password = "+requset.password);
  39. // 构造响应对象
  40. Response response = new Response();
  41. response.ok = true;
  42. // 将Java对象转换为JSON字符串
  43. String respJson = objectMapper.writeValueAsString(response);
  44. // 设置HTTP响应头,指定数据类型为application/json
  45. resp.setContentType("application/json;charset=utf-8");
  46. // 将JSON响应发送给客户端
  47. resp.getWriter().write(respJson);
  48. }
  49. }

这个代码就是readValue的反向操作,能把Java对象映射成json字符串。代码演示了如何使用Jackson库将HTTP POST请求中的JSON数据映射为Java对象,然后将Java对象映射为JSON字符串并进行响应:

  1. 创建ObjectMapper对象:首先,在doPost方法中创建一个ObjectMapper对象,该对象用于将Java对象转换为JSON字符串。
  2. 准备Java对象:在示例中,创建了一个名为Requset的Java类,它具有两属性:username和password。这个类的实例将被映射为JSON字符串。
  3. 执行反向操作:使用ObjectMapper的readValue方法,将HTTP请求的JSON数据解析为Requset类的实例。这一步反向操作允许我们将JSON数据映射为Java对象。
  4. 构造响应对象:创建一个响应对象,用于构造响应的JSON字符串。在示例中,响应的格式遵循JSON格式,其中只有一个属性ok,其值为true。
  5. 将Java对象转换为JSON字符串:使用ObjectMapper的writeValueAsString方法,将Java对象(在这种情况下是响应对象)转换为JSON字符串,即把上述属性名字和属性值按照json格式构造成字符串(返回值)。这里将根据Requset类的结构构造JSON格式的响应。
  6. 设置HTTP响应头:为HTTP响应设置适当的头信息,以确保客户端知道响应包含JSON数据。这通常涉及设置Content-Type头,以指定数据类型为application/json。
  7. 将JSON响应发送给客户端:通过HTTP响应将构造好的JSON字符串发送给客户端。客户端可以接收并解析这个JSON响应,以获取数据。

以上,“前端给后端发送请求,后端解析请求”的过程,就是前后端交互的一部分~~~ 

接下来,让我们创建一个Java类,以及两个方法来进行JSON的序列化和反序列化。

  1. import com.fasterxml.jackson.core.JsonProcessingException;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. public class JsonUtil {
  4. private static final ObjectMapper objectMapper = new ObjectMapper();
  5. // 方法1: 将JSON字符串转换为Java对象
  6. public static <T> T fromJson(String jsonString, Class<T> clazz) throws JsonProcessingException {
  7. return objectMapper.readValue(jsonString, clazz);
  8. }
  9. // 方法2: 将Java对象转换为JSON字符串
  10. public static String toJson(Object object) throws JsonProcessingException {
  11. return objectMapper.writeValueAsString(object);
  12. }
  13. }
  1. public class Main {
  2. public static void main(String[] args) {
  3. try {
  4. // 示例1: 将JSON字符串转换为Java对象
  5. String json = "{\"name\": \"John\", \"age\": 30}";
  6. Person person = JsonUtil.fromJson(json, Person.class);
  7. System.out.println("Name: " + person.getName());
  8. System.out.println("Age: " + person.getAge());
  9. // 示例2: 将Java对象转换为JSON字符串
  10. Person newPerson = new Person("Alice", 25);
  11. String newJson = JsonUtil.toJson(newPerson);
  12. System.out.println("JSON: " + newJson);
  13. } catch (JsonProcessingException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. class Person {
  19. private String name;
  20. private int age;
  21. // 省略构造函数、getter和setter方法
  22. public Person() {
  23. }
  24. public Person(String name, int age) {
  25. this.name = name;
  26. this.age = age;
  27. }
  28. // getter和setter方法
  29. }

那么如果URL中的query string和body中都传递了键值对,req.getParameter优先获得哪一个?

通常情况下,req.getParameter 会优先获取查询字符串中的参数,但具体的行为可能会因环境而异。 

在HTTP请求中,参数可以通过两种主要方式传递:作为查询字符串(query string)或在请求主体(request body)中。当在URL的查询字符串和请求主体中都传递了键值对时,req.getParameter 方法通常优先从查询字符串中获取参数。

这意味着,如果同一个参数在查询字符串中和请求主体中都存在,req.getParameter 通常会返回查询字符串中的参数值。这是因为查询字符串中的参数通常用于GET请求,而请求主体中的参数通常用于POST请求以及其他HTTP方法,因此查询字符串中的参数会优先被解析。

然而,这个行为可能会受到具体的Servlet容器或框架的影响。也就是说,上述情况并非“标准的行为”,Servlet没有承诺这种情况谁更加优先,这完全取决于内部的代码。可能在同一个版本里面,代码改变,得到的结果也就不一样了。如果你换成其它语言的其他框架,得到的结果同样不可预期。有些Servlet容器或框架可能提供了更复杂的参数解析规则,以处理不同HTTP方法和数据类型的参数。因此,在实际开发中,最好查阅你使用的Servlet容器或框架的文档,以了解参数解析的具体规则。

HttpServletResponse

概述

HttpServletResponse 是 Java Servlet API 中的一个核心类,用于处理 HTTP 请求的响应。它提供了一种方式来构建和发送 HTTP 响应,以便将数据返回给客户端浏览器。Servlet 中的 doXXX 方法(如 doGet、doPost 等)的主要目的之一是使用 HttpServletResponse 对象来设置响应内容,并通过 Tomcat 或其他 Servlet 容器将响应发送回客户端。

HttpServletResponse 对象的主要功能

1. 设置响应头信息

  • 设置响应的 HTTP 状态码(如 200 OK、404 Not Found 等)。
  • 添加响应头字段,如内容类型(Content-Type)、字符编码(Charset)、缓存控制(Cache-Control)、Cookie 等。

2. 获取输出流

  • 获取一个输出流(ServletOutputStream)或一个字符输出流(PrintWriter),用于将响应数据发送给客户端。

3. 设置响应内容

  • 将 HTML、文本、XML、JSON 或其他数据写入响应输出流。
  • 设置响应的字符编码,以确保正确地处理文本数据。

4. 处理重定向

  • 使用 sendRedirect 方法执行重定向操作,将客户端重定向到另一个 URL。

5. 处理错误响应

  • 使用 sendError 方法发送错误响应,例如 404 错误页面。

6. 缓存控制

  • 控制响应的缓存行为,以提高性能和安全性。

7. 添加响应头部信息

  • 添加自定义的 HTTP 响应头部信息,用于处理跨域请求等。

 核心方法:

1. void setStatus(int sc)

  • 描述:设置响应的状态码,如 200(OK)、404(Not Found)等。
  • 示例:response.setStatus(200);

2. void setHeader(String name, String value)

  • 描述:设置一个带有给定名称和值的响应头。如果给定名称已经存在,将覆盖旧的值。
  • 示例:response.setHeader("Content-Type", "text/html");

3. void addHeader(String name, String value)
描述:添加一个带有给定名称和值的响应头。如果给定名称已经存在,不会覆盖旧的值,而是添加新的键值对。
示例:response.addHeader("Set-Cookie", "sessionId=123");

4. void setContentType(String type)
描述:设置被发送到客户端的响应的内容类型(MIME 类型)。
示例:response.setContentType("text/html");

5. void setCharacterEncoding(String charset)
描述:设置被发送到客户端的响应的字符编码(MIME 字符集),如 UTF-8。
示例:response.setCharacterEncoding("UTF-8");

6. void sendRedirect(String location)
描述:使用指定的重定向位置 URL 发送临时重定向响应到客户端,将客户端浏览器重定向到另一个 URL。
示例:response.sendRedirect("https://www.example.com/new-page");

7. PrintWriter getWriter()
描述:用于往响应正文(body)中写入文本格式数据,通常用于生成 HTML、XML、JSON 等文本响应。
示例:PrintWriter writer = response.getWriter(); writer.println("Hello, World!");

8. OutputStream getOutputStream()
描述:用于往响应正文中写入二进制格式数据,通常用于传输文件、图像或其他二进制数据。
示例:OutputStream outputStream = response.getOutputStream();

注意事项

对于状态码和响应头的设置,应该在获取 PrintWriter 或 OutputStream 之前进行,否则设置可能会失效。
响应对象包含服务器要返回给浏览器的内容,因此上述方法都用于设置响应内容或响应元信息,即它们都是用来写入响应数据的方法。
了解和熟练使用 HttpServletResponse 的这些核心方法对于构建动态 Web 应用程序和处理客户端请求非常重要。

Tomcat 处理 HttpServletResponse

Tomcat 是一个流行的 Servlet 容器,它负责将 Servlet 中的 HttpServletResponse 对象转化为符合 HTTP 协议的响应数据,并将该数据通过 Socket 传输给客户端浏览器。下面是 Tomcat 处理 HttpServletResponse 的基本流程:

  1. Servlet 开发者在 doXXX 方法中使用 HttpServletResponse 对象设置响应内容、状态码、响应头等信息。

  2. Tomcat 接收到请求并调用相应的 Servlet,传递一个 HttpServletRequest 和 HttpServletResponse 对象给 Servlet。

  3. Servlet 使用 HttpServletResponse  对象设置响应信息。

  4. Tomcat将 HttpServletResponse 对象的内容按照 HTTP 协议的格式进行序列化,包括响应头、响应正文等。

  5. Tomcat将序列化后的响应数据通过 Socket 发送给客户端浏览器。

  6. 客户端浏览器接收到响应数据并渲染显示页面或执行其他相应的操作。

总结

 HttpServletResponse 是 Servlet 编程中非常重要的一个类,它允许开发者构建和发送 HTTP 响应,与客户端浏览器进行通信。Tomcat等 Servlet 容器负责将 HttpServletResponse 中的响应数据序列化成 HTTP 响应,然后通过 Socket 发送给客户端浏览器,实现了与客户端的数据交互。了解和掌握 HttpServletResponse 的使用方法对于开发基于 Servlet 的 Web 应用程序非常重要。

HttpServletResponse

概述

HttpServletResponse 是 Java Servlet API 中的一个核心类,用于处理 HTTP 请求的响应。它提供了一种方式来构建和发送 HTTP 响应,以便将数据返回给客户端浏览器。Servlet 中的 doXXX 方法(如 doGet、doPost 等)的主要目的之一是使用 HttpServletResponse 对象来设置响应内容,并通过 Tomcat 或其他 Servlet 容器将响应发送回客户端。

HttpServletResponse 对象的主要功能

1. 设置响应头信息

  • 设置响应的 HTTP 状态码(如 200 OK、404 Not Found 等)。
  • 添加响应头字段,如内容类型(Content-Type)、字符编码(Charset)、缓存控制(Cache-Control)、Cookie 等。

2. 获取输出流

  • 获取一个输出流(ServletOutputStream)或一个字符输出流(PrintWriter),用于将响应数据发送给客户端。

3. 设置响应内容

  • 将 HTML、文本、XML、JSON 或其他数据写入响应输出流。
  • 设置响应的字符编码,以确保正确地处理文本数据。

4. 处理重定向

  • 使用 sendRedirect 方法执行重定向操作,将客户端重定向到另一个 URL。

5. 处理错误响应

  • 使用 sendError 方法发送错误响应,例如 404 错误页面。

6. 缓存控制

  • 控制响应的缓存行为,以提高性能和安全性。

7. 添加响应头部信息

  • 添加自定义的 HTTP 响应头部信息,用于处理跨域请求等。

 核心方法:

1. void setStatus(int sc)

  • 描述:设置响应的状态码,指定了请求处理的结果,如200表示成功,404表示资源未找到如【200(OK)、404(Not Found)】等。
  • 示例:response.setStatus(200);

2. void setHeader(String name, String value)

  • 描述:用于设置HTTP响应的头部信息,包括响应的各种元数据,如内容类型、字符编码、缓存控制等。这个方法需要提供一个头部名称(name)和对应的值(value)。如果指定的名称已经存在,它将覆盖现有的值。
  • 示例:
    1. response.setHeader("Content-Type", "text/html"); // 设置响应的内容类型为HTML
    2. response.setHeader("Cache-Control", "no-cache"); // 设置缓存控制头

3. void addHeader(String name, String value)

  • 描述:用于向HTTP响应中添加一个带有给定名称和值的头部信息。不同于 setHeader 方法,如果指定的名称已经存在,addHeader 不会覆盖现有的值,而是会将新的键值对添加到响应头中。这可以用于添加多个相同名称的头部,每个名称都对应不同的值。
  • 示例:
    1. response.addHeader("Set-Cookie", "sessionId=123"); // 添加一个Set-Cookie头部
    2. response.addHeader("Set-Cookie", "username=johndoe"); // 添加另一个Set-Cookie头部

从这里也可以看出,对于header,运行一个key有多个value。 

4. void setContentType(String type)

  • 描述:用于设置被发送到客户端的响应的内容类型,通常称为 MIME 类型。这告诉客户端浏览器如何处理响应内容,例如,它可以指定响应是HTML、XML、JSON或其他媒体类型。这个方法需要提供一个字符串参数,表示所需的内容类型。
  • 示例:
    1. response.setContentType("text/html"); // 设置响应内容类型为HTML
    2. response.setContentType("application/json"); // 设置响应内容类型为JSON

5. void setCharacterEncoding(String charset)

  • 描述:用于设置被发送到客户端的响应的字符编码,也称为 MIME 字符集。这告诉客户端浏览器如何解释和显示文本数据。通常,UTF-8 是一种常用的字符编码,用于支持多种语言和字符集。
  • 示例:
    response.setCharacterEncoding("UTF-8"); // 设置响应的字符编码为UTF-8
    

    通过设置字符编码,我们可以确保响应文本在客户端浏览器上以正确的方式显示,尤其对于国际化或包含特殊字符的文本非常重要。

6. void sendRedirect(String location)

  • 描述:用于发送一个临时重定向响应给客户端,将客户端浏览器重定向到指定的URL。这是常用于处理需要将用户导向另一个页面或资源的情况,如页面重定向或身份验证后的跳转。
  • 示例:
    response.sendRedirect("https://www.example.com/new-page"); // 发送重定向响应到新页面
    

    这个方法通常在服务器端控制客户端浏览器的导航,将其引导到指定的URL。客户端浏览器会向新的URL发送一个新的HTTP请求以获取该URL的内容。

7. PrintWriter getWriter()

  • 描述:返回一个 PrintWriter 对象,用于向响应正文(body)中写入文本格式的数据。这通常用于生成 HTML、XML、JSON 等文本响应。通过 PrintWriter,我们可以将文本数据发送到客户端浏览器以供渲染和显示。
  • 示例:
    1. PrintWriter writer = response.getWriter();
    2. writer.println("Hello, World!"); // 向响应中写入文本数据

8. OutputStream getOutputStream()

  • 描述:返回一个 OutputStream 对象,用于向响应正文中写入二进制格式的数据。这通常用于传输文件、图像或其他二进制数据。通过 OutputStream,我们可以将二进制数据传输给客户端浏览器。和请求很像,响应的body也是通过流对象来进行体现,只是此处是写入,要使用输出流。
  • 示例:
    1. OutputStream outputStream = response.getOutputStream();
    2. // 向响应中写入二进制数据,如文件内容

注意事项

在处理HTTP响应时,状态码和响应头信息的设置应该在获取 PrintWriter 或 OutputStream 之前进行,因为这些元信息是关于整个响应的内容的配置,它们需要在响应的主体内容之前被设置,以确保生效。

在HTTP响应中,状态码和响应头部信息通常在响应的开始部分设置,而响应正文数据(文本或二进制数据)是在之后发送给客户端。这确保了状态码和响应头在客户端浏览器接收并处理响应内容之前就已经定义了。

了解和熟练使用 HttpServletResponse 的核心方法是构建动态Web应用程序以及处理客户端请求的关键。正确设置状态码和响应头,以及适当地发送响应数据,对于向客户端提供正确的响应至关重要,它们可以影响客户端的行为和用户体验。

request里的API都是GET系列的方法,而 response 里的API都是SET系列的方法,我们现在来具体应用一下这些核心方法:

设置一个错误页面:

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/status")
  9. public class StatusServlet extends HttpServlet {
  10. @Override
  11. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  12. //不显示设置,默认也是200
  13. resp.setStatus(200);
  14. }
  15. }

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/status")
  9. public class StatusServlet extends HttpServlet {
  10. @Override
  11. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  12. //不显示设置,默认也是200
  13. //resp.setStatus(200);
  14. resp.sendError(404,"这个页面是一个错误的页面");
  15. }
  16. }

 

通过setHeader给响应中设置一些特殊的header,比如,可以设置refresh,让浏览器每秒钟刷新一次页面:

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/refresh")
  9. public class RefreshServlet extends HttpServlet {
  10. @Override
  11. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  12. resp.setHeader("refresh","1");
  13. resp.getWriter().write(""+System.currentTimeMillis());
  14. }
  15. }

好像看不太出来,我们还是用浏览器看看:

你会发现这里虽然是每秒刷新依次,但是却不是精确的1000ms,略多一点。因为浏览器发送请求到服务器返回响应也需要时间。

构造一个重定向响应

重定向响应的特点:

  1. 状态码是3xx(302):HTTP响应的状态码以3开头,表示重定向。常见的状态码包括302(Found)、301(Moved Permanently)、307(Temporary Redirect)等,其中302表示临时重定向。对于一次性重定向,通常会使用302状态码。
  2. 包含Location响应头属性:HTTP响应头中包含一个名为Location的属性,该属性用于指示客户端浏览器应该重定向到的目标URL。这个URL通常是一个新的网页地址或资源的URL。
  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. @WebServlet("/redirect")
  8. public class RedirectServlet extends HelloServlet{
  9. @Override
  10. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  11. resp.setStatus(302);
  12. resp.setHeader("Location","http://www.sougou.com");
  13. }
  14. }

也可以这么写: 

  1. package org.example;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. @WebServlet("/redirect")
  8. public class RedirectServlet extends HelloServlet{
  9. @Override
  10. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  11. /*resp.setStatus(302);
  12. resp.setHeader("Location","http://www.sougou.com");*/
  13. resp.sendRedirect("http://www.sougou.com");
  14. }
  15. }

扩展问题: 页面跳转不仅仅是重定向, 还可以完全基于 JS 实现。 

我们尝试实现页面 3s 钟之后跳转 的功能。 (提示: 搜索关键词: JS 定时器, JS 控制页面跳转)  

要在页面上使用JavaScript实现定时跳转,可以使用JavaScript的setTimeout函数来创建一个计时器,然后在计时器到期时执行页面跳转。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>页面跳转</title>
  8. </head>
  9. <body>
  10. <p>这是一个示例页面,将在3秒后跳转到新页面。</p>
  11. <script>
  12. // 使用setTimeout函数创建一个计时器,等待3秒后执行页面跳转
  13. setTimeout(function() {
  14. // 使用window.location.href来设置新页面的URL
  15. window.location.href = "http://www.sougou.com";
  16. }, 3000); // 3000毫秒 = 3秒
  17. </script>
  18. </body>
  19. </html>

在上述示例中,我们在页面中插入了一个JavaScript块,使用setTimeout函数等待3秒(3000毫秒),然后使用window.location.href来设置新页面的URL。这将在3秒后自动跳转到新页面。我们可以将URL替换为我们希望跳转的目标页面的URL。 

Tomcat 处理 HttpServletResponse

Tomcat 是一个流行的 Servlet 容器,它负责将 Servlet 中的 HttpServletResponse 对象转化为符合 HTTP 协议的响应数据,并将该数据通过 Socket 传输给客户端浏览器。下面是 Tomcat 处理 HttpServletResponse 的基本流程:

  1. Servlet 开发者在do×××方法中使用 HttpServletResponse  对象设置响应内容、状态码、响应头等信息。

    • 在Servlet中,你可以通过获取 HttpServletResponse 对象,使用其方法来定制响应的内容、状态和头信息。这包括设置响应的内容类型、字符编码、状态码以及自定义响应头。
  2. Tomcat 接收到请求并调用相应的 Servlet,传递一个 HttpServletResponse  和  HttpServletResponse 对象给 Servlet。

    • 当客户端发起请求时,Tomcat接收请求并根据URL映射选择适当的Servlet来处理请求。Tomcat会实例化Servlet并传递 HttpServletResponseRequest和 HttpServletResponse 对象,以便Servlet可以访问请求信息并配置响应。
  3. Servlet 使用 HttpServletResponse 对象设置响应信息。

    • 在Servlet中,你可以使用 HttpServletResponse 对象的方法来设置响应的各种属性,如状态码、响应头、内容类型、字符编码等。
  4. Tomcat将 HttpServletResponse 对象的内容按照 HTTP 协议的格式进行序列化,包括响应头、响应正文等。

    • Tomcat负责将Servlet设置的响应信息转化为HTTP响应,包括状态行、响应头部和响应正文。这些信息按照HTTP协议的规范进行格式化和编码。
  5. Tomcat将序列化后的响应数据通过Socket发送给客户端浏览器。

    • Tomcat将完整的HTTP响应通过网络连接(通常是Socket)发送给客户端浏览器,以便客户端可以接收和处理响应。
  6. 客户端浏览器接收到响应数据并渲染显示页面或执行其他相应的操作。

    • 客户端浏览器接收到HTTP响应后,会根据响应的内容类型、状态码和响应正文来渲染显示页面或执行其他操作,例如JavaScript脚本的执行。

总结

 HttpServletResponse 是 Servlet 编程中非常重要的一个类,它允许开发者构建和发送 HTTP 响应,与客户端浏览器进行通信。Tomcat等 Servlet 容器负责将 HttpServletResponse 中的响应数据序列化成 HTTP 响应,然后通过 Socket 发送给客户端浏览器,实现了与客户端的数据交互。了解和掌握 HttpServletResponse 的使用方法对于开发基于 Servlet 的 Web 应用程序非常重要。

掌握上述API之后,我们就可以编写一个简单的具体网站了!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/202370
推荐阅读
相关标签
  

闽ICP备14008679号