Java擴展Nginx之五:五大handler(系列最核心)

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/07/15/17555490.html
-Advertisement-
Play Games

### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《Java擴展Nginx》系列的第 ...


歡迎訪問我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本文是《Java擴展Nginx》系列的第五篇,如題,本篇是整個系列的最核心內容,咱們寫的代碼主要都集中在nginx-clojure定義的五種handler中,不同handler分別發揮著各自的作用,它們是:
  1. Initialization Handler for nginx worker(初始化)
  2. Content Ring Handler for Location(location對應的業務處理)
  3. Nginx Rewrite Handler(地址重定向)
  4. Nginx Access Handler(鑒權)
  5. Nginx Log Handler(日誌輸出)
  • 接下來,一起在實戰中學習它們

源碼下載

名稱 鏈接 備註
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議

maven工程

  • 新建名為handler-demo的maven工程,今天實戰的代碼都在這裡面
  • 我這裡為了統一管理代碼和依賴庫,整個《Java擴展Nginx》系列的源碼都放在父工程nginx-clojure-tutorials下麵,本篇的handler-demo也是nginx-clojure-tutorials的一個子工程
  • 接下來,編碼實戰每種handler

Initialization Handler for nginx worker(初始化)

  • Initialization Handler,顧名思義,是用於執行初始化邏輯的handler,它在nginx配置中是http級別的,有以下幾個特性:
  1. 每個worker都是獨立的進程,啟動的時候都會調用一次Initialization Handler
  2. Initialization Handler也是NginxJavaRingHandler介面的實現類,其invoke方法會被調用,所以初始化邏輯代碼應該寫在invoke方法中
  • 接下來寫代碼試試,新增MyInitHandler.java,代碼如下:
package com.bolingcavalry.handlerdemo;

import nginx.clojure.NginxClojureRT;
import nginx.clojure.java.NginxJavaRingHandler;
import java.io.IOException;
import java.util.Map;

public class MyInitHandler implements NginxJavaRingHandler {
    @Override
    public Object[] invoke(Map<String, Object> map) throws IOException {
        // 可以根據實際需求執行初始化操作,這裡作為演示,只列印日誌
        NginxClojureRT.log.info("MyInitHandler.invoke executed");
        return null;
    }
}
  • 用命令mvn clean package -U,生成名為handler-demo-1.0-SNAPSHOT.jar的文件,將其放入nginx的jars目錄下
  • 再在nginx.conf的http配置中增加以下兩行配置:
jvm_handler_type 'java';
jvm_init_handler_name 'com.bolingcavalry.handlerdemo.MyInitHandler'; 
  • 重啟nginx,打開logs/error.log文件,發現裡面新增一行日誌,這就是初始化日誌:
2022-02-05 23:02:37[info][73954][main]MyInitHandler.invoke executed
  • 如果之前部署的location還在,可以用postman發請求試試,應該可以正常響應,表示nginx的worker已經正常工作:
    在這裡插入圖片描述

Content Ring Handler for Location(location對應的業務處理)

  • content handler是最常用的handler,這是個location配置,定義了nginx收到某個請求後應該如何處理,前面的文章中已經用到了
  • 現在咱們再寫一個content handler,與之前不同的是新增了配置項content_handler_property,該配置項可以添加自定義配置,整個location如下所示:
location /contentdemo {
	# 第一個自定義屬性
    content_handler_property foo.name 'foo.value';
   # 第二個自定義屬性
   content_handler_property bar.name 'bar.value';
   # 邏輯處理類
   content_handler_name 'com.bolingcavalry.handlerdemo.MyContentHandler';
} 
  • 從上面的配置可見,通過content_handler_property增加了兩個配置項,名字分別是foo.namebar.name
  • 再來看MyContentHandler類的源碼,重點是實現了Configurable介面,然後在config方法被調用的時候,入參map中保存的就是content_handler_property配置的key和value了,在invoke方法中可以直接使用:
package com.bolingcavalry.handlerdemo;

import nginx.clojure.Configurable;
import nginx.clojure.java.ArrayMap;
import nginx.clojure.java.NginxJavaRingHandler;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import static nginx.clojure.MiniConstants.CONTENT_TYPE;
import static nginx.clojure.MiniConstants.NGX_HTTP_OK;

