apisix
apisix copied to clipboard
help request: Dynamic routing based on HTTP_STATUS from first API
Description
Hi All, I would like to route the request to 3rd API based on the 1st API HTTP_STATUS response. I mean:
- Call
http://apisix/api1
- Get
api1
response /http_status
- If
http_status = 200
, then call tohttp://apisix/api2
- If
http_status = 400
, then call tohttp://apisix/api3
Please, could anyone share an APISIX route snippet to do that?. Any idea is welcome. Regards.
Environment
- APISIX version (run
apisix version
): - Operating system (run
uname -a
): - OpenResty / Nginx version (run
openresty -V
ornginx -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 can be achieved by combining several plugins, such as the following:
uri: /internal-check
plugins:
response-rewrite:
headers:
X-Api1-Status: '$status'
status_code: 200
proxy-rewrite:
uri: "/api1",
upstream:
nodes:
- host: apisix
port: 80
weight: 1
type: roundrobin
status: 1
---
uri: /testTrafficSplit
host: apisix
plugins:
forward-auth:
upstream_headers:
- X-Api1-Status
uri: http://127.0.0.1/internal-check
serverless-pre-function:
functions:
- |
local core = require('apisix.core');
return function(conf, ctx)
if ctx.var.http_x_api1_status == '200' then
ctx.var.upstream_uri = '/api2'
elseif ctx.var.http_x_api1_status == '400' then
ctx.var.upstream_uri = '/api3'
end
end
phase: before_proxy
upstream:
nodes:
- host: apisix
port: 80
weight: 1
type: roundrobin
status: 1
Thanks @qwzhou89
Quick questions:
- What is
$status
in the first route? - In the 2nd route. Why not using the
/api1
response (HTTP_STATUS or json body) instead of using custom headerX-Api1-Status
?.
I've tried this approach, but I don't know how get the /api1
response (headers, json body) in the 1st proxy-rewrite
plugin from 2nd plugin serverless-post-function
.
This is my only 1 route.
{
"uri": "/api1/*",
"methods":["GET"],
"plugins": {
"proxy-rewrite": {
"host": "random-data-api.com",
"uri": "/api/v2/banks",
"scheme": "https"
},
"serverless-post-function": {
"phase": "body_filter",
"functions": [
"return function(conf, ctx)
-- Print the iban value of json_response
end"
]
}
},
"upstream_id": 1
}'
The Random Banks API (https://random-data-api.com/api/v2/banks) returns this:
{
"id": 5243,
"uid": "f38123fc-7c1d-4705-980f-3e82e770b100",
"account_number": "0638101451",
"iban": "GB86RWJD49924585669735",
"bank_name": "UBS CLEARING AND EXECUTION SERVICES LIMITED",
"routing_number": "290583418",
"swift_bic": "BOFAGB3SSWI"
}
And the upstream is:
{
"name": "Random API upstream",
"type": "roundrobin",
"scheme": "https",
"nodes": {
"random-data-api.com": 1
}
}
Should I use serverless-pre-function
and different phase
?
Or should I use a custom plugin?
Appreciate your support.
Regards.
@chilcano,
What is $status in the first route?
$status
is an nginx variable: https://nginx.org/en/docs/http/ngx_http_core_module.html#var_status. This means, the HTTP_STATUS
will be stored in the X-Api1-Status
http header.
I don't know how get the /api1 response (headers, json body) in the 1st proxy-rewrite plugin from 2nd plugin serverless-post-function.
Why do you want access the response body and headers? 🤔 If you have very complex requirements you might have to write your own plugin.
Why do you want access the response body and headers? thinking If you have very complex requirements you might have to write your own plugin.
Thanks @shreemaan-abhishek for your support.
I've tried to extend existing example available here Chaining API requests with API Gateway Demo..
I've created 2 plugins (api1-plugin
and api2-plugin
) and use both in the same route.
The api1-plugin
call to 3rd party api1
and the api1
json response is used to make a call to 3rd party api2
through api2-plugin
.
In simple words, I'm trying to chain 2 APIs, however the challenge I'm facing is how to retrieve the response of 1st plugin from 2nd plugin.
I've tried ngx.var.*
and ngx.ctx.*
to share info from 1st to 2nd plugin and that didn't work.
I'm using 2 plugins because each plugin is calling different upstreams with different requests.
Any idea on how get this? Appreciate your support. Regards.
I've tried
ngx.var.*
andngx.ctx.*
to share info from 1st to 2nd plugin and that didn't work.
You can refer this
local body = core.response.hold_body_chunk(ctx)
if not body then
return
end
https://github.com/apache/apisix/blob/f832ea2502ce2696b0e7390a1572ab7000fef344/apisix/plugins/response-rewrite.lua#L186C9-L186C9
last_resp, err = httpc:request_uri(node.url, params)
if not last_resp then
return 500, "request failed: " .. err
end
https://github.com/Boburmirzo/apisix-plugin-pipeline-request-demo/blob/3b437f915e4c9ee1c0e928211e3747962c8d50e7/custom-plugins/pipeline-request.lua#L100C10-L100C10
Thanks @qwzhou89
I've found a way to share info from a 1st plugin to 2nd plugin. I enabled nginx.http.custom_lua_shared_dict
in the apisix configuration (config.yaml
). I used this config:
apisix:
...
nginx_config:
http:
custom_lua_shared_dict:
my_shared_dict: 10m # Specify the size in MB of the shared memory zone (change as needed)
deployment:
...
plugins:
...
The example here Chaining API requests with API Gateway Demo works only if the 1st call is a GET with not query-string and the 2nd call manipulate the response. What if the 1st call is POST and a json request is sent?
That is why I'm trying to implement in 2 plugins but in the same route.
However, now I'm stuck and I can not be able to execute the api1_plugin
firstly and api2_plugin
later. I've followed the recommendations Plugins execution order, changed the priority, the phase and always the plugin that implements function _M.access(conf, ctx)
is executed.
Am I missing something?
How I can execute sequentially different plugins configured in the same route? Any recommendation here is appreciated.
Regards.
APISIX plugins are executed sequentially through the phases in openresty:
i.e plugins that run on the rewrite
phase will always execute before the plugins in the access
phase irrespective of priority. (the priority is followed strictly among the plugins that run under the same given execution phase).
Thanks @shreemaan-abhishek for sharing this information.
Definitely, I'm missing out something in my code.
Questions:
1. What libraries are available in each phase?.
I changed the plugin phase from _M.access()
to _M.header_filter()
and now I'm having this error when the plugin is being executed.
..
2023/08/02 11:21:15 [error] 51#51: *4647263 failed to run header_filter_by_lua*: /usr/local/apisix/apisix/patch.lua:372: API disabled in the context of header_filter_by_lua*
stack traceback:
[C]: in function 'error'
/usr/local/openresty/lualib/resty/core/socket/tcp.lua:271: in function 'original_tcp'
/usr/local/apisix/apisix/patch.lua:372: in function 'ngx_socket_tcp'
/usr/local/apisix//deps/share/lua/5.1/resty/http.lua:133: in function 'new'
/opt/apisix/plugins/api2-plugin.lua:41: in function 'make_request_to_url'
/opt/apisix/plugins/api2-plugin.lua:123: in function 'phase_func'
/usr/local/apisix/apisix/plugin.lua:1134: in function 'common_phase'
/usr/local/apisix/apisix/init.lua:760: in function 'http_header_filter_phase'
header_filter_by_lua:2: in main chunk, client: 172.19.0.1, server: _, request: "GET /mychainedapi/hello HTTP/1.1", host: "127.0.0.1:9080"
...
This means there are not libraries available in that phase. If so, what libraries can be used there?
2. If only some libraries are accessible in each phase, how I can chain the execution of 2 plugins where the response of the 1st plugin is available in the 2nd plugin Each plugin (api1-plugin and api2-plugin) call external Endpoint URL
Any idea is welcome. Regards.
- What libraries are available in each phase?
Can't list them all but it's logical thinking: you cannot modify the response headers even before the HTTP request is sent to the upstream, does it makes sense?
- If only some libraries are accessible in each phase, how I can chain the execution of 2 plugins where the response of the 1st plugin is available in the 2nd plugin
I don't have an answer right now, let me think of something.
Hi @chilcano , Please let me know how did you make this plugin work? I am getting below while starting apisix and configuring the same in config.yaml
2023/08/02 02:15:02 [error] 1743857#1743857: init_by_lua error: error loading module 'socket.core' from file '/usr/local/apisix/apisix/plugins/custom/pipeline-request.lua': /usr/local/apisix/apisix/plugins/custom/pipeline-request.lua: invalid ELF header stack traceback: [C]: at 0x7fb18658e130 [C]: in function 'require' /usr/local/apisix//deps/share/lua/5.1/socket.lua:12: in main chunk [C]: in function 'require' /usr/local/apisix/apisix/patch.lua:20: in main chunk [C]: in function 'require' /usr/local/apisix/apisix/init.lua:28: in main chunk [C]: in function 'require' init_by_lua:3: in main chunk
My configuration in config.yaml apisix: extra_lua_path: "/usr/local/apisix/custom/apisix/plugins/pipeline-request.lua"
plugins:
- pipeline-request
Hi @sanjivkamate - I think you should create a separate Github issue, once done, I'm happy to help you.
@chilcano Were you able to achieve your use case? If not, then feel free to reopen this issue.
Please, re-open this entry since I didn't achieve my use case.
I've tried to chain 2 APIs. I used the pipeline_request
plugin explained here Chaining API requests with API Gateway Demo without success.
My use case is:
apisix-route-1/<params> -> /api-a/<encoded-params> -> if response contains EURO -> /api-b
-> if response contains GBP -> /api-c
-
apisix-route-1
is called and it encode<params>
and pass that to/api-a
. - The
/api-a/<encoded-params>
is called. - If
/api-a/<encoded-params>
response containsEURO
, then call/api-b
. - If
/api-a/<encoded-params>
response containsGBP
, then call/api-c
. - Send final response to
apisix-route-1/<params>
call.
I wanted to use pipeline_request
plugin to implement conditional logic without success.
Any idea or shared code is welcome.
Regards.