Download http.js from bingn/ikun2: direct link, hf CLI and curl.
- Browser
- Download file 6.8 kB
-
https://huggingface.co/spaces/bingn/ikun2/resolve/main/http.js
- Command line
-
hf download hf://spaces/bingn/ikun2/http.js
-
curl -L -o http.js https://huggingface.co/spaces/bingn/ikun2/resolve/main/http.js
6.8 kB
| /** | |
| * http.js - HTTP 请求工具模块 | |
| * 纯 Node.js 原生 https/http 实现,零依赖 | |
| */ | |
| import https from 'https'; | |
| import http from 'http'; | |
| import { URL } from 'url'; | |
| import config from './config.js'; | |
| const sleep = (ms) => new Promise(r => setTimeout(r, ms)); | |
| /** | |
| * 通用 HTTP 请求 — 支持 cookie 管理、重定向跟踪、JSON 自动解析 | |
| */ | |
| export async function request(url, options = {}) { | |
| const { | |
| method = 'GET', | |
| headers = {}, | |
| body = null, | |
| timeout = config.timeout || 30000, | |
| followRedirect = true, | |
| maxRedirects = 5, | |
| cookies = null, | |
| } = options; | |
| const urlObj = new URL(url); | |
| const isHttps = urlObj.protocol === 'https:'; | |
| const lib = isHttps ? https : http; | |
| const finalHeaders = { | |
| 'User-Agent': config.userAgent, | |
| 'Accept': 'application/json, text/html, */*', | |
| 'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8,zh-CN;q=0.7', | |
| 'Accept-Encoding': 'identity', | |
| ...headers, | |
| }; | |
| // 注入 cookies | |
| if (cookies && cookies.size > 0) { | |
| finalHeaders['Cookie'] = [...cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; '); | |
| } | |
| // 处理 body | |
| let bodyStr = null; | |
| if (body) { | |
| bodyStr = typeof body === 'string' ? body : JSON.stringify(body); | |
| if (!finalHeaders['Content-Type']) { | |
| finalHeaders['Content-Type'] = 'application/json'; | |
| } | |
| finalHeaders['Content-Length'] = Buffer.byteLength(bodyStr); | |
| } | |
| return new Promise((resolve, reject) => { | |
| const reqOptions = { | |
| hostname: urlObj.hostname, | |
| port: urlObj.port || (isHttps ? 443 : 80), | |
| path: urlObj.pathname + urlObj.search, | |
| method, | |
| headers: finalHeaders, | |
| timeout, | |
| }; | |
| const req = lib.request(reqOptions, (res) => { | |
| // 收集 Set-Cookie | |
| const resCookies = new Map(cookies || []); | |
| const setCookieHeaders = res.headers['set-cookie'] || []; | |
| for (const sc of setCookieHeaders) { | |
| const [pair] = sc.split(';'); | |
| const [name, ...valueParts] = pair.split('='); | |
| resCookies.set(name.trim(), valueParts.join('=').trim()); | |
| } | |
| // 处理重定向 | |
| if (followRedirect && [301, 302, 303, 307, 308].includes(res.statusCode) && maxRedirects > 0) { | |
| const location = res.headers['location']; | |
| if (location) { | |
| const redirectUrl = location.startsWith('http') ? location : new URL(location, url).href; | |
| res.resume(); // 丢弃 body | |
| resolve(request(redirectUrl, { | |
| ...options, | |
| maxRedirects: maxRedirects - 1, | |
| cookies: resCookies, | |
| method: [301, 302, 303].includes(res.statusCode) ? 'GET' : method, | |
| body: [301, 302, 303].includes(res.statusCode) ? null : body, | |
| })); | |
| return; | |
| } | |
| } | |
| let data = ''; | |
| res.setEncoding('utf8'); | |
| res.on('data', chunk => data += chunk); | |
| res.on('end', () => { | |
| resolve({ | |
| ok: res.statusCode >= 200 && res.statusCode < 300, | |
| status: res.statusCode, | |
| statusText: res.statusMessage, | |
| headers: res.headers, | |
| cookies: resCookies, | |
| url, | |
| text: () => data, | |
| json: () => { | |
| try { return JSON.parse(data); } | |
| catch { throw new Error(`JSON parse error: ${data.substring(0, 200)}`); } | |
| }, | |
| html: () => data, | |
| }); | |
| }); | |
| }); | |
| req.on('error', reject); | |
| req.on('timeout', () => { req.destroy(); reject(new Error(`请求超时: ${url}`)); }); | |
| if (bodyStr) req.write(bodyStr); | |
| req.end(); | |
| }); | |
| } | |
| /** | |
| * GET 请求简写 | |
| */ | |
| export async function get(url, options = {}) { | |
| return request(url, { ...options, method: 'GET' }); | |
| } | |
| /** | |
| * POST 请求简写 | |
| */ | |
| export async function post(url, body, options = {}) { | |
| return request(url, { ...options, method: 'POST', body }); | |
| } | |
| /** | |
| * 带重试的请求 | |
| */ | |
| export async function retryRequest(url, options = {}, maxRetries = 3) { | |
| let lastError; | |
| for (let i = 0; i < maxRetries; i++) { | |
| try { | |
| const resp = await request(url, options); | |
| if (resp.ok || resp.status < 500) return resp; | |
| lastError = new Error(`HTTP ${resp.status}: ${resp.text().substring(0, 200)}`); | |
| } catch (e) { | |
| lastError = e; | |
| } | |
| if (i < maxRetries - 1) { | |
| const delay = 1000 * (i + 1) + Math.random() * 1000; | |
| console.log(` [HTTP] 重试 ${i + 1}/${maxRetries} (${Math.round(delay)}ms)...`); | |
| await sleep(delay); | |
| } | |
| } | |
| throw lastError; | |
| } | |
| export { sleep }; | |
| /** | |
| * 流式 HTTP 请求 — 返回原始响应流 (不缓冲 body) | |
| * 用于代理 chataibot SSE 流式响应 | |
| */ | |
| export async function requestStream(url, options = {}) { | |
| const { | |
| method = 'GET', | |
| headers = {}, | |
| body = null, | |
| timeout = config.timeout || 120000, | |
| cookies = null, | |
| } = options; | |
| const urlObj = new URL(url); | |
| const isHttps = urlObj.protocol === 'https:'; | |
| const lib = isHttps ? https : http; | |
| const finalHeaders = { | |
| 'User-Agent': config.userAgent, | |
| 'Accept': 'text/event-stream, application/json', | |
| 'Accept-Language': 'en', | |
| 'Accept-Encoding': 'identity', | |
| ...headers, | |
| }; | |
| if (cookies && cookies.size > 0) { | |
| finalHeaders['Cookie'] = [...cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; '); | |
| } | |
| let bodyStr = null; | |
| if (body) { | |
| bodyStr = typeof body === 'string' ? body : JSON.stringify(body); | |
| if (!finalHeaders['Content-Type']) { | |
| finalHeaders['Content-Type'] = 'application/json'; | |
| } | |
| finalHeaders['Content-Length'] = Buffer.byteLength(bodyStr); | |
| } | |
| return new Promise((resolve, reject) => { | |
| const reqOptions = { | |
| hostname: urlObj.hostname, | |
| port: urlObj.port || (isHttps ? 443 : 80), | |
| path: urlObj.pathname + urlObj.search, | |
| method, | |
| headers: finalHeaders, | |
| timeout, | |
| }; | |
| const req = lib.request(reqOptions, (res) => { | |
| const resCookies = new Map(cookies || []); | |
| const setCookieHeaders = res.headers['set-cookie'] || []; | |
| for (const sc of setCookieHeaders) { | |
| const [pair] = sc.split(';'); | |
| const [name, ...valueParts] = pair.split('='); | |
| resCookies.set(name.trim(), valueParts.join('=').trim()); | |
| } | |
| resolve({ | |
| ok: res.statusCode >= 200 && res.statusCode < 300, | |
| status: res.statusCode, | |
| headers: res.headers, | |
| cookies: resCookies, | |
| stream: res, | |
| }); | |
| }); | |
| req.on('error', reject); | |
| req.on('timeout', () => { req.destroy(); reject(new Error(`流式请求超时: ${url}`)); }); | |
| if (bodyStr) req.write(bodyStr); | |
| req.end(); | |
| }); | |
| } | |