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://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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://127.0.0.1:8080/UserManagement/rest/UserService/getUser/1
以下是一个获取用户的良好URI示例。
https://127.0.0.1:8080/UserManagement/rest/UserService/users/1
RESTful Web服务 - 方法
正如我们在前面章节中讨论的那样,RESTful Web服务使用大量HTTP动词来确定要对指定资源执行的操作。下表列出了最常用的HTTP动词的示例。
序号 | HTTP方法、URI和操作 |
---|---|
1 | GET https://127.0.0.1:8080/UserManagement/rest/UserService/users 获取用户列表。 (只读) |
2 |
GET https://127.0.0.1:8080/UserManagement/rest/UserService/users/1 获取ID为1的用户 (只读) |
3 |
PUT https://127.0.0.1:8080/UserManagement/rest/UserService/users/2 插入ID为2的用户 (幂等) |
4 |
POST https://127.0.0.1:8080/UserManagement/rest/UserService/users/2 更新ID为2的用户 (N/A) |
5 |
DELETE https://127.0.0.1:8080/UserManagement/rest/UserService/users/1 删除ID为1的用户 (幂等) |
6 |
OPTIONS https://127.0.0.1:8080/UserManagement/rest/UserService/users 列出Web服务中支持的操作。 (只读) |
7 |
HEAD https://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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 参考实现。