public class MyContentHandler implements NginxJavaRingHandler, Configurable {

    private Map<String, String> config;

    /**
     * location中配置的content_handler_property屬性會通過此方法傳給當前類
     * @param map
     */
    @Override
    public void config(Map<String, String> map) {
        this.config = map;
    }

    @Override
    public Object[] invoke(Map<String, Object> map) throws IOException {

        String body = "From MyContentHandler, "
                    + LocalDateTime.now()
                    + ", foo : "
                    + config.get("foo.name")
                    + ", bar : "
                    + config.get("bar.name");

        return new Object[] {
                NGX_HTTP_OK, //http status 200
                ArrayMap.create(CONTENT_TYPE, "text/plain"), //headers map
                body
        };
    }
}
  • 編譯、配置、重啟nginx,再用postman訪問/contentdemo,響應如下,可見符合預期,content_handler_property配置的值可以在invoke方法中使用:
    在這裡插入圖片描述

Nginx Rewrite Handler(地址重定向)

  • rewrite handler顧名思義,就是咱們常在nginx上配置的rewrite功能,在nginx-clojure中又略有不同,為了方便記憶,這裡將整個rewrite分為三段處理:
    在這裡插入圖片描述- 下麵就是一個完整的rewrite handler,這些內容都是寫在http配置內的:
# 1. 定義變數,用於保存路徑
set $myhost "";
       
location /myproxy {
	rewrite_handler_type 'java';
	# 2. java代碼中為變數賦值
    rewrite_handler_name 'com.bolingcavalry.handlerdemo.MyRewriteProxyPassHandler';
     # 3. 用變數的值作為地址進行跳轉
     proxy_pass $myhost;
} 
  • 對應的MyRewriteProxyPassHandler.java如下:
package com.bolingcavalry.handlerdemo;

import nginx.clojure.NginxClojureRT;
import nginx.clojure.java.NginxJavaRequest;
import nginx.clojure.java.NginxJavaRingHandler;
import java.util.Map;
import static nginx.clojure.java.Constants.PHASE_DONE;

public class MyRewriteProxyPassHandler implements NginxJavaRingHandler {
    @Override
    public Object[] invoke(Map<String, Object> req) {
        // 根據業務情況定製計算出的path
        String myhost = computeMyHost(req);
        // 用setVariable方法設置myhost變數的值,這個myhost在這個location中被定義,跳轉的時候就用這個值作為path
        ((NginxJavaRequest)req).setVariable("myhost", myhost);
        // 返回PHASE_DONE之後,nginx-clojure框架就會執行proxy_pass邏輯,
        // 如果返回的不是PHONE_DONE,nginx-clojure框架就把這個handler當做content handler處理
        return PHASE_DONE;
    }

    /**
     * 這裡寫入業務邏輯,根據實際情況確定返回的path
     * @param req
     * @return
     */
    private String computeMyHost(Map<String, Object> req) {
        // 確認是http還是https
        String scheme = (String)req.get("scheme");
        // 確認埠號
        String serverPort = (String)req.get("server-port");

        // /contentdemo是nginx.conf中配置的一個location,您可以根據自己的業務情況來決定返回值
        String myhost = scheme + "://127.0.0.1:" + serverPort + "/contentdemo";

        NginxClojureRT.log.info("pass address [" + myhost + "]");

        return myhost;
    }
}
  • 編譯構建運行起來,用postman訪問/myproxy,效果如下圖,從返回結果可見請求被成功轉發到/contentdemo
    在這裡插入圖片描述
  • 此刻,相信聰明的您應該想到了:既然rewrite handler的邏輯代碼可以自己用java寫,那意味著可以按照自己的業務需求隨意定製,那豈不是自己可以在nginx上寫一個負載均衡的功能出來了?沒錯,從下圖可見官方也是這麼說的:
    在這裡插入圖片描述- 如果您的環境中有註冊中心,例如eureka或者nacos,您還可以取得後臺服務列表,這樣,不光是負載均衡,各種轉發調度邏輯都可以在nginx上開發出來了
  • 還有一點要註意的,下圖是剛纔寫的MyRewriteProxyPassHandler.java的源碼,註意紅框位置,是invoke方法的返回值,如果返回的不是PHASE_DONE,nginx-clojure框架就不再執行後面poss_proxy操作,而是把此handler當做普通的content handler來處理了:
    在這裡插入圖片描述

