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、TomcatEclipse

安装 Java 开发工具包 (JDK)

您可以从 Oracle 的 Java 网站下载最新版本的 SDK:Java SE 下载。您将在下载的文件中找到安装 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 Home Page

安装 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 中获得目录结构,如下面的屏幕截图所示。

Jaxrs Directory

您将在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 的更多信息,请参阅此页面中包含的文档。您也可以在 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,如下面的屏幕截图所示:

Dynamic Web Project Wizard

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

Usermanagement Directories

添加所需的库

作为第二步,让我们在项目中添加 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 API, All 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 Request

HTTP请求有五个主要部分:

  • **动词** - 指示HTTP方法,例如GET、POST、DELETE、PUT等。

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

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

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

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

HTTP响应

HTTP Response

HTTP响应有四个主要部分:

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

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

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

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

示例

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

HTTP Request/Response

在这里您可以看到,浏览器发送了一个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 天的过期日期。

  • 不要设置过高的过期日期。

  • 动态内容应仅缓存几个小时。

Best practices for Cache-Control

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 参考实现。

广告