RESTful Web 服务 - 快速指南


RESTful Web 服务 - 简介

什么是 REST 架构?

REST 代表表述性状态转移。REST 是基于 Web 标准的架构并使用 HTTP 协议。它围绕资源,其中每个组件都是一个资源,并且资源可以通过使用 HTTP 标准方法的公共接口进行访问。REST 是由 Roy Fielding 于 2000 年首次提出的。

在 REST 架构中,REST 服务器仅提供对资源的访问,而 REST 客户端则访问和修改资源。这里每个资源都由 URI/全局 ID 来标识。REST 使用各种表示形式来表示资源,例如文本、JSON、XML。JSON 是最流行的一种。

HTTP 方法

基于 REST 的架构中常用以下四种 HTTP 方法。

  • 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 服务

在接下来的章节中,我们将创建一个具有以下功能的网络服务,即用户管理 -

先生。 统一资源标识符 HTTP方法 发布正文 结果
1 /用户服务/用户 得到空的 显示所有用户的列表。
2 /用户服务/addUser 邮政 JSON 字符串 添加新用户的详细信息。
3 /UserService/getUser/:id 得到空的 显示用户的详细信息。

RESTful Web 服务 - 环境设置

本教程将指导您如何准备开发环境以开始使用Jersey Framework创建 RESTful Web 服务。Jersey 框架实现了JAX-RS 2.0 API,这是创建 RESTful Web 服务的标准规范。本教程还将教您如何在设置 Jersey 框架之前在计算机上设置JDK、TomcatEclipse 。

设置 Java 开发工具包 (JDK)

您可以从 Oracle 的 Java 站点 - Java SE 下载下载最新版本的 SDK 。您将在下载的文件中找到安装 JDK 的说明。按照给定的说明安装和配置设置。最后设置PATHJAVA_HOME环境变量以引用包含JavaJavac的目录,通常分别为 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 

成功启动后,如果一切正常,那么您的屏幕应显示以下结果 -

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.apache.org/下载最新版本的 Tomcat 。下载安装后,将二进制发行版解压到一个方便的位置。例如,在 Windows 上的 C:\apache-tomcat-7.0.59 中,或在 Linux/Unix 上的 /usr/local/apache-tomcat-7.0.59 中,并设置指向安装位置的 CATALINA_HOME 环境变量。

Tomcat 可以通过在 Windows 机器上执行以下命令来启动,或者只需双击startup.bat即可。

%CATALINA_HOME%\bin\startup.bat

或者

C:\apache-tomcat-7.0.59\bin\startup.bat 

Tomcat 可以通过在 Unix(Solaris、Linux 等)机器上执行以下命令来启动 -

$CATALINA_HOME/bin/startup.sh

或者

/usr/local/apache-tomcat-7.0.59/bin/startup.sh

成功启动后,可以通过访问http://localhost:8080/ 来使用 Tomcat 附带的默认 Web 应用程序。如果一切正常那么它应该显示以下结果 -

雄猫

有关配置和运行 Tomcat 的更多信息可以在本页包含的文档中找到。此信息也可以在 Tomcat 网站上找到 - https://tomcat.apache.org。

可以通过在 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 框架编写第一个示例之前,您必须确保已按照RESTful Web 服务 - 环境设置一章中的说明正确设置 Jersey 环境。在这里,我还假设您对 Eclipse IDE 有一点使用知识。

因此,让我们继续编写一个简单的 Jersey 应用程序,它将公开一个 Web 服务方法来显示用户列表。

创建 Java 项目

第一步是使用 Eclipse IDE 创建动态 Web 项目。按照选项“文件”→“新建”→“项目”,最后从向导列表中选择“动态 Web 项目”向导。现在使用向导窗口将您的项目命名为UserManagement,如以下屏幕截图所示 -

动态 Web 项目向导

成功创建项目后,您的项目资源管理器中将包含以下内容-

用户管理目录

添加所需的库

第二步,让我们在项目中添加 Jersey Framework 及其依赖项(库)。将下载球衣 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 并按照选项 - New → Package

接下来我们将在com.tutorialspoint包下创建UserService.java、User.java、UserDao.java文件。

用户.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(); 
      } 
   }    
}

用户服务.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(); 
   }  
}

主程序有两点需要注意,

用户服务.java

  • 第一步是使用 UserService 的 @Path 注释指定 Web 服务的路径。

  • 第二步是使用 UserService 方法的 @Path 注释指定特定 Web 服务方法的路径。

创建 Web.xml 配置文件

