feat(plugin): add graphql-limit-count plugin#13372
Open
AlinsRan wants to merge 11 commits into
Open
Conversation
Add a new plugin that limits GraphQL request rates based on query AST depth using a fixed window algorithm. The plugin reuses the limit-count infrastructure for counter management and supports local, Redis, and Redis cluster policies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Apache license header to t/plugin/graphql-limit-count.t - Add plugin and shared dict entries to conf/config.yaml.example - Rewrite EN/ZH docs to follow open-source format with Tabs and realistic curl examples showing expected response headers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add canonical link in <head> section - Use correct attribute table format (Name/Type/Required/Default/Valid values/Description) - Replace Tabs component with plain curl examples - Follow the same structure as traffic-label and exit-transformer docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new graphql-limit-count traffic control plugin that reuses limit-count infrastructure to charge requests by parsed GraphQL query depth.
Changes:
- Adds the new plugin implementation and registers it in default plugin/admin configuration.
- Adds NGINX shared dict configuration for the plugin.
- Adds tests, documentation, and sidebar entries for English and Chinese docs.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
apisix/plugins/graphql-limit-count.lua |
Implements GraphQL parsing, depth calculation, and limit-count integration. |
apisix/cli/config.lua |
Adds plugin and shared dict defaults. |
apisix/cli/ngx_tpl.lua |
Adds conditional shared dict declarations. |
conf/config.yaml.example |
Registers plugin and shared dicts in example config. |
t/plugin/graphql-limit-count.t |
Adds plugin functional tests. |
t/admin/plugins.t |
Adds plugin to admin plugin list expectations. |
docs/en/latest/plugins/graphql-limit-count.md |
Adds English plugin documentation. |
docs/zh/latest/plugins/graphql-limit-count.md |
Adds Chinese plugin documentation. |
docs/en/latest/config.json |
Adds English sidebar entry. |
docs/zh/latest/config.json |
Adds Chinese sidebar entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- fix max_query_depth: use proper max nesting depth traversal instead of counting total selection set nodes - fix content-type matching: use has_prefix instead of exact equality to handle types with charset parameters (e.g. application/json; charset=utf-8) - remove incorrect 'query' keyword check for application/graphql requests: shorthand queries and mutations are valid without 'query' keyword - fix max_size: read graphql.max_size from local config instead of passing nil (no limit) to get_body - fix typo: cant't -> can't in error message - return specific error message from check_graphql_request instead of generic 'no query' - fix ngx_tpl.lua: declare plugin-limit-count-redis-cluster-slot-lock dict when graphql-limit-count is enabled without limit-count - fix tests: add no_shuffle(), redis flush in init_worker, rejection path test, application/graphql content-type success test, and update response_body assertions to match specific error messages
The shared dicts (plugin-graphql-limit-count, plugin-graphql-limit-count-reset-header, and plugin-limit-count-redis-cluster-slot-lock) are already generated by ngx_tpl.lua when graphql-limit-count is enabled. Declaring them again in http_config caused nginx to fail with 'already defined' error.
The test framework t/APISIX.pm maintains a hardcoded list of shared dicts for the test nginx environment. New plugin dicts must be added here, otherwise ngx.shared[dict] returns nil at request time. Add plugin-graphql-limit-count and plugin-graphql-limit-count-reset-header to the list, following the same pattern as plugin-ai-rate-limiting.
Separate test blocks cause nginx to restart between them, resetting the shared dict counter. Merge the two rate limit checks (200 + 503) into a single pipelined_requests block so both requests share the same nginx instance and counter state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Add a new
graphql-limit-countplugin that limits GraphQL request rates based on the depth of the query Abstract Syntax Tree (AST) within a given time window.Why is this change needed?
Standard rate limiting counts every request equally, regardless of its computational cost. This is insufficient for GraphQL endpoints where the cost of execution scales with query complexity. A single deeply nested query can be far more expensive than dozens of simple ones:
Without depth-aware limiting, a client could exhaust server resources with a single request while staying well within a request-count limit.
The
graphql-limit-countplugin solves this by using the GraphQL query AST depth as the cost for each request, powered by the existinglimit-countinfrastructure. This lets operators configure a single depth budget per time window and have it consumed proportionally by query complexity.How does it work?
POSTrequests withapplication/json(containing aqueryfield) orapplication/graphqlcontent type.graphql.parse()to build the AST and recursively counts theselectionsdepth.limit_count.rate_limit(conf, ctx, plugin_name, depth), passing the depth as the cost.count, the request is rejected with the configuredrejected_code.Example
Configure a route to allow a cumulative query depth of 10 per minute per client IP:
A depth-4 query consumes 4 out of 10. After the budget is exhausted:
Changes
apisix/plugins/graphql-limit-count.luat/plugin/graphql-limit-count.tapisix/cli/config.luaplugin-graphql-limit-countandplugin-graphql-limit-count-reset-headershared dict defaultsapisix/cli/ngx_tpl.luaconf/config.yaml.exampledocs/en/latest/plugins/graphql-limit-count.mddocs/zh/latest/plugins/graphql-limit-count.mddocs/en/latest/config.jsondocs/zh/latest/config.jsont/admin/plugins.t