Nginx Access Handler(鑒權)

  • access handler的定位,是用於執行鑒權相關的邏輯
  • 其實看過了前面的rewrite handler,聰明的您應該會想到:rewrite handler既可以重定向,也可以直接返回code和body,那豈不是直接用來做鑒權?鑒權不通過就在rewrite handler上返回401 (Unauthorized)或者403 (Forbidden)
  • 從技術實現的角度來看,您說得沒錯,access handler來自nginx-clojure對功能和職責的劃分,官方建議將鑒權的工作都交給access handler來做:
    在這裡插入圖片描述
  • 正常情況下,一次請求被前面幾種handler執行的順序如下:
    在這裡插入圖片描述
  • 寫一個access handler的配置和代碼驗證試試,為了省事兒,就在前面rewrite handler的基礎上改動吧
  • 首先是配置,如下所示,在剛纔的rewrite handler的配置中,增加了access_handler_typeaccess_handler_name,這就意味著該location的請求,先由MyRewriteProxyPassHandler處理,再交給BasicAuthHandler處理,如果鑒權通過,才會交給proxy_pass處理:
# 1. 定義變數,用於保存路徑
set $myhost "";
       
location /myproxy {
	# 指定access handler的類型是java
    access_handler_type 'java';
    # 指定access handler的執行類類
    access_handler_name 'com.bolingcavalry.handlerdemo.BasicAuthHandler';

    rewrite_handler_type 'java';
    # 2. java代碼中為變數賦值
    rewrite_handler_name 'com.bolingcavalry.handlerdemo.MyRewriteProxyPassHandler';
    # 3. 用變數的值作為地址進行跳轉
    proxy_pass $myhost;
}
  • BasicAuthHandler.java的內容如下,已添加詳細註釋,就不多贅述了:
package com.bolingcavalry.handlerdemo;

import nginx.clojure.java.ArrayMap;
import nginx.clojure.java.NginxJavaRingHandler;
import javax.xml.bind.DatatypeConverter;
import java.util.Map;
import static nginx.clojure.MiniConstants.DEFAULT_ENCODING;
import static nginx.clojure.MiniConstants.HEADERS;
import static nginx.clojure.java.Constants.PHASE_DONE;

public  class BasicAuthHandler implements NginxJavaRingHandler {

    @Override
    public Object[] invoke(Map<String, Object> request) {
        // 從header中獲取authorization欄位
        String auth = (String) ((Map)request.get(HEADERS)).get("authorization");

        // 如果header中沒有authorization,就返回401錯誤,並帶上body
        if (auth == null) {
            return new Object[] { 401, ArrayMap.create("www-authenticate", "Basic realm=\"Secure Area\""),
                    "<HTML><BODY><H1>401 Unauthorized.</H1></BODY></HTML>" };
        }

        // authorization應該是 : Basic xfeep:hello!,所以這裡先將"Basic "去掉,然後再用":"分割
        String[] up = auth.substring("Basic ".length()).split(":");

        // 只是為了演示,所以賬號和密碼的檢查邏輯在代碼中是寫死的,
        // 如果賬號等於"xfeep",並且密碼等於"hello!",就返回PHASE_DONE,這樣nginx-clojure就會繼續執行後面的content handler
        if (up[0].equals("xfeep") && up[1].equals("hello!")) {
            return PHASE_DONE;
        }

        // 如果賬號密碼校驗不過,就返回401,body內容是提示賬號密碼不過
        return new Object[] { 401, ArrayMap.create("www-authenticate", "Basic realm=\"Secure Area\""),
                "<HTML><BODY><H1>401 Unauthorized BAD USER & PASSWORD.</H1></BODY></HTML>" };
    }
}
  • 編譯構建部署之後,咱們來試試效果,用postman再次請求/myproxy,因為header中沒有authorization欄位,所以返回401錯誤:
    在這裡插入圖片描述
  • 然後在header中增加一個屬性,如下圖紅框,名字authorization,值Basic xfeep:hello!,再發一次請求,藍框中顯示返回碼正常,並且返回內容也是重定向後的location生成的:
    在這裡插入圖片描述
  • 然後故意用錯誤的密碼試試,如下圖,鑒權未通過,並且返回body準確描述了具體的錯誤信息:
    在這裡插入圖片描述