您需要创建一个 Web xml 配置文件,该文件是一个 XML 文件,用于为我们的应用程序指定 Jersey 框架 servlet。

网络.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 文件”,最后选择项目“用户管理”和目标文件夹。要在Tomcat中部署war文件,请将UserManagement.war放在Tomcat安装目录→ webapps目录中并启动Tomcat。

运行程序

我们正在使用Postman(一个 Chrome 扩展程序)来测试我们的网络服务。

向 UserManagement 发出请求以获取所有用户的列表。将 http://localhost:8080/UserManagement/rest/UserService/users 放入 POSTMAN 中并使用 GET 请求,然后看到以下结果。

RESTful API,所有用户

恭喜,您已成功创建第一个 RESTful 应用程序。

RESTful Web 服务 - 资源

什么是资源?

REST 架构将每个内容视为资源。这些资源可以是文本文件、Html 页面、图像、视频或动态业务数据。REST 服务器仅提供对资源的访问,而 REST 客户端则访问和修改资源。这里每个资源都由 URI/全局 ID 来标识。REST 使用各种表示形式来表示资源,其中包括 Text、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 请求有五个主要部分 -

  • Verb - 表示 HTTP 方法,例如 GET、POST、DELETE、PUT 等。

  • URI - 用于标识服务器上的资源的统一资源标识符(URI)。

  • HTTP 版本- 指示 HTTP 版本。例如,HTTP v1.1。

  • 请求标头- 包含 HTTP 请求消息的元数据作为键值对。例如客户端(或浏览器)类型、客户端支持的格式、消息体的格式、缓存设置等。

  • 请求正文- 消息内容或资源表示。

HTTP响应

HTTP响应

HTTP 响应有四个主要部分 -

  • 状态/响应代码- 指示所请求资源的服务器状态。例如,404 表示资源未找到,200 表示响应正常。

  • HTTP 版本- 指示 HTTP 版本。例如 HTTP v1.1。

  • 响应标头- 包含 HTTP 响应消息的元数据作为键值对。例如,内容长度、内容类型、响应日期、服务器类型等。

  • 响应正文- 响应消息内容或资源表示。

例子

正如我们在RESTful Web 服务 - 第一个应用程序章节中所解释的,让我们将 http://localhost:8080/UserManagement/rest/UserService/users 放入带有 GET 请求的 POSTMAN 中。如果单击 Postman 发送按钮附近的“预览”按钮,然后单击“发送”按钮,您可能会看到以下输出。

HTTP 请求/响应

在这里您可以看到,浏览器发送了 GET 请求并收到了 XML 形式的响应正文。

RESTful Web 服务 - 寻址

寻址是指定位服务器上的一个或多个资源。这类似于查找一个人的邮政地址。

REST 架构中的每个资源都由其 URI(统一资源标识符)标识。URI 的格式如下:

<protocol>://<service-name>/<ResourceType>/<ResourceID>

URI 的用途是定位托管 Web 服务的服务器上的资源。请求的另一个重要属性是 VERB,它标识要对资源执行的操作。例如,在RESTful Web 服务 - 第一个应用程序章节中,URI 为http://localhost:8080/UserManagement/rest/UserService/users,VERB 为 GET。

构建标准 URI

以下是设计 URI 时需要考虑的要点 -

  • 使用复数名词- 使用复数名词来定义资源。例如,我们使用用户来将用户标识为资源。

  • 避免使用空格- 使用长资源名称时使用下划线 (_) 或连字符 (-)。例如,使用authorized_users 而不是authorized%20users。

  • 使用小写字母- 尽管 URI 不区分大小写,但最好仅将 url 保留为小写字母。

  • 保持向后兼容性- 由于 Web 服务是一项公共服务,因此 URI 一旦公开就应该始终可用。如果 URI 更新,请使用 HTTP 状态代码 300 将旧 URI 重定向到新 URI。

  • 使用 HTTP 动词- 始终使用 HTTP 动词(如 GET、PUT 和 DELETE)对资源执行操作。在 URI 中使用操作名称是不好的。

例子

以下是获取用户的不良 URI 的示例。

http://localhost:8080/UserManagement/rest/UserService/getUser/1 

以下是用于获取用户的良好 URI 的示例。

http://localhost:8080/UserManagement/rest/UserService/users/1

RESTful Web 服务 - 方法

正如我们在前面的章节中讨论的,RESTful Web 服务使用大量 HTTP 动词来确定要对指定资源执行的操作。下表列出了最常用的 HTTP 动词的示例。

先生。 HTTP 方法、URI 和操作

1

