Humphrey-He opened a new issue, #167: URL: https://github.com/apache/apisix-go-plugin-runner/issues/167
### Current Behavior Unexpected RPC Call Termination in APISIX External Plugin (Response Phase) Background I'm developing a custom APISIX external plugin using Go, which consists of two main modules: Strategy Module: This module is responsible for selecting and delivering encryption keys and strategies to the Execution Module. Execution Module: This module receives the keys and strategies (via request/response headers) and performs the actual encryption/decryption on the request/response body. Current Behavior A client request flows through APISIX as follows: Request Phase: The APISIX custom Strategy Module (Go plugin) successfully determines and delivers the key and strategy information to the Execution Module. The Execution Module then correctly performs the encryption of the request body. The encrypted request is successfully sent to the upstream service. The upstream service successfully decrypts the request using the provided key and proceeds to encrypt its response. Response Phase: The upstream service sends back the encrypted response. APISIX receives this response, and the Strategy Module (Go plugin) is invoked. Crucially here: The Strategy Module's role in the response phase is to select and deliver the key and strategy for decryption to the Execution Module. It does this by writing this information into the response headers using ctxHeaders.Set(). Problem: Immediately after the Strategy Module completes its execution (which only involves setting response headers), the RPC call between APISIX and the Go runner is unexpectedly terminated, and the encrypted response is immediately returned to the client. Expected Behavior: At this point, the flow should ideally continue to the Execution Module to perform decryption of the response body. However, the RPC call ends prematurely, preventing the Execution Module from being invoked. What I've Tried I've experimented with the order of plugins in the response phase (Strategy -> Execution vs. Execution -> Strategy). In both arrangements, only the first plugin in the response chain is executed before the RPC call terminates. Further Observation My understanding is that if an external plugin modifies the response body (e.g., by calling http.ResponseWriter.Write()), APISIX will consider the response finalized and terminate the RPC call, returning the response immediately. The Execution Module (when it's the first in the chain) correctly decrypts the response body and modifies it, thus justifying the RPC termination. However, the Strategy Module in the response phase only writes information to the response headers using ctxHeaders.Set(). It does not write to the response body. Therefore, it should not trigger this early RPC termination mechanism. Question What could be causing the RPC call to terminate prematurely after the Strategy Module (which only modifies headers) executes in the response phase? Is there an undocumented behavior or a specific Go http.ResponseWriter method call (even if not Write()) that could lead to this? ### Expected Behavior _No response_ ### Error Logs docker log: 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.317Z INFO server/server.go:170 receive rpc type: 2 data length: 464 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.317Z INFO plugin/plugin.go:120 run plugin EncryptionController 2025-07-31 14:12:42 2025-07-31T06:12:42.317Z INFO plugins/EncrypitionControllerV2.go:170 Appid [app123] 认证成功 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.317Z WARN plugins/EncrypitionControllerV2.go:223 Appid[app123]: 已将 2 个可用解密密钥存入上下文,以支持密钥轮滑 2025-07-31 14:12:42 2025-07-31T06:12:42.317Z INFO plugins/EncrypitionControllerV2.go:227 Policy Controller: Appid 'app123' 已分发策略 'aes-base64-rsa' 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.317Z INFO plugin/plugin.go:120 run plugin EncryptionExecV1 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.318Z INFO http/request.go:375 receive rpc type: 3 data length: 92 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.318Z WARN plugins/EncryptionExecV1.go:322 [Executor-Debug] rsa_key (part 1) Base64: DvEU6+ybCEabK3uEWwYcyWtlNxFB5OI7epbvRDLZqYYNDdD+0dCxRJsMnmbNRcWf+G+mFTqmULQnUDAP4t84mAOIQ84RyDIkMN1xFCGHm1NtSrQSfe5ZYt1cwsh3jvAoDMOiLZFe5J6yNviaqk0MV0cBvyKIcOYjJs3fJp5zAdzLs87/9h1FTZnOtwSnVgGZezAe32bVtu7KGAIc144s6OWpzt49GjrIof0SF7HTdnKwv2WA/i7TJcDrMTWDwujm2m+yy2APaLFRJ7VZyQsFJDcgMypJrE+bUTeLgExHnZFUygdOeAS8bGvr0U3oIgAim2WQ2atYtJdYQGEUKrwJXQ== 2025-07-31 14:12:42 2025-07-31T06:12:42.318Z WARN plugins/EncryptionExecV1.go:323 [Executor-Debug] aes_body (part 2) Base64: bwDznYg10pho3j5iqXCoD7DvntXMqFElCtO36VVdDf493eS5IaoGuIfho/sfGgjBA0Fn9cZ0sQ29ipWMly+tugWm5j30TbF3fbfq1T/zets= 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.318Z WARN plugins/EncryptionExecV1.go:553 Appid[app123]: 请求体加密成功(策略:aes-base64-rsa 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.324Z INFO server/server.go:170 receive rpc type: 4 data length: 268 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.325Z INFO plugin/plugin.go:185 run plugin EncryptionController 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:42 2025/07/31 06:12:42 [warn] 79#79: *173 [lua] init.lua:962: 2025-07-31T06:12:42.325Z WARN plugins/EncrypitionControllerV2.go:277 Appid[app123] (Response Phase): 已决策出解密策略 'aes-base64-rsa' 并准备了 2 个私钥 2025-07-31 14:12:42 , context: ngx.timer 2025-07-31 14:12:43 172.21.0.1 - - [31/Jul/2025:06:12:40 +0000] 127.0.0.1:9080 "POST /test/encrypition HTTP/1.1" 200 529 0.006 "-" "Go-http-client/1.1" - - - "http://127.0.0.1:9080" 2025-07-31 14:12:44 172.21.0.1 - - [31/Jul/2025:06:12:41 +0000] 127.0.0.1:9080 "POST /test/encrypition HTTP/1.1" 200 529 0.008 "-" "Go-http-client/1.1" - - - "http://127.0.0.1:9080" ### Steps to Reproduce Steps to Reproduce To reproduce this issue, please follow these steps: Prepare APISIX and Go Runner Environment: Ensure you have an APISIX instance running, preferably via docker-compose as described in your setup. Compile your Go plugin (go-runnerv2.8De or similar) and ensure it's accessible by the APISIX container at the path configured in config.yaml (e.g., /usr/local/apisix/apisix/plugins/go-runnerv2.8De). Verify your docker-compose.yaml correctly mounts your config.yaml and any necessary Go Runner files or directories. Verify your config.yaml has the ext-plugin-pre-req, ext-plugin-post-resp, and ext-plugin plugins enabled in the plugins list. Go Plugin Setup: Strategy Module: Implement your Strategy Module for both request and response phases. In the request phase, it should set encryption key/strategy into request headers (e.g., X-Ctx-Encryption-Strategy, Pub-Key-Pem). In the response phase, it should read the upstream's encrypted response, select the decryption key/strategy, and write this information back into the response headers using w.Header().Set("X-Decryption-Strategy", strategy) or similar, without writing to the response body (w.Write()). Execution Module: Implement your Execution Module for both request and response phases. In the request phase, it should read key/strategy from request headers and encrypt the request body, then call r.SetBody() and update Content-Length. In the response phase, it should read key/strategy from response headers and decrypt the response body, then call w.Write() with the decrypted body. Important: Ensure your Go Runner binary includes both these modules and they are correctly registered. APISIX Route Configuration: Create an APISIX route that uses both the ext-plugin-pre-req (for the request phase) and ext-plugin-post-resp (for the response phase) plugins. Configure the ext-plugin-pre-req to call your Strategy Module then your Execution Module in the request phase. Configure the ext-plugin-post-resp to call your Strategy Module first, followed by your Execution Module, in the response phase. Example route configuration (via Admin API or Dashboard): JSON { "uri": "/test/encryption-flow", "plugins": { "ext-plugin-pre-req": { "conf": [ { "name": "EncryptionController", "value": "{}" }, { "name": "EncryptionExec", "value": "{}" } ] }, "ext-plugin-post-resp": { "conf": [ { "name": "EncryptionController", "value": "{}" }, { "name": "EncryptionExec", "value": "{}" } ] } }, "upstream": { "type": "roundrobin", "nodes": { "your_upstream_service:80": 1 } } } Replace StrategyModule and ExecutionModule with the actual names you registered for your Go plugins. Replace your_upstream_service:80 with a simple backend service that returns an encrypted response (or a mock service that returns any response that your ExecutionModule can attempt to decrypt). Send a Request: Send a POST request with a small body (e.g., plain text) to the configured APISIX route: Bash curl -X POST http://<APISIX_IP>:9080/test/encryption-flow -d "hello world" -v Observe the Logs and Response: Expected: The ext-plugin-post-resp should execute the Strategy Module, then proceed to the Execution Module for decryption, and finally return the decrypted response. Actual (Issue): Observe the APISIX error/warning logs. After the Strategy Module completes in the response phase (which only sets headers), you will find that the RPC call terminates unexpectedly, and the encrypted response (from the upstream) is immediately returned to the client, without the Execution Module being called. ### Environment - APISIX version (run `apisix version`): - Operating system (run `uname -a`): - OpenResty / Nginx version (run `openresty -V` or `nginx -V`): - etcd version, if relevant (run `curl http://127.0.0.1:9090/v1/server_info`): - APISIX Dashboard version, if relevant: - Plugin runner version, for issues related to plugin runners: - LuaRocks version, for installation issues (run `luarocks --version`): -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
