原文地址: Servlet 5.0 译者:todobugs 时间:2022-03-21

前言

本文档是Jakarta Servlet 规范,描述了Jakarta Servlet API的标准。

补充来源

该规范旨在完整且清晰的介绍Jakarta Servlet API,但如果问题仍存在,可以参考下面的资源:

  • 已经提供了一个参考实现 (RI),它为本规范提供了一个行为基准。 在规范对特定功能的实现开放解释的情况下,实现者可以使用参考实现作为如何执行规范意图的模型。
  • 提供了一个兼容性测试套件 (CTS),用于评估实现是否满足 Jakarta Servlet API 标准的兼容性要求。 测试结果对于解决有关实施是否标准的问题具有规范价值。
  • 如需进一步澄清,应咨询 Jakarta EE 工作组下的 Jakarta Servlet API 工作组,该工作组是此类问题的最终仲裁者。

欢迎进行建议及反馈,并且将用与未来的版本改善。

谁应该阅读此规范

本规范的目标受众包括以下群体:

  • 希望提供符合此标准的servlet 引擎的Web 服务器和应用程序服务器供应商。
  • 希望支持符合此规范的Web 应用程序的创作工具开发人员。
  • 经验丰富的servlet 作者,希望了解servlet 技术的底层机制。

我们强调,本规范不是 servlet 开发人员的用户指南,也不打算这样使用。
定义 Jakarta Servlet API 的类、接口和方法签名的完整规范及其随附的 JavadocTM 文档可在 https://jakarta.ee/specifications/servlet 在线获取。

1. 概述

1.1. 什么是Servlet?

Servelet 是基于JavaTM技术,由容器管理、可生成动态内容的Web组件。与其他基于Java技术的组件一样,servlet 是独立于平台的 Java 类,它们被编译为平台中立的字节码,可以动态加载到支持 Jakarta 技术的 Web 服务器中并由其运行。 容器(有时称为servlet引擎),是提供servlet功能的Web服务器扩展。 Servlet 通过 实现了请求/响应模型的Servlet容器与Web客户端交互。

1.2. 什么是Servlet容器?

Servlet 容器是 Web 服务器或应用程序服务器的一部分,它提供用于发送请求和响应的网络服务、解码基于 MIME 的请求以及格式化基于 MIME 的响应。 servlet 容器还包含并管理 servlet 的整个生命周期。
Servlet 容器可以内置到主机 Web 服务器中,或者通过该服务器的本机扩展 API 作为 Web 服务器的附加组件安装。 Servlet 容器也可以内置或可能安装到支持 Web 的应用程序服务器中。
所有 servlet 容器都必须支持 HTTP 作为请求和响应协议,但可能支持其他基于请求/响应的协议,例如 HTTPS(基于 SSL 的 HTTP)。容器必须实现 HTTP 规范的必需版本是 HTTP/1.1 和 HTTP/2。当支持 HTTP/2 时,servlet 容器必须支持“h2”和“h2c”协议标识符(如 HTTP/2 RFC 的 3.1 节中所指定)。这意味着所有 servlet 容器都必须支持 ALPN。因为容器可能具有 RFC 7234 (HTTP/1.1 Caching) 中描述的缓存机制,它可以在将来自客户端的请求传递给 servlet 之前修改它们,或者在将它们发送给客户端之前修改 servlet 产生的响应,或者可以响应在符合 RFC7234 情况下而不传递给 servlet的请求。
servlet 容器可以对 servlet 执行的环境设置安全限制。可以使用 Java 平台定义的权限体系架构来设置这些限制。例如,一些应用服务器可能会限制线程对象的创建,以确保容器的其他组件不会受到负面影响。
Java SE 8 是构建 servlet 容器时必须使用的底层 Java 平台的最低版本。

1.3. 一个例子

下面是一个典型的事件请求时序:

  1. 客户端(例如浏览器)访问web服务器并发送HTTP请求
  2. 请求由 Web 服务器接收并传递给 servlet 容器。 servlet 容器可以在与主机 Web 服务器相同的进程中运行,也可以在同一主机上的不同进程中运行,或者在与其处理请求的 Web 服务器不同的主机上运行。
  3. Servlet容器基于Servlet配置来决定哪个Servlet将被调用,并携带代表请求和响应的对象来调用他。
  4. servlet 根据请求对象来找出远程用户是谁、HTTP POST 参数以及其他相关数据也会作为该请求的一部分被发送的。 servlet 根据编写任何逻辑执行,并将生成的数据发送回客户端。 它通过响应对象将此数据发送回客户端。
  5. 一旦 servlet 处理完请求,servlet 容器会确保响应正确刷新,并将控制权返回给主机 Web 服务器。