得到

http://localhost:8080/UserManagement/rest/UserService/users

获取用户列表。

(只读)

2

得到

http://localhost:8080/UserManagement/rest/UserService/users/1

获取Id为1的用户

(只读)

3

http://localhost:8080/UserManagement/rest/UserService/users/2

插入 ID 为 2 的用户

(幂等)

4

邮政

http://localhost:8080/UserManagement/rest/UserService/users/2

更新 ID 为 2 的用户

(不适用)

5

删除

http://localhost:8080/UserManagement/rest/UserService/users/1

删除ID为1的用户

(幂等)

6

选项

http://localhost:8080/UserManagement/rest/UserService/users

列出 Web 服务中支持的操作。

(只读)

7

http://localhost:8080/UserManagement/rest/UserService/users

仅返回 HTTP 标头,不返回正文。

(只读)

应考虑以下几点。

  • GET 操作是只读的并且是安全的。

  • PUT 和 DELETE 操作是幂等的,这意味着无论调用这些操作多少次,它们的结果将始终相同。

  • PUT 和 POST 操作几乎相同,区别仅在于结果,PUT 操作是幂等的,而 POST 操作会导致不同的结果。

例子

让我们更新在RESTful Web 服务 - 第一个应用程序章节中创建的示例,以创建一个可以执行 CRUD(创建、读取、更新、删除)操作的 Web 服务。为了简单起见,我们使用文件 I/O 来代替数据库操作。

让我们更新com.tutorialspoint 包下的User.java、UserDao.javaUserService.java文件。

用户.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(); 
      } 
   } 
}

用户服务.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。

测试网络服务

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 = "
   http://localhost: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 运行测试器。右键单击该文件并选择Run as → Java Application选项。您将在 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://localhost: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

日期

资源创建时的日期和时间。

2

上一次更改

上次修改资源时的日期和时间。

3

缓存控制

用于控制缓存的主标头。

4

过期

缓存的到期日期和时间。

5

年龄

从服务器获取资源以来的持续时间(以秒为单位)。

缓存控制头

以下是 Cache-Control 标头的详细信息 -

先生。 指令和说明

1

民众

指示资源可由任何组件缓存。

2

私人的

表示资源只能由客户端和服务器缓存,没有中介可以缓存该资源。

3

无缓存/无存储

指示资源不可缓存。

4

最大年龄

指示缓存在 max-age 以内有效(以秒为单位)。此后,客户必须提出另一个请求。

5

必须重新验证

如果 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

无内容- 当响应正文为空时。例如,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代表用于 RESTful Web 服务的 JAVA API。JAX-RS 是一种基于 JAVA 的编程语言 API 和规范,为创建的 RESTful Web 服务提供支持。它的 2.0 版本于 2013 年 5 月 24 日发布。JAX-RS 使用 Java SE 5 中提供的注释来简化基于 JAVA 的 Web 服务创建和部署的开发。它还支持为 RESTful Web 服务创建客户端。

规格

以下是将资源映射为 Web 服务资源的最常用注释。

先生。 注释和描述

1

@小路

资源类/方法的相对路径。

2

@得到

HTTP Get请求,用于获取资源。

3

@放

HTTP PUT 请求,用于更新资源。

4

@邮政

HTTP POST 请求,用于创建新资源。

5

@删除

HTTP DELETE 请求,用于删除资源。

6

@头

HTTP HEAD 请求,用于获取方法可用性的状态。

7

@产品

说明 Web 服务生成的 HTTP 响应。例如,应用程序/XML、文本/HTML、应用程序/JSON 等。

8

@消耗

说明 HTTP 请求类型。例如,application/x-www-formurlencoded 用于在 POST 请求期间接受 HTTP 正文中的表单数据。

9

@路径参数

将传递给方法的参数绑定到路径中的值。

10

@查询参数

将传递给方法的参数绑定到路径中的查询参数。

11

@MatrixParam

将传递给方法的参数绑定到路径中的 HTTP 矩阵参数。

12

@HeaderParam

将传递给方法的参数绑定到 HTTP 标头。

13

@CookieParam

将传递给该方法的参数绑定到 Cookie。

14

@FormParam

将传递给该方法的参数绑定到表单值。

15

@默认值

为传递给该方法的参数分配默认值。

16

@语境

资源的上下文。例如,HTTPRequest 作为上下文。

注- 我们在RESTful Web 服务 - 第一个应用程序RESTful Web 服务 - 方法章节中使用了 Jersey,它是 Oracle 的 JAX-RS 2.0 的参考实现。