Java擴展Nginx之六:兩大filter

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/07/16/17556024.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》系列的第六篇,前文的五大handler形成了nginx-clojure開發的基本框架,初步評估已經可以支撐簡單的需求開發了,但nginx-clojure並未止步於handler,還提供了豐富的擴展能力,本篇的兩大filter就是比較常用的能力
  • filter一共有兩種:header filter和body filter,nginx-clojure對他們的定位分別是對header的處理和對body的處理,接下來分別細說

Nginx Header Filter

  • header filter顧名思義,是用於header處理的過濾器,它具有如下特點:
  1. header filter是location級別的配置,可以開發一個header filter,然後配置在不同的location中使用
  2. header filter必須實現NginxJavaHeaderFilter介面,功能代碼寫在doFilter方法中
  3. doFilter方法如果返回PHASE_DONE,nginx-clojure框架會繼續執行其他的filter和handler,如果返回的不是PHASE_DONE,nginx-clojure框架就會把當前filter當做普通的content handler來對待,將doFilter的返回值立即返回給客戶端
  4. 官方建議用header filter來動態處理response的header(增加、刪除、修改header項)
  • 接下來開發一個header filter試試,還記得《Java擴展Nginx之一:你好,nginx-clojure》一文中的/java介面嗎,那是個最簡單的helloworld級別的location,content handler是HelloHandler.java,稍後驗證header filter功能的時候會用到它

  • 先用postman請求/java介面,看看沒有使用header filter之前的response header,如下圖:
    在這裡插入圖片描述

  • 接下來新增一個location,配置如下,content handler還是HelloHandler.java,增加了header_filter_type和header_filter_name:

location /headerfilterdemo {
	content_handler_type 'java';
    content_handler_name 'com.bolingcavalry.simplehello.HelloHandler';

    # header filter的類型是java
    header_filter_type 'java';
    # header
    header_filter_name 'com.bolingcavalry.filterdemo.RemoveAndAddMoreHeaders';
}
  • 執行header filter功能的類是RemoveAndAddMoreHeaders.java,如下所示,修改了Content-Type,還增加了兩個header項Xfeep-HeaderServer
package com.bolingcavalry.filterdemo;

import nginx.clojure.java.Constants;
import nginx.clojure.java.NginxJavaHeaderFilter;
import java.util.Map;

public class RemoveAndAddMoreHeaders implements NginxJavaHeaderFilter {
    @Override
    public Object[] doFilter(int status, Map<String, Object> request, Map<String, Object> responseHeaders) {
        // 先刪再加,相當於修改了Content-Type的值
        responseHeaders.remove("Content-Type");
        responseHeaders.put("Content-Type", "text/html");

        // 增加兩個header
        responseHeaders.put("Xfeep-Header", "Hello2!");
        responseHeaders.put("Server", "My-Test-Server");

        // 返回PHASE_DONE表示告知nginx-clojure框架,當前filter正常,可以繼續執行其他的filter和handler
        return Constants.PHASE_DONE;
    }
}
  • simple-hellofilter-demo兩個maven工程都編譯構建,會得到simple-hello-1.0-SNAPSHOT.jar和filter-demo-1.0-SNAPSHOT.jar這兩個jar,將其都放入nginx/jars目錄下,然後重啟nginx
  • 用postman請求/headerfilterdemo,並將響應的header與/java做對比,如下圖,可見先刪再加、添加都正常,另外,由於Server配置項本來就存在,所以filter中的put操作的結果就是修改了配置項的值:
    在這裡插入圖片描述
  • 到這裡header filter就介紹完了,接下來要看的是body filter,顧名思義,這是用於處理響應body的過濾器,與header filter不同的是,由於響應body有不同的類型,因此body filter也不能一概而論,需要分場景開發和使用

