@Yufei-Yan
This commit is contained in:
Xingyu Wang 2020-08-27 07:19:09 +08:00
parent c66e84278a
commit 50b147364a

View File

@ -1,50 +1,48 @@
[#]: collector: (lujun9972)
[#]: translator: (Yufei-Yan)
[#]: reviewer: ( )
[#]: reviewer: (wxy)
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (An example of very lightweight RESTful web services in Java)
[#]: via: (https://opensource.com/article/20/7/restful-services-java)
[#]: author: (Marty Kalin https://opensource.com/users/mkalindepauledu)
An example of very lightweight RESTful web services in Java
一个用 Java 实现的超轻量级 RESTful web 服务示例
======
通过完整的代码例子管理一套图书,来探索轻量级的 RESTful 服务。
> 通过管理一套图书的完整代码示例,来探索轻量级的 RESTful 服务。
![Coding on a computer][1]
![](https://img.linux.net.cn/data/attachment/album/202008/27/071808tt9zlno3b6lmbgl8.jpg)
Web 服务,以这样或那样的形式,已经存在了近二十年。比如,[XML-RPC 服务][2]出现在 90 年代后期,紧接着是 SOAP 分支编写的服务。在 XML-RPC 和 SOAP 这两个开拓者之后出现后不久REST 架构风格的服务在大约 20 年前也出现了。[REST][4]风格(后来的 Restful服务现在主导了流行的网站比如 eBay、Facebook 和 Twitter。尽管分布式计算的 web 服务有很多替代品(如 web sockets微服务和远程过程调用的新框架基于 Restful 的网页服务依然具有吸引力,原因如下:
Web 服务,以这样或那样的形式,已经存在了近二十年。比如,[XML-RPC 服务][2]出现在 90 年代后期,紧接着是 SOAP 分支编写的服务。在 XML-RPC 和 SOAP 这两个开拓者之后出现后不久REST 架构风格的服务在大约 20 年前也出现了。[REST][4] 风格(以下简称 Restful服务现在主导了流行的网站比如 eBay、Facebook 和 Twitter。尽管分布式计算的 Web 服务有很多替代品(如 Web 套接字、微服务和远程过程调用的新框架),但基于 Restful 的 Web 服务依然具有吸引力,原因如下:
* Restful 服务是在现有的基础设施和协议的基础上构建的,特别是 web 服务器和 HTTP/HTTPS 协议。对于那些对数据和底层功能比用 HTML 如何展示内容更感兴趣的客户来说,拥有基于 HTML 网站的公司可以很容易的添加 web 服务。比如,亚马逊就率先通过网站和 web 服务(基于 SOAP 或 Restful提供相同的信息和功能。
* Restful 服务将 HTTP 当作 API因此避免了复杂的软件分层这种分层是基于 SOAP web 服务的明显特征。比如Restful API 支持通过 HTTP 命令POST-GET-PUT-DELETE进行标准的 CRUD增加-读取-更新-删除)操作;通过 HTTP 状态码可以知道请求是否成功或者为什么失败。
* Restful web 服务可以根据需要变得简单或复杂。Restful 是一种风格,实上是一种非常灵活的风格,而不是一套关于如何设计和构造服务的配方。(伴随而来的缺点是,可能很难确定哪些服务不能算作 Restful 服务。)
* 作为使用者或者客户端Restful web 服务与语言和平台无关。客户端发送 HTTP(S) 请求并且接受当前流行的数据交换的对应文本比如JSON。.
* 几乎每一种通用编程语言都至少有足够(而且通常都很强大)的对 HTTP/HTTPS 的支持,也就是说 web 服务的客户端可以用这些语言来编写。
* Restful 服务建立在现有的基础设施和协议上,特别是 Web 服务器和 HTTP/HTTPS 协议。一个拥有基于 HTML 的网站的组织可以很容易地为客户添加 Web 服务,这些客户对数据和底层功能更感兴趣,而不是对 HTML 的表现形式感兴趣。比如,亚马逊就率先通过网站和 Web 服务(基于 SOAP 或 Restful提供相同的信息和功能。
* Restful 服务将 HTTP 当作 API因此避免了复杂的软件分层这种分层是基于 SOAP 的 Web 服务的明显特征。比如Restful API 支持通过 HTTP 命令POST-GET-PUT-DELETE进行标准的 CRUD增加-读取-更新-删除)操作;通过 HTTP 状态码可以知道请求是否成功或者为什么失败。
* Restful Web 服务可以根据需要变得简单或复杂。Restful 是一种风格,实上是一种非常灵活的风格,而不是一套关于如何设计和构造服务的规定。(伴随而来的缺点是,可能很难确定哪些服务不能算作 Restful 服务。)
* 作为使用者或者客户端Restful Web 服务与语言和平台无关。客户端发送 HTTP(S) 请求,并以适合现代数据交换的格式(如 JSON接收文本响应。
* 几乎每一种通用编程语言都至少对 HTTP/HTTPS 有足够的(通常是强大的)支持,这意味着 Web 服务的客户端可以用这些语言来编写。
这篇文章将通过一段完整的 Java 代码来展现轻量级的 Restful 服务。
这篇文章将通过一段完整的 Java 代码示例来探讨轻量级的 Restful 服务。
### 基于 Restful 的”小说“ web 服务
### 基于 Restful 的“小说” Web 服务
基于 Restful 的”小说“ web 服务包含三个程序员定义的类:
基于 Restful 的“小说” web 服务包含三个程序员定义的类:
* `Novel` 类代表了只有三个属性的小说:机器生成的 ID作者和标题。属性可以根据实际情况进行扩展,但我还是想让这个例子看上去更简单一些。
* `Novels` 类包含了用于各种任务的使用工具:将一个 `Novel` 或者这样的一个列表的明文编码成 XML 或者 JSON支持在小说的容器上进行 CRUD 操作;可以从存储在文件中的数据对容器进行初始化。`Novels` 类在 `Novel` 实例和 servlet 之间进行调解
* `NovelsServlet` 类是从 `HttpServlet` 中继承的,`HttpServlet` 是健壮且灵活的一段代码,自 90 年代末的早期企业级 Java 就已经存在了。对于客户端的 CRUD 请求servlet 可以当作 HTTP 的端点。 servlet 代码主要用于处理客户端的请求和生成合适的响应,而将复杂的细节留给 `Novels` 类中的工具类进行处理。
* `Novel` 类代表一个小说,只有三个属性:机器生成的 ID、作者和标题。属性可以根据实际情况进行扩展,但我还是想让这个例子看上去更简单一些。
* `Novels` 类包含了用于各种任务的工具类:将一个 `Novel` 或者它们的列表的纯文本编码转换成 XML 或者 JSON支持在小说集合上进行 CRUD 操作;以及从存储在文件中的数据初始化集合。`Novels` 类在 `Novel` 实例和 servlet 之间起中介作用
* `NovelsServlet` 类是从 `HttpServlet` 中继承的,`HttpServlet` 是一段健壮且灵活的代码,自 90 年代末的早期企业级 Java 就已经存在了。对于客户端的 CRUD 请求servlet 可以当作 HTTP 的端点。 servlet 代码主要用于处理客户端的请求和生成相应的响应,而将复杂的细节留给 `Novels` 类中的工具类进行处理。
一些 Java 框架,比如 JerseyJAX-RS和 Restlet就是为 Restful 服务设计的。尽管如此,`HttpServlet` 本身为完成这些服务提供了轻量、灵活、强大且充分测试过的 API。我会通过下面的“小说”例子来说明。
### 部署“小说”web 服务
### 部署“小说” Web 服务
当然,部署“小说”web服务需要一个 web 服务器。我的选择是 [Tomcat][5],但是如果服务托管在 Jetty 或者甚至是 Java 应用服务器上,那么这个服务应该至少可以工作(著名的最后一句话!)。[在我的网站上][6]有总结了如何安装 Tomcat 的 README 文件和代码。还有一个附带文档的 Apache Ant 脚本,可以用来构建“小说”服务(或者任何其他服务或网站)并且将它部署在 Tomcat 或相同的服务。
当然,部署“小说” Web 服务需要一个 Web 服务器。我的选择是 [Tomcat][5],但是如果服务托管在 Jetty 或者甚至是 Java 应用服务器上,那么这个服务应该至少可以工作(著名的最后一句话!)。[在我的网站上][6]有总结了如何安装 Tomcat 的 README 文件和代码。还有一个附带文档的 Apache Ant 脚本,可以用来构建“小说”服务(或者任何其他服务或网站)并且将它部署在 Tomcat 或相同的服务。
Tomcat 可以从它的[官网][7]上下载。当你在本地安装后,将 `TOMCAT_HOME` 设置为安装目录。有两个子目录值得关注:
* `TOMCAT_HOME/bin` 目录包含类 Unix 系统(`startup.sh` 和 `shutdown.sh`)和 Windows`startup.bat` 和 `shutdown.bat` 的启动和停止脚本。Tomcat 作为 Java 应用程序运行。web 服务器的 servlet 容器叫做 Catalina。在 Jetty 中web 服务器和容器的名字一样。)当 Tomcat 启动后,在浏览器中输入 `http://localhost:8080/` 可以查看详细文档,包括示例。
* `TOMCAT_HOME/webapps` 目录是已部署的 web 网站和服务的默认目录。部署网站或 web 服务的直接方法是复制以 `.war`结尾的 JAR 文件(也就是 WAR 文件)到 `TOMCAT_HOME/webapps` 或它的子目录下。然后 Tomcat 会将 WAR 文件解压到它自己的目录下。比如Tomcat 会将 `novels.war` 文件解压到一个叫做 `novels` 的子目录下,并且保留 `novels.war` 文件。网站或 web 服务可以通过删除 WAR 文件进行移除,也可以用一个新版 WAR 文件来覆盖已有文件进行更新。顺便说一下,调试网站或服务的第一步就是检查 Tomcat 已经正确解压 WAR 文件;如果没有的话,网站或服务就无法发布,因为代码或配置中有致命错误。
* 因为 Tomcat 默认会监听 8080 端口上的 HTTP 请求,所以本机上的 URL 请求以 `http://localhost:8080/` 开始
* `TOMCAT_HOME/bin` 目录包含类 Unix 系统(`startup.sh` 和 `shutdown.sh`)和 Windows`startup.bat` 和 `shutdown.bat` 的启动和停止脚本。Tomcat 作为 Java 应用程序运行。Web 服务器的 servlet 容器叫做 Catalina。在 Jetty 中Web 服务器和容器的名字一样。)当 Tomcat 启动后,在浏览器中输入 `http://localhost:8080/` 可以查看详细文档,包括示例。
* `TOMCAT_HOME/webapps` 目录是已部署的 Web 网站和服务的默认目录。部署网站或 Web 服务的直接方法是复制以 `.war` 结尾的 JAR 文件(也就是 WAR 文件)到 `TOMCAT_HOME/webapps` 或它的子目录下。然后 Tomcat 会将 WAR 文件解压到它自己的目录下。比如Tomcat 会将 `novels.war` 文件解压到一个叫做 `novels` 的子目录下,并且保留 `novels.war` 文件。一个网站或 Web 服务可以通过删除 WAR 文件进行移除,也可以用一个新版 WAR 文件来覆盖已有文件进行更新。顺便说一下,调试网站或服务的第一步就是检查 Tomcat 已经正确解压 WAR 文件;如果没有的话,网站或服务就无法发布,因为代码或配置中有致命错误。
* 因为 Tomcat 默认会监听 8080 端口上的 HTTP 请求,所以本机上的 URL 请求以 `http://localhost:8080/` 开始
通过添加不带 `.war` 后缀的 WAR 文件名来访问由程序员部署的 WAR 文件:
@ -60,25 +58,25 @@ http://locahost:8080/myapps/novels/
我会在靠近文章结尾处的测试部分提供这部分的更多细节。
如前所述,我的主页上有一个包含 Ant 脚本的 ZIP 文件,这个文件可以编译并且部署网站或者服务。(这个 ZIP 文件中也包含一个 `novels.war` 的副本。)对于“小说”这个例子,命令的示例(`%` 作为命令行提示符)如下:
如前所述,我的主页上有一个包含 Ant 脚本的 ZIP 文件,这个文件可以编译并且部署网站或者服务。(这个 ZIP 文件中也包含一个 `novels.war` 的副本。)对于“小说”这个例子,命令的示例(`%` 是命令行提示符)如下:
```
% ant -Dwar.name=novels deploy
```
这个命令首先会编译 Java 源代码,并且创建一个可部署的 `novels.war` 文件,然后将这个文件保存在当前目录中,再复制到 `TOMCAT_HOME/webapps` 目录中。如果一切顺利,`GET`请求(使用浏览器或者命令行工具,比如 `curl`)可以用来做一个测试:
这个命令首先会编译 Java 源代码,并且创建一个可部署的 `novels.war` 文件,然后将这个文件保存在当前目录中,再复制到 `TOMCAT_HOME/webapps` 目录中。如果一切顺利,`GET` 请求(使用浏览器或者命令行工具,比如 `curl`)可以用来做一个测试:
```
% curl http://localhost:8080/novels/
```
默认情况下Tomcat 设置为 _<ruby>热部署<rt>hot deploys</rt></ruby>_web 服务器不需要关闭就可以进行部署,更新或者移除一个 web 应用。
默认情况下Tomcat 设置为 <ruby>热部署<rt>hot deploys</rt></ruby>Web 服务器不需要关闭就可以进行部署、更新或者移除一个 web 应用。
### “小说”服务的代码
让我们回到“小说”这个例子,不过是在代码层面。考虑下面的 `Novel` 类:
#### 例1Novel 类
#### 例 1Novel 类
```java
package novels;
@ -104,11 +102,11 @@ public class Novel implements Serializable, Comparable<Novel> {
}
```
这个类实现了 `Comparable` 接口中的 `compareTo` 方法,因为 `Novel` 实例是存储在一个线程安全的无序 `ConcurrentHashMap` 中。在响应查看集合的请求时,”小说“服务对来自 map 的集合进行排序(一个 `ArrayList``compareTo`的实现通过 `Novel` 的 ID 将它按升序排序。
这个类实现了 `Comparable` 接口中的 `compareTo` 方法,因为 `Novel` 实例是存储在一个线程安全的无序 `ConcurrentHashMap` 中。在响应查看集合的请求时,“小说”服务会对从映射中提取的集合(一个 `ArrayList`)进行排序;`compareTo` 的实现通过 `Novel` 的 ID 将它按升序排序。
`Novels`类中包含多个实用工具:
`Novels` 类中包含多个实用工具函数
#### 例2Novels 实用工具类
#### 例 2Novels 实用工具类
```java
package novels;
@ -205,17 +203,17 @@ public class Novels {
}
```
`populate` 是最复杂的方法,这个方法从一个包含在 WAR 文件中的文本文件读取。这个文本文件包括了”小说“的最初集合。要打开此文件,`populate` 方法需要 `ServletContext`,这是一个 Java 映射类型,包含了关于嵌入在 servlet 容器中的 servlet 的所有关键信息。这个文本文件有包含了像下面这样的记录:
最复杂的方法是 `populate`,这个方法从一个包含在 WAR 文件中的文本文件读取。这个文本文件包括了“小说”的初始集合。要打开此文件,`populate` 方法需要 `ServletContext`,这是一个 Java 映射类型,包含了关于嵌入在 servlet 容器中的 servlet 的所有关键信息。这个文本文件有包含了像下面这样的记录:
```
Jane Austen!Persuasion
```
这一行被解析为两部分(作者和标题),由感叹号(`!`)分隔。然后这个方法创建一个 `Novel` 实例,设置作者和标题属性,并且将”小说“加到容器中,保存在内存中。
这一行被解析为两部分(作者和标题),由感叹号(`!`)分隔。然后这个方法创建一个 `Novel` 实例,设置作者和标题属性,并且将“小说”加到容器中,保存在内存中。
`Novels` 类也有一些实用工具,可以将”小说“容器编码为 XML 或 JSON取决与发出请求的人所要求的格式。默认是 XML 格式,但是也可以请求 JSON 格式。一个轻量级的 XML 转 JSON 包提供了 JSON。下面是关于编码的更多细节。
`Novels` 类也有一些实用工具函数,可以将“小说”容器编码为 XML 或 JSON取决于发出请求的人所要求的格式。默认是 XML 格式,但是也可以请求 JSON 格式。一个轻量级的 XML 转 JSON 包提供了 JSON。下面是关于编码的更多细节。
#### 例3NovelsServlet 类
#### 例 3NovelsServlet 类
```java
package novels;
@ -418,12 +416,12 @@ public class NovelsServlet extends HttpServlet {
NovelsServlet extends HttpServlet extends GenericServlet implements Servlet
```
从名字可以很清楚的看出来,`HttpServlet` 是为实现 HTTP(S) 上的 servlet 设计的。这个类提供了与标准 HTTP 请求动词同名的方法(官方说法,<ruby>_方法_<rt>methods</rt></ruby>
从名字可以很清楚的看出来,`HttpServlet` 是为实现 HTTP(S) 上的 servlet 设计的。这个类提供了以标准 HTTP 请求动词(官方说法,<ruby>方法<rt>methods</rt></ruby>命名的空方法
* `doPost` (Post = Create)
* `doGet` (Get = Read)
* `doPut` (Put = Update)
* `doDelete` (Delete = Delete)
* `doPost` Post = 创建)
* `doGet` Get = 读取)
* `doPut` Put = 更新)
* `doDelete` Delete = 删除)
其他一些 HTTP 动词也会涉及到。`HttpServlet` 的子类,比如 `NovelsServlet`,会重载相关的 `do` 方法,并且保留其他方法为<ruby><rt>no-ops</rt></ruby>。`NovelsServlet` 重载了七个 `do` 方法。
@ -436,7 +434,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) {
`request` 参数是一个 HTTP 请求信息的映射,而 `response` 提供了一个返回给请求者的输出流。像 `doPost` 的方法,结构如下:
* 读取 `request` 信息,采取任何适当的措施生成一个响应。如果该信息丢失或者损坏了,就会生成一个错误。
* 使用提取的请求信息来执行适当的 CRUD 操作(在本例中,创建一个 `Novel`),然后使用 `response` 输出流为请求者编码一个适当的相应。在 `doPost` 例子中,响应就是确认已经成功生成一个新”小说“并且添加到容器中。当响应被发送后,输出流就关闭了,同时也将连接关闭了。
* 使用提取的请求信息来执行适当的 CRUD 操作(在本例中,创建一个 `Novel`),然后使用 `response` 输出流为请求者编码一个适当的响应。在 `doPost` 例子中,响应就是已经成功生成一个新“小说”并且添加到容器中的一个确认。当响应被发送后,输出流就关闭了,同时也将连接关闭了。
### 关于方法重载的更多内容
@ -452,7 +450,7 @@ Accept-type: text/plain  ## ditto
第一行由 HTTP 动词(在本例中是 `GET`)和以名词(在本例中是 `novels`)命名目标资源的 URI 开始。报头中包含键-值对,用冒号分隔左面的键和右面的值。报头中的键 `Host`(大小写敏感)是必须的;主机名 `localhost` 是当前机器上的本地符号地址,`8080` 端口是 Tomcat web 服务器上等待 HTTP 请求的默认端口。默认情况下Tomcat 在 8443 端口上监听 HTTP 请求。)报头元素可以以任意顺序出现。在这个例子中,`Accept-type` 报头的值是 MIME 类型 `text/plain`
一些请求(特别是 `POST``PUT`)会有报文,而其他请求(特别是 `GET``DELETE`没有。如果有报文可能为空两个换行符将报头和报文分隔开HTTP 报文包含一系列键-值对。对于无报文的请求,比如说查询字符串,报头元素就可以用来发送信息。下面是一个用 ID 2 对 `/novels` 资源的 `GET` 请求:
一些请求(特别是 `POST``PUT`)会有报文,而其他请求(特别是 `GET``DELETE`)没有。如果有报文(可能为空),两个换行符将报头和报文分隔开HTTP 报文包含一系列键-值对。对于无报文的请求,比如说查询字符串,报头元素就可以用来发送信息。下面是一个用 ID 2 对 `/novels` 资源的 `GET` 请求:
```
GET /novels?id=2
@ -460,7 +458,7 @@ GET /novels?id=2
通常来说,查询字符串以问号开始,并且包含一个键-值对,尽管这个键-值可能值为空。
带有 `getParameter``getParameterMap` 等方法的 `HttpServlet` 很好地回避了有报文和没有报文的 HTTP 请求之前的差异。在”小说“例子中,`getParameter` 方法用来从 `GET`、`POST` 和 `DELETE` 方法中提取所需的信息。(处理 `PUT` 请求需要更底层的代码,因为 Tomcat 没有提供可以解析 `PUT` 请求的参数映射。)下面展示了一段在 `NovelsServlet` 中被重载的 `doPost` 方法:
带有 `getParameter``getParameterMap` 等方法的 `HttpServlet` 很好地回避了有报文和没有报文的 HTTP 请求之前的差异。在“小说”例子中,`getParameter` 方法用来从 `GET`、`POST` 和 `DELETE` 方法中提取所需的信息。(处理 `PUT` 请求需要更底层的代码,因为 Tomcat 没有提供可以解析 `PUT` 请求的参数映射。)下面展示了一段在 `NovelsServlet` 中被重载的 `doPost` 方法:
```java
@Override
@ -479,13 +477,13 @@ public void doDelete(HttpServletRequest request, HttpServletResponse response) {
   ...
```
`doGet` 方法需要区分 `GET` 请求的两种方式:一种是”获得所有“,而另一种是”获得某一个“。如果 `GET` 请求 URL 中包含一个键是一个 ID 的查询字符串,那么这个请求就被解析为”获得某一个“
`doGet` 方法需要区分 `GET` 请求的两种方式:一种是“获得所有”,而另一种是“获得某一个”。如果 `GET` 请求 URL 中包含一个键是一个 ID 的查询字符串,那么这个请求就被解析为“获得某一个”
```
http://localhost:8080/novels?id=2  ## GET specified
```
如果没有查询字符串,`GET` 请求就会被解析为”获得所有“
如果没有查询字符串,`GET` 请求就会被解析为“获得所有”
```
http://localhost:8080/novels       ## GET all
@ -493,9 +491,9 @@ http://localhost:8080/novels       ## GET all
### 一些值得注意的细节
“小说”服务的设计反映了像 Tomcat 这样基于 Java 的 web 服务器是如何工作的。在启动时Tomcat 构建一个线程池,从中提取请求处理程序,这种方法称为 <ruby>_“每个请求一个线程”_<rt>one thread per request</rt></ruby> 模型。现在版本的 Tomcat 使用非阻塞 I/O 来提高个性能。
“小说”服务的设计反映了像 Tomcat 这样基于 Java 的 web 服务器是如何工作的。在启动时Tomcat 构建一个线程池,从中提取请求处理程序,这种方法称为 <ruby>每个请求一个线程<rt>one thread per request</rt></ruby> 模型。现在版本的 Tomcat 使用非阻塞 I/O 来提高个性能。
“小说”服务是作为 `NovelsServlet` 类的一个单独实例来执行的,该实例也就维护了一个“小说”集合。相应的,也就会出现竞态条件,比如出现两个请求同时被处理:
“小说”服务是作为 `NovelsServlet` 类的单个实例来执行的,该实例也就维护了一个“小说”集合。相应的,也就会出现竞态条件,比如出现两个请求同时被处理:
* 一个请求向集合中添加一个新“小说”。
* 另一个请求获得集合中的所有“小说”。
@ -509,8 +507,7 @@ public class Novels {
    ...
```
默认情况下,对客户端请求的响应被编码为 XML。为了简单“小说”程序使用了以前的 `XMLEncoder` 类;另一个包含更丰富功能的选项是使用 JAX-B 库。代码很简单:
默认情况下,对客户端请求的响应被编码为 XML。为了简单“小说”程序使用了以前的 `XMLEncoder` 类;另一个包含更丰富功能的方式是使用 JAX-B 库。代码很简单:
```java
public String toXml(Object obj) { // default encoding
@ -527,9 +524,9 @@ public String toXml(Object obj) { // default encoding
}
```
`Object` 参数要么是一个有序的”小说“ `ArraList`(用以响应<ruby>”获得所有“<rt>get all</rt></ruby>请求),要么是一个 `Novel` 实例(用以响应<ruby>获得一个<rt>get one</rt></ruby>请求),又或者是一个 `String`(确认消息)。
`Object` 参数要么是一个有序的“小说” `ArraList`(用以响应“<ruby>获得所有<rt>get all</rt></ruby>请求),要么是一个 `Novel` 实例(用以响应<ruby>获得一个<rt>get one</rt></ruby>请求),又或者是一个 `String`(确认消息)。
如果 HTTP 请求报头指定 JSON 作为所需要的类型,那么 XML 就被转化成 JSON。下面是 `NovelsServlet` 中的 `doGet` 方法进行检查:
如果 HTTP 请求报头指定 JSON 作为所需要的类型,那么 XML 就被转化成 JSON。下面是 `NovelsServlet` 中的 `doGet` 方法中的检查:
```java
String accept = request.getHeader("accept"); // "accept" is case insensitive
@ -549,14 +546,14 @@ public String toJson(String xml) { // option for requester
}
```
`NovelsServlet`会对各种类型进行错误检查。比如,`POST` 请求应该包含作者和新”小说“的标题。如果有一个丢了,`doPost`方法会抛出一个异常:
`NovelsServlet`会对各种类型进行错误检查。比如,`POST` 请求应该包含新“小说”的作者和标题。如果有一个丢了,`doPost` 方法会抛出一个异常:
```java
if (author == null || title == null)
   throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST));
```
`SC_BAD_REQUEST` 中的 `SC` 代表的是 <ruby>_状态码_<rt>status code</rt></ruby>`BAD_REQUEST` 的标准 HTTP 数值是 400。如果请求中的 HTTP 动词是 `TRACE`,一个不同的状态码会返回
`SC_BAD_REQUEST` 中的 `SC` 代表的是 <ruby>状态码<rt>status code</rt></ruby>`BAD_REQUEST` 的标准 HTTP 数值是 400。如果请求中的 HTTP 动词是 `TRACE`会返回一个不同的状态码:
```java
public void doTrace(HttpServletRequest request, HttpServletResponse response) {
@ -564,9 +561,9 @@ public void doTrace(HttpServletRequest request, HttpServletResponse response) {
}
```
### 测试”小说“服务
### 测试“小说”服务
用浏览器测试 web 服务会很不顺手。在 CRUD 动词中,现代浏览器只能生成 `POST`Create`GET`Read)请求。甚至从浏览器发送一个 `POST` 请求都有点不好办,因为报文需要包含键-值对;这样的测试通常通过 HTML 表单完成。命令行工具,比如说 [curl][21],是一个更好的选择,因为这个部分展示的一些 `curl` 命令,已经包含在我网站的 ZIP 文件中了。
用浏览器测试 web 服务会很不顺手。在 CRUD 动词中,现代浏览器只能生成 `POST`创建)和 `GET`(读取)请求。甚至从浏览器发送一个 `POST` 请求都有点不好办,因为报文需要包含键-值对;这样的测试通常通过 HTML 表单完成。命令行工具,比如说 [curl][21],是一个更好的选择,这个部分展示的一些 `curl` 命令,已经包含在我网站的 ZIP 文件中了。
下面是一些测试样例,没有展示相应的输出结果:
@ -576,9 +573,9 @@ public void doTrace(HttpServletRequest request, HttpServletResponse response) {
% curl --header "Accept: application/json" localhost:8080/novels/
```
第一条命令请求所有”小说“,默认是 XML 编码。第二条命令请求 ID 为 1 的”小说“XML 编码。最后一条命令通过 `application/json` 添加了 `Accept` 报头元素,作为所需要的 MIME 类型。<ruby>获得一个<rt>get one</rt></ruby>命令也可以用这个报头。这些请求用了 JSON 而不是 XML 编码作为响应。
第一条命令请求所有“小说”,默认是 XML 编码。第二条命令请求 ID 为 1 的“小说”XML 编码。最后一条命令通过 `application/json` 添加了 `Accept` 报头元素,作为所需要的 MIME 类型。<ruby>获得一个<rt>get one</rt></ruby>命令也可以用这个报头。这些请求用了 JSON 而不是 XML 编码作为响应。
两条命令在集合中创建了一个新”小说“,并且确认添加了进去:
面两条命令在集合中创建了一个新“小说”,并且确认添加了进去:
```
% curl --request POST --data "author=Tolstoy&amp;title=War and Peace" localhost:8080/novels/
@ -601,11 +598,11 @@ public void doTrace(HttpServletRequest request, HttpServletResponse response) {
% curl localhost:8080/novels/
```
这个请求是删除 ID 为 2 的”小说“。第二个命令会显示剩余的”小说“
这个请求是删除 ID 为 2 的“小说”。第二个命令会显示剩余的“小说”
### web.xml 配置文件
尽管官方规定是可选的,`web.xml` 配置文件是一个生产级别网站或服务的重要组成部分。这个配置文件可以配置独立于代码的路由、安全性,或者网站或服务的其他功能。”小说“服务的配置通过为该服务的请求分配一个 URL 模式来配置路由:
尽管官方规定是可选的,`web.xml` 配置文件是一个生产级别网站或服务的重要组成部分。这个配置文件可以配置独立于代码的路由、安全性,或者网站或服务的其他功能。“小说”服务的配置通过为该服务的请求分配一个 URL 模式来配置路由:
```xml
<xml version = "1.0" encoding = "UTF-8">
@ -629,21 +626,21 @@ public void doTrace(HttpServletRequest request, HttpServletResponse response) {
http://localhost:8080/novels/
```
端口号后斜杠后的 URI是所请求资源的 _路径_,在这个例子中,就是”小说“服务。因此,`novels` 出现在了第一个单斜杠后。
端口号后斜杠后的 URI是所请求资源的“路径”,在这个例子中,就是“小说”服务。因此,`novels` 出现在了第一个单斜杠后。
`web.xml` 文件中,`url-patter` 被指定为 `/*`,代表 _以 /novels 为起始的任意路径_。假设 Tomcat 遇到了一个不存在的 URL像这样
`web.xml` 文件中,`url-patter` 被指定为 `/*`,代表 “以 `/novels` 为起始的任意路径”。假设 Tomcat 遇到了一个不存在的 URL像这样
```
http://localhost:8080/novels/foobar/
```
`web.xml` 配置也会指定这个请求被分配到”小说“ servlet 中,因为 `/*` 模式也包含 `/foobar`。因此,这个不存在的 URL 也会得到像上面合法路径的相同结果。
`web.xml` 配置也会指定这个请求被分配到“小说” servlet 中,因为 `/*` 模式也包含 `/foobar`。因此,这个不存在的 URL 也会得到像上面合法路径的相同结果。
生产级别的配置文件可能会包含安全相关的信息,包括<ruby>连接级<rt>wire-level</rt></ruby><ruby>用户角色<rt>users-roles</rt></ruby>。即使在这种情况下,配置文件的大小也只会是这个例子中的两到三倍大。
生产级别的配置文件可能会包含安全相关的信息,包括<ruby>连接级<rt>wire-level</rt></ruby><ruby>用户角色<rt>users-roles</rt></ruby>。即使在这种情况下,配置文件的大小也只会是这个例子中的两到三倍大。
### 总结
`HttpServlet` 是 Java web 技术的核心。像”小说“这样的网站或 web 服务继承了这个类,并且根据需求重载了相应的 `do` 动词方法。像 JersayJAX-RS或 Restlet 这样的 Restful 框架通过提供定制的 servlet 完成了基本相同的功能,针对框架中的 web 应用程序的请求,这个 servlet 扮演者着 HTTPS<ruby>终端<rt>endpoint</rt></ruby>的角色。
`HttpServlet` 是 Java web 技术的核心。像“小说”这样的网站或 web 服务继承了这个类,并且根据需求重载了相应的 `do` 动词方法。像 JersayJAX-RS或 Restlet 这样的 Restful 框架通过提供定制的 servlet 完成了基本相同的功能,针对框架中的 web 应用程序的请求,这个 servlet 扮演着 HTTP(S) <ruby>终端<rt>endpoint</rt></ruby>的角色。
当然,基于 servlet 的应用程序可以访问 web 应用程序中所需要的任何 Java 库。如果应用程序遵循<ruby>关注点分离<rt>separation-of-concerns</rt></ruby>原则,那么 servlet 代码仍然相当简单:代码会检查请求,如果存在缺陷,就会发出适当的错误;否则,代码会调用所需要的功能(比如,查询数据库,以特定格式为响应编码),然后向请求这发送响应。`HttpServletRequest` 和 `HttpServletReponse` 类型使得读取请求和编写响应变得简单。
@ -656,7 +653,7 @@ via: https://opensource.com/article/20/7/restful-services-java
作者:[Marty Kalin][a]
选题:[lujun9972][b]
译者:[Yufei-Yan](https://github.com/Yufei-Yan)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出