Skip to content

feat(server): 增加 /p 及 /ap 代理接口的单 IP 并发数限制#2375

Open
Roberta001 wants to merge 2 commits intoOpenListTeam:mainfrom
Roberta001:main
Open

feat(server): 增加 /p 及 /ap 代理接口的单 IP 并发数限制#2375
Roberta001 wants to merge 2 commits intoOpenListTeam:mainfrom
Roberta001:main

Conversation

@Roberta001
Copy link
Copy Markdown

Description / 描述

在/@manage/settings/traffic页下增加了一个配置项proxy_max_concurrent_requests_per_ip,用于控制单个IP请求/p//ap/的并发数

Motivation and Context / 背景

解决启用web代理时被多线程下载器刷爆的场景

How Has This Been Tested? / 测试

将Proxy max concurrent requests per ip配置为-1(无限制)或0(完全禁用/p//ap/接口)以外的数,即可限制并发

Checklist / 检查清单

  • I have read the CONTRIBUTING document.
    我已阅读 CONTRIBUTING 文档。
  • I have formatted my code with go fmt or prettier.
    我已使用 go fmtprettier 格式化提交的代码。
  • I have added appropriate labels to this PR (or mentioned needed labels in the description if lacking permissions).
    我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
  • [x I have requested review from relevant code authors using the "Request review" feature when applicable.
    我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
  • I have updated the repository accordingly (If it’s needed).
    我已相应更新了相关仓库(若适用)。

@Roberta001 Roberta001 changed the title 增加了/p/和/ap/代理接口的并发数控制 feat(server): 增加 /p 及 /ap 代理接口的单 IP 并发数限制 Apr 18, 2026
Copy link
Copy Markdown

@utafrali utafrali left a comment

Choose a reason for hiding this comment

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

The core logic is sound: the mutex-protected per-IP counter, deferred decrement, and sentinel values (-1 for unlimited, 0 for disabled) are all correct. The two substantive concerns are IP spoofability via unvalidated proxy headers and the undocumented per-process scoping of the counter in multi-instance setups.

"message": "Proxy is disabled",
"data": nil,
})
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

c.ClientIP() resolves the client address from X-Forwarded-For / X-Real-IP headers, which a client can freely forge if Gin's trusted proxy list hasn't been configured. An attacker could rotate spoofed IPs in that header on every request and bypass the concurrency limit entirely. Either add a code comment (and a note to the setting description) that engine.SetTrustedProxies(...) must be configured for this limit to have any teeth, or expose a separate admin option to use c.Request.RemoteAddr for strict enforcement (accepting that it breaks deployments behind CDNs/reverse-proxies).

"github.com/OpenListTeam/OpenList/v4/internal/conf"
"github.com/OpenListTeam/OpenList/v4/internal/setting"
"github.com/gin-gonic/gin"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The counters are stored in package-level variables, so in a horizontally-scaled deployment each process maintains its own independent counts. A single IP effectively gets configured_limit * N concurrent requests across N instances. This is not necessarily a bug, but it will surprise operators who set a tight limit and still see it exceeded. Add a comment here (or in the setting's description string in bootstrap/data/setting.go) calling this out, e.g. // NOTE: counts are per-process; in multi-instance deployments the effective limit is limit*N.

Comment thread server/router.go Outdated
signCheck := middlewares.Down(sign.Verify)
g.GET("/d/*path", middlewares.PathParse, signCheck, downloadLimiter, handles.Down)
g.GET("/p/*path", middlewares.PathParse, signCheck, downloadLimiter, handles.Proxy)
g.GET("/p/*path", middlewares.PathParse, signCheck, downloadLimiter, proxyConcurrencyLimiter, handles.Proxy)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For the GET proxy routes, proxyConcurrencyLimiter is placed after downloadLimiter. Requests that are already over the IP limit will be rejected with 429, but only after downloadLimiter has already wrapped the response writer. Swapping the two so the order becomes signCheck → proxyConcurrencyLimiter → downloadLimiter → handler short-circuits rejected requests one step earlier with no change in correctness or security.

ip := c.ClientIP()

proxyIPCountsMu.Lock()
count := proxyIPCounts[ip]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The 429 response body is hand-rolled as gin.H{"code": ..., "message": ..., "data": nil}. Double-check that this matches the project's shared error-response helper (if one exists, e.g. common.ErrorResp or similar). Using a different shape here would make client-side error handling inconsistent with every other endpoint in the API.

@Roberta001 Roberta001 requested a review from utafrali April 18, 2026 10:10
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.

2 participants