使用 Java 編寫 Apache APISIX 插件

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Apache APISIX 支持多語言編寫插件了!不會 Lua 也沒關係,現在可以用你熟悉的語言編寫插件,在官網查看還有","attrs":{}},{"type":"link","attrs":{"href":"https://apisix.apache.org/blog/2021/06/21/use-Java-to-write-Apache-APISIX-plugins","title":"","type":null},"content":[{"type":"text","text":"視頻教程","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1. 簡介","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.1 爲什麼 Apache APISIX 要支持多語言編寫插件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在支持多語言編程插件前,Apache APISIX 只支持使用 Lua 語言編寫插件,需要開發者掌握 Lua 和 OpenResty 相關的開發能力。然而相對於主流開發語言 Java、Go 來說,Lua 和 OpenResty 屬於相對小衆的技術,開發者很少。如果從頭開始學習 Lua 和 OpenResty,需要付出相當多的時間和精力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開發團隊在進行技術選型的時候,最重要的考量就是所選技術是否與本團隊技術棧相匹配,然而小衆的技術棧就限制了 Apache APISIX 在更廣闊的場景下進行技術落地。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在 Apache APISIX 支持多語言開發插件,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"更重要的是支持語言所在的開發生態圈,使用者可以使用自己熟悉的技術棧來開發 Apache APISIX","attrs":{}},{"type":"text","text":"。以支持 Java 爲例,使用者不僅可以使用 Java 語言編寫插件,還可以融入 Spring Cloud 生態圈,廣泛使用生態圈內的各種技術組件。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.2 Apache APISIX 多語言支持架構圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1a6665a46047a6eb9db0fb93b4b00e34.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上圖左邊是 Apache APISIX 的工作流程,右邊的 plugin runner 是指插件運行器,泛指多語言支持的項目。本文檔下面提到的 apisix-java-plugin-runner 項目就是支持 Java 語言的 plugin runner。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當你在 Apache APISIX 中配置一個 plugin runner 時,Apache APISIX 會啓動一個子進程運行 plugin runner,該子進程與 Apache APISIX 進程屬於同一個用戶。當我們重啓或重新加載 Apache APISIX 時,plugin runner 也將被重啓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你爲一個給定的路由配置了 ext-plugin-* 插件,擊中該路由的請求將觸發 Apache APISIX,通過 unix socket 向 plugin runner 執行 RPC 調用。調用細分爲兩個階段:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ext-plugin-pre-req: 在執行 Apache APISIX 內置插件(Lua 語言插件)之前","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ext-plugin-post-req: 在執行 Apache APISIX 內置插件(Lua 語言插件)之後","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據需要配置 plugin runner 的執行時機。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"plugin runner 會處理 RPC 調用,在其內部創建一個模擬請求,然後運行多語言編寫的插件,並將結果返回給 Apache APISIX。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多語言插件的執行順序是在 ext-plugin-* 插件配置項中定義的。像其他插件一樣,它們可以被啓用並在運行中重新定義。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2. 搭建開發環境","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先需要搭建 Apache APISIX 的運行環境或者開發環境,參考 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/apache/apisix/blob/master/docs/zh/latest/how-to-build.md","title":"","type":null},"content":[{"type":"text","text":"構建 Apache APISIX","attrs":{}}]},{"type":"text","text":",以及 Java 項目的開發環境,參考 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/apache/apisix-java-plugin-runner/blob/main/docs/development.md","title":"","type":null},"content":[{"type":"text","text":"構建 apisix-java-plugin-runner","attrs":{}}]},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意","attrs":{}},{"type":"text","text":":Apache APISIX 和 apisix-java-plugin-runner 需要位於同一實例上。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3. 進入調試模式","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.1 設置 Apache APISIX 進入調試模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏是指讓 Apache APISIX 以調試的方式運行外部插件,在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"config.yaml","attrs":{}}],"attrs":{}},{"type":"text","text":" 中增加以下配置","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"ext-plugin:\n path_for_test: /tmp/runner.sock\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個配置的意思是,Apache APISIX 相當於 client 端,會監聽位於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/tmp/runner.sock","attrs":{}}],"attrs":{}},{"type":"text","text":" 位置上的 Unxi Domain Socket 鏈接。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.2 設置 apisix-java-plugin-runner 進入調試模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在啓動 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Main class(org.apache.apisix.plugin.runner.PluginRunnerApplication)","attrs":{}}],"attrs":{}},{"type":"text","text":"之前,需要配置用戶級的環境變量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"APISIX_CONF_EXPIRE_TIME=3600","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你是使用 IDEA 進行開發,那麼配置好的環境變量示意如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1e/1e86c62932d7b1bd919c1cecaf023d29.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"apisix-java-plugin-runner 相當於 server 端,在啓動時會主動創建 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/tmp/runner.sock","attrs":{}}],"attrs":{}},{"type":"text","text":" 文件,並在這個文件上與 Apache APISIX 進行 Unix Domain Socket 通信。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4. 開發","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.1 場景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們以一個場景來代入開發過程:需要驗證請求 header 中 token 的有效性,驗證 token 的方式是用請求中攜帶 token 作爲參數,訪問 SSO 固定的接口,如果 token 驗證通過則放行請求,驗證失敗則阻止請求。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.2 配置 Apache APISIX","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先給插件命名爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TokenValidator","attrs":{}}],"attrs":{}},{"type":"text","text":",然後設計屬性,爲了儘可能做到動態配置,屬性設計如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"{\n \"validate_header\": \"token\",\n \"validate_url\": \"https://www.sso.foo.com/token/validate\",\n \"rejected_code\": \"403\"\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動 Apache APISIX,然後新增一條路由配置,指定該路由需要調用 apisix-java-plugin-runner 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TokenValidator","attrs":{}}],"attrs":{}},{"type":"text","text":" 插件,示例如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '\n{\n \"uri\":\"/get\",\n \"plugins\":{\n \"ext-plugin-pre-req\":{\n \"conf\":[\n {\n \"name\":\"TokenValidator\",\n \"value\":\"{\\\"validate_header\\\":\\\"token\\\",\\\"validate_url\\\":\\\"https://www.sso.foo.com/token/validate\\\",\\\"rejected_code\\\":\\\"403\\\"}\"\n }\n ]\n }\n },\n \"upstream\":{\n \"nodes\":{\n \"httpbin.org:80\":1\n },\n \"type\":\"roundrobin\"\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TokenValidator","attrs":{}}],"attrs":{}},{"type":"text","text":" 的屬性需要經過 json 轉義,作爲 string 類型進行配置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(這裏上游地址配置爲 httpbin.org,方便調試)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.3 開發 Java 插件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 runner-plugin/src/main/java/org/apache/apisix/plugin/runner/filter 目錄下,新增 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TokenValidatr.java","attrs":{}}],"attrs":{}},{"type":"text","text":",代碼初始骨架如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package org.apache.apisix.plugin.runner.filter;\n\nimport org.apache.apisix.plugin.runner.HttpRequest;\nimport org.apache.apisix.plugin.runner.HttpResponse;\nimport org.springframework.stereotype.Component;\nimport reactor.core.publisher.Mono;\n\n\n@Component\npublic class TokenValidator implements PluginFilter {\n\n @Override\n public String name() {\n return \"TokenValidator\";\n }\n\n @Override\n public Mono filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {\n return chain.filter(request, response);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要繼承 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"PluginFilter","attrs":{}}],"attrs":{}},{"type":"text","text":"接口,重寫 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"name","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"filter","attrs":{}}],"attrs":{}},{"type":"text","text":"函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重寫 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"name","attrs":{}}],"attrs":{}},{"type":"text","text":" 的返回值,和前面配置 APISIX 的路由屬性中 \"name\" 保持一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完整代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package org.apache.apisix.plugin.runner.filter;\n\nimport com.google.gson.Gson;\nimport org.apache.apisix.plugin.runner.HttpRequest;\nimport org.apache.apisix.plugin.runner.HttpResponse;\nimport org.springframework.stereotype.Component;\nimport reactor.core.publisher.Mono;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\npublic class TokenValidator implements PluginFilter {\n\n @Override\n public String name() {\n return \"TokenValidator\";\n }\n\n @Override\n public Mono filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {\n // parse `conf` to json\n String configStr = request.getConfig(this);\n Gson gson = new Gson();\n Map conf = new HashMap<>();\n conf = gson.fromJson(configStr, conf.getClass());\n\n // get configuration parameters\n String token = request.getHeader((String) conf.get(\"validate_header\"));\n String validate_url = (String) conf.get(\"validate_url\");\n boolean flag = validate(token, validate_url);\n\n // token verification results\n if (!flag) {\n String rejected_code = (String) conf.get(\"rejected_code\");\n response.setStatusCode(Integer.parseInt(rejected_code));\n return chain.filter(request, response);\n }\n\n return chain.filter(request, response);\n }\n\n private Boolean validate(String token, String validate_url) {\n //TODO: improve the validation process\n return true;\n }\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.4 測試","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Apache APISIX 上配置的上游服務是 httpbin.org,可以訪問 Apache APISIX,觸發路由,讓 Apache APISIX 調用 apisix-java-plugin-runner 去執行 TokenValidator 插件,測試一下 Java 插件效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl -H 'token: 123456' 127.0.0.1:9080/get \n{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"/\", \n \"Host\": \"127.0.0.1\", \n \"Token\": \"123456\", \n \"User-Agent\": \"curl/7.71.1\", \n \"X-Amzn-Trace-Id\": \"Root=1-60cb0424-02b5bf804cfeab5252392f96\", \n \"X-Forwarded-Host\": \"127.0.0.1\"\n }, \n \"origin\": \"127.0.0.1\", \n \"url\": \"http://127.0.0.1/get\"\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"5. 部署","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"插件開發完成後,部署操作可以參考 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/apache/apisix-java-plugin-runner/blob/main/docs/how-it-works.md#run","title":"","type":null},"content":[{"type":"text","text":"部署 apisix-java-plugin-runner","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"6. 視頻教程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在官網查看還有","attrs":{}},{"type":"link","attrs":{"href":"https://apisix.apache.org/blog/2021/06/21/use-Java-to-write-Apache-APISIX-plugins","title":"","type":null},"content":[{"type":"text","text":"視頻教程","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/QwWi10dwWOxfzX3YOJc6Ng","title":"","type":null},"content":[{"type":"text","text":"公衆號","attrs":{}}]},{"type":"text","text":",閱讀最新技術文章。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章