时序图如下:
image.png

1.4. Servlet与其他技术比较

在功能上,servlet 提供了比通用网关接口 (CGI) 程序更高级别的抽象,但比 Jakarta Server Faces 等 Web 框架提供的抽象级别更低。
与其他服务器扩展机制相比,Servlet 具有以下优点:

  • 它们通常比 CGI 脚本快得多,因为使用了不同的流程模型。
  • 他们使用许多Web 服务器支持的标准API。
  • 它们具有Java 编程语言的所有优点,包括易于开发和平台独立性。
  • 他们可以访问Java 平台可用的大量API。

1.5. 与Jakarta EE 平台关系

Jakarta Servlet API v.5.0 是 Jakarta EE 平台的必需 API 。 Servlet 容器和部署到其中的 servlet 必须满足 Jakarta EE 规范中描述的附加要求,才能在 Jakarta EE 环境中执行。

2. Servlet接口

Servlet 接口是 Jakarta Servlet API 的中心抽象。 所有 servlet 都直接实现这个接口,或者更常见的是通过扩展一个实现该接口的类。 Jakarta Servlet API 中实现 Servlet 接口的两个类是 GenericServlet 和 HttpServlet。 对于大多数目的,开发人员将扩展 HttpServlet 以实现他们的 servlet。

2.1. 请求处理方法

基本的 Servlet 接口定义了用于处理客户端请求的服务方法。 对于 servlet 容器路由到 servlet 实例的每个请求,都会调用此方法。
处理对 Web 应用程序的并发请求通常需要 Web 开发人员设计 servlet,这些 servlet 可以处理在特定时间在服务方法中执行的多个线程。
通常,Web 容器通过在不同线程上并发执行 service 方法来处理对同一个 servlet 的并发请求。

2.1.1. HTTP 特定请求处理方法

HttpServlet 抽象子类在基本 Servlet 接口之外添加了其他方法,这些方法由 HttpServlet 类中的service方法自动调用,以帮助处理基于 HTTP 的请求。 这些方法是:

  • doGet 处理 HTTP GET 请求
  • doPost 处理 HTTP POST 请求
  • doPut 处理 HTTP PUT 请求
  • doDelete 处理 HTTP DELETE 请求
  • doHead 处理 HTTP HEAD 请求
  • doOptions 处理 HTTP OPTIONS 请求
  • doTrace 处理 HTTP TRACE 请求

通常在开发基于 HTTP 的 servlet 时,应用程序开发人员关心 doGet 和 doPost 方法。 其他方法被认为是非常熟悉 HTTP 编程的程序员使用的方法。

2.1.2. 补充方法

doPut 和 doDelete 方法允许 Servlet 开发人员支持使用这些特性的 HTTP/1.1 客户端。
doHead 方法是 doGet 方法的一种特殊形式,它只返回由 doGet 方法生成的头信息。
doOptions 方法响应 servlet 支持的 HTTP 方法。
doTrace 方法生成一个包含所有实例的响应在 TRACE 请求中发送的头信息。
CONNECT 方法不支持,因为它适用于代理并且 Jakarta Servlet API 是针对端点。

2.1.3. 有条件的 GET 支持

HttpServlet 类定义了 getLastModified 方法来支持条件 GET 操作。 有条件的 GET 操作仅在资源自指定时间以来已被修改时才请求发送。 在合理的情况下,该方法的实施可能有助于有效利用网络资源。

2.2. 实例数量

通过第 8 章注解和可插入性中描述的注释或第 14 章部署描述符中描述的包含 servlet 的 Web 应用程序的部署描述符的一部分,servlet 声明控制 servlet 容器如何提供小服务程序。
对于不在分布式环境中托管的 servlet(默认设置),servlet 容器必须在每个 servlet 声明中仅使用一个实例。但是,对于实现 SingleThreadModel 接口的 servlet,servlet 容器可能会实例化多个实例来处理繁重的请求负载并将请求序列化到特定实例。
在将 servlet 作为部署描述符中标记为可分发的应用程序的一部分部署的情况下,对于每个 Java 虚拟机 (JVMTM) 的每个 servlet 声明,容器可能只有一个实例。但是如果可分发应用程序中的 servlet 实现了 SingleThreadModel 接口,则容器可以在容器的每个 JVM 中实例化该 servlet 的多个实例。

