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]

Reply via email to