一套跑在 Debian 13 (trixie) 上的软路由管理系统,提供 43 个功能页面的 Web 管理面板:WAN 接入、PPPoE 多拨、DHCP/DNS、IPv6、防火墙、QoS 限速、 访问控制、DDNS、文件共享、Docker 管理等。 核心设计原则:不重新实现任何协议 PPPoE 交给 ppp,DHCP 交给 dnsmasq,IPv6 RA 交给 radvd,NAT 交给 nftables。 Web 面板只做三件事:渲染原生配置文件 → 语法预检 → 原子写入并 reload。 面板删掉后,系统仍是一台配置完好的 Debian 路由器。 安全默认值 · 安装后不接管网络:不改默认路由、不开 IP 转发、不启动 DHCP 服务 · 所有改网络的动作都需要用户在面板上亲手点「应用」 · 避开 5900 端口,不触碰 VNC / X 会话 · 配置写入前先做语法预检,失败则回滚 架构 · backend/ Python 3 标准库,零第三方依赖(9 个模块) · web/ 原生 JS/CSS,无构建步骤 · 常驻 helper 通过 Unix socket 提供 75 个动作,socket 不可用时回退 subprocess · 鉴权走 X-Token 请求头 打包 · packaging/build-deb.sh → drouter_1.0.0_all.deb(含 6 个 systemd 单元) · scripts/make-offline-deps.sh → 236 个依赖 .deb + 本地仓库索引 · packaging/build-offline-bundle.sh → 自包含离线包,目标机零外网可装 · packaging/build-docker.sh → 容器镜像(体验用) 测试 40 套单元测试 + 3 个前端检查脚本(菜单映射 / 离线渲染 / 手机适配), 全部挂在 devtools/sync.sh 的预检链里。 许可:MIT
54 lines
2.3 KiB
Python
54 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""响应压缩与协商缓存契约(#7)。
|
|
|
|
要挡住两类回归:
|
|
1. 压缩被悄悄去掉 —— 局域网里感觉不出来,一走外网(cloudflared / 远程)
|
|
就是 300KB 对 30KB 的差别;
|
|
2. 压缩后忘了发 Vary: Accept-Encoding —— 中间缓存会把 gzip 版喂给
|
|
不支持 gzip 的客户端,表现是「某些浏览器打开面板全是乱码」。
|
|
"""
|
|
import ast
|
|
import io
|
|
import os
|
|
import sys
|
|
|
|
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
|
|
fails = 0
|
|
|
|
|
|
def chk(label, cond, extra=''):
|
|
global fails
|
|
if not cond:
|
|
fails += 1
|
|
print('[%s] %s %s' % ('OK' if cond else 'FAIL', label, extra))
|
|
|
|
|
|
WEB = io.open(os.path.join(ROOT, 'backend/drouter-web.py'),
|
|
encoding='utf-8').read()
|
|
TREE = ast.parse(WEB)
|
|
|
|
chk('定义了压缩下限 GZIP_MIN', 'GZIP_MIN = ' in WEB)
|
|
chk('有 Accept-Encoding 判断', 'def _want_gzip' in WEB)
|
|
chk('压缩结果带内存缓存(不每次重压)', 'def _gzip_cached' in WEB and '_gz_cache' in WEB)
|
|
chk('缓存按 mtime+size 失效(部署新版后自动重压)',
|
|
"int(st.st_mtime), st.st_size" in WEB)
|
|
chk('压缩后必须发 Content-Encoding', "send_header('Content-Encoding', enc)" in WEB)
|
|
chk('压缩后必须发 Vary: Accept-Encoding(否则代理会喂错客户端)',
|
|
WEB.count("send_header('Vary', 'Accept-Encoding')") >= 2,
|
|
'实际 %d 处' % WEB.count("send_header('Vary', 'Accept-Encoding')"))
|
|
chk('压缩比不划算时不压(原始数据更大就直出)', 'if len(z) >= len(data)' in WEB)
|
|
chk('静态资源支持 ETag 协商缓存', "send_header('ETag', etag)" in WEB)
|
|
chk('ETag 命中回 304 且不回传响应体',
|
|
"If-None-Match" in WEB and 'send_response(304)' in WEB)
|
|
chk('JSON 接口也走压缩', '_gzip_cached(None, body)' in WEB)
|
|
chk('静态资源也走压缩', '_gzip_cached(full, data)' in WEB)
|
|
chk('HEAD 请求不回响应体', "if self.command != 'HEAD'" in WEB)
|
|
|
|
# 小响应不能被压:省下的字节还不如压缩开销
|
|
src = io.open(os.path.join(ROOT, 'backend/drouter-web.py'), encoding='utf-8').read()
|
|
chk('小响应不压缩(有体积下限判断)', 'len(data) >= GZIP_MIN' in src)
|
|
|
|
print('\n结果: %s' % ('全部通过' if fails == 0 else '%d 项失败' % fails))
|
|
sys.exit(1 if fails else 0)
|