RESTful Web 服务 - 快速指南
RESTful Web 服务 - 简介
什么是 REST 架构?
REST 代表 REpresentational State Transfer。REST 是一种基于 Web 标准的架构,并使用 HTTP 协议。它围绕资源展开,其中每个组件都是一个资源,并且可以通过使用 HTTP 标准方法的通用接口访问资源。REST 最初由 Roy Fielding 于 2000 年提出。
在 REST 架构中,REST 服务器仅提供对资源的访问,而 REST 客户端则访问和修改资源。此处每个资源都由 URI/全局 ID 标识。REST 使用各种表示来表示资源,例如文本、JSON、XML。JSON 是最流行的一种。
HTTP 方法
以下四种 HTTP 方法通常用于基于 REST 的架构中。
GET - 提供对资源的只读访问。
POST - 用于创建新资源。
DELETE - 用于删除资源。
PUT - 用于更新现有资源或创建新资源。
RESTFul Web 服务简介
Web 服务是一组用于在应用程序或系统之间交换数据的开放协议和标准。用各种编程语言编写并在各种平台上运行的软件应用程序可以使用 Web 服务通过计算机网络(如 Internet)交换数据,其方式类似于在单个计算机上的进程间通信。这种互操作性(例如,Java 和 Python 之间,或 Windows 和 Linux 应用程序之间)是由于使用了开放标准。
基于 REST 架构的 Web 服务称为 RESTful Web 服务。这些 Web 服务使用 HTTP 方法来实现 REST 架构的概念。RESTful Web 服务通常定义一个 URI(统一资源标识符)服务,提供资源表示(如 JSON)和一组 HTTP 方法。
创建 RESTFul Web 服务
在接下来的章节中,我们将创建一个 Web 服务,例如用户管理,它具有以下功能:
| 序号 | URI | HTTP 方法 | POST 主体 | 结果 |
|---|---|---|---|---|
| 1 | /UserService/users | GET | 空 | 显示所有用户的列表。 |
| 2 | /UserService/addUser | POST | JSON 字符串 | 添加新用户的详细信息。 |
| 3 | /UserService/getUser/:id | GET | 空 | 显示用户信息。 |
RESTful Web 服务 - 环境搭建
本教程将指导您如何准备开发环境,以便开始使用Jersey 框架创建 RESTful Web 服务。Jersey 框架实现了JAX-RS 2.0 API,这是一个创建 RESTful Web 服务的标准规范。本教程还将教您如何在安装 Jersey 框架之前在您的机器上安装JDK、Tomcat和Eclipse。
安装 Java 开发工具包 (JDK)
您可以从 Oracle 的 Java 网站下载最新版本的 SDK:Java SE 下载。您将在下载的文件中找到安装 JDK 的说明。按照给定的说明安装和配置设置。最后设置PATH和JAVA_HOME环境变量以引用包含Java和Javac的目录,通常分别为 java_install_dir/bin 和 java_install_dir。
如果您正在运行 Windows 并将 JDK 安装在 C:\jdk1.7.0_75 中,则需要在您的 C:\autoexec.bat 文件中添加以下行。
set PATH = C:\jdk1.7.0_75\bin;%PATH% set JAVA_HOME = C:\jdk1.7.0_75
或者,在 Windows NT/2000/XP 上,您也可以右键单击“我的电脑”→选择“属性”→然后“高级”→然后“环境变量”。然后,您将更新 PATH 值并按“确定”按钮。
在 Unix(Solaris、Linux 等)上,如果 SDK 安装在 /usr/local/jdk1.7.0_75 中并且您使用的是 C Shell,则需要将以下内容放入您的 .cshrc 文件中。
setenv PATH /usr/local/jdk1.7.0_75/bin:$PATH setenv JAVA_HOME /usr/local/jdk1.7.0_75
或者,如果您使用集成开发环境 (IDE),如 Borland JBuilder、Eclipse、IntelliJ IDEA 或 Sun ONE Studio,则编译并运行一个简单的程序以确认 IDE 知道您安装 Java 的位置,否则请按照 IDE 文档中提供的说明进行正确的设置。
安装 Eclipse IDE
本教程中的所有示例均使用 Eclipse IDE 编写。因此,我建议您应该在您的机器上安装最新版本的 Eclipse。
要安装 Eclipse IDE,请从https://www.eclipse.org/downloads/下载最新的 Eclipse 二进制文件。下载安装后,将二进制分发版解压缩到方便的位置。例如,在 Windows 上的 C:\eclipse 中,或在 Linux/Unix 上的 /usr/local/eclipse 中,最后适当地设置 PATH 变量。
可以通过在 Windows 机器上执行以下命令启动 Eclipse,或者您可以简单地双击 eclipse.exe。
%C:\eclipse\eclipse.exe
可以通过在 Unix(Solaris、Linux 等)机器上执行以下命令启动 Eclipse:
$/usr/local/eclipse/eclipse
成功启动后,如果一切正常,则您的屏幕应显示以下结果:
安装 Jersey 框架库
现在,如果一切正常,那么您可以继续安装 Jersey 框架。以下是在您的机器上下载和安装框架的几个简单步骤。
选择是否要在 Windows 或 Unix 上安装 Jersey,然后继续执行下一步以下载 Windows 的 .zip 文件,然后下载 Unix 的 .tz 文件。
从以下链接下载最新版本的 Jersey 框架二进制文件:https://jersey.java.net/download.html。
在编写本教程时,我在 Windows 机器上下载了jaxrs-ri-2.17.zip,解压缩下载的文件后,您将在 E:\jaxrs-ri-2.17\jaxrs-ri 中获得目录结构,如下面的屏幕截图所示。
您将在C:\jaxrs-ri-2.17\jaxrs-ri\lib目录中找到所有 Jersey 库,并在C:\jaxrs-ri-2.17\jaxrs-ri\ext目录中找到依赖项。确保您正确设置了此目录上的 CLASSPATH 变量,否则在运行应用程序时会出现问题。如果您使用的是 Eclipse,则无需设置 CLASSPATH,因为所有设置都将通过 Eclipse 完成。
安装 Apache Tomcat
您可以从https://tomcat.net.cn/下载最新版本的 Tomcat。下载安装后,将二进制分发版解压缩到方便的位置。例如,在 Windows 上的 C:\apache-tomcat-7.0.59 中,或在 Linux/Unix 上的 /usr/local/apache-tomcat-7.0.59 中,并设置指向安装位置的 CATALINA_HOME 环境变量。
可以通过在 Windows 机器上执行以下命令启动 Tomcat,或者您可以简单地双击 startup.bat。
%CATALINA_HOME%\bin\startup.bat
或者
C:\apache-tomcat-7.0.59\bin\startup.bat
可以通过在 Unix(Solaris、Linux 等)机器上执行以下命令启动 Tomcat:
$CATALINA_HOME/bin/startup.sh
或者
/usr/local/apache-tomcat-7.0.59/bin/startup.sh
成功启动后,可以通过访问https://:8080/访问 Tomcat 附带的默认 Web 应用程序。如果一切正常,则应显示以下结果:
有关配置和运行 Tomcat 的更多信息,请参阅此页面中包含的文档。您也可以在 Tomcat 网站上找到这些信息:https://tomcat.net.cn。
可以通过在 Windows 机器上执行以下命令停止 Tomcat:
%CATALINA_HOME%\bin\shutdown
或者
C:\apache-tomcat-7.0.59\bin\shutdown
可以通过在 Unix(Solaris、Linux 等)机器上执行以下命令停止 Tomcat:
$CATALINA_HOME/bin/shutdown.sh
或者
/usr/local/apache-tomcat-7.0.59/bin/shutdown.sh
完成最后一步后,您就可以继续进行第一个 Jersey 示例,您将在下一章中看到。
RESTful Web 服务 - 第一个应用程序
让我们开始使用 Jersey 框架编写实际的 RESTful Web 服务。在开始使用 Jersey 框架编写第一个示例之前,您必须确保已正确设置 Jersey 环境,如RESTful Web 服务 - 环境搭建章节中所述。在这里,我还假设您对 Eclipse IDE 有基本的了解。
因此,让我们继续编写一个简单的 Jersey 应用程序,该应用程序将公开一个 Web 服务方法以显示用户列表。
创建 Java 项目
第一步是使用 Eclipse IDE 创建一个动态 Web 项目。依次选择文件→新建→项目,最后从向导列表中选择动态 Web 项目向导。现在,使用向导窗口将您的项目命名为UserManagement,如下面的屏幕截图所示:
成功创建项目后,您的项目资源管理器中将包含以下内容:
添加所需的库
作为第二步,让我们在项目中添加 Jersey 框架及其依赖项(库)。将以下下载的 jersey zip 文件夹中的所有 jar 文件复制到项目的 WEB-INF/lib 目录中。
- \jaxrs-ri-2.17\jaxrs-ri\api
- \jaxrs-ri-2.17\jaxrs-ri\ext
- \jaxrs-ri-2.17\jaxrs-ri\lib
现在,右键单击您的项目名称UserManagement,然后选择上下文菜单中的可用选项:构建路径→配置构建路径以显示 Java 构建路径窗口。
现在,使用库选项卡下可用的添加 JAR按钮添加 WEBINF/lib 目录中存在的 JAR。
创建源文件
现在让我们在UserManagement项目下创建实际的源文件。首先,我们需要创建一个名为com.tutorialspoint的包。为此,右键单击包资源管理器部分中的 src,然后选择:新建→包。
接下来,我们将在 com.tutorialspoint 包下创建UserService.java、User.java、UserDao.java文件。
User.java
package com.tutorialspoint;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String profession;
public User(){}
public User(int id, String name, String profession){
this.id = id;
this.name = name;
this.profession = profession;
}
public int getId() {
return id;
}
@XmlElement
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
@XmlElement
public void setName(String name) {
this.name = name;
}
public String getProfession() {
return profession;
}
@XmlElement
public void setProfession(String profession) {
this.profession = profession;
}
}
UserDao.java
package com.tutorialspoint;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class UserDao {
public List<User> getAllUsers(){
List<User> userList = null;
try {
File file = new File("Users.dat");
if (!file.exists()) {
User user = new User(1, "Mahesh", "Teacher");
userList = new ArrayList<User>();
userList.add(user);
saveUserList(userList);
}
else{
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
userList = (List<User>) ois.readObject();
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return userList;
}
private void saveUserList(List<User> userList){
try {
File file = new File("Users.dat");
FileOutputStream fos;
fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(userList);
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UserService.java
package com.tutorialspoint;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/UserService")
public class UserService {
UserDao userDao = new UserDao();
@GET
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
public List<User> getUsers(){
return userDao.getAllUsers();
}
}
关于主程序需要注意两点:
UserService.java
第一步是使用 @Path 注解为 UserService 指定 Web 服务的路径。
第二步是使用 @Path 注解为 UserService 的方法指定特定 Web 服务方法的路径。
创建Web.xml配置文件
您需要创建一个Web xml配置文件,它是一个XML文件,用于为我们的应用程序指定Jersey框架servlet。
web.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<web-app xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns = "http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id = "WebApp_ID" version = "3.0">
<display-name>User Management</display-name>
<servlet>
<servlet-name>Jersey RESTful Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.tutorialspoint</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey RESTful Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
部署程序
完成创建源文件和Web配置文件后,您就可以进行此步骤,即编译和运行您的程序。为此,使用Eclipse将您的应用程序导出为war文件,并在Tomcat中部署该文件。
要使用Eclipse创建WAR文件,请按照以下选项操作:**文件→导出→Web→WAR文件**,最后选择项目UserManagement和目标文件夹。要在Tomcat中部署war文件,请将UserManagement.war放在**Tomcat安装目录→webapps目录**中,然后启动Tomcat。
运行程序
我们使用Postman(一个Chrome扩展程序)来测试我们的Web服务。
向UserManagement发送请求以获取所有用户的列表。在POSTMAN中使用GET请求输入https://:8080/UserManagement/rest/UserService/users,然后查看以下结果。
恭喜您,您已成功创建了第一个RESTful应用程序。
RESTful Web服务 - 资源
什么是资源?
REST架构将每个内容都视为资源。这些资源可以是文本文件、HTML页面、图像、视频或动态业务数据。REST服务器只需提供对资源的访问,而REST客户端则访问和修改资源。这里每个资源都由URI/全局ID标识。REST使用各种表示来表示资源,例如文本、JSON、XML。最流行的资源表示是XML和JSON。
资源的表示
REST中的资源类似于面向对象编程中的对象,或类似于数据库中的实体。一旦识别了资源,就需要使用标准格式确定其表示形式,以便服务器可以使用上述格式发送资源,并且客户端可以理解相同的格式。
例如,在RESTful Web服务 - 第一个应用程序章节中,用户是一个资源,使用以下XML格式表示:
<user> <id>1</id> <name>Mahesh</name> <profession>Teacher</profession> </user>
相同的资源可以用JSON格式表示如下:
{
"id":1,
"name":"Mahesh",
"profession":"Teacher"
}
良好的资源表示
REST不对资源表示的格式施加任何限制。一个客户端可以请求JSON表示,而另一个客户端可以请求服务器对相同资源的XML表示,依此类推。REST服务器有责任以客户端理解的格式向客户端传递资源。
在RESTful Web服务中设计资源表示格式时,需要考虑以下一些要点。
**可理解性** - 服务器和客户端都应该能够理解和利用资源的表示格式。
**完整性** - 格式应该能够完整地表示资源。例如,一个资源可以包含另一个资源。格式应该能够表示简单和复杂的资源结构。
**可链接性** - 一个资源可以与另一个资源链接,格式应该能够处理这种情况。
但是,目前大多数Web服务都使用XML或JSON格式来表示资源。有大量库和工具可用于理解、解析和修改XML和JSON数据。
RESTful Web服务 - 消息
RESTful Web服务使用HTTP协议作为客户端和服务器之间通信的媒介。客户端以HTTP请求的形式发送消息,服务器以HTTP响应的形式响应。此技术称为消息传递。这些消息包含消息数据和元数据,即有关消息本身的信息。让我们看看HTTP 1.1的HTTP请求和HTTP响应消息。
HTTP请求
HTTP请求有五个主要部分:
**动词** - 指示HTTP方法,例如GET、POST、DELETE、PUT等。
**URI** - 统一资源标识符(URI),用于标识服务器上的资源。
**HTTP版本** - 指示HTTP版本。例如,HTTP v1.1。
**请求头** - 以键值对的形式包含HTTP请求消息的元数据。例如,客户端(或浏览器)类型、客户端支持的格式、消息正文的格式、缓存设置等。
**请求正文** - 消息内容或资源表示。
HTTP响应
HTTP响应有四个主要部分:
**状态/响应代码** - 指示服务器对请求资源的状态。例如,404表示资源未找到,200表示响应正常。
**HTTP版本** - 指示HTTP版本。例如HTTP v1.1。
**响应头** - 以键值对的形式包含HTTP响应消息的元数据。例如,内容长度、内容类型、响应日期、服务器类型等。
**响应正文** - 响应消息内容或资源表示。
示例
正如我们在RESTful Web服务 - 第一个应用程序章节中所解释的,让我们在POSTMAN中使用GET请求输入https://:8080/UserManagement/rest/UserService/users。如果您点击Postman发送按钮旁边的“预览”按钮,然后点击“发送”按钮,您可能会看到以下输出。
在这里您可以看到,浏览器发送了一个GET请求,并接收了一个XML格式的响应正文。
RESTful Web服务 - 地址
寻址是指定位服务器上存在的资源或多个资源。这类似于查找某人的邮政地址。
REST架构中的每个资源都由其URI(统一资源标识符)标识。URI具有以下格式:
<protocol>://<service-name>/<ResourceType>/<ResourceID>
URI的目的是定位托管Web服务的服务器上的资源。请求的另一个重要属性是VERB,它标识要对资源执行的操作。例如,在RESTful Web服务 - 第一个应用程序章节中,URI为**https://:8080/UserManagement/rest/UserService/users**,VERB为GET。
构建标准URI
设计URI时,需要考虑以下要点:
**使用复数名词** - 使用复数名词来定义资源。例如,我们使用users来标识用户作为资源。
**避免使用空格** - 使用下划线(_)或连字符(-)来使用长资源名称。例如,使用authorized_users而不是authorized%20users。
**使用小写字母** - 尽管URI不区分大小写,但最好只使用小写字母。
**保持向后兼容性** - 由于Web服务是公共服务,因此一旦公开的URI应始终可用。如果URI更新,请使用HTTP状态代码300将旧URI重定向到新URI。
**使用HTTP动词** - 始终使用HTTP动词(如GET、PUT和DELETE)对资源执行操作。在URI中使用操作名称不是一个好习惯。
示例
以下是一个获取用户的糟糕URI示例。
https://:8080/UserManagement/rest/UserService/getUser/1
以下是一个获取用户的良好URI示例。
https://:8080/UserManagement/rest/UserService/users/1
RESTful Web服务 - 方法
正如我们在前面章节中讨论的那样,RESTful Web服务使用大量HTTP动词来确定要对指定资源执行的操作。下表列出了最常用的HTTP动词的示例。
| 序号 | HTTP方法、URI和操作 |
|---|---|
| 1 | GET https://:8080/UserManagement/rest/UserService/users 获取用户列表。 (只读) |
2 |
GET https://:8080/UserManagement/rest/UserService/users/1 获取ID为1的用户 (只读) |
3 |
PUT https://:8080/UserManagement/rest/UserService/users/2 插入ID为2的用户 (幂等) |
4 |
POST https://:8080/UserManagement/rest/UserService/users/2 更新ID为2的用户 (N/A) |
5 |
DELETE https://:8080/UserManagement/rest/UserService/users/1 删除ID为1的用户 (幂等) |
6 |
OPTIONS https://:8080/UserManagement/rest/UserService/users 列出Web服务中支持的操作。 (只读) |
7 |
HEAD https://:8080/UserManagement/rest/UserService/users 仅返回HTTP标头,不返回正文。 (只读) |
需要考虑以下几点。
GET操作是只读且安全的。
PUT和DELETE操作是幂等的,这意味着无论调用这些操作多少次,其结果都将始终相同。
PUT和POST操作几乎相同,区别仅在于结果,其中PUT操作是幂等的,而POST操作可能导致不同的结果。
示例
让我们更新RESTful Web服务 - 第一个应用程序章节中创建的示例,以创建一个可以执行CRUD(创建、读取、更新、删除)操作的Web服务。为简单起见,我们使用了文件I/O来代替数据库操作。
让我们更新com.tutorialspoint包下的**User.java、UserDao.java**和**UserService.java**文件。
User.java
package com.tutorialspoint;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String profession;
public User(){}
public User(int id, String name, String profession){
this.id = id;
this.name = name;
this.profession = profession;
}
public int getId() {
return id;
}
@XmlElement
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
@XmlElement
public void setName(String name) {
this.name = name;
}
public String getProfession() {
return profession;
}
@XmlElement
public void setProfession(String profession) {
this.profession = profession;
}
@Override
public boolean equals(Object object){
if(object == null){
return false;
}else if(!(object instanceof User)){
return false;
}else {
User user = (User)object;
if(id == user.getId()
&& name.equals(user.getName())
&& profession.equals(user.getProfession())){
return true;
}
}
return false;
}
}
UserDao.java
package com.tutorialspoint;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class UserDao {
public List<User> getAllUsers(){
List<User> userList = null;
try {
File file = new File("Users.dat");
if (!file.exists()) {
User user = new User(1, "Mahesh", "Teacher");
userList = new ArrayList<User>();
userList.add(user);
saveUserList(userList);
}
else{
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
userList = (List<User>) ois.readObject();
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return userList;
}
public User getUser(int id){
List<User> users = getAllUsers();
for(User user: users){
if(user.getId() == id){
return user;
}
}
return null;
}
public int addUser(User pUser){
List<User> userList = getAllUsers();
boolean userExists = false;
for(User user: userList){
if(user.getId() == pUser.getId()){
userExists = true;
break;
}
}
if(!userExists){
userList.add(pUser);
saveUserList(userList);
return 1;
}
return 0;
}
public int updateUser(User pUser){
List<User> userList = getAllUsers();
for(User user: userList){
if(user.getId() == pUser.getId()){
int index = userList.indexOf(user);
userList.set(index, pUser);
saveUserList(userList);
return 1;
}
}
return 0;
}
public int deleteUser(int id){
List<User> userList = getAllUsers();
for(User user: userList){
if(user.getId() == id){
int index = userList.indexOf(user);
userList.remove(index);
saveUserList(userList);
return 1;
}
}
return 0;
}
private void saveUserList(List<User> userList){
try {
File file = new File("Users.dat");
FileOutputStream fos;
fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(userList);
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UserService.java
package com.tutorialspoint;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
@Path("/UserService")
public class UserService {
UserDao userDao = new UserDao();
private static final String SUCCESS_RESULT = "<result>success</result>";
private static final String FAILURE_RESULT = "<result>failure</result>";
@GET
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
public List<User> getUsers(){
return userDao.getAllUsers();
}
@GET
@Path("/users/{userid}")
@Produces(MediaType.APPLICATION_XML)
public User getUser(@PathParam("userid") int userid){
return userDao.getUser(userid);
}
@PUT
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String createUser(@FormParam("id") int id,
@FormParam("name") String name,
@FormParam("profession") String profession,
@Context HttpServletResponse servletResponse) throws IOException{
User user = new User(id, name, profession);
int result = userDao.addUser(user);
if(result == 1){
return SUCCESS_RESULT;
}
return FAILURE_RESULT;
}
@POST
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String updateUser(@FormParam("id") int id,
@FormParam("name") String name,
@FormParam("profession") String profession,
@Context HttpServletResponse servletResponse) throws IOException{
User user = new User(id, name, profession);
int result = userDao.updateUser(user);
if(result == 1){
return SUCCESS_RESULT;
}
return FAILURE_RESULT;
}
@DELETE
@Path("/users/{userid}")
@Produces(MediaType.APPLICATION_XML)
public String deleteUser(@PathParam("userid") int userid){
int result = userDao.deleteUser(userid);
if(result == 1){
return SUCCESS_RESULT;
}
return FAILURE_RESULT;
}
@OPTIONS
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
public String getSupportedOperations(){
return "<operations>GET, PUT, POST, DELETE</operations>";
}
}
现在使用Eclipse将您的应用程序导出为**WAR文件**,并在Tomcat中部署该文件。要使用Eclipse创建WAR文件,请按照以下路径操作:**文件→导出→Web→WAR文件**,最后选择项目UserManagement和目标文件夹。要在Tomcat中部署WAR文件,请将UserManagement.war放在**Tomcat安装目录→webapps**目录中,然后启动Tomcat。
测试Web服务
Jersey提供API来创建Web服务客户端以测试Web服务。我们在同一项目的com.tutorialspoint包下创建了一个示例测试类**WebServiceTester.java**。
WebServiceTester.java
package com.tutorialspoint;
import java.util.List;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
public class WebServiceTester {
private Client client;
private String REST_SERVICE_URL = "
https://:8080/UserManagement/rest/UserService/users";
private static final String SUCCESS_RESULT = "<result>success</result>";
private static final String PASS = "pass";
private static final String FAIL = "fail";
private void init(){
this.client = ClientBuilder.newClient();
}
public static void main(String[] args){
WebServiceTester tester = new WebServiceTester();
//initialize the tester
tester.init();
//test get all users Web Service Method
tester.testGetAllUsers();
//test get user Web Service Method
tester.testGetUser();
//test update user Web Service Method
tester.testUpdateUser();
//test add user Web Service Method
tester.testAddUser();
//test delete user Web Service Method
tester.testDeleteUser();
}
//Test: Get list of all users
//Test: Check if list is not empty
private void testGetAllUsers(){
GenericType<List<User>> list = new GenericType<List<User>>() {};
List<User> users = client
.target(REST_SERVICE_URL)
.request(MediaType.APPLICATION_XML)
.get(list);
String result = PASS;
if(users.isEmpty()){
result = FAIL;
}
System.out.println("Test case name: testGetAllUsers, Result: " + result );
}
//Test: Get User of id 1
//Test: Check if user is same as sample user
private void testGetUser(){
User sampleUser = new User();
sampleUser.setId(1);
User user = client
.target(REST_SERVICE_URL)
.path("/{userid}")
.resolveTemplate("userid", 1)
.request(MediaType.APPLICATION_XML)
.get(User.class);
String result = FAIL;
if(sampleUser != null && sampleUser.getId() == user.getId()){
result = PASS;
}
System.out.println("Test case name: testGetUser, Result: " + result );
}
//Test: Update User of id 1
//Test: Check if result is success XML.
private void testUpdateUser(){
Form form = new Form();
form.param("id", "1");
form.param("name", "suresh");
form.param("profession", "clerk");
String callResult = client
.target(REST_SERVICE_URL)
.request(MediaType.APPLICATION_XML)
.post(Entity.entity(form,
MediaType.APPLICATION_FORM_URLENCODED_TYPE),
String.class);
String result = PASS;
if(!SUCCESS_RESULT.equals(callResult)){
result = FAIL;
}
System.out.println("Test case name: testUpdateUser, Result: " + result);
}
//Test: Add User of id 2
//Test: Check if result is success XML.
private void testAddUser(){
Form form = new Form();
form.param("id", "2");
form.param("name", "naresh");
form.param("profession", "clerk");
String callResult = client
.target(REST_SERVICE_URL)
.request(MediaType.APPLICATION_XML)
.put(Entity.entity(form,
MediaType.APPLICATION_FORM_URLENCODED_TYPE),
String.class);
String result = PASS;
if(!SUCCESS_RESULT.equals(callResult)){
result = FAIL;
}
System.out.println("Test case name: testAddUser, Result: " + result );
}
//Test: Delete User of id 2
//Test: Check if result is success XML.
private void testDeleteUser(){
String callResult = client
.target(REST_SERVICE_URL)
.path("/{userid}")
.resolveTemplate("userid", 2)
.request(MediaType.APPLICATION_XML)
.delete(String.class);
String result = PASS;
if(!SUCCESS_RESULT.equals(callResult)){
result = FAIL;
}
System.out.println("Test case name: testDeleteUser, Result: " + result);
}
}
现在使用Eclipse运行测试器。右键单击该文件,然后选择**以Java应用程序方式运行**选项。您将在Eclipse控制台中看到以下结果:
Test case name: testGetAllUsers, Result: pass Test case name: testGetUser, Result: pass Test case name: testUpdateUser, Result: pass Test case name: testAddUser, Result: pass Test case name: testDeleteUser, Result: pass
RESTful Web服务 - 无状态性
根据REST架构,RESTful Web服务不应在服务器上保留客户端状态。此限制称为无状态性。客户端有责任将其上下文传递给服务器,然后服务器可以存储此上下文以处理客户端的后续请求。例如,服务器维护的会话由客户端传递的会话标识符标识。
RESTful Web服务应遵守此限制。我们在RESTful Web服务 - 方法章节中看到,Web服务方法没有存储任何来自调用它们客户端的信息。
考虑以下URL:
https://:8080/UserManagement/rest/UserService/users/1
如果您使用浏览器或基于Java的客户端或Postman访问上述URL,结果将始终是ID为1的用户XML,因为服务器不会存储任何有关客户端的信息。
<user> <id>1</id> <name>mahesh</name> <profession>1</profession> </user>
无状态性的优点
以下是RESTful Web服务中无状态性的好处:
Web服务可以独立处理每个方法请求。
Web服务不需要维护客户端之前的交互。它简化了应用程序设计。
由于HTTP本身就是一种无状态协议,因此RESTful Web服务可以与HTTP协议无缝协作。
无状态性的缺点
以下是RESTful Web服务中无状态性的缺点:
Web服务需要在每个请求中获取额外信息,然后进行解释以获取客户端的状态,以防需要处理客户端交互。
RESTful Web服务 - 缓存
缓存是指将服务器响应存储在客户端本身,以便客户端无需反复向服务器请求相同的资源。服务器响应应包含有关如何执行缓存的信息,以便客户端在一段时间内缓存响应或从不缓存服务器响应。
以下是可以配置客户端缓存的服务器响应标头:
| 序号 | 标头和描述 |
|---|---|
1 |
Date 资源创建时的日期和时间。 |
2 |
Last Modified 资源上次修改时的日期和时间。 |
3 |
Cache-Control 控制缓存的主要标头。 |
4 |
Expires 缓存的到期日期和时间。 |
5 |
Age 从资源从服务器获取开始的持续时间(以秒为单位)。 |
Cache-Control 头部
以下是 Cache-Control 头部的详细信息:
| 序号 | 指令 & 描述 |
|---|---|
1 |
Public 指示任何组件都可以缓存资源。 |
2 |
Private 指示只有客户端和服务器可以缓存资源,任何中间代理都不能缓存资源。 |
3 |
no-cache/no-store 指示资源不可缓存。 |
4 |
max-age 指示缓存有效期最长为 max-age 秒。之后,客户端必须再次发出请求。 |
5 |
must-revalidate 指示服务器在 max-age 过期后重新验证资源。 |
最佳实践
始终将静态内容(如图像、CSS、JavaScript)设置为可缓存,并设置 2 到 3 天的过期日期。
不要设置过高的过期日期。
动态内容应仅缓存几个小时。
RESTful Web 服务 - 安全性
由于 RESTful Web 服务使用 HTTP URL 路径,因此以与保护网站相同的方式保护 RESTful Web 服务非常重要。
以下是设计 RESTful Web 服务时应遵循的最佳实践:
验证 - 在服务器上验证所有输入。保护您的服务器免受 SQL 或 NoSQL 注入攻击。
基于会话的身份验证 - 使用基于会话的身份验证在每次向 Web 服务方法发出请求时对用户进行身份验证。
URL 中不包含敏感数据 - 切勿在 URL 中使用用户名、密码或会话令牌,这些值应通过 POST 方法传递给 Web 服务。
限制方法执行 - 限制 GET、POST 和 DELETE 等方法的使用。GET 方法不应能够删除数据。
验证格式错误的 XML/JSON - 检查传递给 Web 服务方法的输入格式是否正确。
抛出通用错误消息 - Web 服务方法应使用 HTTP 错误消息(如 403 表示访问被禁止等)。
HTTP 代码
| 序号 | HTTP 代码 & 描述 |
|---|---|
1 |
200 OK - 表示成功。 |
2 |
201 CREATED - 当使用 POST 或 PUT 请求成功创建资源时。使用位置头部返回新创建资源的链接。 |
3 |
204 NO CONTENT - 当响应正文为空时。例如,DELETE 请求。 |
4 |
304 NOT MODIFIED - 用于在条件 GET 请求的情况下减少网络带宽使用。响应正文应为空。头部应包含日期、位置等。 |
5 |
400 BAD REQUEST - 表示提供了无效的输入。例如,验证错误、数据丢失。 |
6 |
401 UNAUTHORIZED - 表示用户使用的身份验证令牌无效或错误。 |
7 |
403 FORBIDDEN - 表示用户无权访问正在使用的方法。例如,在没有管理员权限的情况下执行删除操作。 |
8 |
404 NOT FOUND - 表示该方法不可用。 |
9 |
409 CONFLICT - 表示执行方法时发生冲突的情况。例如,添加重复条目。 |
10 |
500 INTERNAL SERVER ERROR - 表示服务器在执行方法时抛出了一些异常。 |
RESTful Web 服务 - Java (JAX-RS)
JAX-RS 代表 JAVA API for RESTful Web Services。JAX-RS 是一个基于 JAVA 的编程语言 API 和规范,用于提供对创建 RESTful Web 服务的支持。其 2.0 版本于 2013 年 5 月 24 日发布。JAX-RS 使用 Java SE 5 中提供的注解来简化基于 JAVA 的 Web 服务的创建和部署。它还提供对创建 RESTful Web 服务客户端的支持。
规范
以下是将资源映射为 Web 服务资源最常用的注解。
| 序号 | 注解 & 描述 |
|---|---|
1 |
@Path 资源类/方法的相对路径。 |
2 |
@GET HTTP Get 请求,用于获取资源。 |
3 |
@PUT HTTP PUT 请求,用于更新资源。 |
4 |
@POST HTTP POST 请求,用于创建新资源。 |
5 |
@DELETE HTTP DELETE 请求,用于删除资源。 |
6 |
@HEAD HTTP HEAD 请求,用于获取方法可用性的状态。 |
7 |
@Produces 指定 Web 服务生成的 HTTP 响应。例如,APPLICATION/XML、TEXT/HTML、APPLICATION/JSON 等。 |
8 |
@Consumes 指定 HTTP 请求类型。例如,application/x-www-formurlencoded 用于在 POST 请求期间接受 HTTP 正文中的表单数据。 |
9 |
@PathParam 将传递给方法的参数绑定到路径中的值。 |
10 |
@QueryParam 将传递给方法的参数绑定到路径中的查询参数。 |
11 |
@MatrixParam 将传递给方法的参数绑定到路径中的 HTTP 矩阵参数。 |
12 |
@HeaderParam 将传递给方法的参数绑定到 HTTP 头部。 |
13 |
@CookieParam 将传递给方法的参数绑定到 Cookie。 |
14 |
@FormParam 将传递给方法的参数绑定到表单值。 |
15 |
@DefaultValue 为传递给方法的参数分配默认值。 |
16 |
@Context 资源的上下文。例如,HTTPRequest 作为上下文。 |
注意 - 我们在RESTful Web 服务 - 第一个应用程序和RESTful Web 服务 - 方法章节中使用了 Jersey,它是 Oracle 提供的 JAX-RS 2.0 参考实现。