@juzi/file-box 1.8.6 → 1.8.9

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/src/misc.ts CHANGED
@@ -91,6 +91,15 @@ export async function httpHeadHeader (url: string, headers: http.OutgoingHttpHea
91
91
  }
92
92
 
93
93
  const res = await fetchHead(url, headers, proxyUrl)
94
+
95
+ // HEAD 返回 4xx 时,服务器可能违规携带 body(如 425 返回 HTML),
96
+ // 这些 body 字节会残留在 keep-alive socket 中,污染连接池。
97
+ // 直接销毁 socket 并抛错,让 httpStream 走非 Range 模式用新连接。
98
+ if (res.statusCode && res.statusCode >= 400) {
99
+ res.socket?.destroy()
100
+ res.destroy()
101
+ throw new Error(`HEAD returned ${res.statusCode}`)
102
+ }
94
103
  res.destroy()
95
104
 
96
105
  if (!/^3/.test(String(res.statusCode))) {
@@ -127,8 +136,15 @@ export function httpHeaderToFileName (headers: http.IncomingHttpHeaders): null |
127
136
  }
128
137
 
129
138
  export async function httpStream (url: string, headers: http.OutgoingHttpHeaders = {}, proxyUrl?: string): Promise<Readable> {
130
- const headHeaders = await httpHeadHeader(url, headers, proxyUrl)
131
- if (headHeaders.location) {
139
+ let headHeaders: http.IncomingHttpHeaders | undefined
140
+ try {
141
+ headHeaders = await httpHeadHeader(url, headers, proxyUrl)
142
+ } catch {
143
+ // HEAD 完全失败(如 HPE 解析错误 + Range probe 也失败)
144
+ // 跳过 HEAD 阶段,直接以非 Range 模式下载
145
+ }
146
+
147
+ if (headHeaders?.location) {
132
148
  url = headHeaders.location
133
149
  }
134
150
  const { protocol, hostname, port } = new URL(url)
@@ -143,20 +159,18 @@ export async function httpStream (url: string, headers: http.OutgoingHttpHeaders
143
159
  const defaultPort = protocol === 'https:' ? '443' : '80'
144
160
  const hostKey = `${hostname}:${port || defaultPort}`
145
161
 
146
- // A2:若 HEAD 明确声明 Accept-Ranges: none,记录到运行期黑名单
147
- // 以便 downloadFileInChunks 本次请求就直接以非 Range 模式发起
148
- // Accept-Ranges header 可能是 string | string[],归一化后匹配 'none'
149
- const acceptRangesRaw = headHeaders['accept-ranges']
150
- const acceptRanges = Array.isArray(acceptRangesRaw) ? acceptRangesRaw[0] : acceptRangesRaw
151
- if (typeof acceptRanges === 'string' && acceptRanges.trim().toLowerCase() === 'none') {
162
+ if (headHeaders) {
163
+ // A2:若 HEAD 明确声明 Accept-Ranges: none,记录到运行期黑名单
164
+ const acceptRangesRaw = headHeaders['accept-ranges']
165
+ const acceptRanges = Array.isArray(acceptRangesRaw) ? acceptRangesRaw[0] : acceptRangesRaw
166
+ if (typeof acceptRanges === 'string' && acceptRanges.trim().toLowerCase() === 'none') {
167
+ addUnsupportedRangeDomain(hostKey)
168
+ }
169
+ } else {
170
+ // HEAD 失败:加入黑名单,downloadFileInChunks 将直接以非 Range 模式发起
152
171
  addUnsupportedRangeDomain(hostKey)
153
172
  }
154
173
 
155
- // 直接尝试分片下载,不检查 fileSize
156
- // 原因:
157
- // 1. 有些服务器 HEAD 不返回 Accept-Ranges 但实际支持分片
158
- // 2. 有些服务器 HEAD 返回 fileSize=0 但实际支持分片
159
- // downloadFileInChunks 内部有完善的回退机制处理不支持的情况(见 B1)
160
174
  const result = await downloadFileInChunks(url, options, proxyUrl, hostKey)
161
175
  return result
162
176
  }
@@ -243,8 +257,9 @@ async function fetch (url: string, options: http.RequestOptions, proxyUrl?: stri
243
257
  async function fetchHead (url: string, headers: http.OutgoingHttpHeaders = {}, proxyUrl?: string): Promise<http.IncomingMessage> {
244
258
  try {
245
259
  return await fetch(url, {
246
- headers,
260
+ headers: { ...headers, Connection: 'close' },
247
261
  method: 'HEAD',
262
+ agent: false,
248
263
  }, proxyUrl)
249
264
  } catch (error) {
250
265
  if (!shouldFallbackHeadToRangeGet(error)) {
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * This file was auto generated from scripts/generate-version.sh
3
3
  */
4
- export const VERSION: string = '1.8.6'
4
+ export const VERSION: string = '1.8.9'