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.apache.org/下載最新版本的 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 的更多資訊,請參閱此頁面中包含的文件。您也可以在 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 框架編寫第一個示例之前,您必須確保已正確設定 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://: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://: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://: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 天的過期日期。

  • 不要設定過高的過期日期。

  • 動態內容應僅快取幾個小時。

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 參考實現。

廣告