Nginx Log Handler(日誌輸出)

  • 最後一個handler是作為輔助作用的日誌輸出,儘管在其他handler中,我們可以直接調用NginxClojureRT.log方法將日誌輸出到error.log文件中,但還是可以猜出官方定義Log Handler的用意:
  1. 明確劃分各個handler的職責
  2. 讓日誌與業務功能解耦合,讓Log Handler做純粹的日誌輸出工作
  3. 日誌模塊偏向於組件化,各個location可以按照需求選擇用或者不用,而且還可以設計成多個location復用
  • 另外Log Handler也有屬於自己的特性:
  1. 依舊是NginxJavaRingHandler介面的實現,invoke方法被執行的時機是request被銷毀前
  2. 有專用的配置屬性log_handler_property
  3. invoke方法的返回值無意義,會被nginx-clojure忽略
  • 接下來通過實例學習log handler,找到前面的content handler的demo,給它加上日誌輸出試試,將配置文件修改如下,可見增加了log_handler_name用於指定日誌輸出的執行類,另外還有兩個log_handler_property配置項作為自定義屬性傳入:
       location /contentdemo {
         # 第一個自定義屬性
         content_handler_property foo.name 'foo.value';
         # 第二個自定義屬性
         content_handler_property bar.name 'bar.value';
         content_handler_name 'com.bolingcavalry.handlerdemo.MyContentHandler';

         # log handler類型是java
         log_handler_type java;
         # log handler的執行類
         log_handler_name 'com.bolingcavalry.handlerdemo.MyLogHandler';
         # 自定義屬性,在MyLogHandler中作為是否列印User Agent的開關
         log_handler_property log.user.agent on;
         # 自定義屬性,在MyLogHandler中作為日誌目錄
         log_handler_property log.file.path logs/contentdemo.log;
       }
  • 對應的MyLogHandler.java,有幾處要註意的地方稍後會提到:
package com.bolingcavalry.handlerdemo;

import nginx.clojure.Configurable;
import nginx.clojure.NginxClojureRT;
import nginx.clojure.java.NginxJavaRequest;
import nginx.clojure.java.NginxJavaRingHandler;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;

public class MyLogHandler implements NginxJavaRingHandler, Configurable {

    /**
     * 是否將User Agent列印在日誌中
     */
    private boolean logUserAgent;

    /**
     * 日誌文件路徑
     */
    private String filePath;

    @Override
    public Object[] invoke(Map<String, Object> request) throws IOException {
        File file = new File(filePath);
        NginxJavaRequest r = (NginxJavaRequest) request;
        try (FileOutputStream out = new FileOutputStream(file, true)) {
            String msg = String.format("%s - %s [%s] \"%s\" %s \"%s\" %s %s\n",
                    r.getVariable("remote_addr"),
                    r.getVariable("remote_user", "x"),
                    r.getVariable("time_local"),
                    r.getVariable("request"),
                    r.getVariable("status"),
                    r.getVariable("body_bytes_sent"),
                    r.getVariable("http_referer", "x"),
                    logUserAgent ? r.getVariable("http_user_agent") : "-");
            out.write(msg.getBytes("utf8"));
        }
        return null;
    }

    @Override
    public void config(Map<String, String> properties) {
        logUserAgent = "on".equalsIgnoreCase(properties.get("log.user.agent"));
        filePath = properties.get("log.file.path");
        NginxClojureRT.log.info("MyLogHandler, logUserAgent [" + logUserAgent + "], filePath [" + filePath + "]");
    }