Nginx Body Filter的第一個場景:字元串body(string faced Java body filter)

  • Body Filter的作用很明確:修改原響應body的值,然後返回給客戶端
  • 如果響應的body是字元串,那麼body filter相對簡單一些,以下幾個規則要註意:
  1. 繼承抽象類StringFacedJavaBodyFilter,
  2. 處理一次web請求的時候,doFilter方法可能被調用多次,有個名為isLast的入參,作用是標記當前調用是不是最後一次(true表示最後一次)
  3. doFilter方法的返回值與之前的NginxJavaRingHandler.invoke方法類似,是個一維數組,只有三個元素:status, headers, filtered_chunk,一旦status值不為空,nginx-clojure框架會用這次doFilter的返回值作為最後一次調用,返回給客戶端
  4. 結合2和3的特性,我們在編碼時要註意了:假設一次web請求,doFilter會被調用10次(每次body入參的值都是整個response body的一部分),那麼前9次的isLast都等於false,第10次的isLast等於true,假設第1次調用doFilter方法的時候返回的status不為空,就會導致後面9次的doFilter都不再被調用了!
  • 接下來的實戰再次用到之前的HelloHandler.java作為content handler,因為它返回的body是字元串
  • 先增加一個location配置,body_filter_type和body_filter_name是body filter的配置項:
# body filter的demo,response body是字元串類型
location /stringbodyfilterdemo {
	content_handler_type 'java';
	content_handler_name 'com.bolingcavalry.simplehello.HelloHandler';

	# body filter的類型是java
	body_filter_type 'java';
    # body filter的類
    body_filter_name 'com.bolingcavalry.filterdemo.StringFacedUppercaseBodyFilter';
}
  • StringFacedUppercaseBodyFilter.java源碼如下(請重點閱讀註釋),可見該filter的功能是將原始body改為大寫,並且,代碼中檢查了isLast的值,isLast等於false的時候,status的值保持為null,這樣才能確保doFilter的調用不會提前結束,如此才能返回完整的body:
package com.bolingcavalry.filterdemo;

import nginx.clojure.java.StringFacedJavaBodyFilter;
import java.io.IOException;
import java.util.Map;

public class StringFacedUppercaseBodyFilter extends StringFacedJavaBodyFilter {
    @Override
    protected Object[] doFilter(Map<String, Object> request, String body, boolean isLast) throws IOException {
        if (isLast) {
            // isLast等於true,表示當前web請求過程中最後一次調用doFilter方法,
            // body是完整response body的最後一部分,
            // 此時返回的status應該不為空,這樣nginx-clojure框架就會完成body filter的執行流程,將status和聚合後的body返回給客戶端
            return new Object[] {200, null, body.toUpperCase()};
        }else {
            // isLast等於false,表示當前web請求過程中,doFilter方法還會被繼續調用,當前調用只是多次中的一次而已,
            // body是完整response body的其中一部分,
            // 此時返回的status應該為空,這樣nginx-clojure框架就繼續body filter的執行流程,繼續調用doFilter
            return new Object[] {null, null, body.toUpperCase()};
        }
    }
}
  • 編譯,構建,部署之後,用postman訪問/stringbodyfilterdemo,得到的響應如下,可見body的內容已經全部大寫了,符合預期:
    在這裡插入圖片描述
  • 接下來要學習的還是body filter,只不過這次的body類型是二進位流(stream faced Java body filter)

