Skip to content

docs: add stream proxy example for limit-conn plugin - #13936

Open
bhuvan-somisetty wants to merge 4 commits into
apache:masterfrom
bhuvan-somisetty:docs/limit-conn-stream-proxy-example
Open

bhuvan-somisetty wants to merge 4 commits into
apache:masterfrom
bhuvan-somisetty:docs/limit-conn-stream-proxy-example

Conversation

@bhuvan-somisetty

@bhuvan-somisetty bhuvan-somisetty commented Sep 14, 2026

Copy link
Copy Markdown

Description

The limit-conn plugin doc has no example showing how to use it in stream (L4/TCP) proxy mode, even though the plugin has supported stream proxy since it was written (apisix/stream/plugins/limit-conn.lua).

Root cause: the doc gap, plus a prior attempt (#13052) that tried to close this issue documented an incorrect restriction: only remote_addr/server_addr can be used as key. That's not accurate: in apisix/plugins/limit-conn/init.lua, when key_type is var (the default), the key is resolved via ctx.var[conf_key], i.e. plain NGINX stream-variable lookup. Any variable available in the stream context works (e.g. server_port), not just remote_addr/server_addr.

Solution:

  • Added an "Apply Rate Limiting in Stream Proxy" section to docs/en/latest/plugins/limit-conn.md, following the doc's existing Admin API / ADC / Ingress Controller tabbed example format, with a full, copy-pasteable stream_routes example (including upstream).
  • Documented the key resolution behavior accurately instead of the narrower (incorrect) restriction from the earlier attempt.
  • Added a regression test in t/stream-plugin/limit-conn.t (key: "server_port" with default key_type: var) so the corrected documentation claim is backed by an actual test, guarding against future regressions in the key-resolution logic.

Testing: ran the modified t/stream-plugin/limit-conn.t (all 12 test blocks, including the 2 new ones) locally against a real OpenResty + etcd environment; all pass. Also ran markdownlint-cli against the modified doc file with the repo's .markdownlint.yml config; no findings.

Which issue(s) this PR fixes:

Fixes #4933

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible

The limit-conn plugin doc had no example for stream proxy usage,
even though the plugin has supported it since it was written.

Add a "Apply Rate Limiting in Stream Proxy" section with a full,
copy-pasteable stream_routes example (Admin API, ADC, and Ingress
Controller), and correct a common misconception: when key_type is
var (the default), key is resolved as an NGINX stream variable via
ctx.var[key], so it is not limited to remote_addr/server_addr. Any
stream variable (e.g. server_port) can be used.

Add a regression test in t/stream-plugin/limit-conn.t that exercises
key_type "var" with a variable other than remote_addr/server_addr,
guarding the corrected documentation claim against future regressions
in the key resolution logic.

Resolves apache#4933
@bhuvan-somisetty
bhuvan-somisetty force-pushed the docs/limit-conn-stream-proxy-example branch from 788e2fb to 451911e Compare September 14, 2026 04:39

@kayx23 kayx23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a few issues that affect the accuracy and reproducibility of the new stream example.

Comment thread docs/en/latest/plugins/limit-conn.md
Comment thread docs/en/latest/plugins/limit-conn.md Outdated
Comment thread docs/en/latest/plugins/limit-conn.md Outdated

:::note

When `key_type` is `var` (the default), `key` is resolved as an [NGINX stream module variable](https://nginx.org/en/docs/stream/ngx_stream_core_module.html). It is not limited to `remote_addr` or `server_addr`: any variable available in the stream context, such as `server_port`, can be used.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream plugin has a distinct, smaller schema: conn, burst, default_conn_delay, only_use_default_delay, key, and key_type; conn and burst are integers. The page’s existing Attributes table describes the HTTP plugin and includes HTTP-only fields such as Redis policies, rules, degradation, and custom HTTP rejection fields. Adding a stream example without separating or explicitly scoping those attributes can lead readers to apply unsupported fields to stream routes. Could this PR add a separate stream attribute table or clearly qualify the existing table?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream attributes are now separated, but the only_use_default_delay description still reflects HTTP behavior. The stream subsystem does not calculate request latency, so this setting does not change stream behavior. Also, connections at conn + burst are accepted; use “at or below,” not “below.”

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One factual detail remains: “excess connections are always delayed by default_conn_delay” is not correct. resty.limit.conn calculates the delay as default_conn_delay × floor((current connections - 1) / conn), so it can be a multiple of default_conn_delay. Please keep the first two sentences and replace the final clause with that calculation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, fixed the formula

Comment thread t/stream-plugin/limit-conn.t
- make the example runnable by documenting the stream_proxy listener
  prerequisite and providing a real TCP echo upstream via socat
- scope the attribute table to the stream plugin's actual schema
  instead of reusing the HTTP one
- replace the outdated Gateway API limitation with a TCPRoute +
  L4RoutePolicy example (supported since Ingress Controller 2.2.0),
  and fix the undefined external Service in the CRD example
- use conn=1/burst=0 with a deterministic connection test that shows
  the actual rejection instead of a racy nc fan-out
- strengthen the stream test to assert the resolved limit key and
  rule out a silent fallback to remote_addr
@bhuvan-somisetty

Copy link
Copy Markdown
Author

Thanks for the detailed review! Fixed all of these - added the missing listener/upstream prerequisites, scoped the attribute table to the stream schema, swapped the outdated Gateway API note for a TCPRoute + L4RoutePolicy example, made the rejection test deterministic with conn=1/burst=0, and tightened the stream test to actually assert the resolved key. PTAL

@kayx23 kayx23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three details remain in the revised documentation.

Comment thread docs/en/latest/plugins/limit-conn.md Outdated
Comment thread docs/en/latest/plugins/limit-conn.md Outdated
Comment thread docs/en/latest/plugins/limit-conn.md Outdated
…ample

- note the Docker networking caveat for the local socat upstream
- correct the only_use_default_delay description for stream Routes
  and fix the conn/burst boundary wording
- use gateway.networking.k8s.io/v1 for TCPRoute, matching what
  Ingress Controller 2.2.0's Gateway API 1.6.0 actually serves
- make the background connection in the rejection test deterministic
  with sleep + $!, and rework the closing steps so they don't
  contradict the kill already in the script
- drop the dangling ❶❷ markers that didn't point at anything in the
  examples
@bhuvan-somisetty

Copy link
Copy Markdown
Author

Good catches, all fixed - docker networking note added, only_use_default_delay description corrected for stream, TCPRoute bumped to v1, the nc test now uses sleep+$! and doesn't contradict itself, and dropped the dangling markers. PTAL

The description said excess connections are always delayed by a flat
default_conn_delay, but resty.limit.conn scales it by how far over
conn the current connection count is.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: limit-conn plugin in stream proxy

2 participants