    // 下麵這段代碼來自官方demo,實測發現這段代碼在列印日誌的邏輯中並未發揮作用,
    // 不論是否刪除,日誌輸出的內容都是相同的
    /*
    @Override
    public String[] variablesNeedPrefetch() {
        return new String[] { "remote_addr", "remote_user", "time_local", "request", "status", "body_bytes_sent",
                "http_referer", "http_user_agent" };
    }
    */
}
  • 上述代碼中,有下麵幾處地方要註意:
  1. 以上代碼來自官方demo,我這裡做了點小的改動(主要是文件路徑改為外部參數傳入)
  2. 整體功能是取出請求和響應的一些參數,列印在日誌文件中
  3. logUserAgent參數控制了user agent是否列印,這個比較實用,可以通過配置來做一些開關控制
  4. 這個demo不要用於生產環境,從代碼可以看出,每一次請求都做了一次io操作,這是存在性能隱患的,官方的demo只是展示log handler的作用而已,看看就好
  5. variablesNeedPrefetch方法的代碼被我註釋掉了,因為實際嘗試發現不論這段代碼是否存在,都不回影響日誌的輸出,去看源碼也沒弄明白...(水平有限,望理解),於是就註釋掉了,畢竟只要日誌輸出正常就行
  • 編譯構建部署運行,先看logs/error.log,如下,可見MyLogHandler成功的接收到了配置項的值:
2022-02-08 08:59:22[info][69035][main]MyLogHandler, logUserAgent [true], filePath [logs/contentdemo.log]
  • 再用postman請求/contentdemo試試,如下圖,首先確保響應和之前一致,證明log handler不影響主業務:
    在這裡插入圖片描述

  • 去logs目錄下查看,發現新增了contentdemo.log文件,內容如下,postman自帶的header參數已經被成功獲取並列印在日誌中了:

127.0.0.1 - x [08/Feb/2022:09:45:36 +0800] "GET /contentdemo HTTP/1.1" 200 "80" x PostmanRuntime/7.29.0
  • 至此,五大handler咱們已經全部實戰體驗過了,對nginx-clojure的主要能力已經熟悉,接下來的章節會繼續深入挖掘,歡迎繼續關註欣宸原創

歡迎關註博客園:程式員欣宸

學習路上,你不孤單,欣宸原創一路相伴...


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、jupyter notebook介紹 1、簡介 Jupyter Notebook是基於網頁的用於交互計算的應用程式。其可被應用於全過程計算:開發、文檔編寫、運行代碼和展示結果。——Jupyter Notebook官方介紹 簡而言之,Jupyter Notebook是以網頁的形式打開,可以在網頁頁 ...
  • # java操作zookeeper 1. 創建一個maven項目在pom文件里引入如下依賴: ~~~XML junit junit 4.10 test org.apache.curator curator-framework 4.0.0 org.apache.curator curator-reci ...
  • # 尾碼數組是什麼 尾碼數組就是主要處理字元串尾碼問題的,它的實現演算法主要有兩種:倍增法和 DC3,複雜度分別是 $O(n\log n)$ 和 $O(n)$。這裡由於 DC3 代碼答辯且難以理解,我就只寫了倍增法的實現。 # 例題引入 [P3809 【模板】尾碼排序](https://www.luo ...
  • > 出於各種限制,很多公司依然停留在Java8,部分小伙伴轉向了Kotlin。Kotlin作為靜態編譯語言,提供大量語法糖,而且編譯後的位元組碼跟Java一致。 > > 當時,Java8於2014年發佈,Kotlin於2016年,很多宣稱的語法糖都是對比的Java8。不禁要問,相對今天的Java17, ...
  • """ # 一、axios是什麼 Axios 是一個基於 promise 網路請求庫,作用於node.js 和瀏覽器中。 它是 isomorphic 的(即同一套代碼可以運行在瀏覽器和node.js中)。在服務端它使用原生 node.js http 模塊, 而在客戶端 (瀏覽端) 則使用 XMLHt ...
  • 中大型項目中,我們都會把項目結構劃分多個模塊。它清晰的定義,便於項目結果維護,同時在日常代碼變更時,各個模塊的隔離也一定程度上保證了變更質量…… ...
  • python的開發工具有很多款,很多都是非常好用的,其中vscode作為其中一款Python的開發工具,是非常輕量級的,今天我們來介紹一下vs code的下載與安裝。 # vscode的下載與安裝 首先需要到vscode的官網,這個谷歌或者百度一下就可以搜到,然後根據你的系統下載你對應的版本,我這裡 ...
  • 第三方鏡像是在Docker Hub或其他容器註冊表上提供的預構建Docker容器鏡像。這些鏡像由個人或組織創建和維護,可以作為您容器化應用程式的起點。 ### 查找第三方鏡像 [**Docker Hub**](https://hub.docker.com/) 是最大和最受歡迎的容器鏡像註冊表,包含官 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...