Nginx Body Filter的第二個場景:二進位流body(stream faced Java body filter)

  • 當響應body是二進位流的時候,如果想對響應body做讀寫操作,nginx-clojure的建議是在body filter中執行,這種body filter是專門用在二進位流body的場景下,有以下特點:
  1. 實現介面NginxJavaBodyFilter(註意區別:字元串body的filter是繼承抽象類StringFacedJavaBodyFilter),
  2. 處理一次web請求的時候,doFilter方法可能被調用多次,有個名為isLast的入參,作用是標記當前調用是不是最後一次(true表示最後一次)
  3. doFilter方法的返回值與之前的NginxJavaRingHandler.invoke方法類似,是個一維數組,只有三個元素:status, headers, filtered_chunk,一旦status值不為空,nginx-clojure框架會用這次doFilter的返回值作為最後一次調用,返回給客戶端
  4. 結合2和3的特性,我們在編碼時要註意了:假設一次web請求,doFilter會被調用10次(每次body入參的值都是整個response body的一部分),那麼前9次的isLast都等於false,第10次的isLast等於true,假設第1次調用doFilter方法的時候返回的status不為空,就會導致後面9次的doFilter都不再被調用了!
  5. doFilter方法有個入參名為bodyChunk,這表示真實響應body的一部分(假設一次web請求有十次doFilter調用,可以將每次doFilter的bodyChunk認為是完整響應body的十分之一),這裡有個重點註意的地方:bodyChunk只在當前doFilter執行過程中有效,不要將bodyChunk保存下來用於其他地方(例如放入body filter的成員變數中)
  6. 繼續看doFilter方法的返回值,剛剛提到返回值是一維數組,只有三個元素:status, headers, filtered_chunk,對於status和headers,如果之前已經設置好了(例如content handler或者header filter中),那麼此時返回的status和headers值就會被忽略掉(也就是說,其實nginx-clojure框架只判斷status是否為空,用於結束body filter的處理流程,至於status的具體值是多少並不關心)
  7. 再看doFilter方法的返回值的第三個元素filtered_chunk,它可以是以下四種類型之一:
  • File, viz. java.io.File

  • String

  • InputStream

  • Array/Iterable, e.g. Array/List/Set of above types

  • 接下來進入實戰了,詳細步驟如下圖:
    在這裡插入圖片描述

  • 首先是開發一個返回二進位流的web介面,為了簡單省事兒,直接用nginx-clojure的另一個能力來實現:clojure類型的服務,在nginx.conf中添加以下內容即可,代碼雖然不是java但也能勉強看懂(能看懂就行,畢竟不是重點),就是持續寫入1024行字元串,每行的內容都是'123456789':

