@fastls/fastls 0.0.0-stage → 0.10.1-dev.0
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.
- package/LICENSE +203 -0
- package/README.md +385 -2
- package/dist/cookies.d.ts +23 -0
- package/dist/cookies.js +27 -0
- package/dist/duplex.d.ts +21 -0
- package/dist/duplex.js +94 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +48 -0
- package/dist/headers.d.ts +12 -0
- package/dist/headers.js +28 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +26 -0
- package/dist/index.mjs +2 -0
- package/dist/library-path.d.ts +2 -0
- package/dist/library-path.js +77 -0
- package/dist/native.d.ts +54 -0
- package/dist/native.js +208 -0
- package/dist/notifications.d.ts +40 -0
- package/dist/notifications.js +222 -0
- package/dist/request-options.d.ts +28 -0
- package/dist/request-options.js +172 -0
- package/dist/response.d.ts +84 -0
- package/dist/response.js +242 -0
- package/dist/scheduler.d.ts +12 -0
- package/dist/scheduler.js +75 -0
- package/dist/session.d.ts +101 -0
- package/dist/session.js +390 -0
- package/dist/sse.d.ts +52 -0
- package/dist/sse.js +197 -0
- package/dist/text.d.ts +3 -0
- package/dist/text.js +55 -0
- package/dist/timeout.d.ts +9 -0
- package/dist/timeout.js +19 -0
- package/dist/upload.d.ts +11 -0
- package/dist/upload.js +141 -0
- package/dist/websocket.d.ts +63 -0
- package/dist/websocket.js +248 -0
- package/package.json +41 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
203
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,386 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Fastls Node.js SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node.js 22+ 的 Fastls C ABI v1 绑定,提供 Promise API、TypeScript 声明、ESM 和 CommonJS 入口。当前开发包版本为 `0.10.1-dev.0`,npm 预发布渠道使用 `next`。
|
|
4
|
+
|
|
5
|
+
支持 HTTP/1.1、HTTP/2、流式上传下载、双工、SSE、WebSocket、会话 Cookie 和 HTTP/HTTPS/SOCKS 代理。网络请求由 Go 执行,通过 Koffi 在当前 Node 进程中调用;使用者无需 Python 或 RPC 服务。
|
|
6
|
+
|
|
7
|
+
只支持 Node.js 主线程。暂不支持 worker_threads、Electron、Bun 或浏览器。默认校验证书;生成的客户端身份不代表已获得与真实浏览器完全相同的线上指纹。
|
|
8
|
+
|
|
9
|
+
- 用户 API 与安装方式:本文。
|
|
10
|
+
- Python 行为对应、语言差异与测试证据:[PARITY.md](https://github.com/FastTLS/fastls-ffi/blob/main/node/PARITY.md)。
|
|
11
|
+
- 维护者打包、首次发布和 Trusted Publisher:[NPM_RELEASE.md](https://github.com/FastTLS/fastls-ffi/blob/main/node/NPM_RELEASE.md)。
|
|
12
|
+
- 当前交付状态及后续批次:[NEXT_PHASE.md](https://github.com/FastTLS/fastls-ffi/blob/main/node/NEXT_PHASE.md)。
|
|
13
|
+
|
|
14
|
+
## 安装与快速开始
|
|
15
|
+
|
|
16
|
+
npm 的 `next` 版本发布后,在消费项目中安装:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @fastls/fastls@next
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
主包通过同版本的可选依赖自动安装当前平台的 Fastls 原生库。保留 optional dependencies;支持 Windows x64、Linux glibc x64/arm64、macOS x64/arm64。Linux musl 和其它架构没有内置包。具体系统最低版本见原生包的 `native/manifest.json`。
|
|
23
|
+
|
|
24
|
+
npm 分发包没有 Fastls 编译或下载脚本;安装成功后不需要 Go、Python、C 编译器或 `FASTLS_LIBRARY`。Koffi 自身也使用对应平台的原生组件。
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { Session } from '@fastls/fastls';
|
|
28
|
+
// CommonJS: const { Session } = require('@fastls/fastls');
|
|
29
|
+
|
|
30
|
+
const session = new Session({ profile: 'chrome/156/windows', timeoutMs: 30_000 });
|
|
31
|
+
try {
|
|
32
|
+
const response = await session.get('https://example.com', {
|
|
33
|
+
headers: { 'User-Agent': 'my-client/1.0' },
|
|
34
|
+
signal: AbortSignal.timeout(5_000),
|
|
35
|
+
});
|
|
36
|
+
response.raiseForStatus();
|
|
37
|
+
console.log(response.status, response.url, response.httpVersion, response.text());
|
|
38
|
+
} finally {
|
|
39
|
+
await session.closeAsync();
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
后续片段假定 `session` 已创建且未关闭;`consume`、`produceBytes` 等是应用自行实现的业务函数。每次实际使用均应关闭会话和未消费完的响应。
|
|
44
|
+
|
|
45
|
+
从源码开发时,在仓库根目录构建动态库和测试服务,再安装 Node 开发依赖:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
python scripts/build.py
|
|
49
|
+
go build -o build/ffi-fixture ./cmd/ffi-fixture
|
|
50
|
+
npm ci --prefix node
|
|
51
|
+
npm run build --prefix node
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Windows 测试服务文件名使用 `build/ffi-fixture.exe`,原生构建需要可用的 C 编译器,可通过 `--cc` 指定。`scripts/build.py --local` 会生成忽略跟踪的 `go.work`,使用同级源码仓库;也可用 `FASTLS_LOCAL_FASTLS`、`FASTLS_LOCAL_FTRANSPORT`、`FASTLS_LOCAL_IMITATE` 指向固定副本。当前脚本没有 `--workspace` 参数。正式发布消费同提交 CI 的固定依赖构建,不能用任意本地构建替代。
|
|
55
|
+
|
|
56
|
+
源码默认加载仓库 `dist/`;普通 `npm pack ./node` 生成需要外部动态库的 thin 包。thin 包可通过 `FASTLS_LIBRARY` 或 `new Session({ library: '/absolute/path/to/library' })` 指定库。GitHub 内置库候选和 npm 拆分包的区别见下文。
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## 请求和响应
|
|
60
|
+
|
|
61
|
+
- `await session.request(method, url, options)`,以及 `get`、`post`,返回完整缓存的 Response。
|
|
62
|
+
- 请求选项包括 `profile`、`headers`、`trailers`、`params`、`json`、`form`、`files`、`body`、`timeoutMs`、`allowRedirects`、`signal`、`proxy`、`proxyCaFile`、`decodeContent`、`maxDecodedBytes`、`duplex`。会话级选项还包括 `library`、`caFile`、`cookies`、`notifications`;以公开 TypeScript 类型为准。
|
|
63
|
+
- `body` 为字符串、Buffer、Uint8Array、同步/异步字节迭代器、Node Readable 或接收 AbortSignal 的生产者工厂。超过512 KiB的字符串/字节自动使用上传通道。
|
|
64
|
+
- `timeoutMs` 为毫秒整数或 `{ total, connect?, write?, headers?, read? }`,每项范围1..86400000;默认总时限30000。请求级数字覆盖会清除会话分层限制,结构化覆盖替换整份策略。
|
|
65
|
+
- 会话默认头和请求头按大小写不敏感方式合并,请求值优先,包括空字符串。
|
|
66
|
+
- `Response` 提供 `status`、`ok`、`headers`、`wireHeaders`、`decoded`、`match`、`httpVersion`、`url`、`trailers`、`content: Buffer`,以及同步 `text()` / `json()`。
|
|
67
|
+
|
|
68
|
+
`ok` 表示状态小于400,与Python一致,因此302/304也为true。
|
|
69
|
+
`raiseForStatus()` 只对400–599抛出 `FastlsHTTPError`,异常包含原响应 `response` 和 `status`,消息仅含状态码;流式响应可在读取正文前检查,随后仍需关闭。
|
|
70
|
+
`encoding` 从Content-Type的charset初始化,缺省utf-8,可修改;`text()`替换坏字节,未知编码报错。Latin-1保留完整字节映射,其它字符集使用Node内置TextDecoder的WHATWG编码表,不自动猜测。
|
|
71
|
+
`json()`直接读取原始字节,支持UTF-8/16/32及BOM,独立于encoding;JSON数字仍受JavaScript数字精度限制。
|
|
72
|
+
- 4xx/5xx 仍返回 Response;参数、网络、TLS 和原生错误抛出异常。
|
|
73
|
+
- `Headers.get(name)` 返回第一个值或 undefined;`getAll(name)` 保留所有重复值,不按逗号拆分。
|
|
74
|
+
`entries()` / 默认迭代返回每个字段的首值,`multiItems()` 返回全部字段值。
|
|
75
|
+
- `listProfiles()` 返回规范 ID;`stats()` 返回 Go 运行时只读快照,其统计范围是该动态库实例,不仅是当前会话。
|
|
76
|
+
|
|
77
|
+
未知 profile 报错;Match 保留生成身份和协议基线信息,不承诺与真实浏览器指纹完全等价。
|
|
78
|
+
|
|
79
|
+
`request('PUT', url, options)` 等可使用其它合法方法;便捷方法只有 `get` 和 `post`。请求头的值为字符串;`trailers` 的值为字符串或只读字符串数组,名称和值由原生层继续验证,且要求 `request_trailers` 能力。
|
|
80
|
+
|
|
81
|
+
`url` 是最终响应 URL,`httpVersion` 是实际 HTTP 协议版本。缓存响应已经读到 EOF,包含最终 `trailers`;流式响应先消费正文,再读取最终尾部字段。提前关闭不代表已经收到全部 trailers。
|
|
82
|
+
|
|
83
|
+
## 请求参数
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const response = await session.get('https://example.com/search?old=a%20b#fragment', {
|
|
87
|
+
params: [['q', '中文'], ['tag', ['a', 'b']], ['empty', '']],
|
|
88
|
+
});
|
|
89
|
+
response.raiseForStatus();
|
|
90
|
+
await session.post('https://example.com/api', { json: null });
|
|
91
|
+
await session.post('https://example.com/form', { form: { tag: ['a', 'b'], enabled: true } });
|
|
92
|
+
|
|
93
|
+
// FileHandle归调用方拥有;适配read签名,从当前位置读取,始终由调用方关闭。
|
|
94
|
+
const file = await open('/path/to/archive.bin', 'r'); // import { open } from 'node:fs/promises'
|
|
95
|
+
try {
|
|
96
|
+
await session.post('https://example.com/upload', {
|
|
97
|
+
form: { description: '附件' },
|
|
98
|
+
files: { file: ['archive.bin', {
|
|
99
|
+
async read(maxBytes, signal) {
|
|
100
|
+
signal.throwIfAborted();
|
|
101
|
+
const buffer = Buffer.allocUnsafe(maxBytes);
|
|
102
|
+
const { bytesRead } = await file.read(buffer, 0, maxBytes, null);
|
|
103
|
+
return buffer.subarray(0, bytesRead);
|
|
104
|
+
},
|
|
105
|
+
}, 'application/octet-stream'] },
|
|
106
|
+
});
|
|
107
|
+
} finally { await file.close(); }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- `params`/`form`接受普通对象、Map或键值对数组。列表值展开成重复键;null/undefined值省略,空字符串保留;布尔值与Python一致为True/False,Unicode用UTF-8,空格为+。键可用字符串/Uint8Array,值可用字符串/Uint8Array、有限数或布尔值。与Python不同,查询及表单拒绝非有限数。
|
|
111
|
+
- `params`另接受不含#的已编码ASCII字符串,原查询编码和fragment保留。fragment不发送到服务器。
|
|
112
|
+
- `json`只接受严格JSON值、普通对象及数组,拒绝NaN/Infinity、undefined、bigint、稀疏数组、循环和toJSON。显式null发送null;省略json无正文。`json`与body/form/files冲突;`body`与form/files冲突。不要把Python的data映射直接传给body;Node用form显式编码,现有字节块数组仍是原始body。
|
|
113
|
+
- JSON/表单在内存中编码;超过512KiB自动转为流式上传,不是大型数据增量编码器。默认Content-Type仅在没有会话/请求自定义头时设置;编码正文拒绝Content-Length/Transfer-Encoding。原始body沿用原有规则。
|
|
114
|
+
- `files`接受对象、Map或重复字段对数组。每个文件是Uint8Array、`MultipartReader`,或`[filename, body, contentType?]`;filename为null省略filename参数。默认从读取器name/字段名取文件名;内置固定MIME推断只覆盖txt/json/html/csv/png/jpg/jpeg/pdf,其它类型请显式指定,缺省application/octet-stream。不支持自定义分部头。
|
|
115
|
+
- multipart可配合form字段,每次read最多64KiB,空Uint8Array表示EOF;支持同步/Promise读取器。无需seek/长度/临时文件,不全量缓存文件;调用方负责close和重新定位,成功、失败、取消均保留读取器所有权。提供给read的AbortSignal可中断自定义等待;不可取消的Promise会延后closeAsync清理。Node Readable及FileHandle不能直接作为此读取器,应适配其读取接口;现有原始body的Readable销毁规则不变。
|
|
116
|
+
- multipart管理随机boundary及Content-Type,拒绝会话或请求中显式Content-Type;元数据拒绝控制字符。正文仅使用一次,307/308不自动重放,301/302/303沿用核心GET转换规则。
|
|
117
|
+
- request/get/post/stream/sse与自动duplex共享参数处理;multipart不支持duplex。手动duplex只接受params,正文继续send()/finishUpload()。自动双工支持JSON/表单且关闭重定向。
|
|
118
|
+
|
|
119
|
+
## 流式下载和取消
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
const response = await session.stream('GET', 'https://example.com/large-file', {
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
for await (const chunk of response.body) {
|
|
128
|
+
await consume(chunk);
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
response.close();
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
响应头到达后 `stream()` 即返回。`body` 是单次消费的 `AsyncIterable<Buffer>`,每个块独立持有内存,
|
|
136
|
+
可保留供后续使用。停止拉取时 SDK 不继续读取;块边界不代表行、SSE 事件或 HTTP 帧。
|
|
137
|
+
可通过 `Readable.from(response.body, { objectMode: false })` 接入 Node 流并使用 `pipeline()`。
|
|
138
|
+
|
|
139
|
+
`StreamResponse.read()` / `text()` / `json()` 异步读取完整正文,上限同普通请求的 32 MiB;更大响应必须逐块消费。
|
|
140
|
+
默认在Go侧逐块解码gzip、deflate、br及zstd,`decodeContent: false`取得原始字节;`text()` 按响应 `encoding` 解码,缺省 UTF-8,替换非法字节;JSON 解码规则与缓存响应相同。
|
|
141
|
+
解码时headers移除Content-Encoding/Content-Length,wireHeaders保留线上字段;decoded表示解码视图。
|
|
142
|
+
`maxDecodedBytes`默认256 MiB,范围1..1 TiB,只限制解码输出;普通缓存32 MiB限制仍独立适用。未知编码、校验失败、截断或超限抛出FastlsDecodeError。
|
|
143
|
+
|
|
144
|
+
正常 EOF、读取异常或提前 `break` 会关闭请求。尚未开始迭代的响应需要显式 `close()`,
|
|
145
|
+
也可调用 `cancel()` 或中止 AbortSignal。AbortSignal 在等待响应头和正文读取期间都有效,
|
|
146
|
+
抛出 `AbortError`(`code: 'ABORT_ERR'`,`cause` 保留 signal.reason)。超时抛出 FastlsTimeoutError,其phase为connect/write/headers/read/total。解码错误、超时错误和AbortError均继承FastlsError。
|
|
147
|
+
`session.close()` 取消并关闭所有活动请求,重复关闭无害;不依赖 GC 清理资源。
|
|
148
|
+
|
|
149
|
+
## 连接池、Cookie 和证书
|
|
150
|
+
|
|
151
|
+
Session 复用原生会话的 H1 keep-alive 和 H2 多路复用;不同 Session 隔离。
|
|
152
|
+
`session.closeIdleConnections()` 释放空闲连接,保留 Cookie。
|
|
153
|
+
Cookie 自动管理和重定向处理均由 Go 完成:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
session.cookies.set('token', 'value', { url: 'https://example.com', path: '/', secure: true });
|
|
157
|
+
session.cookies.get('token', 'https://example.com/account');
|
|
158
|
+
session.cookies.getAll('https://example.com/account'); // [{ name, value }, ...]
|
|
159
|
+
session.cookies.delete('token', { url: 'https://example.com', path: '/' });
|
|
160
|
+
session.cookies.clear();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
设置选项还包括 `domain`、`httpOnly`、`maxAge`(秒)和 `expires`(RFC3339)。
|
|
164
|
+
`cookies: false` 禁用自动收发。显式 Cookie 请求头优先。规则和存储限制见上级 COOKIES_HEADERS.md。
|
|
165
|
+
|
|
166
|
+
始终校验证书。可通过 `caFile` 使用仅属于本会话的私有根证书(替代系统根);文件在构造时读取。
|
|
167
|
+
不修改系统信任库,不提供关闭 TLS 验证选项。
|
|
168
|
+
|
|
169
|
+
## 代理
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
const session = new Session({
|
|
173
|
+
proxy: 'http://user:password@127.0.0.1:8080',
|
|
174
|
+
timeoutMs: { total: 30000, connect: 5000 },
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
await session.get('https://example.com/');
|
|
178
|
+
await session.get('https://example.com/', { proxy: null }); // 显式直连
|
|
179
|
+
await session.get('https://example.com/', { proxy: 'socks5h://127.0.0.1:1080' });
|
|
180
|
+
} finally { await session.closeAsync(); }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Session和请求的`proxy`接受URL字符串或null。请求省略或undefined继承会话默认值;null或空字符串直连。普通请求、stream、sse、自动/手动duplex遵循相同规则。传入其它类型会报TypeError。显式代理要求原生`proxy_tunnels`能力;旧ABI-v1库仍可直连,代理请求在登记请求句柄前报错。
|
|
184
|
+
|
|
185
|
+
`http://`对HTTP和HTTPS目标均使用HTTP/1.1 CONNECT,默认端口80。`socks5://`本机解析目标域名,`socks5h://`交给代理解析,默认端口1080。支持HTTP Basic和SOCKS用户名/密码认证,凭据按URL规则转义,例如密码中的@写成%40。不读取HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY及其小写形式。`https://`在代理TLS中运行HTTP/1.1 CONNECT,默认端口443,证书和主机名验证始终开启,不回退到明文。HTTPS代理另要求原生`https_proxy_tls`能力;暂不支持SOCKS4或PAC,详情见上级PROXY.md。
|
|
186
|
+
|
|
187
|
+
`proxyCaFile`只控制HTTPS代理信任,`caFile`只控制目标信任,两者互不代替。未设置代理CA使用系统根;提供文件则替换系统根。构造Session时读取默认代理CA一次,请求指定路径时重新读取;请求undefined继承默认,null恢复系统根。直连、HTTP和SOCKS忽略代理CA默认值及请求覆盖。代理地址及凭据、profile、代理CA和目标CA共同隔离连接池。目标证书仍需验证;目标请求禁止设置Proxy-Authorization和Proxy-Connection。认证只用于代理握手,错误消息不回显凭据或代理任意响应短语。握手计入connect和total预算,AbortSignal取消会关闭未建立的隧道;取消单条H2流继续复用健康的共享隧道。代理拒绝后不降级或自动重试。
|
|
188
|
+
|
|
189
|
+
代理测试使用Node测试进程中的独立、仅访问loopback的CONNECT/RFC1928/RFC1929靶场;SDK本身不依赖Python或代理辅助进程。十三组Python行为映射、源码及内置库安装包统一验收;语言适配见PARITY.md。
|
|
190
|
+
|
|
191
|
+
`npm run test:proxy:soak`默认以32路并发运行60秒,轮换正常请求、共享H2流取消、错误认证和停滞握手取消;四种scheme分别使用真实loopback代理。FASTLS_NODE_PROXY_SOAK_SECONDS可设为1..300。Linux当前Go/Node组合执行该独立测试,沿用现有RSS、Go堆、goroutine及句柄阈值,逐批记录写入build/node-proxy-recovery-*.json,不计入225项主套件。
|
|
192
|
+
|
|
193
|
+
代理独立长测与SSE长测一样,显式使用`--max-semi-space-size=16`固定测试进程的V8半空间上限。本机Windows Node24默认策略在持续分配时将new_space保留空间从4MiB扩大至128MiB,而Go堆、原生请求和隧道已回收。该参数仅作用于独立测试进程,不修改SDK用户进程的运行配置,也不放宽RSS或句柄阈值。证据保存运行参数、V8空间快照,以及阈值判定之后的完整GC诊断;判定使用GC前的最终采样。
|
|
194
|
+
|
|
195
|
+
私有HTTPS代理示例:`new Session({ proxy: 'https://proxy.example:443', proxyCaFile: '/path/to/proxy-ca.pem', caFile: '/path/to/target-ca.pem' })`。两类CA分别验证代理与目标;请求可用`{ proxyCaFile: null }`恢复代理系统信任,或指定其它代理CA文件,均不会复用不相符的已有隧道。
|
|
196
|
+
|
|
197
|
+
代理长测按靶场握手阶段同步取消,避免固定毫秒计时在慢runner上提前中止TLS而未触达待测CONNECT阶段。HTTPS靶场延迟50毫秒处理CONNECT,确保该时序在本地也能覆盖。H2可合并同一代理的在途建连;每批每种scheme至少触发一次真实握手取消,并逐批核对启动/回收计数,不依赖请求数等于物理建连数。资源阈值保持不变。
|
|
198
|
+
|
|
199
|
+
## HTTP/2 双工
|
|
200
|
+
|
|
201
|
+
`duplex: true` 允许响应头到达后继续上传,可同时读取响应。仅支持 HTTP/2,并始终禁止自动重定向;普通上传仍拒绝未完成上传时提前返回的 2xx。
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
const response = await session.stream('POST', url, {
|
|
205
|
+
duplex: true,
|
|
206
|
+
body: signal => produceBytes(signal),
|
|
207
|
+
});
|
|
208
|
+
try {
|
|
209
|
+
for await (const chunk of response.body) await consume(chunk);
|
|
210
|
+
await response.waitUpload();
|
|
211
|
+
} finally {
|
|
212
|
+
await response.closeAsync();
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`waitUpload()` 等待原生上传 EOF 接受;需要与响应读取并行,单独等待可能被 HTTP/2 流量控制阻塞。
|
|
217
|
+
手动模式可先发送数据,再等待响应头,也可在获得响应后交替收发:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
const exchange = session.duplex('POST', url);
|
|
221
|
+
try {
|
|
222
|
+
const reading = exchange.response.then(response => consumeResponse(response.body));
|
|
223
|
+
const writing = (async () => {
|
|
224
|
+
await exchange.send(Buffer.from('first'));
|
|
225
|
+
await exchange.finishUpload();
|
|
226
|
+
})();
|
|
227
|
+
await Promise.all([reading, writing]);
|
|
228
|
+
} finally {
|
|
229
|
+
exchange.close();
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`send()` 接受 Buffer/Uint8Array,按64 KiB分块等待原生队列容量;同一 exchange 同时只允许一个 send/finish 调用,重叠调用会被拒绝。发送完成前调用方应保持输入字节不变。
|
|
234
|
+
`finishUpload()` 只关闭上传方向,响应仍可继续读取;响应EOF、取消、超时或会话关闭会停止生产者。
|
|
235
|
+
服务器提前结束上传时,手动send会报错,已返回的响应仍可读取;waitUpload会指出上传EOF未被接受。
|
|
236
|
+
响应头到达后的生产者异常会中止读取并保留原异常。
|
|
237
|
+
|
|
238
|
+
`close()` 立即释放原生请求;`response.closeAsync()` / `session.closeAsync()` 还等待生产者清理,并报告清理中的异常。
|
|
239
|
+
应使用接收 AbortSignal 的工厂或可销毁 Readable 来取消挂起I/O。JavaScript不能中断任意Promise;不配合信号的生产者可能使等待清理的Promise持续挂起,但原生请求仍会及时释放。双工 `read()`、`request()` 和迭代结束也会等待清理。
|
|
240
|
+
示例见 `examples/duplex.mjs`。
|
|
241
|
+
|
|
242
|
+
重复收发、取消和提前拒绝可运行 `npm run test:duplex:soak`(默认60秒,FASTLS_NODE_DUPLEX_SOAK_SECONDS可设为1..300)。Linux当前Go组合把这项独立压力检查纳入CI,检查原生请求、JS生产者任务、RSS、句柄和goroutine的回收。
|
|
243
|
+
|
|
244
|
+
## SSE 事件流
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
const events = await session.sse('GET', 'https://example.com/events', {
|
|
248
|
+
timeoutMs: { total: 3_600_000, read: 45_000 },
|
|
249
|
+
lastEventId: '41', // 可选:上次完整提交的事件 ID
|
|
250
|
+
});
|
|
251
|
+
try {
|
|
252
|
+
for await (const event of events) {
|
|
253
|
+
console.log(event.event, event.id, event.data);
|
|
254
|
+
if (event.data === '[DONE]') break;
|
|
255
|
+
}
|
|
256
|
+
} finally { await events.closeAsync(); }
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`session.sse(method, url, options)` 返回单消费者 `SSEStream`,复用普通请求的配置、Cookie、连接池和 AbortSignal。
|
|
260
|
+
事件为不可变的 `ServerSentEvent`,含 `data`、`event`(默认message)、`id` 和可选的 `retry`(毫秒);`event.json()` 解析 JSON,非法 JSON 抛出 SyntaxError。
|
|
261
|
+
`[DONE]` 是普通数据,由应用决定是否停止。`events.response` 提供响应元数据,请勿同时读取其正文。
|
|
262
|
+
|
|
263
|
+
解析支持任意字节分块、UTF-8替换、初始BOM、CR/LF/CRLF、多行及空data;空行提交事件,EOF丢弃未完成的事件。
|
|
264
|
+
ID跨事件保留,空值重置,含NUL的ID忽略。`events.lastEventId` 仅在完整块结束时更新,包括仅含ID的控制块;`events.retry` 保留最近合法的重试提示。
|
|
265
|
+
retry只接受ASCII数字并截到2147483647;单个事件的retry只反映其当前块。
|
|
266
|
+
`lastEventId` 同时设置请求头和初始解析状态,拒绝NUL/CR/LF。SDK不会自动重连或重放请求。
|
|
267
|
+
|
|
268
|
+
默认请求头为Accept:text/event-stream和Cache-Control:no-cache,请求headers可覆盖,优先于同名会话默认头。
|
|
269
|
+
HTTP200要求唯一的text/event-stream内容类型(可带MIME参数);HTTP204返回空流,其它状态拒绝。
|
|
270
|
+
始终按UTF-8解析;压缩沿用原生解码及完整性检查,收到压缩流时不能关闭decodeContent。
|
|
271
|
+
|
|
272
|
+
`maxEventBytes` 默认1MiB,范围1..64MiB,分别限制原始单行及累计data的UTF-8字节数(含多行分隔符);超限关闭请求。
|
|
273
|
+
无后台事件队列,停止拉取时停止解析和原生读取,整个事件流可超过32MiB缓存上限。解析器还持有当前块、ID、事件类型及解码开销。
|
|
274
|
+
事件之间让出事件循环,确保同一网络块包含大量事件时,计时器和取消仍能执行。
|
|
275
|
+
|
|
276
|
+
默认总超时仍为30秒;可显式增加total(最长24小时),目前不支持null或关闭总超时。
|
|
277
|
+
注释心跳刷新网络read预算,total仍从请求开始计时。
|
|
278
|
+
`cancel()`、`close()`、`closeAsync()` 和提前break沿用响应清理;未开始迭代时也应显式关闭。
|
|
279
|
+
HTTP/2可组合 `{ duplex: true, body: signal => source(signal) }` 边上传边接收事件;上传完成检查为 `events.response.waitUpload()`。
|
|
280
|
+
可运行示例 `examples/sse.mjs`,完整解析规则与Python对应验收见上级SSE.md和本目录PARITY.md。
|
|
281
|
+
`npm run test:sse:soak` 默认以32路并发重复运行60秒,验证正常结束、提前退出、错误头、解析超限和解码失败后的资源回收;可设置FASTLS_NODE_SSE_SOAK_SECONDS为1..300。Linux当前Go组合纳入CI。
|
|
282
|
+
该独立长测固定V8年轻代参数以比较RSS增量,保留原有资源阈值,记录运行参数和堆空间快照;不会更改SDK用户进程的配置,说明见PARITY.md。
|
|
283
|
+
|
|
284
|
+
## WebSocket
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
const ws = await session.websocket('wss://example.com/chat', {
|
|
288
|
+
subprotocols: ['chat.v1'], handshakeTimeoutMs: 30000,
|
|
289
|
+
readTimeoutMs: null, writeTimeoutMs: 30000, closeTimeoutMs: 1000,
|
|
290
|
+
maxMessageBytes: 1024 * 1024,
|
|
291
|
+
});
|
|
292
|
+
try {
|
|
293
|
+
await ws.send('你好');
|
|
294
|
+
const message = await ws.recv(); // string或Buffer,空消息也是有效消息
|
|
295
|
+
} finally { await ws.closeAsync(1000, 'done'); }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
send/ping/pong等到原生写入完成;同时最多一名读者和一名写入者,重叠调用立即报错。recv返回完整消息,入站分片重组由原生完成。默认1MiB消息上限,可设置1..16MiB;控制帧最多125字节。Node组装、复制和返回当前消息另占有界内存,业务持有的消息不计入原生队列。不是无限文件流接口。
|
|
299
|
+
|
|
300
|
+
正常远端关闭抛出WebSocketClosed(code/reason),连接保留closeCode/closeReason。closeAsync的整个握手受closeTimeoutMs约束(最多60000),超时直接释放;abort立即中断。连接级AbortSignal中断当前及后续操作,Session关闭回收全部所属WS。读/写/握手超时使连接终止,FastlsTimeoutError.phase为read/write/handshake;HTTP会话总超时不限定WS寿命。
|
|
301
|
+
|
|
302
|
+
复用Session的profile、头、CA、代理与代理CA;params、请求级覆盖/显式直连沿用HTTP规则。wss始终验证证书和主机名,ALPN适配HTTP/1.1;不承诺适配后的Upgrade头序与浏览器完全一致。使用websocket_v1和三个可选ABI符号,旧库的HTTP继续可用。通知默认共享一条会话连接,poll可显式选择,不阻塞主线程或增加网络线程。
|
|
303
|
+
|
|
304
|
+
默认与HTTP共享Cookie罐,要求原生库提供websocket_cookies能力。cookieMode:"manual"可禁用本次握手的自动收发,并兼容旧库。显式Cookie头(包括空值)优先,默认仍接受握手Set-Cookie;Session的cookies:false禁用自动收发。域/路径/Secure/过期规则与HTTP一致。有效101及普通HTTP拒绝/重定向响应可写回;无效101或TLS失败不写回。Authorization/Origin由调用方设置。无压缩、H2/H3 CONNECT、握手重定向、自动心跳/重连或消息重放。ping只证明写入完成,可用info.websocket.pongs/last_pong_base64观察响应。协议细节与原生资源限制见根目录WEBSOCKET.md。
|
|
305
|
+
|
|
306
|
+
npm run test:quick先运行类型、清单及WS/原生用例;test:ws针对25项WS和16项共享Cookie;test:ws:soak运行独立60秒/32路资源回收。快速反馈不能替代225项源码/安装包完整回归与六组平台验收。
|
|
307
|
+
|
|
308
|
+
原生websocket_cookie_control_v1明确保证按连接禁用自动Cookie,并保留0.10的websocket_cookies契约。仅提供websocket_cookies_v1的历史分发库需要升级:SDK会在建立连接前拒绝manual,防止Cookie被静默发送或保存。无自动Cookie的旧WS库仍支持manual。npm run test:compat用真实原生库验证收发、显式头和失败握手;可通过FASTLS_COOKIE_COMPAT_LIBRARIES传入含library与kind(full/legacy/automatic-only)的JSON数组,逐库在独立进程运行。源码快速检查与隔离安装包都会执行当前库兼容验收。
|
|
309
|
+
|
|
310
|
+
## 原生就绪通知
|
|
311
|
+
|
|
312
|
+
默认使用`notifications: 'auto'`:新库带session_notifications能力且符号完整时启用,旧ABI-v1库继续轮询。`required`缺少能力时在创建原生会话前报错;`poll`显式使用原调度器。
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
const session = await Session.open({ notifications: 'required' });
|
|
316
|
+
try {
|
|
317
|
+
console.log(session.notificationMode); // async-events
|
|
318
|
+
const response = await session.get(url);
|
|
319
|
+
} finally { await session.closeAsync(); }
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
普通`new Session()`仍是同步构造;首次请求自动初始化,也可先`await session.ready(signal)`。Session.open初始化失败会关闭新会话,ready失败允许重试。首次并发请求共享初始化;取消一个等待者不会取消其它等待者,全部退出时关闭未就绪的候选通道。HTTP总预算从创建原生请求开始,通道初始化另限3秒。
|
|
323
|
+
|
|
324
|
+
每会话最多一条经32字节随机令牌认证的IPv4本机TCP连接,监听地址为127.0.0.1临时端口,认证后关闭监听器;候选连接最多两个。线上只有握手和唤醒字节,HTTP数据仍经原ABI读取。没有Go到JavaScript回调或JavaScript工作线程。
|
|
325
|
+
|
|
326
|
+
`notificationMode`为async-events或poll。通道运行中断开会报错并唤醒等待者,不静默回退;关闭后可显式poll重建会话。关闭会话释放通道,closeAsync还等待候选socket关闭及上传清理。状态集合只含活动请求,溢出提示重查本会话全部请求。
|
|
327
|
+
|
|
328
|
+
手动duplex仍同步返回交换对象,send/response在通道就绪后才创建请求;初始化期间关闭交换不会发送HTTP请求。上传、SSE、总超时及AbortSignal继续适用。验收记录包含空闲请求的ABI调用次数,不以单次套件运行时间证明性能优劣。
|
|
329
|
+
|
|
330
|
+
## 调度与边界
|
|
331
|
+
|
|
332
|
+
网络由 Go goroutine 执行。新库默认通过每会话一条本机TCP连接接收就绪提示,Node事件循环随后重查非阻塞C接口,不定时查询空闲请求。
|
|
333
|
+
通知是重查提示,不保证读写一定成功;通过代次快照防止漏唤醒,并独立取消双工的读写等待。
|
|
334
|
+
立即调用会让出事件循环;不承诺固定吞吐量或延迟,原生控制调用及JSON/Buffer转换仍在主线程执行。
|
|
335
|
+
显式poll或旧库回退使用共享约10ms定时器,保留每轮处理数量及时间上限。
|
|
336
|
+
|
|
337
|
+
句柄使用 bigint,无损解析和序列化 ABI JSON 数字;返回的 C 字符串逐次由 fastls_free 释放。
|
|
338
|
+
外部动态库基础能力要求为 `stream_decode`、`phase_timeouts`、`stream_upload`、`write_timeout`;双工、代理、WebSocket 和请求 trailers 在调用时另检查对应能力。加载时检查 ABI 及所需能力,并使用系统级固定加载(Windows PIN / POSIX NODELETE),避免Node环境清理时卸载仍有Go后台线程的动态库。
|
|
339
|
+
原生限制仍适用:最多 64 会话、256 未关闭请求,每会话最多 16 种传输配置。
|
|
340
|
+
代理支持HTTP/HTTPS CONNECT、socks5及socks5h,见下节;WebSocket见对应章节。
|
|
341
|
+
|
|
342
|
+
## 候选包与原生库加载
|
|
343
|
+
|
|
344
|
+
构建本平台Go动态库后,运行 `npm run pack:native`,产物位于上级 `dist/`。
|
|
345
|
+
例如Windows x64:`fastls-fastls-0.10.1-dev.0-win32-x64.tgz`;Linux包名另带`-glibc`。
|
|
346
|
+
在目标项目执行 `npm install <候选包绝对路径> --ignore-scripts`,随后即可 `new Session()`,无需Go、Python、编译器或FASTLS_LIBRARY。
|
|
347
|
+
安装时保留npm可选依赖,Koffi使用对应平台的预编译组件。`npm run test:package`在空工具搜索路径、清除FASTLS_LIBRARY且关闭安装脚本的临时项目中重复完整225项测试。靶场是仓库中已构建的独立测试服务,SDK和动态库均从安装目录加载。
|
|
348
|
+
|
|
349
|
+
当前候选覆盖Windows x64、Linux glibc x64/arm64、macOS x64/arm64,Node22+。每个tarball只含一个系统/架构,不能将同名版本的这些变体当成可直接发布到npm的通用包。npm的os/cpu/libc字段限制安装目标;首次使用还检查平台、ABI声明、库SHA256及Linux的glibc版本。Linux最低glibc从ELF符号版本提取,macOS最低版本从Mach-O提取,具体要求及系统动态依赖见包内`native/manifest.json`,不将CI宿主机上的通过扩大为所有旧系统均已测试。musl不在当前范围。
|
|
350
|
+
|
|
351
|
+
加载顺序:显式 `SessionOptions.library` → `FASTLS_LIBRARY` → 包内 `native/manifest.json` 对应的库 → 已安装主包的本平台可选依赖;源码目录没有内置 manifest 时使用仓库 `dist/`。已安装的 thin 包没有仓库回退。可选原生包必须与主包同版本;缺包、错误平台、glibc 不兼容或摘要不一致均报错。显式路径覆盖仍检查 ABI 与必需能力。SHA-256 只验证内容完整性,不能替代来源签名。
|
|
352
|
+
|
|
353
|
+
清单还保存Go构建来源摘要、模块版本/校验和、FastTLS仓库提交和收集到的许可证文本。`native/THIRD_PARTY_NOTICES.txt`仅是可用材料清单。依赖许可证按实际构建输入收集;imitate已补齐Apache-2.0许可证,npm发布要求所有平台的licenseReviewRequired均为空。GitHub内置库候选保持private且没有安装/构建脚本;npm主包与平台包的发布流程见NPM_RELEASE.md。
|
|
354
|
+
|
|
355
|
+
npm 拆分包通过 `npm run test:package` 后再执行 `npm run test:npm` 生成,主包与本机原生包位于 `dist/npm/`。五个同名同版本的 GitHub 内置库候选不能作为五个 npm 版本发布。首次 registry 发布必须使用同提交 main CI 汇总得到的六包组,详见 NPM_RELEASE.md。
|
|
356
|
+
|
|
357
|
+
## 验证
|
|
358
|
+
|
|
359
|
+
所有命令从仓库根目录执行,先完成动态库和测试服务构建。
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
npm run test:quick --prefix node
|
|
363
|
+
npm test --prefix node
|
|
364
|
+
npm run test:package --prefix node
|
|
365
|
+
npm run test:npm --prefix node
|
|
366
|
+
python node/scripts/test-npm-release.py
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`test:quick` 提供类型、映射、原生绑定、WebSocket 和 Cookie 兼容的短反馈。`npm test`、`test:package`、`test:npm` 分别运行源码、内置库候选、npm 拆分包的完整 225 项套件:十五组 Python 的 209 项行为映射,加 2 项绑定和 14 项 Node 专有测试。类型、Cookie 真实库兼容、安装元数据和发布边界检查另计;当前发布边界测试为 9 项。
|
|
370
|
+
|
|
371
|
+
两类安装测试都在独立消费项目运行,关闭安装脚本、清除外部库环境变量并移除编译工具搜索路径。拆分包测试由临时 registry 提供尚未发布的本项目平台包,npm 自行按平台筛选;其它依赖仍使用正常 registry。
|
|
372
|
+
|
|
373
|
+
主 CI 覆盖五个平台的 Node 24,以及 Linux x64 的 Node 22 组合,复用固定的原生构建输入。源码证据位于 `build/node-*.json`;内置候选和摘要在 `dist/`;拆分包、`npm-package-info.json` 和独立安装证据在 `dist/npm/`。具体映射、资源阈值及长测命令见 PARITY.md。功能检查和资源长测分开执行,不把清单存在或历史绿色结果当作新提交已通过。
|
|
374
|
+
|
|
375
|
+
## 与 Python 的语言差异
|
|
376
|
+
|
|
377
|
+
Node统一使用Promise API,不提供Python同步Session或共享同步会话多线程调用;对应场景采用同步迭代器输入和96路Promise请求验证,不宣称线程安全。Node Readable按自身流的生命周期管理文件句柄,区别于Python保留调用方文件对象。
|
|
378
|
+
JavaScript无法强行中断任意Promise:取消会立即释放原生请求,并请求迭代器return/destroy;生产者正在等待的自定义I/O必须支持取消,才能保证其finally及时执行。推荐使用接收AbortSignal的上传工厂。Python的协程强制取消场景对应为工厂信号取消,报告中明确标记适配。
|
|
379
|
+
|
|
380
|
+
## 发布与授权
|
|
381
|
+
|
|
382
|
+
维护者在 main 的同提交完整 CI 成功后,运行 `npm-publish.yml` 准备或发布 npm 六包组;首次发布、账号权限、2FA、Trusted Publisher 和重试步骤见 NPM_RELEASE.md。
|
|
383
|
+
|
|
384
|
+
`node-release.yml` 单独签发 GitHub 内置库候选和 thin 包,生成 SHA256SUMS、Sigstore 来源证明及清单,不会发布 npm。既有 `node-v0.10.1-dev.0` GitHub 预发布使用历史包名 `@fasttls/fastls`;本次 npm 包名为 `@fastls/fastls`,两套归档不能互换。后续 GitHub 签发使用新版本号,不能覆盖既有 tag 的来源记录。
|
|
385
|
+
|
|
386
|
+
每次构建收集原生依赖许可证,npm 发布要求五个平台的 `licenseReviewRequired` 均为空。`imitate` 已固定到补齐 Apache-2.0 的提交;是否满足要求以本次候选清单为准。发布状态以 npm registry 和已完成的工作流为准。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CookieOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
domain?: string;
|
|
5
|
+
secure?: boolean;
|
|
6
|
+
httpOnly?: boolean;
|
|
7
|
+
maxAge?: number;
|
|
8
|
+
/** RFC3339 timestamp. */
|
|
9
|
+
expires?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Cookie {
|
|
12
|
+
name: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class Cookies {
|
|
16
|
+
private readonly command;
|
|
17
|
+
constructor(command: (input: unknown) => unknown);
|
|
18
|
+
getAll(url: string): Cookie[];
|
|
19
|
+
get(name: string, url: string): string | undefined;
|
|
20
|
+
set(name: string, value: string, options: CookieOptions): void;
|
|
21
|
+
delete(name: string, options: Pick<CookieOptions, "url" | "path" | "domain">): void;
|
|
22
|
+
clear(): void;
|
|
23
|
+
}
|