Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 麻豆国产96在线|中国,午夜视频网站,好男人神马视频www在线观看

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          用C++寫出HTML,使用web服務,可以免費搭建個人博客

          eb服務是.net中讓人激動的部分,幾乎所有你能叫出名字的服務都有一些執行服務器端代碼的機制:正巧每種語言都一個類庫,因此在HTTP中生成一個GET請求變得很簡單,解析出XML也有了些捷徑。

          這種方案給你提供了一種跨平臺,跨語言,跨廠商乃至一切的方法,只要它們都在INTERNET上或是以其他的方式相連,我們就可以在某個程序的代碼中調用另外一個完全不同的機器上的代碼。



          這就是隱藏在WEB服務背后的基本觀念。使用類似于WEB服務描述語言(說 wizdle會更酷一些)開發有一定的標準,它們涵蓋了這些技術細節。

          如果你用Visual Studio.NET創建一個WEB服務,它將滿足這些標準。如果你只是需要WEB服務,而不管它是如何創建的,通過Visual Studio.NET,你會發現借用他人的代碼是如此簡單。

          編寫一個WEB服務

          為了編寫一個WEB服務,你至少要用一種方法寫一個類。這個類必須有WebService屬性,方法也要有WebMethod屬性。WEB方法能夠接受和返回任何可用的類型,包括你定義的對象實例。它們能做任何事情:維護數據庫數據的內外一致性,做任何形式的運算,甚至調用另外一個WEB方法來完成任務。

          在Visual Studio.NET中創建一個新工程。在Visual C++工程模板中,選擇可管理的WEB服務。修改后是:

          <@ WebService Class=Calculator.CalculatorService %>

          我獲得了一個叫做HelloWorld()的方法,把它改成Add()很簡單——我僅僅更改了.cpp文件和.h文件的名稱,改變了簽名以便它能夠接受浮點數,然后加了些代碼以返回和。



          類聲明的結束部分:

          using <System.Web.Services.dll>
          using namespace System;
          using namespace System::Web;
          using namespace System::Web::Services;
          namespace Calculator
          {
          public __gc
          class CalculatorService : public WebService
          {
          public:
          [System::Web::Services::WebMethod]
          double Add(double x, double y);
          };
          }

          實現的部分:

          #include "stdafx.h"
          #include "Calculator.h"
          #include "Global.asax.h"
          namespace Calculator
          {
          double CalculatorService::Add(double x, double y)
          {
          return x + y;
          }
          }

          第一個 CGI 程序

          
          #include <iostream>using namespace std;
          int main (){   
            cout << "Content-type:text/html\r\n\r\n";
            cout << "<html>\n";
            cout << "<head>\n";
            cout << "<title>Hello World - 第一個 CGI 程序</title>\n";
            cout << "</head>\n";
            cout << "<body>\n";
            cout << "<h2>Hello World! 這是我的第一個 CGI 程序</h2>\n";
            cout << "</body>\n";
            cout << "</html>\n";
            return 0;}

          使用WEB服務



          寫一個WEB服務相當簡單:你只需要一個類屬性,一個方法屬性和calculator.asmx文件,而這三個都由Visual Studio生成。

          最簡單的方法是鍵入URL到Calculator.asmx然后按回車。你會看到和以前運行WEB服務工程時同樣的文件。點擊添加參數結束這個過程。



          參數一旦添加,調用WEB服務就像調用任何C++類一樣。添加參數建立一個頭文件,這個文件在任何你想使用WEB服務時都可以包括進去。

          章涵蓋

          用 Rust 編寫 TCP 服務器

          用 Rust 編寫 HTTP 服務器

          在本章中,您將深入研究使用 Rust 進行 TCP 和 HTTP 通信。

          這些協議通常是通過用于構建 Web 應用程序的更高級別的庫和框架為開發人員抽象出來的。 那么,為什么討論低層協議很重要? 這是一個公平的問題。

          學習使用 TCP 和 HTTP 非常重要,因為它們構成了 Internet 上大多數通信的基礎。 流行的應用程序通信協議和技術(例如 REST、gRPC 和 Websocket)使用 HTTP 和 TCP 進行傳輸。 在 Rust 中設計和構建基本的 TCP 和 HTTP 服務器可以讓您有信心設計、開發更高級別的應用程序后端服務并對其進行故障排除。

          但是,如果您渴望開始使用示例應用程序,則可以轉到第 3 章,然后在適合您的時間再返回到本章。

          在本章中,您將學習以下內容:

          編寫TCP客戶端和服務器。

          構建一個庫以在 TCP 原始字節流和 HTTP 消息之間進行轉換。

          構建一個可以提供靜態網頁(又名 Web 服務器)以及 json 數據(又名 Web 服務)的 HTTP 服務器。 使用標準 HTTP 客戶端(例如 cURL(命令行)工具和 Web 瀏覽器)測試服務器。

          通過本練習,您將了解如何使用 Rust 數據類型和特征來建模現實世界的網絡協議,并增強您的 Rust 基礎知識。

          本章分為兩節。 在第一部分中,您將使用 Rust 開發一個可以通過 TCP/IP 進行通信的基本網絡服務器。 在第二部分中,您將構建一個 Web 服務器,用于響應網頁和 json 數據的 GET 請求。 您只需使用 Rust 標準庫(無需外部板條箱)即可實現這一切。 您要構建的 HTTP 服務器并不是功能齊全或可用于生產的。 但這將服務于我們既定的目的。

          讓我們開始吧。

          我們談到現代應用程序被構建為一組獨立的組件和服務,一些屬于前端,一些屬于后端,一些屬于分布式軟件基礎設施。

          每當我們有單獨的組件時,就會出現這些組件如何相互通信的問題。 客戶端(網絡瀏覽器或移動應用程序)如何與后端服務通信? 后端服務如何與數據庫等軟件基礎設施通信? 這就是網絡模型發揮作用的地方。

          網絡模型描述了消息發送者與其接收者之間如何進行通信。 它解決了諸如應以什么格式發送和接收消息、應如何將消息分解為物理數據傳輸的字節、如果數據包未到達目的地應如何處理錯誤等問題。 OSI 模型 是最流行的網絡模型,是根據全面的七層框架定義的。 但出于互聯網通信的目的,稱為 TCP/IP 模型的簡化四層模型通常足以描述發出請求的客戶端與處理該請求的服務器之間如何通過互聯網進行通信。 TCP/IP 模型在此處描述 (https://www.w3.org/People/Frystyk/thesis/TcpIp.html)。

          TCP/IP 模型是一組簡化的互聯網通信標準和協議。 它分為四個抽象層:網絡訪問層、互聯網層、傳輸層和應用層,每個層都可以使用有線協議的靈活性。 該模型以其構建的兩個主要協議命名:傳輸控制協議 (TCP) 和互聯網協議 (IP)。 如圖 2.1 所示。 主要需要注意的是,這四層相輔相成,確保消息從發送進程成功發送到接收進程。


          圖 2.1。 TCP/IP網絡模型

          現在我們將了解這四層中每一層在通信中的作用。

          應用層是最高的抽象層。 該層可以理解消息的語義。 例如,Web瀏覽器和Web服務器使用HTTP進行通信,或者電子郵件客戶端和電子郵件服務器使用SMTP(簡單郵件傳輸協議)進行通信。 還有其他此類協議,例如 DNS(域名服務)和 FTP(文件傳輸協議)。 所有這些都被稱為應用層協議,因為它們處理特定的用戶應用程序——例如網頁瀏覽、電子郵件或文件傳輸。在本書中,我們將主要關注應用層的HTTP協議。

          傳輸層提供可靠的端到端通信。 應用層處理具有特定語義的消息(例如發送 GET 請求以獲取貨運詳細信息),而傳輸協議則處理發送和接收原始字節。 (注意:所有應用層協議消息最終都會轉換為原始字節以供傳輸層傳輸)。 TCP 和 UDP 是該層使用的兩個主要協議,QUIC(快速 UDP 互聯網連接)也是最近加入的協議。 TCP 是一種面向連接的協議,允許對數據進行分區傳輸并在接收端以可靠的方式重新組裝。 UDP 是一種無連接協議,與 TCP 不同,它不提供傳送保證。 因此,UDP 速度更快,適合某些類別的應用程序,例如 DNS 查找、語音或視頻應用程序。在本書中,我們將重點關注傳輸層的 TCP 協議。

          網絡層使用 IP 地址和路由器來定位信息包并將其路由到網絡上的主機。 雖然 TCP 層專注于在由 IP 地址和端口號標識的兩個服務器之間發送和接收原始字節,但網絡層擔心將數據包從源發送到目的地的最佳路徑是什么。 我們不需要直接使用網絡層,因為 Rust 的標準庫提供了使用 TCP 和套接字的接口,并處理網絡層通信的內部結構。

          網絡訪問層是 TCP/IP 網絡模型的最低層。 它負責通過主機之間的物理鏈路(例如使用網卡)傳輸數據。就我們的目的而言,使用什么物理介質進行網絡通信并不重要。

          現在我們已經了解了 TCP/IP 網絡模型,我們將學習如何使用 TCP/IP 協議在 Rust 中發送和接收消息。

          2.1 用 Rust 編寫 TCP 服務器

          在本節中,您將學習如何相當輕松地在 Rust 中執行基本的 TCP/IP 網絡通信。 讓我們首先了解如何使用 Rust 標準庫中的 TCP/IP 結構。

          2.1.1 設計TCP/IP通信流程

          Rust 標準庫通過 std::net 模塊提供網絡原語,其文檔可以在以下位置找到:https://doc.rust-lang.org/std/net/。 該模塊支持基本的 TCP 和 UDP 通信。 有兩種特定的數據結構:TcpListener 和 TcpStream,它們具有實現我們的場景所需的大量方法。

          讓我們看看如何使用這兩種數據結構。

          TcpListener 用于創建綁定到特定端口的 TCP 套接字服務器。 客戶端可以向指定套接字地址(機器的 IP 地址和端口號的組合)的套接字服務器發送消息。 一臺機器上可能運行多個 TCP 套接字服務器。 當網卡上有傳入網絡連接時,操作系統使用端口號將消息路由到正確的 TCP 套接字服務器。

          這里顯示了創建套接字服務器的示例代碼。

          use std::net::TcpListener;
          
          let listener = TcpListener::bind("127.0.0.1:3000")

          綁定到端口后,套接字服務器應開始偵聽下一個傳入連接。 這是實現的,如下所示:

          listener.accept()

          要連續(循環)偵聽傳入連接,請使用以下方法:

          listener.incoming()

          Listener.incoming() 方法返回在此偵聽器上接收到的連接的迭代器。 每個連接代表一個 TcpStream 類型的字節流。 可以在此 TcpStream 對象上發送或接收數據。 請注意,對 TcpStream 的讀取和寫入是以原始字節完成的。 接下來顯示代碼片段。(注意:為簡單起見,排除了錯誤處理)

          for stream in listener.incoming() {
              //Read from stream into a bytes buffer
              stream.read(&mut [0;1024]);
              // construct a message and write to stream
              let message = "Hello".as_bytes();
              stream.write(message)
          }

          注意

          為了從流中讀取數據,我們構建了一個字節緩沖區(在 Rust 中稱為字節切片)。

          為了寫入流,我們構造了一個字符串切片并使用 as_bytes() 方法將其轉換為字節切片

          到目前為止,我們已經了解了 TCP 套接字服務器的服務器端。 在客戶端,可以與 TCP 套接字服務器建立連接,如下所示:

          let stream = TcpStream.connect("172.217.167.142:80")

          回顧一下,連接管理函數可從 std::net 模塊的 TcpListener 結構中獲得。 要在連接上讀取和寫入,請使用 TcpStream 結構。

          現在讓我們應用這些知識來編寫一個有效的 TCP 客戶端和服務器。

          2.1.2 編寫TCP服務器和客戶端

          我們首先設置一個項目結構。 圖 2.2 顯示了名為 scene1 的工作區,其中包含四個項目 - tcpclient、tcpserver、http 和 httpserver。

          對于 Rust 項目,工作區是一個包含其他項目的容器項目。 工作區結構的好處是它使我們能夠將多個項目作為一個單元進行管理。 它還有助于將所有相關項目無縫存儲在單個 git 存儲庫中。 我們將創建一個名為 scene1 的工作區項目。 在此工作區下,我們將使用 Rust 項目構建和依賴項工具 Cargo 創建四個新的 Rust 項目。 這四個項目分別是tcpclient、tcpserver、http和httpserver。


          圖 2.2。 cargo工作空間項目

          此處列出了用于創建工作區和關聯項目的命令。

          開始一個新的cargo項目:

          cargo new scenario1 && cd scenario1

          scenario 1 目錄也可以稱為工作空間根目錄。

          在scenario1目錄下,創建以下四個新的Rust項目:

          cargo new tcpserver
          cargo new tcpclient
          cargo new httpserver
          cargo new --lib http
          • tcpserver 將是 TCP 服務器代碼的二進制項目
          • tcpclient 將是 TCP 客戶端代碼的二進制項目
          • httpserver 將是 HTTP 服務器代碼的二進制項目
          • http 將是 http 協議功能的庫項目

          現在項目已創建,我們必須將 scene1 項目聲明為工作區并指定其與四個子項目的關系。 添加以下內容:

          清單 2.1。 scenario1/Cargo.toml

          [workspace]
          members = [
              "tcpserver","tcpclient", "http", "httpserver",
          ]

          我們現在將分兩次迭代編寫 TCP 服務器和客戶端的代碼:

          在第一次迭代中,我們將編寫 TCP 服務器和客戶端來對從客戶端到服務器建立的連接進行健全性檢查。

          在第二次迭代中,我們將從客戶端發送文本到服務器,并讓服務器回顯它。

          關于遵循代碼的一般注意事項

          本章(以及整本書)中顯示的許多代碼片段都有內聯編號的代碼注釋來描述代碼。 如果您將代碼(從本書的任何章節)復制并粘貼到代碼編輯器中,請確保刪除代碼注釋編號(否則程序將無法編譯)。 另外,粘貼的代碼有時可能會錯位,因此可能需要手動驗證將粘貼的代碼與本章中的代碼片段進行比較,以防出現編譯錯誤。

          迭代1

          進入tcpserver文件夾,修改src/main.rs如下:

          清單 2.2。 TCP 服務器的第一次迭代 (tcpserver/src/main.rs)

          use std::net::TcpListener;
          
          fn main() {
              let connection_listener = TcpListener::bind("127.0.0.1:3000").unwrap(); #1
              println!("Running on port 3000");
              for stream in connection_listener.incoming() {             #2
                  let _stream = stream.unwrap();                          #3
                  println!("Connection established");
              }
          }

          從工作區的根文件夾(scenario 1),運行:

          cargo run -p tcpserver   #1

          服務器將啟動,并且消息 Running on port 3000 將打印到終端。 現在,我們有一個正在運行的 TCP 服務器正在偵聽 localhost 上的端口 3000。

          接下來我們來編寫一個 TCP 客戶端來與 TCP 服務器建立連接。

          清單 2.3。 tcpclient/src/main.rs

          use std::net::TcpStream;
          
          fn main() {
              let _stream = TcpStream::connect("localhost:3000").unwrap();  #1
          }

          在新終端中,從工作區的根文件夾中運行:

          cargo run -p tcpclient

          您將看到消息“連接已建立”打印到運行 TCP 服務器的終端,如下所示:

          Running on port 3000
          Connection established

          現在我們有一個在端口 3000 上運行的 TCP 服務器,以及一個可以與其建立連接的 TCP 客戶端。

          我們現在可以嘗試從客戶端發送消息并讓服務器回顯該消息。

          迭代 2:

          修改tcpserver/src/main.rs文件如下:

          清單 2.4。 完成TCP服務器

          use std::io::{Read, Write};     #1
          use std::net::TcpListener;
          fn main() {
              let connection_listener = TcpListener::bind("127.0.0.1:3000").unwrap();
              println!("Running on port 3000");
              for stream in connection_listener.incoming() {
                  let mut stream = stream.unwrap();   #2
                  println!("Connection established");
                  let mut buffer = [0; 1024];
                  stream.read(&mut buffer).unwrap();  #3
                  stream.write(&mut buffer).unwrap(); #4
              }
          }

          在所示的代碼中,我們正在向客戶端回顯,無論我們從客戶端收到什么。 從工作區根目錄使用 Cargo run -p tcpserver 運行 TCP 服務器。

          讀和寫特征

          Rust 中的特征定義了共享行為。 它們與其他語言的界面類似,但也有一些差異。 Rust 標準庫(std)定義了由 std 中的數據類型實現的幾個特征。 這些特征也可以通過用戶定義的數據類型(例如結構和枚舉)來實現。

          讀和寫是 Rust 標準庫中定義的兩個這樣的特征。

          讀取特征允許從源讀取字節。 實現 Read 特征的源示例包括 File、Stdin(標準輸入)和 TcpStream。 Read 特征的實現者需要實現一種方法 - read()。 這允許我們使用相同的 read() 方法從 File、Stdin、TcpStream 或實現 Read 特征的任何其他類型中讀取。

          類似地,Write 特征表示面向字節接收器的對象。 Write 特征的實現者實現了兩個方法 - write() 和flush()。 實現 Write 特征的類型示例包括 File、Stderr、Stdout 和 TcpStream。 這個特性允許我們使用 write() 方法寫入文件、標準輸出、標準錯誤或 TcpStream。

          下一步是修改 TCP 客戶端以將消息發送到服務器,然后打印從服務器接收回的內容。 修改文件tcpclient/src/main.rs如下:

          清單 2.5。 完成TCP客戶端

          use std::io::{Read, Write};
          use std::net::TcpStream;
          use std::str;
          
          fn main() {
              let mut stream = TcpStream::connect("localhost:3000").unwrap();
              stream.write("Hello".as_bytes()).unwrap();  #1
              let mut buffer = [0; 5];
              stream.read(&mut buffer).unwrap();          #2
              println!(
                  "Got response from server:{:?}",        #3
                  str::from_utf8(&buffer).unwrap()
              );
          }

          從工作區根目錄使用 Cargo run -p tcpclient 運行 TCP 客戶端。 確保 TCP 服務器也在另一個終端窗口中運行。

          您將看到以下消息打印到 TCP 客戶端的終端窗口:

          Got response from server:"Hello"

          恭喜。 您已經編寫了可以相互通信的 TCP 服務器和 TCP 客戶端。

          結果類型和 unwrap() 方法

          在 Rust 中,函數或方法無法返回 Result<T,E> 類型是慣用的。 這意味著如果成功,Result 類型會包裝另一個數據類型 T;如果失敗,則包裝一個 Error 類型,然后將其返回給調用函數。 調用函數依次檢查 Result 類型并將其解包以接收類型 T 或類型 Error 的值以進行進一步處理。

          在到目前為止的示例中,我們在多個地方使用了 unwrap() 方法,以通過標準庫方法檢索嵌入到 Result 對象中的值。 如果操作成功,unwrap() 方法返回 T 類型的值,如果發生錯誤,則發生恐慌。 在現實應用程序中,這不是正確的方法,因為 Rust 中的 Result 類型用于可恢復的故障,而恐慌用于不可恢復的故障。 然而,我們使用它是因為使用 unwrap() 簡化了我們的代碼以用于學習目的。 我們將在后面的章節中介紹正確的錯誤處理。

          在本節中,我們學習了如何在 Rust 中進行 TCP 通信。 您還注意到 TCP 是一個低級協議,僅處理字節流。 它對所交換的消息和數據的語義沒有任何理解。 對于編寫 Web 應用程序,語義消息比原始字節流更容易處理。 因此,我們需要使用更高級別的應用程序協議,例如 HTTP,而不是 TCP。 這就是我們將在下一節中討論的內容。

          2.2 用 Rust 編寫 HTTP 服務器

          在本節中,我們將用 Rust 構建一個可以與 HTTP 消息通信的 Web 服務器。

          但 Rust 沒有內置對 HTTP 的支持。 沒有我們可以使用的 std::http 模塊。 盡管有第三方 HTTP 包可用,但我們將從頭開始編寫一個。 通過這個,我們將學習如何應用 Rust 來開發現代 Web 應用程序所依賴的底層庫和服務器。

          讓我們首先想象一下我們要構建的 Web 服務器的功能。 客戶端與Web服務器各模塊之間的通信流程如圖2.3所示。


          圖 2.3 Web服務器消息流

          我們的 Web 服務器將有四個組件 - 服務器、路由器、處理程序和 HTTP 庫。 每個組件都有特定的用途,符合單一職責原則 (SRP)。 服務器偵聽傳入的 TCP 字節流。 HTTP 庫解釋字節流并將其轉換為 HTTP 請求(消息)。 路由器接受 HTTP 請求并確定要調用哪個處理程序。 處理程序處理 HTTP 請求并構造 HTTP 響應。 HTTP 響應消息使用 HTTP 庫轉換回字節流,然后發送回客戶端。

          圖 2.4 顯示了 HTTP 客戶端-服務器通信的另一個視圖,這次描述了 HTTP 消息如何流經 TCP/IP 協議棧。 TCP/IP 通信在客戶端和服務器端的操作系統級別進行處理,Web 應用程序開發人員僅使用 HTTP 消息。


          圖 2.4. HTTP 協議棧通信

          讓我們按以下順序構建代碼:

          構建 HTTP 庫

          為項目編寫main()函數

          編寫服務器模塊

          編寫路由器模塊

          編寫處理程序模塊

          為了方便起見,圖 2.5 顯示了代碼設計的摘要,顯示了 http 庫和 httpserver 項目的關鍵模塊、結構和方法。


          圖 2.5。 Web服務器設計概述

          我們將為該圖中所示的模塊、結構和方法編寫代碼。 以下是圖中每個組件的作用的簡短摘要:

          http:包含 HttpRequest 和 HttpResponse 類型的庫。 它實現了 HTTP 請求和響應之間的轉換邏輯,以及相應的 Rust 數據結構。

          httpserver:主 Web 服務器,包含 main() 函數、套接字服務器、處理程序和路由器,并管理它們之間的協調。 它既充當 Web 服務器(提供 html)又充當 Web 服務(提供 json)。

          我們開始吧?

          2.2.1 解析HTTP請求報文

          在本節中,我們將構建一個 HTTP 庫。 該庫將包含執行以下操作的數據結構和方法:

          解釋傳入的字節流并將其轉換為 HTTP 請求消息

          構造 HTTP 響應消息并將其轉換為字節流以便通過網絡傳輸

          我們現在準備編寫一些代碼。

          回想一下,我們已經在 scene1 工作區下創建了一個名為 http 的庫。

          HTTP 庫的代碼將放置在 http/src 文件夾下。

          在http/src/lib.rs中添加以下代碼:

          pub mod httprequest;

          這告訴編譯器我們正在 http 庫中創建一個名為 httprequest 的新的可公開訪問的模塊。

          另外,從此文件中刪除預先生成的測試腳本(通過 Cargo 工具)。 稍后我們將編寫測試用例。

          在http/src下創建兩個新文件httprequest.rs和httpresponse.rs,分別包含處理HTTP請求和響應的功能。

          我們將從設計 Rust 數據結構來保存 HTTP 請求開始。 當 TCP 連接上有傳入字節流時,我們將對其進行解析并將其轉換為強類型 Rust 數據結構以進行進一步處理。 然后,我們的 HTTP 服務器程序可以使用這些 Rust 數據結構,而不是 TCP 流。

          表 1 顯示了表示傳入 HTTP 請求所需的 Rust 數據結構摘要:

          表 2.1。 顯示我們將要構建的數據結構列表的表格。

          數據結構名稱

          Rust 數據類型

          描述

          HttpRequest

          struct

          代表一個HTTP請求

          Method

          enum

          指定 HTTP 方法的允許值(動作)

          Version

          enum

          指定 HTTP 版本允許的值

          我們將在這些數據結構上實現一些特征,以傳遞一些行為。 表 2 顯示了我們將在三種數據結構上實現的特征的描述。

          表 2.2。 顯示 HTTP 請求的數據結構實現的特征列表的表。

          Rust trait 實現

          描述

          From<&str>

          此特征可以將傳入的字符串切片轉換為 HttpRequest 數據結構

          Debug

          用于打印調試消息

          PartialEq

          用于比較值作為解析和自動化測試腳本的一部分

          現在讓我們將此設計轉換為代碼。 我們將編寫數據結構和方法。

          方法

          我們將在這里編寫方法枚舉和特征實現的代碼。

          將以下代碼添加到http/src/httprequest.rs。

          此處顯示了方法枚舉的代碼。 我們使用枚舉數據結構,因為我們希望在實現中僅允許 HTTP 方法的預定義值。 在此版本的實現中,我們僅支持兩種 HTTP 方法 - GET 和 POST 請求。 我們還將添加第三種類型 - 未初始化,在運行程序中的數據結構初始化期間使用。

          將以下代碼添加到http/src/httprequest.rs:

          #[derive(Debug, PartialEq)]
          pub enum Method {
              Get,
              Post,
              Uninitialized,
          }

          Method 的特征實現如下所示(將添加到 httprequest.rs):

          impl From<&str> for Method {
              fn from(s: &str) -> Method {
                  match s {
                      "GET" => Method::Get,
                      "POST" => Method::Post,
                      _ => Method::Uninitialized,
                  }
              }
          }

          在 From 特征中實現 from 方法使我們能夠從 HTTP 請求行讀取方法字符串,并將其轉換為 Method::Get 或 Method::Post 變體。 為了了解實現此特征的好處并測試此方法是否有效,讓我們編寫一些測試代碼。 將以下內容添加到 http/src/httprequest.rs:

          #[cfg(test)]
          mod tests {
              use super::*;
              #[test]
              fn test_method_into() {
                  let m: Method = "GET".into();
                  assert_eq!(m, Method::Get);
              }
          }

          從工作區根目錄運行以下命令:

          cargo test -p http

          您將注意到一條與此類似的消息,表明測試已通過。

          running 1 test
          test httprequest::tests::test_method_into ... ok
          
          test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

          僅使用 .into() 語法將字符串“GET”轉換為 Method::Get 變體,這是實現 From 特征的好處。 它可以生成干凈、可讀的代碼。

          現在讓我們看一下 Version 枚舉的代碼。

          版本

          Version 枚舉的定義如下所示。 盡管我們僅使用 HTTP/1.1 作為示例,但我們將支持兩個 HTTP 版本以供說明。 還有第三種類型 - 未初始化,用作默認初始值。

          將以下代碼添加到http/src/httprequest.rs:

          #[derive(Debug, PartialEq)]
          pub enum Version {
              V1_1,
              V2_0,
              Uninitialized,
          }

          Version 的特征實現與 Method enum 的特征實現類似(將添加到 httprequest.rs)。

          impl From<&str> for Version {
              fn from(s: &str) -> Version {
                  match s {
                      "HTTP/1.1" => Version::V1_1,
                      _ => Version::Uninitialized,
                  }
              }
          }

          在 From 特征中實現 from 方法使我們能夠從傳入的 HTTP 請求中讀取 HTTP 協議版本,并將其轉換為 Version 變體。

          我們來測試一下這個方法是否有效。 將以下內容添加到先前添加的 mod 測試塊內的 http/src/httprequest.rs 中(在 test_method_into() 函數之后),并使用 Cargo test -p http 從工作空間根運行測試:

              #[test]
              fn test_version_into() {
                  let m: Version = "HTTP/1.1".into();
                  assert_eq!(m, Version::V1_1);
              }

          您將在終端上看到以下消息:

          running 2 tests
          test httprequest::tests::test_method_into ... ok
          test httprequest::tests::test_version_into ... ok
          
          test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

          現在這兩個測試都通過了。 僅使用 .into() 語法將字符串“HTTP/1.1”轉換為 Version::V1_1 變體,這是實現 From 特征的好處。

          Http請求

          這代表完整的 HTTP 請求。 此處的代碼顯示了該結構。 將此代碼添加到文件 http/src/httprequest.rs 的開頭。

          清單 2.6。 HTTP請求的結構

          use std::collections::HashMap;
          
          #[derive(Debug, PartialEq)]
          pub enum Resource {
              Path(String),
          }
          
          #[derive(Debug)]
          pub struct HttpRequest {
              pub method: Method,
              pub version: Version,
              pub resource: Resource,
              pub headers: HashMap<String, String>,
              pub msg_body: String,
          }

          HttpRequest 結構的 From<&str> 特征實現是我們練習的核心。 這使我們能夠將傳入的請求轉換為便于進一步處理的 Rust HTTP 請求數據結構。

          圖 2.6 顯示了典型 HTTP 請求的結構。


          圖2.6 HTTP請求的結構

          該圖顯示了一個示例 HTTP 請求,其中包含一個請求行、一組一個或多個標頭行,后跟一個空行,然后是一個可選的消息正文。 我們必須解析所有這些行并將它們轉換為我們的 HTTPRequest 類型。 這將是 from() 函數的工作,作為 From<&str> 特征實現的一部分。

          這里列出了 From<&str> 特征實現的核心邏輯:

          讀取傳入 HTTP 請求中的每一行。 每行由 CRLF (\r\n) 分隔。

          按如下方式評估每一行:

          如果該行是請求行(我們正在查找關鍵字 HTTP 來檢查它是否是請求行,因為所有請求行都包含 HTTP 關鍵字和版本號),請從該行中提取方法、路徑和 HTTP 版本。

          如果該行是標頭行(由分隔符“:”標識),則提取標頭項的鍵和值并將它們添加到請求標頭列表中。 請注意,HTTP 請求中可以有多個標頭行。 為了簡單起見,我們假設鍵和值必須由可打印的 ASCII 字符組成(即以 10 為基數,值在 33 到 126 之間的字符,冒號除外)。

          如果一行為空 (\n\r),則將其視為分隔行。 在這種情況下無需采取任何措施

          如果消息正文存在,則掃描并將其存儲為字符串。

          將以下代碼添加到http/src/httprequest.rs。

          讓我們以較小的塊來看一下代碼。 首先,這是代碼的框架。 暫時不要輸入它,這只是為了顯示代碼的結構。

          impl From<String> for HttpRequest {
              fn from(req: String) -> Self {}
          }
          fn process_req_line(s: &str) -> (Method, Resource, Version) {}
          fn process_header_line(s: &str) -> (String, String) {}

          我們有一個 from() 方法,我們應該為 From 特征實現它。 還有另外兩個支持函數,分別用于解析請求行和標題行。

          我們首先看一下 from() 方法。 將以下內容添加到 httprequest.rs。

          清單 2.7 解析傳入的 HTTP 請求:from() 方法

          impl From<String> for HttpRequest {
              fn from(req: String) -> Self {
                  let mut parsed_method = Method::Uninitialized;
                  let mut parsed_version = Version::V1_1;
                  let mut parsed_resource = Resource::Path("".to_string());
                  let mut parsed_headers = HashMap::new();
                  let mut parsed_msg_body = "";
          
                  // Read each line in the incoming HTTP request
                  for line in req.lines() {
                      // If the line read is request line, call function process_req_line()
                      if line.contains("HTTP") {
                          let (method, resource, version) = process_req_line(line);
                          parsed_method = method;
                          parsed_version = version;
                          parsed_resource = resource;
                      // If the line read is header line, call function process_header_line()
                      } else if line.contains(":") {
                          let (key, value) = process_header_line(line);
                          parsed_headers.insert(key, value);
                      //  If it is blank line, do nothing
                      } else if line.len() == 0 {
                          // If none of these, treat it as message body
                      } else {
                          parsed_msg_body = line;
                      }
                  }
                  // Parse the incoming HTTP request into HttpRequest struct
                  HttpRequest {
                      method: parsed_method,
                      version: parsed_version,
                      resource: parsed_resource,
                      headers: parsed_headers,
                      msg_body: parsed_msg_body.to_string(),
                  }
              }
          }

          根據前面描述的邏輯,我們嘗試檢測傳入 HTTP 請求中的各種類型的行,然后使用解析的值構造一個 HTTPRequest 結構體。 接下來我們將看看這兩種支持方法。

          下面是處理傳入請求的請求行的代碼。 將其添加到 httprequest.rs,位于 impl From<String> for HttpRequest {} 塊之后。

          清單 2.8 解析傳入的 HTTP 請求:process_req_line() 函數

          fn process_req_line(s: &str) -> (Method, Resource, Version) {
              // Parse the request line into individual chunks split by whitespaces.
              let mut words = s.split_whitespace();
              // Extract the HTTP method from first part of the request line
              let method = words.next().unwrap();
              // Extract the resource (URI/URL) from second part of the request line
              let resource = words.next().unwrap();
              // Extract the HTTP version from third part of the request line
              let version = words.next().unwrap();
          
              (
                  method.into(),
                  Resource::Path(resource.to_string()),
                  version.into(),
              )
          }

          這是解析標題行的代碼。 在 process_req_line() 函數之后將其添加到 httprequest.rs 中。

          清單 2.9 解析傳入的 HTTP 請求:process_header_line() 函數

          fn process_header_line(s: &str) -> (String, String) {
              // Parse the header line into words split by separator (':')
              let mut header_items = s.split(":");
              let mut key = String::from("");
              let mut value = String::from("");
              // Extract the key part of the header
              if let Some(k) = header_items.next() {
                  key = k.to_string();
              }
              // Extract the value part of the header
              if let Some(v) = header_items.next() {
                  value = v.to_string()
              }
          
              (key, value)
          }

          這就完成了 HTTPRequest 結構的 From 特征實現的代碼。

          讓我們在 http/src/httprequest.rs 中的 mod 測試(測試模塊)內為 HTTP 請求解析邏輯編寫一個單元測試。 回想一下,我們已經在測試模塊中編寫了測試函數 test_method_into() 和 test_version_into()。 此時,測試模塊在 httprequest.rs 文件中應如下所示:

          #[cfg(test)]
          mod tests {
              use super::*;
              #[test]
              fn test_method_into() {
                  let m: Method = "GET".into();
                  assert_eq!(m, Method::Get);
              }
              #[test]
              fn test_version_into() {
                  let m: Version = "HTTP/1.1".into();
                  assert_eq!(m, Version::V1_1);
              }
          }

          現在,在 test_version_into() 函數之后,將另一個測試函數添加到文件中的同一測試模塊中。

          清單 2.10。 用于解析 HTTP 請求的測試腳本

              #[test]
              fn test_read_http() {
                  let s: String = String::from("GET /greeting HTTP/1.1\r\nHost:localhost:3000\r\nUser-Agent: curl/7.64.1\r\nAccept:*/*\r\n\r\n");               #1
                  let mut headers_expected = HashMap::new();  #2
                  headers_expected.insert("Host".into(), " localhost".into());
                  headers_expected.insert("Accept".into(), " */*".into());
                  headers_expected.insert("User-Agent".into(), " curl/7.64.1".into());
                  let req: HttpRequest = s.into();            #3
                  assert_eq!(Method::Get, req.method);        #4
                  assert_eq!(Version::V1_1, req.version);     #5
                  assert_eq!(Resource::Path("/greeting".to_string()), req.resource); #6
                  assert_eq!(headers_expected, req.headers);              #7
              }

          從工作區根文件夾中使用 Cargo test -p http 運行測試。

          您應該看到以下消息,表明所有三個測試均已通過:

          running 3 tests
          test httprequest::tests::test_method_into ... ok
          test httprequest::tests::test_version_into ... ok
          test httprequest::tests::test_read_http ... ok
          
          test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

          我們已經完成了HTTP請求處理的代碼。 該庫能夠解析傳入的 HTTP GET 或 POST 消息,并將其轉換為 Rust 數據結構。

          現在讓我們編寫處理 HTTP 響應的代碼。

          2.2.2 構造HTTP響應消息

          讓我們定義一個 HTTPResponse 結構體,它將代表我們程序中的 HTTP 響應消息。 我們還將編寫一個方法來將此結構轉換(序列化)為 HTTP 客戶端(例如 Web 瀏覽器)可以理解的格式正確的 HTTP 消息。

          我們首先回顧一下 HTTP 響應消息的結構。 這將幫助我們定義我們的結構。

          圖 2.7 顯示了典型 HTTP 響應的結構。


          圖 2.7。 HTTP響應的結構

          首先創建一個文件 http/src/httpresponse.rs(如果之前沒有創建)。 將 httpresponse 添加到 http/lib.rs 的模塊導出部分,如下所示:

          pub mod httprequest;
          pub mod httpresponse;

          將以下代碼添加到http/src/httpresponse.rs。

          清單 2.11。 HTTP響應的結構

          use std::collections::HashMap;
          use std::io::{Result, Write};
          
          #[derive(Debug, PartialEq, Clone)]
          pub struct HttpResponse<'a> {
              version: &'a str,
              status_code: &'a str,
              status_text: &'a str,
              headers: Option<HashMap<&'a str, &'a str>>,
              body: Option<String>,
          }

          HttpResponse 結構體包含協議版本、狀態代碼、狀態描述、可選標頭列表和可選主體。 請注意,對所有引用類型的成員字段都使用了生命周期注釋 'a。

          Rust 的生命周期

          在 Rust 中,每個引用都有一個生命周期,即引用有效的范圍。 Rust 中的生命周期是一項重要功能,旨在防止在手動管理內存的語言(例如 C/C++)中常見的懸空指針和釋放后使用錯誤。 Rust 編譯器推斷(如果未指定)或使用(如果指定)引用的生命周期注釋來驗證引用不會比它所指向的基礎值的生命周期長。

          另請注意對特征 Debug、PartialEq 和 Clone 使用 #[derive] 注釋。 這些稱為可派生特征,因為我們要求編譯器為 HttpResponse 結構派生這些特征的實現。 通過實現這些特征,我們的結構體獲得了出于調試目的而打印出來的能力,可以將其成員值與其他值進行比較,并克隆自身。

          我們將為 HttpResponse 結構實現的方法列表如下所示:

          默認特征實現:我們之前使用#[derive]注釋自動派生了一些特征。 我們現在將手動實現默認特征。 這讓我們可以為結構成員指定默認值。

          方法 new():此方法創建一個新結構,其成員具有默認值。

          方法 send_response():此方法將 Http 結構的內容序列化為用于在線傳輸的有效 HTTP 響應消息,并通過 TCP 連接發送原始字節。

          Getter 方法:我們還將為版本、status_code、status_text、headers 和 body 實現一組 getter 方法,它們是 struct HttpResponse 的成員字段。

          From 特征實現:最后,我們將實現 From 特征,幫助我們將 HttpResponse 結構轉換為表示有效 HTTP 響應消息的 String 類型。

          讓我們在 http/src/httpresponse.rs 下添加所有這些的代碼。

          默認特征實現

          我們將從 HttpResponse 結構的默認特征實現開始。

          清單 2.12。 HTTP 響應的默認特征實現

          impl<'a> Default for HttpResponse<'a> {
              fn default() -> Self {
                  Self {
                      version: "HTTP/1.1".into(),
                      status_code: "200".into(),
                      status_text: "OK".into(),
                      headers: None,
                      body: None,
                  }
              }
          }

          實現 Default 特征允許我們執行以下操作來創建具有默認值的新結構:

          let mut response: HttpResponse<'a> = HttpResponse::default();

          new()方法的實現

          new() 方法接受一些參數,設置其他參數的默認值并返回 HttpResponse 結構。 在 HttpResponse 結構體的 impl 塊下添加以下代碼。 由于該結構對其成員之一具有引用類型,因此 impl 塊聲明還必須指定生命周期參數(此處顯示為“a”)。

          清單 2.13。 HttpResponse 的 new() 方法 (httpresponse.rs)

          impl<'a> HttpResponse<'a> {
              pub fn new(
                  status_code: &'a str,
                  headers: Option<HashMap<&'a str, &'a str>>,
                  body: Option<String>,
              ) -> HttpResponse<'a> {
                  let mut response: HttpResponse<'a> = HttpResponse::default();
                  if status_code != "200" {
                      response.status_code = status_code.into();
                  };
                  response.headers = match &headers {
                      Some(_h) => headers,
                      None => {
                          let mut h = HashMap::new();
                          h.insert("Content-Type", "text/html");
                          Some(h)
                      }
                  };
                  response.status_text = match response.status_code {
                      "200" => "OK".into(),
                      "400" => "Bad Request".into(),
                      "404" => "Not Found".into(),
                      "500" => "Internal Server Error".into(),
                      _ => "Not Found".into(),
                  };
                  response.body = body;
          
                  response
              }
          }

          new() 方法首先使用默認參數構造一個結構體。 然后評估作為參數傳遞的值并將其合并到結構中。

          send_response() 方法

          send_response() 方法用于將 HttpResponse 結構轉換為字符串,并通過 TCP 連接傳輸。 可以將其添加到 impl 塊中,位于 httpresponse.rs 中的 new() 方法之后。

          impl<'a> HttpResponse<'a> {
              // new() method not shown here
              pub fn send_response(&self, write_stream: &mut impl Write) -> Result<()> {
                  let res = self.clone();
                  let response_string: String = String::from(res);
                  let _ = write!(write_stream, "{}", response_string);
                  Ok(())
              }
          }

          此方法接受 TCP 流(實現 Write 特征)作為輸入,并將格式良好的 HTTP 響應消息寫入該流。

          HTTP 響應結構的 Getter 方法

          讓我們為結構體的每個成員編寫 getter 方法。 我們需要這些來在 httpresponse.rs 中構建 HTML 響應消息。

          清單 2.14。 HttpResponse 的 Getter 方法

          impl<'a> HttpResponse<'a> {
              fn version(&self) -> &str {
                  self.version
              }
              fn status_code(&self) -> &str {
                  self.status_code
              }
              fn status_text(&self) -> &str {
                  self.status_text
              }
              fn headers(&self) -> String {
                  let map: HashMap<&str, &str> = self.headers.clone().unwrap();
                  let mut header_string: String = "".into();
                  for (k, v) in map.iter() {
                      header_string = format!("{}{}:{}\r\n", header_string, k, v);
                  }
                  header_string
              }
              pub fn body(&self) -> &str {
                  match &self.body {
                      Some(b) => b.as_str(),
                      None => "",
                  }
              }
          }

          getter 方法允許我們將數據成員轉換為字符串類型。

          Form trait

          最后,讓我們在 httpresponse.rs 中實現用于將 HTTPResponse 結構轉換(序列化)為 HTTP 響應消息字符串的方法。

          清單 2.15。 將 Rust 結構序列化為 HTTP 響應消息的代碼

          impl<'a> From<HttpResponse<'a>> for String {
              fn from(res: HttpResponse) -> String {
                  let res1 = res.clone();
                  format!(
                      "{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",
                      &res1.version(),
                      &res1.status_code(),
                      &res1.status_text(),
                      &res1.headers(),
                      &res.body.unwrap().len(),
                      &res1.body()
                  )
              }
          }

          請注意格式字符串中 \r\n 的使用。 這用于插入新行字符。 回想一下,HTTP 響應消息由以下序列組成:狀態行、標頭、空行和可選的消息正文。

          讓我們編寫一些單元測試。 如圖所示創建一個測試模塊塊,并將每個測試添加到該塊中。 暫時不要輸入它,這只是為了顯示測試代碼的結構。

          #[cfg(test)]
          mod tests {
              use super::*;
              // Add unit tests here. Each test needs to have a  #[test] annotation
          }

          我們將首先檢查狀態代碼為 200(成功)的消息的 HTTP 響應結構的構造。

          將以下內容添加到 httpresponse.rs 文件末尾。

          清單 2.16。 HTTP 成功 (200) 消息的測試腳本

          #[cfg(test)]
          mod tests {
              use super::*;
          #[test]
              fn test_response_struct_creation_200() {
                  let response_actual = HttpResponse::new(
                      "200",
                      None,
                      Some("Item was shipped on 21st Dec 2020".into()),
                  );
                  let response_expected = HttpResponse {
                      version: "HTTP/1.1",
                      status_code: "200",
                      status_text: "OK",
                      headers: {
                          let mut h = HashMap::new();
                          h.insert("Content-Type", "text/html");
                          Some(h)
                      },
                      body: Some("Item was shipped on 21st Dec 2020".into()),
                  };
                  assert_eq!(response_actual, response_expected);
              }
          }

          我們將測試 404(未找到頁面)HTTP 消息。 在 mod test {} 塊中的測試函數 test_response_struct_creation_200() 之后添加以下測試用例:

          清單 2.17。 404消息的測試腳本

              #[test]
              fn test_response_struct_creation_404() {
                  let response_actual = HttpResponse::new(
                      "404",
                      None,
                      Some("Item was shipped on 21st Dec 2020".into()),
                  );
                  let response_expected = HttpResponse {
                      version: "HTTP/1.1",
                      status_code: "404",
                      status_text: "Not Found",
                      headers: {
                          let mut h = HashMap::new();
                          h.insert("Content-Type", "text/html");
                          Some(h)
                      },
                      body: Some("Item was shipped on 21st Dec 2020".into()),
                  };
                  assert_eq!(response_actual, response_expected);
              }

          最后,我們將檢查HTTP響應結構體是否以正確的格式被正確地序列化為在線HTTP響應消息。在測試函數test_response_struct_creation_404()之后,在mod tests {}塊中添加以下測試。

          清單2.18.用于檢查格式正確的HTTP響應消息的測試腳本

              #[test]
              fn test_http_response_creation() {
                  let response_expected = HttpResponse {
                      version: "HTTP/1.1",
                      status_code: "404",
                      status_text: "Not Found",
                      headers: {
                          let mut h = HashMap::new();
                          h.insert("Content-Type", "text/html");
                          Some(h)
                      },
                      body: Some("Item was shipped on 21st Dec 2020".into()),
                  };
                  let http_string: String = response_expected.into();
                  let response_actual = "HTTP/1.1 404 Not Found\r\nContent-Type:text/html\r\nContent-Length: 33\r\n\r\n Item was shipped on 21st Dec 2020";
                  assert_eq!(http_string, response_actual);
              }

          我們現在就做測試。從工作區根目錄運行以下命令:

          cargo test -p http

          您應該看到以下消息,顯示http模塊中已通過6個測試。注意,這包括對HTTP請求和HTTP響應模塊的測試。

          running 6 tests
          test httprequest::tests::test_method_into ... ok
          test httprequest::tests::test_version_into ... ok
          test httpresponse::tests::test_http_response_creation ... ok
          test httpresponse::tests::test_response_struct_creation_200 ... ok
          test httprequest::tests::test_read_http ... ok
          test httpresponse::tests::test_response_struct_creation_404 ... ok
          
          test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

          如果測試失敗,檢查代碼中是否有任何錯別字或未對齊(如果您復制粘貼了它)。特別是重新檢查下面的字符串字面量(它相當長,容易出錯):

          "HTTP/1.1 404 Not Found\r\nContent-Type:text/html\r\nContent-Length:33\r\n\r\nItem was shipped on 21st Dec 2020";

          如果你仍然在執行測試時遇到問題,請參考git repo。

          這就完成了http庫的代碼。讓我們回顧一下http服務器的設計,再次如圖2.8所示


          圖2.8. Web服務器消息流

          我們編寫了http庫。讓我們編寫main()函數、服務器、路由器和處理程序。從這里開始,我們將不得不從http項目切換到httpserver項目目錄,以編寫代碼。

          為了引用httpserver項目中的http庫,在后者的Cargo.toml中添加以下內容。

          [dependencies]
          http = {path = "../http"}

          2.2.3編寫main()函數和服務器模塊

          讓我們采用自上而下的方法。我們main.rs

          清單2.19. main()函數

          mod handler;
          mod server;
          mod router;
          use server::Server;
          fn main() {
              // Start a server
              let server = Server::new("localhost:3000");
              //Run the server
              server.run();
          }

          主函數導入了三個模塊-處理器、服務器和路由器。

          接下來,在httpserver/src下創建三個文件:handler.rs、server.rs和router.rs。

          服務器模塊

          讓我們在httpserver/src/server.rs中編寫服務器模塊的代碼。

          清單2.20.服務器模塊

          use super::router::Router;
          use http::httprequest::HttpRequest;
          use std::io::prelude::*;
          use std::net::TcpListener;
          use std::str;
          pub struct Server<'a> {
              socket_addr: &'a str,
          }
          impl<'a> Server<'a> {
              pub fn new(socket_addr: &'a str) -> Self {
                  Server { socket_addr }
              }
              pub fn run(&self) {
                  // Start a server listening on socket address
                  let connection_listener = TcpListener::bind(self.socket_addr).unwrap();
                  println!("Running on {}", self.socket_addr);
                  // Listen to incoming connections in a loop
                  for stream in connection_listener.incoming() {
                      let mut stream = stream.unwrap();
                      println!("Connection established");
                      let mut read_buffer = [0; 90];
                      stream.read(&mut read_buffer).unwrap();
                      // Convert HTTP request to Rust data structure
                      let req: HttpRequest = String::from_utf8(read_buffer.to_vec()).unwrap().into();
                      // Route request to appropriate handler
                      Router::route(req, &mut stream);
                  }
              }
          }

          服務器模塊有兩種方法:

          new() 接受一個套接字地址(主機和端口),并返回一個 Server 實例。 run() 方法執行以下操作:

          綁定在套接字上,

          監聽傳入的連接,

          在有效連接上讀取字節流,

          將流轉換為 HttpRequest 結構實例

          將請求傳遞給 Router 進行進一步處理

          2.2.4 編寫router和handler模塊

          路由器模塊檢查傳入的 HTTP 請求并確定將請求路由到的正確處理程序以進行處理。 將以下代碼添加到httpserver/src/router.rs。

          清單 2.21。 路由器模塊

          use super::handler::{Handler, PageNotFoundHandler, StaticPageHandler, WebServiceHandler};
          use http::{httprequest, httprequest::HttpRequest, httpresponse::HttpResponse};
          use std::io::prelude::*;
          pub struct Router;
          impl Router {
              pub fn route(req: HttpRequest, stream: &mut impl Write) -> () {
                  match req.method {
                      // If GET request
                      httprequest::Method::Get => match &req.resource {
                          httprequest::Resource::Path(s) => {
                              // Parse the URI
                              let route: Vec<&str> = s.split("/").collect();
                              match route[1] {
                                  // if the route begins with /api, invoke Web service
                                  "api" => {
                                      let resp: HttpResponse = WebServiceHandler::handle(&req);
                                      let _ = resp.send_response(stream);
                                  }
                                   // Else, invoke static page handler
                                  _ => {
                                      let resp: HttpResponse = StaticPageHandler::handle(&req);
                                      let _ = resp.send_response(stream);
                                  }
                              }
                          }
                      },
                      // If method is not GET request, return 404 page
                      _ => {
                          let resp: HttpResponse = PageNotFoundHandler::handle(&req);
                          let _ = resp.send_response(stream);
                      }
                  }
              }
          }

          Router 檢查傳入方法是否為 GET 請求。 如果是這樣,它將按以下順序執行檢查:

          如果 GET 請求路由以 /api 開頭,則會將請求路由到 WebServiceHandler

          如果 GET 請求針對任何其他資源,則假定該請求針對靜態頁面并將請求路由到 StaticPageHandler

          如果不是GET請求,則返回404錯誤頁面

          接下來我們看一下Handler模塊。

          處理程序

          對于處理程序模塊,讓我們添加幾個外部 crate 來處理 json 序列化和反序列化 - serde 和 serde_json。 httpserver 項目的 Cargo.toml 文件如下所示:

          [dependencies]
          http = {path = "../http"}
          serde = {version = "1.0.117",features = ["derive"]}
          serde_json = "1.0.59"

          將以下代碼添加到httpserver/src/handler.rs。

          讓我們從模塊導入開始:

          use http::{httprequest::HttpRequest, httpresponse::HttpResponse};
          use serde::{Deserialize, Serialize};
          use std::collections::HashMap;
          use std::env;
          use std::fs;

          讓我們定義一個名為Handler的trait,如下所示:

          清單2.22. Trait Handler定義

          pub trait Handler {
              fn handle(req: &HttpRequest) -> HttpResponse;
              fn load_file(file_name: &str) -> Option<String> {
                  let default_path = format!("{}/public", env!("CARGO_MANIFEST_DIR"));
                  let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
                  let full_path = format!("{}/{}", public_path, file_name);
          
                  let contents = fs::read_to_string(full_path);
                  contents.ok()
              }
          }

          請注意,trait Handler 包含兩個方法:

          handle():必須為任何其他用戶數據類型實現此方法才能實現該特征。

          load_file() :此方法是從httpserver根文件夾中的公共目錄加載文件(非json)。 該實現已作為特征定義的一部分提供。

          我們現在將定義以下數據結構:

          StaticPageHandler - 提供靜態網頁服務,

          WebServiceHandler - 提供 json 數據

          PageNotFoundHandler - 提供 404 頁面服務

          OrderStatus - 用于加載從 json 文件讀取的數據的結構

          將以下代碼添加到httpserver/src/handler.rs。

          清單 2.23。 處理程序的數據結構

          #[derive(Serialize, Deserialize)]
          pub struct OrderStatus {
              order_id: i32,
              order_date: String,
              order_status: String,
          }
          
          pub struct StaticPageHandler;
          
          pub struct PageNotFoundHandler;
          
          pub struct WebServiceHandler;

          讓我們為三個處理程序結構實現 Handler 特征。 讓我們從 PageNotFoundHandler 開始。

          impl Handler for PageNotFoundHandler {
              fn handle(_req: &HttpRequest) -> HttpResponse {
                  HttpResponse::new("404", None, Self::load_file("404.html"))
              }
          }

          如果調用 PageNotFoundHandler 結構體上的 handle 方法,它將返回一個新的 HttpResponse 結構體實例,其狀態碼為:404,并且正文包含從文件 404.html 加載的一些 html。

          這是 StaticPageHandler 的代碼。

          清單 2.24。 提供靜態網頁的處理程序

          impl Handler for StaticPageHandler {
              fn handle(req: &HttpRequest) -> HttpResponse {
                  // Get the path of static page resource being requested
                  let http::httprequest::Resource::Path(s) = &req.resource;
          
                  // Parse the URI
                  let route: Vec<&str> = s.split("/").collect();
                  match route[1] {
                      "" => HttpResponse::new("200", None, Self::load_file("index.html")),
                      "health" => HttpResponse::new("200", None, Self::load_file("health.html")),
                      path => match Self::load_file(path) {
                          Some(contents) => {
                              let mut map: HashMap<&str, &str> = HashMap::new();
                              if path.ends_with(".css") {
                                  map.insert("Content-Type", "text/css");
                              } else if path.ends_with(".js") {
                                  map.insert("Content-Type", "text/javascript");
                              } else {
                                  map.insert("Content-Type", "text/html");
                              }
                              HttpResponse::new("200", Some(map), Some(contents))
                          }
                          None => HttpResponse::new("404", None, Self::load_file("404.html")),
                      },
                  }
              }
          }

          如果在StaticPageHandler上調用handle()方法,則會進行以下處理:

          如果傳入請求是針對 localhost:3000/,則加載文件index.html 中的內容并構造一個新的 HttpResponse 結構

          如果傳入請求針對 localhost:3000/health,則加載文件 health.html 中的內容,并構造一個新的 HttpResponse 結構

          如果傳入請求針對任何其他文件,該方法會嘗試在 httpserver/public 文件夾中查找并加載該文件。 如果未找到文件,則會發回 404 錯誤頁面。 如果找到該文件,則會加載內容并將其嵌入到 HttpResponse 結構中。 請注意,HTTP 響應消息中的 Content-Type 標頭是根據文件類型設置的。

          讓我們看一下代碼的最后一部分——WebServiceHandler。

          清單 2.25。 提供 json 數據的處理程序

          impl WebServiceHandler {
              fn load_json() -> Vec<OrderStatus> {                #1
                  let default_path = format!("{}/data", env!("CARGO_MANIFEST_DIR"));
                  let data_path = env::var("DATA_PATH").unwrap_or(default_path);
                  let full_path = format!("{}/{}", data_path, "orders.json");
                  let json_contents = fs::read_to_string(full_path);
                  let orders: Vec<OrderStatus> =
                      serde_json::from_str(json_contents.unwrap().as_str()).unwrap();
                  orders
              }
          }
          // Implement the Handler trait
          impl Handler for WebServiceHandler {
              fn handle(req: &HttpRequest) -> HttpResponse {
                  let http::httprequest::Resource::Path(s) = &req.resource;
          
                  // Parse the URI
                  let route: Vec<&str> = s.split("/").collect();
                  // if route if /api/shipping/orders, return json
                  match route[2] {
                      "shipping" if route.len() > 2 && route[3] == "orders" => {
                          let body = Some(serde_json::to_string(&Self::load_json()).unwrap());
                          let mut headers: HashMap<&str, &str> = HashMap::new();
                          headers.insert("Content-Type", "application/json");
                          HttpResponse::new("200", Some(headers), body)
                      }
                      _ => HttpResponse::new("404", None, Self::load_file("404.html")),
                  }
              }
          }

          如果在WebServiceHandler結構體上調用handle()方法,則會進行以下處理:

          如果 GET 請求針對 localhost:3000/api/shipping/orders,則會加載包含訂單的 json 文件,并將其序列化為 json,作為響應正文的一部分返回。

          如果是其他路由,則返回404錯誤頁面。

          我們已經完成了代碼。 我們現在必須創建 html 和 json 文件,以便測試 Web 服務器。

          2.2.5 測試Web服務器

          在本節中,我們將首先創建測試網頁和 json 數據。 然后,我們將針對各種場景測試 Web 服務器并分析結果。

          在httpserver根文件夾下創建兩個子文件夾data和public。 在 public 文件夾下,創建四個文件 - index.html、health.html、404.html、styles.css。 在數據文件夾下,創建以下文件 -orders.json。

          此處顯示指示性內容。 您可以根據自己的喜好更改它們。

          httpserver/public/index.html

          清單 2.26。 索引網頁

          <!DOCTYPE html>
          <html lang="en">
            <head>
              <meta charset="utf-8" />
              <link rel="stylesheet" href="styles.css">
              <title>Index!</title>
            </head>
            <body>
              <h1>Hello, welcome to home page</h1>
              <p>This is the index page for the web site</p>
            </body>
          </html>

          httpserver/public/styles.css

          h1 {
            color: red;
            margin-left: 25px;
          }

          httpserver/public/health.html

          清單 2.27。 健康測試頁

          <!DOCTYPE html>
          <html lang="en">
            <head>
              <meta charset="utf-8" />
              <title>Health!</title>
            </head>
            <body>
              <h1>Hello welcome to health page!</h1>
              <p>This site is perfectly fine</p>
            </body>
          </html>

          httpserver/public/404.html

          <!DOCTYPE html>
            <html lang="en">
          <head>
          <meta charset="utf-8" /> <title>Not Found!</title>
              </head>
              <body>
                <h1>404 Error</h1>
                <p>Sorry the requested page does not exist</p>
              </body>
          </html>

          httpserver/data/orders.json

          清單 2.28。 訂單的 Json 數據文件

          [
              {
                  "order_id": 1,
                  "order_date": "21 Jan 2020",
                  "order_status": "Delivered"
              },
              {
                  "order_id": 2,
                  "order_date": "2 Feb 2020",
                  "order_status": "Pending"
              }
          ]

          我們現在已經準備好運行服務器了。

          從工作區根目錄運行 Web 服務器,如下所示:

          cargo run -p httpserver

          然后從瀏覽器窗口或使用curl工具測試以下URL:

          localhost:3000/
          localhost:3000/health
          localhost:3000/api/shipping/orders
          localhost:3000/invalid-path

          您會注意到,如果您在瀏覽器上調用這些命令,對于第一個 URL,您應該看到紅色字體的標題。 轉到 Chrome 瀏覽器(或其他瀏覽器上的等效開發工具)中的網絡選項卡并查看瀏覽器下載的文件。 您將看到,除了 index.html 文件之外,瀏覽器還會自動下載 styles.css,從而將樣式應用于索引頁面。 如果進一步檢查,您可以看到 CSS 文件的 Content-Type 為 text/css,HTML 文件的 Content-Type 為 text/html,從我們的 Web 服務器發送到瀏覽器。

          同樣,如果您檢查為 /api/shipping/orders 路徑發送的響應內容類型,您將看到瀏覽器收到的 application/json 作為響應標頭的一部分。

          關于構建 Web 服務器的部分到此結束。

          在本節中,我們編寫了一個 HTTP 服務器和一個可以提供靜態頁面以及 json 數據的 http 消息庫。 雖然前一個功能與術語 Web 服務器相關,但后者是我們開始看到 Web 服務功能的地方。 我們的 httpserver 項目既充當靜態 Web 服務器,又充當提供 json 數據的 Web 服務。 當然,常規 Web 服務將提供更多方法,而不僅僅是 GET 請求。 但本練習的目的是演示 Rust 從頭開始構建此類 Web 服務器和 Web 服務的能力,無需使用任何 Web 框架或外部 http 庫。

          我希望您喜歡按照代碼進行操作,并獲得正常運行的服務器。 如果您有任何困難,可以參考第 2 章的代碼庫。

          這結束了本章的兩個核心目標,即構建 TCP 服務器/客戶端和構建 HTTP 服務器。

          本章的完整代碼可以在 https://git.manning.com/agileauthor/eshwarla/-/tree/master/code 找到。

          2.3 總結

          TCP/IP 模型是一組簡化的 Internet 通信標準和協議。 它分為四個抽象層:網絡訪問層、互聯網層、傳輸層和應用層。 TCP 是傳輸層協議,其他應用程序級協議(例如 HTTP)通過它進行操作。 我們構建了一個使用 TCP 協議交換數據的服務器和客戶端。

          TCP 也是一種面向流的協議,其中數據作為連續的字節流進行交換。

          我們使用 Rust 標準庫構建了一個基本的 TCP 服務器和客戶端。 TCP 不理解 HTTP 等消息的語義。 我們的 TCP 客戶端和服務器只是交換字節流,而不了解所傳輸數據的語義。

          HTTP 是應用層協議,是大多數 Web 服務的基礎。 HTTP 在大多數情況下使用 TCP 作為傳輸協議。

          我們構建了一個 HTTP 庫來解析傳入的 HTTP 請求并構建 HTTP 響應。 HTTP 請求和響應是使用 Rust 結構和枚舉進行建模的。

          我們構建了一個 HTTP 服務器,它提供兩種類型的內容:靜態網頁(帶有樣式表等相關文件)和 json 數據。

          我們的 Web 服務器可以接受請求并向標準 HTTP 客戶端(例如瀏覽器和curl 工具)發送響應。

          我們通過實現幾個特征向自定義結構添加了額外的行為。 其中一些是使用 Rust 注釋自動派生的,另一些是手動編碼的。 我們還使用生命周期注釋來指定結構內引用的生命周期。

          您現在已經掌握了基礎知識,可以了解如何使用 Rust 來開發低級 HTTP 庫和 Web 服務器,以及 Web 服務的開端。 在下一章中,我們將直接使用用 Rust 編寫的生產就緒 Web 框架來開發 Web 服務。

          .什么是webservice?

          WebService是一個SOA(面向服務的編程)的架構,它是不依賴于語言,不依賴于平臺,可以實現不同的語言間的相互調用,通過Internet進行基于Http協議的網絡應用間的交互。 WebService實現不同語言間的調用,是依托于一個標準,webservice是需要遵守WSDL(web服務定義語言)/SOAP(簡單請求協議)規范的。 WebService=WSDL+SOAP+UDDI(webservice的注冊)Soap是由Soap的part和0個或多個附件組成,一般只有part,在part中有Envelope和Body。 Web Service是通過提供標準的協議和接口,可以讓不同的程序集成的一種SOA架構。

          Web Service的優點

          • 可以讓異構的程序相互訪問(跨平臺)
          • 松耦合
          • 基于標準協議(通用語言,允許其他程序訪問)

          Web Service的基本原理

          • Service Provider采用WSDL描述服務
          • Service Provider 采用UDDI將服務的描述文件發布到UDDI服務器(Register server)
          • Service Requestor在UDDI服務器上查詢并 獲取WSDL文件
          • Service requestor將請求綁定到SOAP,并訪問相應的服務。

          什么是SOAP?

          SOAP請求(Simple Object Access Protocol,簡單對象訪問協議)是HTTP POST的一個專用版本,遵循一種特殊的XML消息格式,Content-type設置為:text/xml ,任何數據都可以XML化。 SOAP:簡單對象訪問協議。SOAP是一種輕量的,簡單的,基于XML的協議,它被設計成在web上交換結構化的和固化的信息。SOAP可以和現存的許多因特網協議和格式結合使用,包括超文本傳輸協議(HTTP),簡單郵件傳輸協議(SMTP),多用途網際郵件擴充協議(MIME)。它還支持從消息系統到遠程過程調用(RPC)等大量的應用程序。

          2.代碼工程

          實驗目標:

          實現webservice服務,并通過client調用服務端

          服務端

          pom.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <parent>
                  <artifactId>webservice</artifactId>
                  <groupId>com.et</groupId>
                  <version>1.0-SNAPSHOT</version>
              </parent>
              <modelVersion>4.0.0</modelVersion>
          
              <artifactId>webservice-server</artifactId>
          
              <properties>
                  <maven.compiler.source>11</maven.compiler.source>
                  <maven.compiler.target>11</maven.compiler.target>
              </properties>
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
          
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-autoconfigure</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>
                  <!-- 引入Spring Boot Web Services Starter -->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web-services</artifactId>
                  </dependency>
          
                  <!-- 引入Apache CXF Spring Boot Starter for JAX-WS -->
                  <dependency>
                      <groupId>org.apache.cxf</groupId>
                      <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
                      <version>3.3.4</version>
                  </dependency>
          
          
              </dependencies>
          </project>

          config

          package com.et.webservice.server.config;
          
          import com.et.webservice.server.service.MyWebService;
          import org.apache.cxf.bus.spring.SpringBus;
          import org.apache.cxf.jaxws.EndpointImpl;
          import org.apache.cxf.transport.servlet.CXFServlet;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.web.servlet.ServletRegistrationBean;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          
          
          /**
           * CXF配置類,負責初始化CXF相關組件、發布Webservice服務以及配置CXF Servlet。
           */
          @Configuration
          public class CxfConfig {
             
          
              /**
               * 自動注入Spring Bus實例,它是CXF的核心組件之一,用于管理和配置CXF運行時環境。
               */
              @Autowired
              private SpringBus bus;
          
              /**
               * 自動注入實現了MyWebService接口的服務實現類實例,該實例將被發布為Webservice供外部調用。
               */
              @Autowired
              private MyWebService myWebServiceImpl;
          
              /**
               * 創建并返回Webservice端點(Endpoint)實例,用于發布MyWebService服務。
               * 將服務實現類與Spring Bus關聯,并指定發布地址為"/1"。
               *
               * @return Webservice端點實例
               */
              @Bean
              public EndpointImpl endpoint() {
             
          
                  EndpointImpl endpoint = new EndpointImpl(bus, myWebServiceImpl);
                  endpoint.publish("/1"); // 發布地址
                  return endpoint;
              }
          
              /**
               * 創建并返回CXF Servlet的ServletRegistrationBean實例,用于注冊CXF Servlet到Spring Boot的Servlet容器中。
               * 設置CXF Servlet的映射路徑為"/services/*",表示所有以"/services/"開頭的HTTP請求都將由CXF Servlet處理。
               *
               * @return CXF Servlet的ServletRegistrationBean實例
               */
              @Bean
              public ServletRegistrationBean wsServlet() {
             
                  return new ServletRegistrationBean(new CXFServlet(), "/services/*");
              }
          }

          service

          package com.et.webservice.server.service;
          
          
          import javax.jws.WebMethod;
          import javax.jws.WebService;
          
          @WebService(
                  name = "MyWebService",
                  targetNamespace = "http://liuhaihua.cn/mywebservice"
          )
          public interface MyWebService {
             
              @WebMethod
              String sayHello(String name);
          }
          package com.et.webservice.server.service;
          
          
          import org.springframework.stereotype.Service;
          
          import javax.jws.WebService;
          
          
          @Service
          @WebService
          public class MyWebServiceImpl implements MyWebService {
             
          
              @Override
              public String sayHello(String name) {
             
                  System.err.println("sayHello is called..."); // 只是為了更明顯的輸出,采用err
          
                  return "Hello, " + name + "!";
              }
          }

          DemoApplication.java

          package com.et.webservice.server;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          
          @SpringBootApplication
          public class DemoApplication {
          
             public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
             }
          }

          客戶端

          pom.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <parent>
                  <artifactId>webservice</artifactId>
                  <groupId>com.et</groupId>
                  <version>1.0-SNAPSHOT</version>
              </parent>
              <modelVersion>4.0.0</modelVersion>
          
              <artifactId>webservice-client</artifactId>
          
              <properties>
                  <maven.compiler.source>8</maven.compiler.source>
                  <maven.compiler.target>8</maven.compiler.target>
              </properties>
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
          
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-autoconfigure</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>
                  <!-- 測試依賴配置 -->
                  <dependency>
                      <groupId>org.junit</groupId>
                      <artifactId>junit-bom</artifactId>
                      <version>5.9.1</version>
                      <type>pom</type>
                      <scope>test</scope>
                  </dependency>
                  <dependency>
                      <groupId>org.junit.jupiter</groupId>
                      <artifactId>junit-jupiter</artifactId>
                      <version>5.9.1</version>
                      <scope>test</scope>
                  </dependency>
          
                  <!-- 主要依賴配置 -->
                  <dependency>
                      <groupId>com.sun.xml.bind</groupId>
                      <artifactId>jaxb-impl</artifactId>
                      <version>4.0.5</version>
                  </dependency>
                  <dependency>
                      <groupId>javax.xml.bind</groupId>
                      <artifactId>jaxb-api</artifactId>
                      <version>2.3.1</version>
                  </dependency>
                  <dependency>
                      <groupId>jakarta.activation</groupId>
                      <artifactId>jakarta.activation-api</artifactId>
                      <version>2.1.3</version>
                  </dependency>
                  <dependency>
                      <groupId>jakarta.jws</groupId>
                      <artifactId>jakarta.jws-api</artifactId>
                      <version>3.0.0</version>
                  </dependency>
                  <dependency>
                      <groupId>jakarta.xml.ws</groupId>
                      <artifactId>jakarta.xml.ws-api</artifactId>
                      <version>4.0.1</version>
                  </dependency>
                  <dependency>
                      <groupId>jakarta.xml.bind</groupId>
                      <artifactId>jakarta.xml.bind-api</artifactId>
                      <version>4.0.1</version>
                  </dependency>
          
                  <!-- Apache CXF相關依賴 -->
                  <dependency>
                      <groupId>org.apache.cxf</groupId>
                      <artifactId>cxf-rt-transports-http-jetty</artifactId>
                      <version>3.3.4</version>
                  </dependency>
                  <dependency>
                      <groupId>org.apache.cxf</groupId>
                      <artifactId>cxf-rt-frontend-jaxws</artifactId>
                      <version>3.3.4</version>
                  </dependency>
                  <dependency>
                      <groupId>org.slf4j</groupId>
                      <artifactId>slf4j-reload4j</artifactId>
                      <version>2.1.0-alpha1</version>
                  </dependency>
              </dependencies>
          </project>

          service

          package com.et.webservice.client;
          
          import javax.jws.WebService;
          
          @WebService(
                name = "MyWebService",
                targetNamespace = "http://liuhaihua.cn/mywebservice"
          )
          public interface HelloService {
              // 接口名一樣
          
              String sayHello(String name); // 方法定義名一樣
          }

          cilent

          package com.et.webservice.client;
          
          import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
          
          /**
           * 客戶端調用類,用于通過JAX-WS代理方式訪問HelloService Web服務。
           */
          public class Client {
             
          
              /**
               * 程序主入口方法。
               *
               * @param args 命令行參數
               */
              public static void main(String[] args) {
             
                  // 創建JAX-WS代理工廠對象
                  JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
          
                  // 設置要訪問的服務地址
                  jaxWsProxyFactoryBean.setAddress("http://localhost:8088/services/1?wsdl");
          
                  // 設置服務接口類,即HelloService
                  jaxWsProxyFactoryBean.setServiceClass(HelloService.class);
          
                  // 使用工廠對象創建HelloService接口的代理實例
                  HelloService helloService = jaxWsProxyFactoryBean.create(HelloService.class);
          
                  System.out.println(helloService.getClass());
          
                  // 調用代理實例的方法,向服務端發送請求,并打印返回結果
                  System.out.println(helloService.sayHello("hello world"));
              }
          }

          以上只是一些關鍵代碼,所有代碼請參見下面代碼倉庫

          代碼倉庫

          • https://github.com/Harries/springboot-demo

          3.測試

          啟動服務端,訪問http://localhost:8088/services/1?wsdl

          調用客戶端,返回結果

          11:20:20.148 [main] DEBUG org.apache.cxf.phase.PhaseInterceptorChain - Invoking handleMessage on interceptor org.apache.cxf.ws.policy.PolicyVerificationInInterceptor@1e8823d2
          11:20:20.148 [main] DEBUG org.apache.cxf.ws.policy.PolicyVerificationInInterceptor - Verified policies for inbound message.
          Hello, hello world!

          4.引用

          • https://en.wikipedia.org/wiki/Web_service
          • http://www.liuhaihua.cn/archives/710832.html

          主站蜘蛛池模板: 一区二区在线观看视频| 日韩精品一区二三区中文| 影院成人区精品一区二区婷婷丽春院影视| 久草新视频一区二区三区| 国模私拍一区二区三区| 中文字幕日韩一区| 亚洲狠狠久久综合一区77777| 日韩精品一区二区三区中文| 精品在线一区二区| 亚洲福利一区二区三区| 97av麻豆蜜桃一区二区| 日本高清无卡码一区二区久久 | 国产免费一区二区三区| 亚洲第一区视频在线观看| 手机福利视频一区二区| 精品一区二区三区免费观看 | 精品国产一区二区22| 国产主播一区二区| 日本免费一区二区三区最新| 国产美女口爆吞精一区二区| 青青青国产精品一区二区| 日韩伦理一区二区| 福利一区二区三区视频在线观看| 亚洲欧美国产国产综合一区 | 久久99国产精一区二区三区| 在线精品国产一区二区| 秋霞鲁丝片一区二区三区| 国产精品丝袜一区二区三区 | 久久精品一区二区三区不卡| 亚洲综合色一区二区三区| 亚洲乱码一区二区三区在线观看| 无码视频免费一区二三区| 高清一区二区三区| 亚洲无码一区二区三区| av无码人妻一区二区三区牛牛| 亚洲国产AV一区二区三区四区 | 亚洲欧美日韩中文字幕在线一区 | 91精品一区二区三区久久久久| 立川理惠在线播放一区| 国产一区二区三区乱码网站| 中文字幕在线观看一区二区三区|