Repository navigation
修复机房中继的错误响应、镜像链路与开放代理问题 - #2
Merged
Merged
Conversation
Error responses now return real status codes, pip and npm package links stay on the teacher machine, and the proxy refuses private targets and disallowed ports. Cached replies are no longer shared across users, CONNECT tunnels use blocking bidirectional copies, and offline tests cover the classroom flows. Co-authored-by: LoveLH <LoveLH@users.noreply.github.com>
LoveLH
marked this pull request as ready for review
October 20, 2026 11:20
Windows fsync rejects a read-only handle, so cache commits never landed and later fetches stayed MISS. Closing a full socket also aborted the 503, and getaddrinfo does not parse inet_aton literals. Help text now uses UTF-8 so --help survives a cp1252 console. Co-authored-by: LoveLH <LoveLH@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.
针对 2026-10-20 审计(main@4f55a8f,并对照未合并分支
agent/forge/c9caff7f73dd@0014acd)做了一轮修复。运行方式仍是单文件、仅标准库、python classrelay.py。认证默认关闭;若开启,使用http://用户:密码@教师机:端口,不再使用会弄坏 pip 的?token=。测试在本地假上游上跑,不访问外网:
python -m pytest。其中包含真实的pip install和npm install。GitHub Actions 在 Linux / Windows × Python 3.9 / 3.13 上跑同一套测试。Windows CI(87d8d4c 失败,本提交修复)
87d8d4c上 Ubuntu 的 Python 3.9 / 3.13 通过。windows-latest的 3.9 和 3.13 在两次运行(39414192779、39414148125)里都是 11 failed / 37 passed / 1 skipped。失败的测试:test_cache_ttl_size_and_partial_filetest_chunked_upstream_and_cachetest_singleflight_and_no_partial_cachetest_rewritten_index_is_not_shared_across_addressestest_pip_install_through_relaytest_npm_install_through_relaytest_docker_registry_flowtest_connection_limittest_decimal_and_odd_literals_resolve_to_loopbacktest_connect_policy_and_malformed_targetstest_cli_help_stays_compatible原因:
commit用只读方式重新打开临时文件再os.fsync。Windows 对只读句柄返回[Errno 9] Bad file descriptor,提交失败,盘上没有缓存。上面前 7 个测试因此一直是 MISS;singleflight 的跟随者在两次领头都没写入后得到 502。现改为以读写方式打开再 fsync。close()。Windows 对仍有未读数据的套接字发 RST,客户端是ConnectionAbortedError(WinError 10053),读不到 503。现先读完请求头,写出 503,shutdown(SHUT_WR)后再关闭。2130706433、0x7f000001、127.1在 glibc 上是127.0.0.1,Windows 的getaddrinfo不认,DNS 失败变成 502 而不是 403。现按 inet_aton 规则先规范成点分地址,再做内网判断。--help在 cp1252 控制台崩溃。 帮助和横幅是中文,UnicodeEncodeError导致退出码 1。启动时把 stdout/stderr 设为 UTF-8(无法编码的字符替换掉)。没有为了让 Windows 变绿而跳过这些测试。
唯一仍跳过的是
test_bash_setup_persists_and_restore_removes_it:它要真的执行 bash,并往HOME的.bashrc写入标记。Windows 学生机用的是 PowerShell 脚本,不跑这段 bash。脚本正文(不含strict-ssl false、含还原命令)在所有平台都有断言。本机没有 bash 时同样跳过执行,只保留正文检查。每个 job 两条注解:
actions/checkout@v4、actions/setup-python@v5、actions/setup-node@v4运行在已弃用的 Node.js 20。工作流已改到这三个 action 的 v6(Node 24)。setup-node关闭了自动缓存,本仓库没有package.json。ubuntu-latest将切到 Ubuntu 26。这是托管镜像的说明,不是失败。Windows job 的另一条是Process completed with exit code 1,来自上面的测试失败。P0
log_message改为format % args,不再固定取args[0..2]。send_error能正常返回 404 / 501 / 502,并带响应体。请求处理外包了一层,未发出响应头时的异常会落到 500。../../packages/...以及files.pythonhosted.org等包地址,会按上游页面解析后改写成http://<本次连接地址>/pypi/packages/...(保留#sha256=)。/pypi/<name>/、/pypi/simple/<name>/、/pypi/packages/...分别落到 simple 根和文件根。测试里pip install --index-url http://127.0.0.1/pypi/ classrelay-local-demo能装上本地造的 wheel。--allow-host再限制域名。--allow-private默认关闭。可选--auth user:password(HTTP Basic / Proxy-Authorization),覆盖 CONNECT、正向代理和镜像路由,比较使用hmac.compare_digest。默认仍不强制认证,方便教室零配置。公网 80/443 仍然放行,否则 PlatformIO 和 git clone 无法使用;需要收紧时用--allow-host。Cache-Control: private/no-store/no-cache、Vary: Cookie/*时不缓存。落盘头里去掉 Set-Cookie。转发到上游的 Authorization(例如 Docker Bearer)会计入缓存键,避免不同身份共用一份响应。P1
sendall。200 已经发出后不再补 502。空闲超时默认 600 秒(--tunnel-idle,0 为不限制)。测试在对端暂停读取时传完 2MB。WWW-Authenticate的 realm 封成带 HMAC 的/_classrelay/docker-token/...,token 请求经教师机代拉,且不能拿这个入口去任意网址。manifest 按 Accept 分缓存键。README 仍标为实验性:没有 push、跨仓库 blob mount、Range 断点续传,也没有对真实 Docker daemon 做联调。开启--auth时,Docker 自己的 Bearer 和本代理的 Basic 共用Authorization,这一组合不承诺可用。dist.tarball改写成教师机上的绝对http://<本次连接地址>/npm/...。测试包含协议级下载,以及本机npm install(不设置strict-ssl false)。os.replace再写 meta;读者用 meta 里的大小核对,对不上就当未命中。同一缓存键 singleflight,只有一个上游请求。容量统计在锁内按差额更新,不再每次全盘扫描后重复累加。索引和包文件按「学生连到的地址」分开缓存,避免一块网卡的绝对链接被另一块网卡复用。npm config set strict-ssl false。bash 把代理写进~/.bashrc和~/.zshrc的标记块,并说明curl | bash改不了当前 shell。unbash / unwindows 会npm config delete strict-ssl并删掉标记块。控制台展示脚本 SHA256,并提示先下载核对。没有做非对称签名(没有密钥分发),用 SHA256 加明文 HTTP 警告代替。connection.getsockname()。启动时探测到的其他网卡只作参考,且gethostbyname_ex有超时,不再每个请求都查。P2
host:abc、缺端口、IPv6[::1]:443都在 try 里解析,返回 400 或 403,而不是空连接。--max-cache-mb,0 表示不落盘),按最旧淘汰。Proxy-Connection、Proxy-Authorization、Transfer-Encoding等逐跳头。镜像请求不把 Cookie 和本代理的 Basic 认证转给清华 / npmmirror。客户端查询串里的token等参数会丢掉,不会拼进上游 URL,也不会因为 token 不同而拆成多份缓存。logging,StreamHandler每条都 flush。URL 里的user:pass@会打成***:***@。start.sh/start.bat使用python -u。test_relay.py已换成tests/test_relay.py。假上游、临时目录、pytest。工作流见.github/workflows/test.yml。--max-connections默认 64,超出直接 503。只实现 GET / HEAD / CONNECT;其他方法返回 501,不读取请求体。索引类响应有 16 MB 上限。没有再做全局带宽配额。未合并分支 0014acd 里采纳和没有继承的部分
采纳了:安全的
log_message、PyPI href 重写和/pypi/packages/路由、npm tarball 重写、缓存 TTL / 容量、IPv6 CONNECT 解析、避免重复 Content-Length。没有继承的问题:
Proxy-Authorization。?token=把 pip 索引拼坏,改写链接也不带 token:改为http://user:pass@host,并用带认证的pip install验证。token/password等参数。/pypi/simple/...把子路径当成上游基址:改为拼到 simple 根上。==比较、token 进日志:改为摘要后compare_digest,日志打码。cached_bytes先加再重算:改为内存索引和差额更新。仍刻意留着的行为
学生通过教师机访问公网 443(以及明文 80)是教学场景本身的能力,默认不按域名封死。Docker 只保证测试里覆盖的 Registry v2 pull 流程,README 里写明实验性。