2.2.1. 单线程模型的注意事项

SingleThreadModel 接口的使用保证了一次只有一个线程将在给定的 servlet 实例的服务方法中执行。 需要注意的是,此保证仅适用于每个 servlet 实例,因为容器可能会选择池化此类对象。 一次可被多个 servlet 实例访问的对象,例如 HttpSession 的实例,可能在任何特定时间对多个 servlet 可用,包括那些实现 SingleThreadModel 的 servlet。
建议开发人员采取其他方式来解决这些问题,而不是实现此接口,例如避免使用实例变量或同步访问这些资源的代码块。 自本规范 2.4 版起,SingleThreadModel 接口已被弃用。

2.3 Servlet 生命周期

servlet 通过定义良好的生命周期进行管理,该生命周期定义了如何加载和实例化、初始化、处理来自客户端的请求以及停止服务。 这个生命周期在 API 中由 jakarta.servlet.Servlet 接口的 init、service 和 destroy 方法表示,所有 servlet 必须通过 GenericServlet 或 HttpServlet 抽象类直接或间接实现这些方法。

2.3.1 加载及实例化

servlet 容器负责加载和实例化 servlet。 加载和实例化可以在容器启动时发生,也可以延迟到容器确定需要 servlet 来为请求提供服务。
当 servlet 引擎启动时,所需的 servlet 类必须由 servlet 容器定位。 servlet 容器使用普通的 Java 类加载工具加载 servlet 类。 加载可能来自本地文件系统、远程文件系统或其他网络服务。
加载 Servlet 类后,容器将其实例化以供使用。

2.3.2. 初始化

servlet 对象实例化后,容器必须先初始化 servlet,然后才能处理来自客户端的请求。 提供了初始化,以便 servlet 可以读取持久配置数据、初始化昂贵的资源(例如基于 JDBCTM API 的连接)以及执行其他一次性活动。 容器通过使用实现 ServletConfig 接口的唯一(每个 servlet 声明)对象调用 Servlet 接口的 init 方法来初始化 servlet 实例。 此配置对象允许 servlet 从 Web 应用程序的配置信息中访问键-值对初始化参数。 配置对象还允许 servlet 访问描述 servlet 运行时环境的对象(实现 ServletContext 接口)。请参阅第 4 章, 有关 ServletContext 接口的更多信息Servlet 上下文。

2.3.2.1. 初始化错误情况

在初始化期间,servlet 实例可能抛出 UnavailableException 或 ServletException。 在这种情况下,servlet 不得置于活跃服务中,并且必须由 servlet 容器释放。 不调用destroy方法因为它被认为是不成功的初始化。
在初始化失败后,容器可能会实例化及初始化一个新实例。 此规则的例外情况是 UnavailableException 指示不可用的最短时间,并且容器必须等待该时间段过去,然后才能创建和初始化新的 servlet 实例。

2.3.2.1. 工具注意事项

当工具加载和自省 Web 应用程序时触发静态初始化方法与调用 init 方法区别开来。 在调用 Servlet 接口的 init 方法之前,开发人员不应假定 servlet 处于活动容器运行时中。 例如,当仅调用静态(类)初始化方法时,servlet 不应尝试建立与数据库或 Jakarta Enterprise Beans 容器的连接。

2.3.3. 请求处理

servlet 正确初始化后,servlet 容器可以使用它来处理客户端请求。 请求由 ServletRequest 类型的请求对象表示。 servlet 通过调用提供的 ServletResponse 类型对象的方法来填写对请求的响应。 这些对象作为参数传递给 Servlet 接口的service方法。
对于 HTTP 请求,容器提供的对象是 HttpServletRequest 和 HttpServletResponse 类型。
请注意,由 servlet 容器投入服务的 servlet 实例在其生命周期内可能不处理任何请求。

2.3.3.1. 多线程问题