location /largebody {
	content_handler_type 'clojure';
    content_handler_code '
    	(do
        	(use \'[nginx.clojure.core])
            (fn[req]
            	{:status 200
                 :headers {}
                 :body (for [i (range 1024)] "123456789\n")})
        )';
}
  • 接下來是重點面向二進位流的body filter,StreamFacedBodyFilter.java,用來處理二進位流的body filter,可見這是非常簡單的邏輯,您可以按照實際需要去使用這個InputStream:
package com.bolingcavalry.filterdemo;

import nginx.clojure.NginxChainWrappedInputStream;
import nginx.clojure.NginxClojureRT;
import nginx.clojure.java.NginxJavaBodyFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

public class StreamFacedBodyFilter implements NginxJavaBodyFilter {

    @Override
    public Object[] doFilter(Map<String, Object> request, InputStream bodyChunk, boolean isLast) throws IOException {
        // 這裡僅將二進位文件長度列印到日誌,您可以按照業務實際情況自行修改
        NginxClojureRT.log.info("isLast [%s], total [%s]", String.valueOf(isLast), String.valueOf(bodyChunk.available()));

        // NginxChainWrappedInputStream的成員變數index記錄的讀取的位置,本次用完後要重置位置,因為doFilter之外的代碼中可能也會讀取bodyChunk
        ((NginxChainWrappedInputStream)bodyChunk).rewind();

        if (isLast) {
            // isLast等於true,表示當前web請求過程中最後一次調用doFilter方法,
            // body是完整response body的最後一部分,
            // 此時返回的status應該不為空,這樣nginx-clojure框架就會完成body filter的執行流程,將status和聚合後的body返回給客戶端
            return new Object[] {200, null, bodyChunk};
        }else {
            // isLast等於false,表示當前web請求過程中,doFilter方法還會被繼續調用,當前調用只是多次中的一次而已,
            // body是完整response body的其中一部分,
            // 此時返回的status應該為空,這樣nginx-clojure框架就繼續body filter的執行流程,繼續調用doFilter
            return new Object[] {null, null, bodyChunk};
        }
    }
}
  • 還要在nginx.conf上做好配置,讓StreamFacedBodyFilter處理/largebody返回的body,如下所示,新增一個介面/streambodyfilterdemo,該介面會直接透傳到/largebody,而且會用StreamFacedBodyFilter處理響應body:
        location /streambodyfilterdemo {
            # body filter的類型是java
            body_filter_type java;
            body_filter_name 'com.bolingcavalry.filterdemo.StreamFacedBodyFilter';
            proxy_http_version 1.1;
            proxy_buffering off;
            proxy_pass http://localhost:8080/largebody;
        }
  • 寫完後,編譯出jar文件,複製到jars目錄下,重啟nginx
  • 在postman上訪問/streambodyfilterdemo,響應如下,符合預期:
    在這裡插入圖片描述
  • 再檢查文件nginx-clojure-0.5.2/logs/error.log,見到了StreamFacedBodyFilter的日誌,證明body filter確實已經生效,另外還可以看出一次請求中,StreamFacedBodyFilter對象的doFilter方法會被neginx-clojure多次調用:
2022-02-15 21:34:38[info][23765][main]isLast [false], total [3929]
2022-02-15 21:34:38[info][23765][main]isLast [false], total [4096]
2022-02-15 21:34:38[info][23765][main]isLast [false], total [2215]
2022-02-15 21:34:38[info][23765][main]isLast [true], total [0]
  • 至此,咱們一同完成了header和body的filter和學習實踐,nginx-clojure的大體功能咱們已經瞭解得差不多了,但是《Java擴展Nginx》系列還沒結束呢,還有精彩的內容會陸續登場,敬請關註,欣宸原創必不辜負您的期待~

源碼下載

名稱 鏈接 備註
項目主頁 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協議

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

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


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

-Advertisement-
Play Games
更多相關文章
  • 一. 介紹 在Java中,集合是一種用於存儲和操作多個元素的容器。它更方便地操作和管理一組對象,集合類提供了比傳統的數組更強大和靈活的功能,可以動態地添加、刪除和查找元素,以及進行排序、過濾等操作。集合類有一個共同特點,就是它們只容納對象,如果想在集合中使用基本類型數據,可以使用其對應的包裝類。 集 ...
  • ## 超詳細整合SSM框架--(Spring + Spring MVC + MyBatis) 閱讀該文章之前首先要清楚Spring框架,SpringMVC框架,Mybatis框架。 SSM框架,是Spring + Spring MVC + MyBatis的縮寫,這個是繼SSH之後,目前比較主流的Ja ...
  • 通道數據統計增加 卡數量、總流量 已用流量 剩餘流量 統計 卡詳情增加會話信息查詢會話記錄(分頁查詢)、導出歷史記錄 新增智能診斷 卡狀態、凍結狀態、停機原因、區域限制狀態、區域限制地區、設備狀態、業務變更歷史、已開通APN查詢、診斷建議提示;(基本完美還原官方智能診斷) 優化卡號同步演算法bug 優... ...
  • # Sping JdbcTemplate ## JdbcTemplate概述 JdbcTemplate 是 Spring JDBC 核心包(core)中的核心類,它可以通過配置文件、註解、Java 配置類等形式獲取資料庫的相關信息,實現了對 JDBC 開發過程中的驅動載入、連接的開啟和關閉、SQL ...
  • 問題:在退出登錄後(廣義場景)在未重新登錄的情況下仍能進入界面,用戶數據保密問題沒有解決。 分析問題:需要進行登錄校驗(即當服務端接收到請求後,首先要將請求進行校驗,如果已經登錄,則正常訪問,沒有則返回錯誤結果) ![](https://img2023.cnblogs.com/blog/323633 ...
  • 本文已收錄至Github,推薦閱讀 👉 [Java隨想錄](https://github.com/ZhengShuHai/JavaRecord) 微信公眾號:[Java隨想錄](https://mmbiz.qpic.cn/mmbiz_jpg/jC8rtGdWScMuzzTENRgicfnr91C5 ...
  • PyCharm是一種專業的Python集成開發環境(IDE),由JetBrains公司開發和維護。它提供了豐富的功能和工具,幫助開發人員更高效地編寫、調試和測試Python代碼。如果是一些大型Python項目強烈推薦用這個來開發。今天我們來介紹一下PyCharm的下載與安裝。 # PyCharm的下 ...
  • 本篇談一談單鏈表的改,具體操作就是找到他,然後修改元素即可,上一篇有相關代碼,可以參考。 改函數代碼如下: void Correct(LinkList header, int site_, char letter_) { LinkList q = Search_Site(header,site_); ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...