I recently read 6 Ways To Pass Parameters to Spring REST API. Though the title is a bit misleading, as it's unrelated to REST, it does an excellent job listing all ways to send parameters to a Spring application. I want to do the same for Apache APISIX; it's beneficial when you write a custom plugin. General setup The general setup uses Docker Compose and static configuration. I'll have one plugin per way to pass parameters. services: httpbin: image: kennethreitz/httpbin #1 apisix: image: apache/apisix:3.9.0-debian volumes: - ./apisix/conf/config.yml:/usr/local/apisix/conf/config.yaml:ro - ./apisix/conf/apisix.yml:/usr/local/apisix/conf/apisix.yaml:ro #2 - ./apisix/plugins:/opt/apisix/plugins:ro #3 ports: - "9080:9080" Local httpbin for more reliable results and less outbound network traffic Static configuration file Plugins folder, one file per plugin deployment: role: data_plane role_data_plane: config_provider: yaml #1 apisix: extra_lua_path: /opt/?.lua #2 plugins: - proxy-rewrite #3 - path-variables #4 # ... Set static configuration Use every Lua file under /opt/apisix/plugins as a plugin Regular plugin Custom plugin, one per alternative Path variables Path variables are a straightforward way to pass data. Their main issue is that they are limited to simple values, e.g., /links/{n}/{offset}. The naive approach is to write the following Lua code: local core = require("apisix.core") function _M.access(_, ctx) local captures, _ = ngx.re.match(ctx.var.uri, '/path/(.*)/(.*)') --1-2 for k, v in pairs(captures) do core.log.warn('Order-Value pair: ', k, '=', v) end end APISIX stores the URI in ctx.var.uri Nginx offers a regular expression API Let's try: curl localhost:9080/path/15/3 The log displays: Order-Value pair: 0=/path/15/3 Order-Value pair: 1=15 Order-Value pair: 2=3 I didn't manage errors, though. Alternatively, we can rely on Apache APISIX features: a specific router. The default router, radixtree_host_uri, uses both the host and the URI to match requests. radixtree_uri_with_parameter lets go of the host part but also matches parameters. apisix: extra_lua_path: /opt/?.lua router: http: radixtree_uri_with_parameter We need to update the route: routes: - path-variables - uri: /path/:n/:offset #1 upstream_id: 1 plugins: path-variables: ~ Store n and offset in the context, under ctx.curr_req_matched We keep the plugin just to log the path variables: function _M.access(_, ctx) core.log.warn('n: ', ctx.curr_req_matched.n, ', offset: ', ctx.curr_req_matched.offset) end The result is as expected with the same request as above: n: 15, offset: 3 Query parameters Query parameters are another regular way to pass data. Like path variables, you can only pass simple values, e.g., /?foo=bar. The Lua code doesn't require regular expressions: local core = require("apisix.core") function _M.access(_, _) local args, _ = ngx.req.get_uri_args() for k, v in pairs(args) do core.log.warn('Order-Value pair: ', k, '=', v) end end Let's try: curl localhost:9080/query\?foo=one\&bar=three The log displays: Key-Value pair: bar=three Key-Value pair: foo=one Remember that query parameters have no order. Our code contains an issue, though. The ngx.req.get_uri_args() accepts parameters. Remember that the client can pass a query parameter multiple times with different values, e.g., ?foo=one&foo=two? The first parameter is the maximum number of values returned for a single query parameter. To avoid ignoring value, we should set it to 0, i.e., unbounded. Since every plugin designer must remember it, we can add the result to the context for other plugins down the chain. The updated code looks like this: local core = require("apisix.core") function _M.get_uri_args(ctx) if not ctx then ctx = ngx.ctx.api_ctx end if not ctx.req_uri_args then local args, _ = ngx.req.get_uri_args(0) ctx.req_uri_args = args end return ctx.req_uri_args end function _M.access(_, ctx) for k, v in pairs(ctx.req_uri_args) do core.log.warn('Key-Value pair: ', k, '=', v) end end Request headers Request headers are another way to pass parameters. While they generally only contain simple values, you can also use them to send structured values, e.g., JSON. Depending on your requirement, APISIX can list all request headers or a specific one. Here, I get all of them: local core = require("apisix.core") function _M.access(_, _) local headers = core.request.headers() for k, v in pairs(headers) do core.log.warn('Key-Value pair: ', k, '=', v) end end We test with a simple request: curl -H 'foo: 1' -H 'bar: two' localhost:9080/headers And we got more than we expected because curl added default headers: Key-Value pair: user-agent=curl/8.4.0 Key-Value pair: bar=two Key-Value pair: foo=1 Key-Value pair: host=localhost:9080 Key-Value pair: accept=*/* Request body Setting a request body is the usual way to send structured data, e.g, JSON. Nginx offers a simple API to collect such data. local core = require("apisix.core") function _M.access(_, _) local args = core.request.get_post_args() --1 local body = next(args, nil) --2 core.log.warn('Body: ', body) end Access the body as a regular Lua table A table is necessary in case of multipart payloads, e.g., file uploads. Here, we assume there's a single arg, the content body. It's time to test: curl localhost:9080/body -X POST -d '{ "foo": 1, "bar": { "baz": "two" } }' The result is as expected: Body: { "foo": 1, "bar": { "baz": "two" } } Cookies Last but not least, we can send parameters via cookies. The difference with previous alternatives is that cookies persist on the client side, and the browser sends them with each request. On the Lua side, we need to know the cookie name instead of listing all query parameters or headers. local core = require("apisix.core") function _M.access(_, ctx) local foo = ctx.var.cookie_foo --1 core.log.warn('Cookie value: ', foo) end The cookie is named foo and is case-insensitive Let's test: curl --cookie "foo=Bar" localhost:9080/cookies The result is correct: Cookie value: Bar Summary In this post, we listed five alternatives to pass parameters server-side and explained how to access them on Apache APISIX. Here's the API summary: Alternative Source API Path variable APISIX Router Use the radixtree_uri_with_parameter router Query parameter Nginx ngx.req.get_uri_args(0) Request header APISIX core lib core.request.headers() Request body APISIX core lib core.request.get_post_args() Cookie Method context parameter ctx.var.cookie_<name> Thanks a lot to Zeping Bai for his review and explanations. The complete source code for this post can be found on GitHub. To go further: 6 Ways To Pass Parameters to Spring REST API How to Build an Apache APISIX Plugin From 0 to 1? I recently read 6 Ways To Pass Parameters to Spring REST API . Though the title is a bit misleading, as it's unrelated to REST, it does an excellent job listing all ways to send parameters to a Spring application. I want to do the same for Apache APISIX ; it's beneficial when you write a custom plugin. 6 Ways To Pass Parameters to Spring REST API REST, Apache APISIX General setup The general setup uses Docker Compose and static configuration. I'll have one plugin per way to pass parameters. Docker services: httpbin: image: kennethreitz/httpbin #1 apisix: image: apache/apisix:3.9.0-debian volumes: - ./apisix/conf/config.yml:/usr/local/apisix/conf/config.yaml:ro - ./apisix/conf/apisix.yml:/usr/local/apisix/conf/apisix.yaml:ro #2 - ./apisix/plugins:/opt/apisix/plugins:ro #3 ports: - "9080:9080" services: httpbin: image: kennethreitz/httpbin #1 apisix: image: apache/apisix:3.9.0-debian volumes: - ./apisix/conf/config.yml:/usr/local/apisix/conf/config.yaml:ro - ./apisix/conf/apisix.yml:/usr/local/apisix/conf/apisix.yaml:ro #2 - ./apisix/plugins:/opt/apisix/plugins:ro #3 ports: - "9080:9080" Local httpbin for more reliable results and less outbound network traffic Static configuration file Plugins folder, one file per plugin Local httpbin for more reliable results and less outbound network traffic Static configuration file Plugins folder, one file per plugin deployment: role: data_plane role_data_plane: config_provider: yaml #1 apisix: extra_lua_path: /opt/?.lua #2 plugins: - proxy-rewrite #3 - path-variables #4 # ... deployment: role: data_plane role_data_plane: config_provider: yaml #1 apisix: extra_lua_path: /opt/?.lua #2 plugins: - proxy-rewrite #3 - path-variables #4 # ... Set static configuration Use every Lua file under /opt/apisix/plugins as a plugin Regular plugin Custom plugin, one per alternative Set static configuration Use every Lua file under /opt/apisix/plugins as a plugin /opt/apisix/plugins Regular plugin Custom plugin, one per alternative Path variables Path variables are a straightforward way to pass data. Their main issue is that they are limited to simple values, e.g. , /links/{n}/{offset} . e.g. /links/{n}/{offset} The naive approach is to write the following Lua code: The naive approach is to write the following Lua code: local core = require("apisix.core") function _M.access(_, ctx) local captures, _ = ngx.re.match(ctx.var.uri, '/path/(.*)/(.*)') --1-2 for k, v in pairs(captures) do core.log.warn('Order-Value pair: ', k, '=', v) end end local core = require("apisix.core") function _M.access(_, ctx) local captures, _ = ngx.re.match(ctx.var.uri, '/path/(.*)/(.*)') --1-2 for k, v in pairs(captures) do core.log.warn('Order-Value pair: ', k, '=', v) end end APISIX stores the URI in ctx.var.uri Nginx offers a regular expression API APISIX stores the URI in ctx.var.uri ctx.var.uri Nginx offers a regular expression API Let's try: Let's try: curl localhost:9080/path/15/3 curl localhost:9080/path/15/3 The log displays: The log displays: Order-Value pair: 0=/path/15/3 Order-Value pair: 1=15 Order-Value pair: 2=3 Order-Value pair: 0=/path/15/3 Order-Value pair: 1=15 Order-Value pair: 2=3 I didn't manage errors, though. Alternatively, we can rely on Apache APISIX features: a specific router . The default router, radixtree_host_uri , uses both the host and the URI to match requests. radixtree_uri_with_parameter lets go of the host part but also matches parameters. router radixtree_host_uri radixtree_uri_with_parameter apisix: extra_lua_path: /opt/?.lua router: http: radixtree_uri_with_parameter apisix: extra_lua_path: /opt/?.lua router: http: radixtree_uri_with_parameter We need to update the route: routes: - path-variables - uri: /path/:n/:offset #1 upstream_id: 1 plugins: path-variables: ~ routes: - path-variables - uri: /path/:n/:offset #1 upstream_id: 1 plugins: path-variables: ~ Store n and offset in the context, under ctx.curr_req_matched Store n and offset in the context, under ctx.curr_req_matched n offset ctx.curr_req_matched We keep the plugin just to log the path variables: function _M.access(_, ctx) core.log.warn('n: ', ctx.curr_req_matched.n, ', offset: ', ctx.curr_req_matched.offset) end function _M.access(_, ctx) core.log.warn('n: ', ctx.curr_req_matched.n, ', offset: ', ctx.curr_req_matched.offset) end The result is as expected with the same request as above: n: 15, offset: 3 n: 15, offset: 3 Query parameters Query parameters are another regular way to pass data. Like path variables, you can only pass simple values, e.g. , /?foo=bar . The Lua code doesn't require regular expressions: e.g. /?foo=bar local core = require("apisix.core") function _M.access(_, _) local args, _ = ngx.req.get_uri_args() for k, v in pairs(args) do core.log.warn('Order-Value pair: ', k, '=', v) end end local core = require("apisix.core") function _M.access(_, _) local args, _ = ngx.req.get_uri_args() for k, v in pairs(args) do core.log.warn('Order-Value pair: ', k, '=', v) end end Let's try: curl localhost:9080/query\?foo=one\&bar=three curl localhost:9080/query\?foo=one\&bar=three The log displays: Key-Value pair: bar=three Key-Value pair: foo=one Key-Value pair: bar=three Key-Value pair: foo=one Remember that query parameters have no order. Our code contains an issue, though. The ngx.req.get_uri_args() accepts parameters . Remember that the client can pass a query parameter multiple times with different values, e.g. , ?foo=one&foo=two ? The first parameter is the maximum number of values returned for a single query parameter. To avoid ignoring value, we should set it to 0, i.e. , unbounded. ngx.req.get_uri_args() parameters e.g. ?foo=one&foo=two i.e. Since every plugin designer must remember it, we can add the result to the context for other plugins down the chain. The updated code looks like this: local core = require("apisix.core") function _M.get_uri_args(ctx) if not ctx then ctx = ngx.ctx.api_ctx end if not ctx.req_uri_args then local args, _ = ngx.req.get_uri_args(0) ctx.req_uri_args = args end return ctx.req_uri_args end function _M.access(_, ctx) for k, v in pairs(ctx.req_uri_args) do core.log.warn('Key-Value pair: ', k, '=', v) end end local core = require("apisix.core") function _M.get_uri_args(ctx) if not ctx then ctx = ngx.ctx.api_ctx end if not ctx.req_uri_args then local args, _ = ngx.req.get_uri_args(0) ctx.req_uri_args = args end return ctx.req_uri_args end function _M.access(_, ctx) for k, v in pairs(ctx.req_uri_args) do core.log.warn('Key-Value pair: ', k, '=', v) end end Request headers Request headers are another way to pass parameters. While they generally only contain simple values, you can also use them to send structured values, e.g. , JSON. Depending on your requirement, APISIX can list all request headers or a specific one. Here, I get all of them: e.g. local core = require("apisix.core") function _M.access(_, _) local headers = core.request.headers() for k, v in pairs(headers) do core.log.warn('Key-Value pair: ', k, '=', v) end end local core = require("apisix.core") function _M.access(_, _) local headers = core.request.headers() for k, v in pairs(headers) do core.log.warn('Key-Value pair: ', k, '=', v) end end We test with a simple request: curl -H 'foo: 1' -H 'bar: two' localhost:9080/headers curl -H 'foo: 1' -H 'bar: two' localhost:9080/headers And we got more than we expected because curl added default headers: Key-Value pair: user-agent=curl/8.4.0 Key-Value pair: bar=two Key-Value pair: foo=1 Key-Value pair: host=localhost:9080 Key-Value pair: accept=*/* Key-Value pair: user-agent=curl/8.4.0 Key-Value pair: bar=two Key-Value pair: foo=1 Key-Value pair: host=localhost:9080 Key-Value pair: accept=*/* Request body Setting a request body is the usual way to send structured data, e.g , JSON . Nginx offers a simple API to collect such data. e.g JSON local core = require("apisix.core") function _M.access(_, _) local args = core.request.get_post_args() --1 local body = next(args, nil) --2 core.log.warn('Body: ', body) end local core = require("apisix.core") function _M.access(_, _) local args = core.request.get_post_args() --1 local body = next(args, nil) --2 core.log.warn('Body: ', body) end Access the body as a regular Lua table A table is necessary in case of multipart payloads, e.g., file uploads. Here, we assume there's a single arg, the content body. Access the body as a regular Lua table A table is necessary in case of multipart payloads, e.g. , file uploads. Here, we assume there's a single arg, the content body. e.g. It's time to test: curl localhost:9080/body -X POST -d '{ "foo": 1, "bar": { "baz": "two" } }' curl localhost:9080/body -X POST -d '{ "foo": 1, "bar": { "baz": "two" } }' The result is as expected: Body: { "foo": 1, "bar": { "baz": "two" } } Body: { "foo": 1, "bar": { "baz": "two" } } Cookies Last but not least, we can send parameters via cookies. The difference with previous alternatives is that cookies persist on the client side, and the browser sends them with each request. On the Lua side, we need to know the cookie name instead of listing all query parameters or headers. local core = require("apisix.core") function _M.access(_, ctx) local foo = ctx.var.cookie_foo --1 core.log.warn('Cookie value: ', foo) end local core = require("apisix.core") function _M.access(_, ctx) local foo = ctx.var.cookie_foo --1 core.log.warn('Cookie value: ', foo) end The cookie is named foo and is case-insensitive The cookie is named foo and is case-insensitive foo Let's test: curl --cookie "foo=Bar" localhost:9080/cookies curl --cookie "foo=Bar" localhost:9080/cookies The result is correct: Cookie value: Bar Cookie value: Bar Summary In this post, we listed five alternatives to pass parameters server-side and explained how to access them on Apache APISIX. Here's the API summary: Alternative Source API Path variable APISIX Router Use the radixtree_uri_with_parameter router Query parameter Nginx ngx.req.get_uri_args(0) Request header APISIX core lib core.request.headers() Request body APISIX core lib core.request.get_post_args() Cookie Method context parameter ctx.var.cookie_<name> Alternative Source API Path variable APISIX Router Use the radixtree_uri_with_parameter router Query parameter Nginx ngx.req.get_uri_args(0) Request header APISIX core lib core.request.headers() Request body APISIX core lib core.request.get_post_args() Cookie Method context parameter ctx.var.cookie_<name> Alternative Source API Alternative Alternative Source Source API API Path variable APISIX Router Use the radixtree_uri_with_parameter router Path variable Path variable APISIX Router APISIX Router Use the radixtree_uri_with_parameter router Use the radixtree_uri_with_parameter router radixtree_uri_with_parameter Query parameter Nginx ngx.req.get_uri_args(0) Query parameter Query parameter Nginx Nginx ngx.req.get_uri_args(0) ngx.req.get_uri_args(0) ngx.req.get_uri_args(0) Request header APISIX core lib core.request.headers() Request header Request header APISIX core lib APISIX core lib core.request.headers() core.request.headers() core.request.headers() Request body APISIX core lib core.request.get_post_args() Request body Request body APISIX core lib APISIX core lib core.request.get_post_args() core.request.get_post_args() core.request.get_post_args() Cookie Method context parameter ctx.var.cookie_<name> Cookie Cookie Method context parameter Method context parameter ctx.var.cookie_<name> ctx.var.cookie_<name> ctx.var.cookie_<name> Thanks a lot to Zeping Bai for his review and explanations. Zeping Bai The complete source code for this post can be found on GitHub . GitHub To go further: To go further: 6 Ways To Pass Parameters to Spring REST API How to Build an Apache APISIX Plugin From 0 to 1? 6 Ways To Pass Parameters to Spring REST API 6 Ways To Pass Parameters to Spring REST API How to Build an Apache APISIX Plugin From 0 to 1? How to Build an Apache APISIX Plugin From 0 to 1?