ipclick 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ipclick might be problematic. Click here for more details.
- ipclick-0.3.0/README.md → ipclick-0.4.0/PKG-INFO +150 -16
- ipclick-0.3.0/PKG-INFO → ipclick-0.4.0/README.md +104 -62
- {ipclick-0.3.0 → ipclick-0.4.0}/pyproject.toml +13 -9
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/__init__.py +1 -1
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/browser_engines.py +140 -43
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/drission_adapter.py +38 -14
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/niquests_adapter.py +62 -19
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/registry.py +48 -15
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cli/main.py +17 -2
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/forwarder.py +137 -2
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/node.py +9 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/pool.py +52 -0
- ipclick-0.4.0/src/ipclick/cluster/probe.py +242 -0
- ipclick-0.4.0/src/ipclick/components.py +239 -0
- ipclick-0.4.0/src/ipclick/config_loader/placeholders.py +81 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/configs/default_config.toml +16 -2
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task.proto +29 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task_pb2.py +11 -7
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task_pb2.pyi +22 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task_pb2_grpc.py +46 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/secrets.py +34 -6
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/server.py +97 -11
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/services/task_service.py +122 -21
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/trace.py +75 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/log_util.py +3 -4
- ipclick-0.4.0/src/ipclick/utils/module_probe.py +105 -0
- ipclick-0.4.0/src/ipclick/utils/path_util.py +89 -0
- ipclick-0.4.0/src/ipclick/web/assets.py +657 -0
- ipclick-0.4.0/src/ipclick/web/curl_parser.py +274 -0
- ipclick-0.4.0/src/ipclick/web/installer.py +469 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/pages.py +327 -39
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/server.py +98 -18
- ipclick-0.4.0/src/ipclick/web/templates.py +1354 -0
- ipclick-0.3.0/src/ipclick/utils/path_util.py +0 -37
- ipclick-0.3.0/src/ipclick/web/templates.py +0 -966
- {ipclick-0.3.0 → ipclick-0.4.0}/LICENSE +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/__main__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/base.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/browser_adapter.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/browser_settings.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/curl_cffi_adapter.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/settings.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/aio.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/auth.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cli/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/balancer.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/client.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/discovery.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/status_page.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/tokens.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/compression.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/dotenv.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/loader.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/writer.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/configs/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/models.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/generate.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/response.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/exceptions.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/factory.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/health.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/limiter.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/py.typed +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/resume.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/sdk.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/services/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/tls.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/config_util.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/secure_util.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/url_util.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/__init__.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/auth.py +0 -0
- {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/editable.py +0 -0
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ipclick
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: IPClick Tool
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: 元气码农少女酱
|
|
8
|
+
Author-email: 1276162429@qq.com
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Provides-Extra: camoufox
|
|
17
|
+
Provides-Extra: drissionpage
|
|
18
|
+
Provides-Extra: linux
|
|
19
|
+
Provides-Extra: niquests
|
|
20
|
+
Provides-Extra: patchright
|
|
21
|
+
Provides-Extra: playwright
|
|
22
|
+
Provides-Extra: win
|
|
23
|
+
Requires-Dist: camoufox (>=0.5.4,<1.0.0) ; extra == "camoufox"
|
|
24
|
+
Requires-Dist: camoufox (>=0.5.4,<1.0.0) ; extra == "linux"
|
|
25
|
+
Requires-Dist: click (>=8.4.2,<9.0.0)
|
|
26
|
+
Requires-Dist: curl-cffi (>=0.16.0,<0.17.0)
|
|
27
|
+
Requires-Dist: drissionpage (>=4.1.1,<5.0.0) ; extra == "drissionpage"
|
|
28
|
+
Requires-Dist: drissionpage (>=4.1.1,<5.0.0) ; extra == "win"
|
|
29
|
+
Requires-Dist: fake-useragent (>=2.2.0,<3.0.0)
|
|
30
|
+
Requires-Dist: grpcio (>=1.83.0,<2.0.0)
|
|
31
|
+
Requires-Dist: grpcio-health-checking (>=1.83.0,<2.0.0)
|
|
32
|
+
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
|
33
|
+
Requires-Dist: niquests (>=3.21.0,<4.0.0) ; extra == "niquests"
|
|
34
|
+
Requires-Dist: patchright (>=1.61.2,<2.0.0) ; extra == "linux"
|
|
35
|
+
Requires-Dist: patchright (>=1.61.2,<2.0.0) ; extra == "patchright"
|
|
36
|
+
Requires-Dist: playwright (>=1.60.0,<2.0.0) ; extra == "playwright"
|
|
37
|
+
Requires-Dist: protobuf (>=6.33.2,<8.0.0)
|
|
38
|
+
Requires-Dist: python-box[all] (>=7.4.1,<8.0.0)
|
|
39
|
+
Requires-Dist: typing-extensions (>=4.16.0,<5.0.0)
|
|
40
|
+
Requires-Dist: uuid-utils (>=0.17.0,<0.18.0)
|
|
41
|
+
Project-URL: Bug Tracker, https://github.com/yuanqimanong/IPClick/issues
|
|
42
|
+
Project-URL: Homepage, https://github.com/yuanqimanong/IPClick
|
|
43
|
+
Project-URL: Repository, https://github.com/yuanqimanong/IPClick
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
1
46
|
# IPClick
|
|
2
47
|
|
|
3
48
|

|
|
@@ -40,7 +85,9 @@ IPClick 是一个轻量级、高性能的分布式 HTTP 请求代理工具,基
|
|
|
40
85
|
- **链路记录**:每个请求记一条(谁执行的、重试几次、排队多久),内存环形缓冲 + 可选 SQLite,零第三方依赖
|
|
41
86
|
- **SSRF 防护**:服务端对目标 URL 做协议白名单与内网/元数据地址拦截
|
|
42
87
|
- **Web 管理端**:`ipclick run --web` 起一个带登录的界面——总览看板、实时请求流、
|
|
43
|
-
|
|
88
|
+
「试一试」就地发请求看源码(支持粘贴 curl、可点名目标节点)、配置改完写回
|
|
89
|
+
`ipclick.toml`、集群节点增删(保存即生效)与就地测连通性 / 鉴权、可选组件的
|
|
90
|
+
装卸。左中右三栏布局 + 明暗主题切换,前端零构建链路
|
|
44
91
|
- **命令行工具**:提供便捷的 CLI 工具,支持快速启动服务和查看配置
|
|
45
92
|
- **Docker 支持**:多阶段构建、非 root 运行的镜像
|
|
46
93
|
- **完整类型标注**:随包提供 `py.typed`,下游可直接享受类型检查
|
|
@@ -83,6 +130,9 @@ playwright install chromium # playwright
|
|
|
83
130
|
只做第一步的话,`ipclick config-info` 与 Web 端会明确显示 **「包已装,浏览器本体未就绪」**,
|
|
84
131
|
请求会直接报 `FAILED_PRECONDITION` 并告诉你该跑哪条命令。
|
|
85
132
|
|
|
133
|
+
上面这些也可以**直接在 Web 端点按钮完成**(0.4 起):`/components` 页列出全部五个
|
|
134
|
+
可选组件的两级安装状态,就地装 / 卸、下载浏览器本体,装完不用重启进程。
|
|
135
|
+
|
|
86
136
|
> **刻意不让它在请求里自动下载。** camoufox 的 API 默认行为是"缺本体就当场下"
|
|
87
137
|
> (`camoufox_path(download_if_missing=True)`),而那一刻已经在 gRPC 的请求处理线程上:
|
|
88
138
|
> 第一个请求会卡着下 1 GB 然后超时,超时返回后下载还在后台跑,并发的多个首请求
|
|
@@ -142,6 +192,9 @@ ipclick run --config /path/to/ipclick.toml
|
|
|
142
192
|
|
|
143
193
|
# 显示详细日志
|
|
144
194
|
ipclick run --verbose
|
|
195
|
+
|
|
196
|
+
# 同一目录起第二个实例:gRPC 与 Web 两个端口都要岔开
|
|
197
|
+
ipclick run --port 9528 --web-port 9531 -w
|
|
145
198
|
```
|
|
146
199
|
|
|
147
200
|
初始化配置(推荐):
|
|
@@ -220,20 +273,41 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
|
|
|
220
273
|
|
|
221
274
|
或写进 `.env`,或配 `[WEB].username` / `password`。
|
|
222
275
|
|
|
223
|
-
####
|
|
276
|
+
#### 六个页面
|
|
277
|
+
|
|
278
|
+
0.4 布局大改版:**左侧导航 + 主内容 + 右侧状态栏**的 CSS Grid,右上角有
|
|
279
|
+
**跟随系统 / 亮 / 暗** 三态主题切换(记在 `localStorage`,刷新不丢)。
|
|
224
280
|
|
|
225
281
|
| 页面 | 干什么 |
|
|
226
282
|
|---|---|
|
|
227
|
-
| **总览** `/` |
|
|
228
|
-
| **请求流** `/trace` |
|
|
229
|
-
| **试一试** `/test` |
|
|
230
|
-
|
|
|
231
|
-
|
|
|
283
|
+
| **总览** `/` | 吞吐、成功率、在途与峰值、状态码分布、各适配器耗时与流量、集群拓扑、最近请求;右栏常驻服务端 / 安全 / 链路 / 限流 / 组件状态 |
|
|
284
|
+
| **请求流** `/trace` | **实时**看请求打进来。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
|
|
285
|
+
| **试一试** `/test` | 填一个网址(或直接**粘一条 curl**)就地发一次请求,看链路信息与返回的**源码**;可**点名**打到某个节点 |
|
|
286
|
+
| **组件** `/components` | 五个可选 extras 的两级安装状态,可以就地**安装 / 卸载 / 下载浏览器本体** |
|
|
287
|
+
| **配置** `/config` | 白名单内的行为配置可改,**写回 `ipclick.toml`**;可一键**生成凭据** |
|
|
288
|
+
| **节点** `/nodes` | 集群节点的增删改(**保存即生效**,不用重启)+ 就地**测试连接** |
|
|
232
289
|
|
|
233
290
|
「试一试」走的是本进程 `TaskService` 的**同一条**代码路径——SSRF 准入、限流、
|
|
234
291
|
以及开了转发时的分发全都照常生效,请求也会像真实请求一样出现在请求流里。
|
|
235
292
|
另写一条只在页面上成立的路径毫无意义:那验证的就不是线上行为了。
|
|
236
293
|
|
|
294
|
+
**从 curl 导入**:浏览器 DevTools 里对着请求「复制为 cURL」,粘进输入框就能自动
|
|
295
|
+
填好 URL / 方法 / 请求头 / 请求体。认不出的参数会**明确列出来**——静默丢掉一个
|
|
296
|
+
`-F` 比不支持它更糟。
|
|
297
|
+
|
|
298
|
+
**指定目标节点**:开了服务端转发时,「试一试」多一个"目标节点"下拉,选中后跳过
|
|
299
|
+
负载均衡强制打到那一台。验证新加的机器配对没有,不用再反复点、靠轮询碰运气。
|
|
300
|
+
|
|
301
|
+
**测试连接**:`/nodes` 每行一个按钮,只验**连通性**与**集群内部鉴权**,不发业务
|
|
302
|
+
请求。失败时区分"连不上"(查进程和网络)和"鉴权不通过"(核对各节点 `.env` 里的
|
|
303
|
+
`IPCLICK_CLUSTER_SECRET`)——这两种的排查方向完全相反。底层是 0.4 新增的
|
|
304
|
+
`Ping` RPC;对着 0.3 的节点调会收到 `UNIMPLEMENTED`,那恰恰说明鉴权是通的,
|
|
305
|
+
页面会如实这么说,不会误报成鉴权失败。
|
|
306
|
+
|
|
307
|
+
**生成凭据**:`/config` 可以为鉴权令牌、Web 密码、集群共享密钥各生成一个随机值,
|
|
308
|
+
**只显示一次**(服务端不保存、不写进任何文件,取完即弃),并明确区分"本机独有"
|
|
309
|
+
和"必须复制到所有其他节点的 `.env`"——集群共享密钥每台各自生成一个就全对不上了。
|
|
310
|
+
|
|
237
311
|
#### 能改什么、不能改什么
|
|
238
312
|
|
|
239
313
|
- **能改**(写回 toml,保留注释与格式,改动前留 `.bak`):超时、重试、按 host
|
|
@@ -241,20 +315,33 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
|
|
|
241
315
|
- **不能改**:`[SECURITY]` 全部(令牌、TLS、SSRF 三个开关)、Web 自己的登录凭据、
|
|
242
316
|
集群共享密钥与各节点 token、`[BROWSER].allow_scripts`。这些只展示,要改请编辑
|
|
243
317
|
文件 / `.env` 后重启。
|
|
244
|
-
-
|
|
245
|
-
|
|
318
|
+
- **能装依赖**(0.4 放开,此前刻意不给):`/components` 可以装 / 卸 IPClick 自己
|
|
319
|
+
声明的那五个 extras,以及下载浏览器本体。包名走**白名单常量**(表单里传来的
|
|
320
|
+
extra 必须能在清单里查到),命令以列表形式交给 `subprocess`(`shell=False`),
|
|
321
|
+
并**绑定当前解释器**——不依赖 PATH 上的 `pip`,装到别的环境去比装不上更糟。
|
|
322
|
+
`pip` 和 `uv pip` 两条路自动探测(uv 建的 venv 默认不装 pip),两者都没有时
|
|
323
|
+
明确报错并给出手动命令。长任务(`camoufox fetch` 要下约 1 GB)跑在后台,
|
|
324
|
+
页面轮询状态。**卸载只卸 Python 包**,浏览器本体不动——界面会把它的路径和
|
|
325
|
+
体积摆出来,让人自己决定,从网页递归删一个 GB 级目录风险和收益不成比例。
|
|
246
326
|
|
|
247
327
|
这条线是刻意划的:这个服务能代任意 URL 发请求,一个能从网页关掉内网拦截、改掉
|
|
248
|
-
|
|
249
|
-
|
|
328
|
+
令牌的管理端,等于给自己装了个跳板。而调超时、加节点、装一个自己声明过的可选
|
|
329
|
+
依赖,这类操作改错了也只是性能或可用性问题,可逆、可见。
|
|
250
330
|
|
|
251
331
|
写回是**定点文本替换**而不是"读成 dict 再整体 dump":那份配置里几乎每一项都带着
|
|
252
332
|
解释(为什么默认是这个值、配错了什么症状),整体 dump 会把它们全抹掉。所以只换
|
|
253
333
|
被改动那一行等号右边的值,行尾注释都保留。写入用临时文件 + `os.replace`,
|
|
254
334
|
断电不会留下半个配置文件。
|
|
255
335
|
|
|
256
|
-
|
|
257
|
-
|
|
336
|
+
前端**没有构建链路**:没有模板引擎、框架、打包工具,也没有任何外部资源。
|
|
337
|
+
布局是纯 CSS Grid,交互是几十行原生 JS——0.3 时页面里一行 JS 都没有,0.4 加了
|
|
338
|
+
三件没有 JS 就做不好的事:手动切主题、装依赖的状态轮询、请求流的**局部刷新**
|
|
339
|
+
(`<meta refresh>` 每 3 秒重载整页,滚动位置丢失、正在填的过滤条件被冲掉、
|
|
340
|
+
页面白闪;现在只换那一块的 HTML,而且仍然由服务端渲染,不在 JS 里复制一份逻辑)。
|
|
341
|
+
|
|
342
|
+
CSP 里 `script-src` 用的是那两段内联脚本的 **sha256 哈希**,不是
|
|
343
|
+
`'unsafe-inline'`——脚本是源码里的常量,哈希能精确放行它们,同时让任何注入进来的
|
|
344
|
+
`<script>` 执行不了。
|
|
258
345
|
|
|
259
346
|
安全措施:会话 cookie 带 `HttpOnly` + `SameSite=Strict`,所有写操作校验 CSRF
|
|
260
347
|
token,登录失败按来源 IP 限速(5 次后锁定 5 分钟,锁定期间正确密码也不放行),
|
|
@@ -842,6 +929,7 @@ IPClick/
|
|
|
842
929
|
│ ├── health.py # grpc.health.v1 健康检查
|
|
843
930
|
│ ├── trace.py # 链路记录(内存环形缓冲 + 可选 SQLite)
|
|
844
931
|
│ ├── compression.py # 请求压缩策略(自动化脚本压缩收益最大)
|
|
932
|
+
│ ├── components.py # 五个可选 extras 的清单、两级安装状态、分类
|
|
845
933
|
│ ├── limiter.py # 按 host 的并发与 QPS 闸门
|
|
846
934
|
│ ├── exceptions.py # 异常层次
|
|
847
935
|
│ ├── py.typed # 类型标注标记
|
|
@@ -863,17 +951,22 @@ IPClick/
|
|
|
863
951
|
│ │ ├── forwarder.py # ForwardingTaskService(服务端转发)
|
|
864
952
|
│ │ ├── tokens.py # 由共享密钥派生每节点独立令牌
|
|
865
953
|
│ │ ├── discovery.py # static / dns 节点发现
|
|
954
|
+
│ │ ├── probe.py # 节点探测:连得上吗、鉴权配对吗
|
|
866
955
|
│ │ └── status_page.py # 只读状态页
|
|
867
|
-
│ ├── web/ # Web
|
|
868
|
-
│ │ ├── server.py # HTTP 服务、会话、CSRF
|
|
956
|
+
│ ├── web/ # Web 管理端(仅标准库,无前端构建链路)
|
|
957
|
+
│ │ ├── server.py # HTTP 服务、会话、CSRF、路由、CSP
|
|
869
958
|
│ │ ├── pages.py # 各页面的数据与操作
|
|
870
959
|
│ │ ├── templates.py # HTML 渲染(每处插值都要过 esc)
|
|
960
|
+
│ │ ├── assets.py # CSS 与 JS(CSP 用脚本哈希放行)
|
|
961
|
+
│ │ ├── installer.py # 装 / 卸可选组件(白名单 + 后台任务)
|
|
962
|
+
│ │ ├── curl_parser.py # 把 curl 命令解析成「试一试」表单
|
|
871
963
|
│ │ ├── editable.py # 可编辑配置项白名单
|
|
872
964
|
│ │ └── auth.py # 凭据、会话、登录限速
|
|
873
965
|
│ ├── cli/ # 命令行工具
|
|
874
966
|
│ ├── config_loader/ # 配置加载
|
|
875
967
|
│ │ ├── loader.py # 优先级与环境变量覆盖
|
|
876
968
|
│ │ ├── dotenv.py # .env 解析
|
|
969
|
+
│ │ ├── placeholders.py # 路径里的 {port} 占位符
|
|
877
970
|
│ │ └── writer.py # 写回 toml(定点替换,保留注释)
|
|
878
971
|
│ ├── configs/ # 默认配置文件
|
|
879
972
|
│ ├── dto/ # 数据传输对象
|
|
@@ -882,6 +975,7 @@ IPClick/
|
|
|
882
975
|
│ │ └── proto/ # Protobuf 定义与生成脚本
|
|
883
976
|
│ ├── services/ # gRPC 服务实现
|
|
884
977
|
│ └── utils/ # 工具模块(日志、配置、URL 安全校验等)
|
|
978
|
+
│ └── module_probe.py # 不执行模块代码的"装没装"探测(find_spec)
|
|
885
979
|
├── docker/ # Docker 相关文件
|
|
886
980
|
├── examples/ # 示例代码
|
|
887
981
|
├── tests/ # 测试代码
|
|
@@ -987,7 +1081,7 @@ healthy, status = check_health("10.0.0.1:9527", timeout=3)
|
|
|
987
1081
|
[TRACE]
|
|
988
1082
|
memory_size = 500 # 内存环形缓冲,始终开启,零磁盘
|
|
989
1083
|
sqlite_enabled = false # 落盘(默认关)。开了 Web 端才能查历史与跨天统计
|
|
990
|
-
sqlite_path = "ipclick-trace.db"
|
|
1084
|
+
sqlite_path = "ipclick-trace.{port}.db" # {port} = 运行时实际监听端口
|
|
991
1085
|
retention_days = 30 # 超期的每小时清一次;0 = 永久
|
|
992
1086
|
only_errors = false # 只记失败请求(成功量极大时省磁盘)
|
|
993
1087
|
record_url = true # 关掉后只记 host
|
|
@@ -1022,6 +1116,45 @@ print(resp.raw.trace.queued_ms) # 在限流闸门里排了多久
|
|
|
1022
1116
|
> 爆炸),所以回答不了"我刚才那个请求为什么 403"。而这个库的使用场景恰恰是后者。
|
|
1023
1117
|
> 聚合数字这边也有——进程内计数器实时累加,代价只是重启归零。
|
|
1024
1118
|
|
|
1119
|
+
#### 同一目录起多个实例:`{port}` 占位符
|
|
1120
|
+
|
|
1121
|
+
`sqlite_path` 与 `[LOG].output` 的路径值支持 `{port}` 占位符,替换成**运行时实际
|
|
1122
|
+
生效**的监听端口(`--port` 覆盖之后的那个,不是配置文件里的原始值):
|
|
1123
|
+
|
|
1124
|
+
```toml
|
|
1125
|
+
[TRACE]
|
|
1126
|
+
sqlite_path = "ipclick-trace.{port}.db" # -> ipclick-trace.9528.db
|
|
1127
|
+
|
|
1128
|
+
[LOG]
|
|
1129
|
+
output = "logs/{port}/app.log" # 占位符在路径中间也行
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
不这么做的话,同一目录用 `--port` 起的多个实例会**静默**写同一个库、抢同一个日志
|
|
1133
|
+
文件:不报错、不提示,用户以为在看单个实例的数据,实际是多进程合并的结果。
|
|
1134
|
+
(真撞上了也会打一条明确的告警,但那是兜底,不是解法。)
|
|
1135
|
+
|
|
1136
|
+
用占位符而不是在代码里"自动加端口后缀"是刻意的:新用户从模板拿到的默认值就带
|
|
1137
|
+
`{port}`,天然按端口分离;已有部署里自己写死的路径**一个字符都不会被改**,否则
|
|
1138
|
+
旧日志和 trace 数据看起来就像丢了。而且这条规则写在 toml 里是**看得见的**。
|
|
1139
|
+
|
|
1140
|
+
Web 端口也有了对应的命令行覆盖(gRPC 端口一直有 `--port`,Web 端此前没有):
|
|
1141
|
+
|
|
1142
|
+
```bash
|
|
1143
|
+
ipclick run --port 9528 --web-port 9531 -w
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
#### `[LOG].output` 的两种写法
|
|
1147
|
+
|
|
1148
|
+
```toml
|
|
1149
|
+
[LOG]
|
|
1150
|
+
output = "stdout" # 只打控制台(默认)
|
|
1151
|
+
output = "logs/app.log" # 写这个文件(没有扩展名时自动补 .log)
|
|
1152
|
+
output = "logs/" # 以斜杠结尾 = 写进这个**目录**,文件名用 ipclick.log
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
最后一种在 0.3 会被静默改写成同级的 `logs.log`(目录名被当成缺扩展名的文件名),
|
|
1156
|
+
0.4 修了。
|
|
1157
|
+
|
|
1025
1158
|
### 安全配置
|
|
1026
1159
|
|
|
1027
1160
|
服务端会代替调用方请求任意 URL。**若监听在公网或不可信网络,请开启内网拦截**,
|
|
@@ -1238,3 +1371,4 @@ uv run python src/ipclick/dto/proto/generate.py
|
|
|
1238
1371
|
本项目采用 [MIT License](https://github.com/yuanqimanong/IPClick/blob/master/LICENSE) 开源许可证。
|
|
1239
1372
|
|
|
1240
1373
|
Copyright (c) 2025 元气码农少女酱
|
|
1374
|
+
|
|
@@ -1,48 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ipclick
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: IPClick Tool
|
|
5
|
-
License: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Author: 元气码农少女酱
|
|
8
|
-
Author-email: 1276162429@qq.com
|
|
9
|
-
Requires-Python: >=3.11
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
-
Provides-Extra: camoufox
|
|
17
|
-
Provides-Extra: drissionpage
|
|
18
|
-
Provides-Extra: linux
|
|
19
|
-
Provides-Extra: niquests
|
|
20
|
-
Provides-Extra: patchright
|
|
21
|
-
Provides-Extra: playwright
|
|
22
|
-
Provides-Extra: win
|
|
23
|
-
Requires-Dist: camoufox (>=0.5.0,<1.0.0) ; extra == "camoufox"
|
|
24
|
-
Requires-Dist: camoufox (>=0.5.0,<1.0.0) ; extra == "linux"
|
|
25
|
-
Requires-Dist: click (>=8.4.2,<9.0.0)
|
|
26
|
-
Requires-Dist: curl-cffi (>=0.16.0,<0.17.0)
|
|
27
|
-
Requires-Dist: drissionpage (>=4.1.0,<5.0.0) ; extra == "drissionpage"
|
|
28
|
-
Requires-Dist: drissionpage (>=4.1.0,<5.0.0) ; extra == "win"
|
|
29
|
-
Requires-Dist: fake-useragent (>=2.2.0,<3.0.0)
|
|
30
|
-
Requires-Dist: grpcio (>=1.83.0,<2.0.0)
|
|
31
|
-
Requires-Dist: grpcio-health-checking (>=1.83.0,<2.0.0)
|
|
32
|
-
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
|
33
|
-
Requires-Dist: niquests (>=3.14.0,<4.0.0) ; extra == "niquests"
|
|
34
|
-
Requires-Dist: patchright (>=1.52.0,<2.0.0) ; extra == "linux"
|
|
35
|
-
Requires-Dist: patchright (>=1.52.0,<2.0.0) ; extra == "patchright"
|
|
36
|
-
Requires-Dist: playwright (>=1.49.0,<2.0.0) ; extra == "playwright"
|
|
37
|
-
Requires-Dist: protobuf (>=6.33.2,<8.0.0)
|
|
38
|
-
Requires-Dist: python-box[all] (>=7.4.1,<8.0.0)
|
|
39
|
-
Requires-Dist: typing-extensions (>=4.15.0,<5.0.0)
|
|
40
|
-
Requires-Dist: uuid-utils (>=0.17.0,<0.18.0)
|
|
41
|
-
Project-URL: Bug Tracker, https://github.com/yuanqimanong/IPClick/issues
|
|
42
|
-
Project-URL: Homepage, https://github.com/yuanqimanong/IPClick
|
|
43
|
-
Project-URL: Repository, https://github.com/yuanqimanong/IPClick
|
|
44
|
-
Description-Content-Type: text/markdown
|
|
45
|
-
|
|
46
1
|
# IPClick
|
|
47
2
|
|
|
48
3
|

|
|
@@ -85,7 +40,9 @@ IPClick 是一个轻量级、高性能的分布式 HTTP 请求代理工具,基
|
|
|
85
40
|
- **链路记录**:每个请求记一条(谁执行的、重试几次、排队多久),内存环形缓冲 + 可选 SQLite,零第三方依赖
|
|
86
41
|
- **SSRF 防护**:服务端对目标 URL 做协议白名单与内网/元数据地址拦截
|
|
87
42
|
- **Web 管理端**:`ipclick run --web` 起一个带登录的界面——总览看板、实时请求流、
|
|
88
|
-
|
|
43
|
+
「试一试」就地发请求看源码(支持粘贴 curl、可点名目标节点)、配置改完写回
|
|
44
|
+
`ipclick.toml`、集群节点增删(保存即生效)与就地测连通性 / 鉴权、可选组件的
|
|
45
|
+
装卸。左中右三栏布局 + 明暗主题切换,前端零构建链路
|
|
89
46
|
- **命令行工具**:提供便捷的 CLI 工具,支持快速启动服务和查看配置
|
|
90
47
|
- **Docker 支持**:多阶段构建、非 root 运行的镜像
|
|
91
48
|
- **完整类型标注**:随包提供 `py.typed`,下游可直接享受类型检查
|
|
@@ -128,6 +85,9 @@ playwright install chromium # playwright
|
|
|
128
85
|
只做第一步的话,`ipclick config-info` 与 Web 端会明确显示 **「包已装,浏览器本体未就绪」**,
|
|
129
86
|
请求会直接报 `FAILED_PRECONDITION` 并告诉你该跑哪条命令。
|
|
130
87
|
|
|
88
|
+
上面这些也可以**直接在 Web 端点按钮完成**(0.4 起):`/components` 页列出全部五个
|
|
89
|
+
可选组件的两级安装状态,就地装 / 卸、下载浏览器本体,装完不用重启进程。
|
|
90
|
+
|
|
131
91
|
> **刻意不让它在请求里自动下载。** camoufox 的 API 默认行为是"缺本体就当场下"
|
|
132
92
|
> (`camoufox_path(download_if_missing=True)`),而那一刻已经在 gRPC 的请求处理线程上:
|
|
133
93
|
> 第一个请求会卡着下 1 GB 然后超时,超时返回后下载还在后台跑,并发的多个首请求
|
|
@@ -187,6 +147,9 @@ ipclick run --config /path/to/ipclick.toml
|
|
|
187
147
|
|
|
188
148
|
# 显示详细日志
|
|
189
149
|
ipclick run --verbose
|
|
150
|
+
|
|
151
|
+
# 同一目录起第二个实例:gRPC 与 Web 两个端口都要岔开
|
|
152
|
+
ipclick run --port 9528 --web-port 9531 -w
|
|
190
153
|
```
|
|
191
154
|
|
|
192
155
|
初始化配置(推荐):
|
|
@@ -265,20 +228,41 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
|
|
|
265
228
|
|
|
266
229
|
或写进 `.env`,或配 `[WEB].username` / `password`。
|
|
267
230
|
|
|
268
|
-
####
|
|
231
|
+
#### 六个页面
|
|
232
|
+
|
|
233
|
+
0.4 布局大改版:**左侧导航 + 主内容 + 右侧状态栏**的 CSS Grid,右上角有
|
|
234
|
+
**跟随系统 / 亮 / 暗** 三态主题切换(记在 `localStorage`,刷新不丢)。
|
|
269
235
|
|
|
270
236
|
| 页面 | 干什么 |
|
|
271
237
|
|---|---|
|
|
272
|
-
| **总览** `/` |
|
|
273
|
-
| **请求流** `/trace` |
|
|
274
|
-
| **试一试** `/test` |
|
|
275
|
-
|
|
|
276
|
-
|
|
|
238
|
+
| **总览** `/` | 吞吐、成功率、在途与峰值、状态码分布、各适配器耗时与流量、集群拓扑、最近请求;右栏常驻服务端 / 安全 / 链路 / 限流 / 组件状态 |
|
|
239
|
+
| **请求流** `/trace` | **实时**看请求打进来。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
|
|
240
|
+
| **试一试** `/test` | 填一个网址(或直接**粘一条 curl**)就地发一次请求,看链路信息与返回的**源码**;可**点名**打到某个节点 |
|
|
241
|
+
| **组件** `/components` | 五个可选 extras 的两级安装状态,可以就地**安装 / 卸载 / 下载浏览器本体** |
|
|
242
|
+
| **配置** `/config` | 白名单内的行为配置可改,**写回 `ipclick.toml`**;可一键**生成凭据** |
|
|
243
|
+
| **节点** `/nodes` | 集群节点的增删改(**保存即生效**,不用重启)+ 就地**测试连接** |
|
|
277
244
|
|
|
278
245
|
「试一试」走的是本进程 `TaskService` 的**同一条**代码路径——SSRF 准入、限流、
|
|
279
246
|
以及开了转发时的分发全都照常生效,请求也会像真实请求一样出现在请求流里。
|
|
280
247
|
另写一条只在页面上成立的路径毫无意义:那验证的就不是线上行为了。
|
|
281
248
|
|
|
249
|
+
**从 curl 导入**:浏览器 DevTools 里对着请求「复制为 cURL」,粘进输入框就能自动
|
|
250
|
+
填好 URL / 方法 / 请求头 / 请求体。认不出的参数会**明确列出来**——静默丢掉一个
|
|
251
|
+
`-F` 比不支持它更糟。
|
|
252
|
+
|
|
253
|
+
**指定目标节点**:开了服务端转发时,「试一试」多一个"目标节点"下拉,选中后跳过
|
|
254
|
+
负载均衡强制打到那一台。验证新加的机器配对没有,不用再反复点、靠轮询碰运气。
|
|
255
|
+
|
|
256
|
+
**测试连接**:`/nodes` 每行一个按钮,只验**连通性**与**集群内部鉴权**,不发业务
|
|
257
|
+
请求。失败时区分"连不上"(查进程和网络)和"鉴权不通过"(核对各节点 `.env` 里的
|
|
258
|
+
`IPCLICK_CLUSTER_SECRET`)——这两种的排查方向完全相反。底层是 0.4 新增的
|
|
259
|
+
`Ping` RPC;对着 0.3 的节点调会收到 `UNIMPLEMENTED`,那恰恰说明鉴权是通的,
|
|
260
|
+
页面会如实这么说,不会误报成鉴权失败。
|
|
261
|
+
|
|
262
|
+
**生成凭据**:`/config` 可以为鉴权令牌、Web 密码、集群共享密钥各生成一个随机值,
|
|
263
|
+
**只显示一次**(服务端不保存、不写进任何文件,取完即弃),并明确区分"本机独有"
|
|
264
|
+
和"必须复制到所有其他节点的 `.env`"——集群共享密钥每台各自生成一个就全对不上了。
|
|
265
|
+
|
|
282
266
|
#### 能改什么、不能改什么
|
|
283
267
|
|
|
284
268
|
- **能改**(写回 toml,保留注释与格式,改动前留 `.bak`):超时、重试、按 host
|
|
@@ -286,20 +270,33 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
|
|
|
286
270
|
- **不能改**:`[SECURITY]` 全部(令牌、TLS、SSRF 三个开关)、Web 自己的登录凭据、
|
|
287
271
|
集群共享密钥与各节点 token、`[BROWSER].allow_scripts`。这些只展示,要改请编辑
|
|
288
272
|
文件 / `.env` 后重启。
|
|
289
|
-
-
|
|
290
|
-
|
|
273
|
+
- **能装依赖**(0.4 放开,此前刻意不给):`/components` 可以装 / 卸 IPClick 自己
|
|
274
|
+
声明的那五个 extras,以及下载浏览器本体。包名走**白名单常量**(表单里传来的
|
|
275
|
+
extra 必须能在清单里查到),命令以列表形式交给 `subprocess`(`shell=False`),
|
|
276
|
+
并**绑定当前解释器**——不依赖 PATH 上的 `pip`,装到别的环境去比装不上更糟。
|
|
277
|
+
`pip` 和 `uv pip` 两条路自动探测(uv 建的 venv 默认不装 pip),两者都没有时
|
|
278
|
+
明确报错并给出手动命令。长任务(`camoufox fetch` 要下约 1 GB)跑在后台,
|
|
279
|
+
页面轮询状态。**卸载只卸 Python 包**,浏览器本体不动——界面会把它的路径和
|
|
280
|
+
体积摆出来,让人自己决定,从网页递归删一个 GB 级目录风险和收益不成比例。
|
|
291
281
|
|
|
292
282
|
这条线是刻意划的:这个服务能代任意 URL 发请求,一个能从网页关掉内网拦截、改掉
|
|
293
|
-
|
|
294
|
-
|
|
283
|
+
令牌的管理端,等于给自己装了个跳板。而调超时、加节点、装一个自己声明过的可选
|
|
284
|
+
依赖,这类操作改错了也只是性能或可用性问题,可逆、可见。
|
|
295
285
|
|
|
296
286
|
写回是**定点文本替换**而不是"读成 dict 再整体 dump":那份配置里几乎每一项都带着
|
|
297
287
|
解释(为什么默认是这个值、配错了什么症状),整体 dump 会把它们全抹掉。所以只换
|
|
298
288
|
被改动那一行等号右边的值,行尾注释都保留。写入用临时文件 + `os.replace`,
|
|
299
289
|
断电不会留下半个配置文件。
|
|
300
290
|
|
|
301
|
-
|
|
302
|
-
|
|
291
|
+
前端**没有构建链路**:没有模板引擎、框架、打包工具,也没有任何外部资源。
|
|
292
|
+
布局是纯 CSS Grid,交互是几十行原生 JS——0.3 时页面里一行 JS 都没有,0.4 加了
|
|
293
|
+
三件没有 JS 就做不好的事:手动切主题、装依赖的状态轮询、请求流的**局部刷新**
|
|
294
|
+
(`<meta refresh>` 每 3 秒重载整页,滚动位置丢失、正在填的过滤条件被冲掉、
|
|
295
|
+
页面白闪;现在只换那一块的 HTML,而且仍然由服务端渲染,不在 JS 里复制一份逻辑)。
|
|
296
|
+
|
|
297
|
+
CSP 里 `script-src` 用的是那两段内联脚本的 **sha256 哈希**,不是
|
|
298
|
+
`'unsafe-inline'`——脚本是源码里的常量,哈希能精确放行它们,同时让任何注入进来的
|
|
299
|
+
`<script>` 执行不了。
|
|
303
300
|
|
|
304
301
|
安全措施:会话 cookie 带 `HttpOnly` + `SameSite=Strict`,所有写操作校验 CSRF
|
|
305
302
|
token,登录失败按来源 IP 限速(5 次后锁定 5 分钟,锁定期间正确密码也不放行),
|
|
@@ -887,6 +884,7 @@ IPClick/
|
|
|
887
884
|
│ ├── health.py # grpc.health.v1 健康检查
|
|
888
885
|
│ ├── trace.py # 链路记录(内存环形缓冲 + 可选 SQLite)
|
|
889
886
|
│ ├── compression.py # 请求压缩策略(自动化脚本压缩收益最大)
|
|
887
|
+
│ ├── components.py # 五个可选 extras 的清单、两级安装状态、分类
|
|
890
888
|
│ ├── limiter.py # 按 host 的并发与 QPS 闸门
|
|
891
889
|
│ ├── exceptions.py # 异常层次
|
|
892
890
|
│ ├── py.typed # 类型标注标记
|
|
@@ -908,17 +906,22 @@ IPClick/
|
|
|
908
906
|
│ │ ├── forwarder.py # ForwardingTaskService(服务端转发)
|
|
909
907
|
│ │ ├── tokens.py # 由共享密钥派生每节点独立令牌
|
|
910
908
|
│ │ ├── discovery.py # static / dns 节点发现
|
|
909
|
+
│ │ ├── probe.py # 节点探测:连得上吗、鉴权配对吗
|
|
911
910
|
│ │ └── status_page.py # 只读状态页
|
|
912
|
-
│ ├── web/ # Web
|
|
913
|
-
│ │ ├── server.py # HTTP 服务、会话、CSRF
|
|
911
|
+
│ ├── web/ # Web 管理端(仅标准库,无前端构建链路)
|
|
912
|
+
│ │ ├── server.py # HTTP 服务、会话、CSRF、路由、CSP
|
|
914
913
|
│ │ ├── pages.py # 各页面的数据与操作
|
|
915
914
|
│ │ ├── templates.py # HTML 渲染(每处插值都要过 esc)
|
|
915
|
+
│ │ ├── assets.py # CSS 与 JS(CSP 用脚本哈希放行)
|
|
916
|
+
│ │ ├── installer.py # 装 / 卸可选组件(白名单 + 后台任务)
|
|
917
|
+
│ │ ├── curl_parser.py # 把 curl 命令解析成「试一试」表单
|
|
916
918
|
│ │ ├── editable.py # 可编辑配置项白名单
|
|
917
919
|
│ │ └── auth.py # 凭据、会话、登录限速
|
|
918
920
|
│ ├── cli/ # 命令行工具
|
|
919
921
|
│ ├── config_loader/ # 配置加载
|
|
920
922
|
│ │ ├── loader.py # 优先级与环境变量覆盖
|
|
921
923
|
│ │ ├── dotenv.py # .env 解析
|
|
924
|
+
│ │ ├── placeholders.py # 路径里的 {port} 占位符
|
|
922
925
|
│ │ └── writer.py # 写回 toml(定点替换,保留注释)
|
|
923
926
|
│ ├── configs/ # 默认配置文件
|
|
924
927
|
│ ├── dto/ # 数据传输对象
|
|
@@ -927,6 +930,7 @@ IPClick/
|
|
|
927
930
|
│ │ └── proto/ # Protobuf 定义与生成脚本
|
|
928
931
|
│ ├── services/ # gRPC 服务实现
|
|
929
932
|
│ └── utils/ # 工具模块(日志、配置、URL 安全校验等)
|
|
933
|
+
│ └── module_probe.py # 不执行模块代码的"装没装"探测(find_spec)
|
|
930
934
|
├── docker/ # Docker 相关文件
|
|
931
935
|
├── examples/ # 示例代码
|
|
932
936
|
├── tests/ # 测试代码
|
|
@@ -1032,7 +1036,7 @@ healthy, status = check_health("10.0.0.1:9527", timeout=3)
|
|
|
1032
1036
|
[TRACE]
|
|
1033
1037
|
memory_size = 500 # 内存环形缓冲,始终开启,零磁盘
|
|
1034
1038
|
sqlite_enabled = false # 落盘(默认关)。开了 Web 端才能查历史与跨天统计
|
|
1035
|
-
sqlite_path = "ipclick-trace.db"
|
|
1039
|
+
sqlite_path = "ipclick-trace.{port}.db" # {port} = 运行时实际监听端口
|
|
1036
1040
|
retention_days = 30 # 超期的每小时清一次;0 = 永久
|
|
1037
1041
|
only_errors = false # 只记失败请求(成功量极大时省磁盘)
|
|
1038
1042
|
record_url = true # 关掉后只记 host
|
|
@@ -1067,6 +1071,45 @@ print(resp.raw.trace.queued_ms) # 在限流闸门里排了多久
|
|
|
1067
1071
|
> 爆炸),所以回答不了"我刚才那个请求为什么 403"。而这个库的使用场景恰恰是后者。
|
|
1068
1072
|
> 聚合数字这边也有——进程内计数器实时累加,代价只是重启归零。
|
|
1069
1073
|
|
|
1074
|
+
#### 同一目录起多个实例:`{port}` 占位符
|
|
1075
|
+
|
|
1076
|
+
`sqlite_path` 与 `[LOG].output` 的路径值支持 `{port}` 占位符,替换成**运行时实际
|
|
1077
|
+
生效**的监听端口(`--port` 覆盖之后的那个,不是配置文件里的原始值):
|
|
1078
|
+
|
|
1079
|
+
```toml
|
|
1080
|
+
[TRACE]
|
|
1081
|
+
sqlite_path = "ipclick-trace.{port}.db" # -> ipclick-trace.9528.db
|
|
1082
|
+
|
|
1083
|
+
[LOG]
|
|
1084
|
+
output = "logs/{port}/app.log" # 占位符在路径中间也行
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
不这么做的话,同一目录用 `--port` 起的多个实例会**静默**写同一个库、抢同一个日志
|
|
1088
|
+
文件:不报错、不提示,用户以为在看单个实例的数据,实际是多进程合并的结果。
|
|
1089
|
+
(真撞上了也会打一条明确的告警,但那是兜底,不是解法。)
|
|
1090
|
+
|
|
1091
|
+
用占位符而不是在代码里"自动加端口后缀"是刻意的:新用户从模板拿到的默认值就带
|
|
1092
|
+
`{port}`,天然按端口分离;已有部署里自己写死的路径**一个字符都不会被改**,否则
|
|
1093
|
+
旧日志和 trace 数据看起来就像丢了。而且这条规则写在 toml 里是**看得见的**。
|
|
1094
|
+
|
|
1095
|
+
Web 端口也有了对应的命令行覆盖(gRPC 端口一直有 `--port`,Web 端此前没有):
|
|
1096
|
+
|
|
1097
|
+
```bash
|
|
1098
|
+
ipclick run --port 9528 --web-port 9531 -w
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
#### `[LOG].output` 的两种写法
|
|
1102
|
+
|
|
1103
|
+
```toml
|
|
1104
|
+
[LOG]
|
|
1105
|
+
output = "stdout" # 只打控制台(默认)
|
|
1106
|
+
output = "logs/app.log" # 写这个文件(没有扩展名时自动补 .log)
|
|
1107
|
+
output = "logs/" # 以斜杠结尾 = 写进这个**目录**,文件名用 ipclick.log
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
最后一种在 0.3 会被静默改写成同级的 `logs.log`(目录名被当成缺扩展名的文件名),
|
|
1111
|
+
0.4 修了。
|
|
1112
|
+
|
|
1070
1113
|
### 安全配置
|
|
1071
1114
|
|
|
1072
1115
|
服务端会代替调用方请求任意 URL。**若监听在公网或不可信网络,请开启内网拦截**,
|
|
@@ -1283,4 +1326,3 @@ uv run python src/ipclick/dto/proto/generate.py
|
|
|
1283
1326
|
本项目采用 [MIT License](https://github.com/yuanqimanong/IPClick/blob/master/LICENSE) 开源许可证。
|
|
1284
1327
|
|
|
1285
1328
|
Copyright (c) 2025 元气码农少女酱
|
|
1286
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ipclick"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "IPClick Tool"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "元气码农少女酱", email = "1276162429@qq.com" }
|
|
@@ -18,7 +18,7 @@ dependencies = [
|
|
|
18
18
|
"loguru>=0.7.3,<0.8.0",
|
|
19
19
|
"python-box[all]>=7.4.1,<8.0.0",
|
|
20
20
|
"uuid-utils>=0.17.0,<0.18.0",
|
|
21
|
-
"typing-extensions>=4.
|
|
21
|
+
"typing-extensions>=4.16.0,<5.0.0",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
[project.optional-dependencies]
|
|
@@ -26,25 +26,29 @@ dependencies = [
|
|
|
26
26
|
# 另一个可选的是 niquests —— requests 的 drop-in 替代,额外支持 HTTP/2 与 HTTP/3。
|
|
27
27
|
# (httpx 曾经也在这里,0.3.0 移除:它和 niquests 能力重叠,
|
|
28
28
|
# 而 niquests 还多支持 HTTP/3,维护两套等价的 HTTP 适配器不划算。)
|
|
29
|
-
niquests = ["niquests>=3.
|
|
29
|
+
niquests = ["niquests>=3.21.0,<4.0.0"]
|
|
30
30
|
|
|
31
31
|
# 浏览器渲染引擎。装完还要各自准备浏览器本体:
|
|
32
32
|
# camoufox python -m camoufox fetch
|
|
33
33
|
# patchright patchright install chromium
|
|
34
34
|
# playwright playwright install chromium
|
|
35
35
|
# drissionpage 用本机已装的 Chrome/Chromium,无需下载
|
|
36
|
-
camoufox = ["camoufox>=0.5.
|
|
37
|
-
patchright = ["patchright>=1.
|
|
38
|
-
drissionpage = ["drissionpage>=4.1.
|
|
39
|
-
|
|
36
|
+
camoufox = ["camoufox>=0.5.4,<1.0.0"]
|
|
37
|
+
patchright = ["patchright>=1.61.2,<2.0.0"]
|
|
38
|
+
drissionpage = ["drissionpage>=4.1.1,<5.0.0"]
|
|
39
|
+
# ⚠️ 上限**必须**留在 camoufox 能接受的范围内:camoufox 0.5.4 自己钉着
|
|
40
|
+
# playwright<1.61,把这里的下限提到 1.62 会让 [camoufox] 和 [playwright]
|
|
41
|
+
# 直接互斥(uv 会报 unsatisfiable)。而 camoufox 是 Linux 上的默认引擎,
|
|
42
|
+
# 为了追一个更新的 playwright 把它挤掉不划算。
|
|
43
|
+
playwright = ["playwright>=1.60.0,<2.0.0"]
|
|
40
44
|
|
|
41
45
|
# 按平台打包好的组合。刻意不提供 [all]——把四个浏览器内核全装上去
|
|
42
46
|
# 有 70+ 个包和上 G 的浏览器本体,而一台机器只会用其中一个。
|
|
43
47
|
# curl_cffi 是核心依赖,不在这里重复声明(重复写还会引入版本区间打架的风险)。
|
|
44
48
|
# win : DrissionPage 直接驱动本机已装的 Chrome,Windows 上最省事
|
|
45
49
|
# linux : Camoufox / Patchright 自带无头浏览器,适合服务器
|
|
46
|
-
win = ["drissionpage>=4.1.
|
|
47
|
-
linux = ["camoufox>=0.5.
|
|
50
|
+
win = ["drissionpage>=4.1.1,<5.0.0"]
|
|
51
|
+
linux = ["camoufox>=0.5.4,<1.0.0", "patchright>=1.61.2,<2.0.0"]
|
|
48
52
|
|
|
49
53
|
[project.urls]
|
|
50
54
|
"Homepage" = "https://github.com/yuanqimanong/IPClick"
|
|
@@ -29,7 +29,7 @@ try:
|
|
|
29
29
|
__version__ = importlib.metadata.version("ipclick")
|
|
30
30
|
__author__ = importlib.metadata.metadata("ipclick")["Author"]
|
|
31
31
|
except importlib.metadata.PackageNotFoundError: # pragma: no cover - 仅在未安装时走到
|
|
32
|
-
__version__ = "0.
|
|
32
|
+
__version__ = "0.4.0"
|
|
33
33
|
__author__ = "元气码农少女酱"
|
|
34
34
|
|
|
35
35
|
|