servlet 容器可以通过 servlet 的 service 方法发送并发请求。为了处理此类请求,应用程序开发人员必须为service方法中的多线程并发处理做出足够的准备。
应用开发人员的替代方法是实现 SingleThreadModel 接口,但现在已弃用。 SingleThreadModel 接口要求容器保证service方法中一次只有一个请求线程。 servlet 容器可以通过序列化 servlet 上的请求或维护 servlet 实例池来满足此要求。如果 servlet 是已标记为可分发的 Web 应用程序的一部分,则容器可以在每个 JVM 中维护一个 servlet 实例池,该应用程序分布在该应用程序上。
对于没有实现 SingleThreadModel 接口的 servlet,如果service方法(或 HttpServlet 抽象类的服务方法分派到的 doGet 或 doPost 等方法)已经用 synchronized 关键字定义,则 servlet 容器不能使用实例池方式,但必须通过它序列化请求。强烈建议开发人员不要在这些情况下同步service方法(或分派给它的方法),因为这会对性能产生不利影响。

2.3.3.2. 请求期间的异常

servlet 在请求服务期间可能会抛出 ServletException 或 UnavailableException 异常。 ServletException 表示在处理请求期间发生了一些错误,并且容器应该采取适当的措施来清理请求。
UnavailableException 表示 servlet 暂时或永久无法处理请求。
如果 UnavailableException 指示永久不可用,则 servlet 容器必须调用destroy 方法将其从服务中删除 servlet,并释放 servlet 实例。 任何因该原因被容器拒绝的请求都必须返回一个 SC_NOT_FOUND (404) 响应。 如果 UnavailableException 指示临时不可用,则容器可以选择在临时不可用期间不通过 servlet 路由任何请求。 在此期间被容器拒绝的任何请求都必须返回一个 SC_SERVICE_UNAVAILABLE (503) 响应状态以及一个指示不可用性何时终止的 Retry-After 标头。
容器可以选择忽略永久不可用和临时不可用之间的区别,并将所有 UnavailableExceptions 视为永久异常,从而从服务中删除抛出任何 UnavailableException 的 servlet。

2.3.3.3. 异步处理

有时,过滤器和/或 servlet 无法完成请求的处理,而无需在生成响应之前等待资源或事件。例如,servlet 可能需要等待可用的 JDBC 连接、来自远程 Web 服务的响应、JMS 消息或应用程序事件,然后才能继续生成响应。在 servlet 中等待是一个低效的操作,因为它是一个消耗线程和其他有限资源的阻塞操作。通常诸如数据库之类的慢速资源可能会阻止许多线程等待访问,并可能导致线程不足和整个 Web 容器的服务质量差。
引入异步请求处理,让线程可以返回容器执行其他任务。当对请求开始异步处理时,另一个线程或回调可能会生成响应并调用complete或分派请求,以便它可以使用 AsyncContext.dispatch 方法在容器的上下文中运行。异步处理的典型事件序列是:

  1. 请求通过普通过滤器进行身份验证等被接收或通过,并传递给 servlet。
  2. servlet 处理请求参数和/或内容以确定请求的性质。
  3. servlet 发出对资源或数据的请求,例如,发送远程 Web 服务请求或加入等待 JDBC 连接的队列。
  4. servlet 不产生响应就返回。
  5. 一段时间后,请求的资源变得可用,处理该事件的线程继续在同一线程中处理或通过使用 AsyncContext 分派到容器中的资源来处理。

Jakarta EE 功能,例如第 15.2.2 节,“Web 应用程序环境”和第 15.3.1 节,“在 Jakarta Enterprise Bean 调用中传播安全身份”仅适用于执行初始请求或通过 AsyncContext.dispatch 方法将请求分派到容器时的线程 。 Jakarta EE 功能可用于通过 AsyncContext.start(Runnable) 方法直接在响应对象上运行的其他线程。
第 8 章中描述的 @WebServlet 和 @WebFilter 注释有一个属性 asyncSupported,它是一个布尔值,默认值为 false。 当 asyncSupported 设置为 true 时,应用程序可以通过调用 startAsync(见下文)在单独的线程中启动异步处理,向其传递对请求和响应对象的引用,然后在原始线程上退出容器。 这意味着响应将遍历(以相反的顺序)在进入过程中遍历的相同过滤器(或过滤器链)。在 AsyncContext 上调用完成(见下文)之前不会提交响应。 如果异步任务在调用 startAsync 的容器启动调度返回容器之前执行,则应用程序负责处理对请求和响应对象的并发访问。

3. 请求

4. Servlet 上下文

5. 响应

6. 过滤

7. 会话

8. 注解和可插拔性

9. 请求调度

10. Web应用

11. 应用生命周期

12. 请求映射到Servlets

13. 安全

14. 部署描述符

15. 要求涉及的其他规范