feat: 添加多项功能和修复
功能新增: - OSS 存储使用情况显示(文件页面) - OSS 当日流量统计(阿里云云监控API) - 分享页面路由修复(/s/xxx 格式支持) Bug修复: - 修复分享页面资源路径(相对路径改绝对路径) - 修复分享码获取逻辑(支持路径格式) - 修复OSS配额undefined显示问题 - 修复登录流程OSS配置检查 - 修复文件数为null时的显示问题 依赖更新: - 添加 @alicloud/cms20190101 云监控SDK - 添加 @alicloud/openapi-client Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
31
backend/node_modules/httpx/lib/index.d.ts
generated
vendored
Normal file
31
backend/node_modules/httpx/lib/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IncomingMessage, Agent, OutgoingHttpHeaders } from "http";
|
||||
import { Readable } from "stream";
|
||||
|
||||
export interface Options {
|
||||
'method'?: string;
|
||||
'readTimeout'?: number;
|
||||
'connectTimeout'?: number;
|
||||
'timeout'?: number;
|
||||
'agent'?: Agent;
|
||||
'headers'?: OutgoingHttpHeaders;
|
||||
'rejectUnauthorized'?: boolean;
|
||||
'compression'?: boolean;
|
||||
'beforeRequest'?(options: Options): void;
|
||||
'data'?: string | Buffer | Readable | undefined;
|
||||
'key'?: string;
|
||||
'cert'?: string;
|
||||
'ca'?: string;
|
||||
}
|
||||
|
||||
export function request(url: string, options: Options): Promise<IncomingMessage>;
|
||||
|
||||
export function read(response: IncomingMessage, encoding: string): Promise<string|Buffer>;
|
||||
|
||||
export interface Event {
|
||||
'data'?: string;
|
||||
'id'?: string;
|
||||
'event'?: string;
|
||||
'retry'?: number;
|
||||
}
|
||||
|
||||
export function readAsSSE(response: IncomingMessage): AsyncGenerator<Event, void, unknown>;
|
||||
396
backend/node_modules/httpx/lib/index.js
generated
vendored
Normal file
396
backend/node_modules/httpx/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,396 @@
|
||||
'use strict';
|
||||
|
||||
const zlib = require('zlib');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const parse = require('url').parse;
|
||||
const format = require('url').format;
|
||||
|
||||
const debugBody = require('debug')('httpx:body');
|
||||
const debugHeader = require('debug')('httpx:header');
|
||||
|
||||
const httpAgent = new http.Agent({ keepAlive: true });
|
||||
const httpsAgent = new https.Agent({ keepAlive: true });
|
||||
|
||||
const TIMEOUT = 3000; // 3s
|
||||
|
||||
const READ_TIMER = Symbol('TIMER::READ_TIMER');
|
||||
const READ_TIME_OUT = Symbol('TIMER::READ_TIME_OUT');
|
||||
const READ_TIMER_START_AT = Symbol('TIMER::READ_TIMER_START_AT');
|
||||
|
||||
/**
|
||||
* Check the content-encoding header, and auto decompress it.
|
||||
* @param {Readable} response http response
|
||||
* @returns Readable
|
||||
*/
|
||||
function decompress(response) {
|
||||
switch (response.headers['content-encoding']) {
|
||||
// or, just use zlib.createUnzip() to handle both cases
|
||||
case 'gzip':
|
||||
return response.pipe(zlib.createGunzip());
|
||||
case 'deflate':
|
||||
return response.pipe(zlib.createInflate());
|
||||
default:
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
var append = function (err, name, message) {
|
||||
err.name = name + err.name;
|
||||
err.message = `${message}. ${err.message}`;
|
||||
return err;
|
||||
};
|
||||
|
||||
const isNumber = function (num) {
|
||||
return num !== null && !isNaN(num);
|
||||
};
|
||||
|
||||
exports.request = function (url, opts) {
|
||||
opts || (opts = {});
|
||||
|
||||
const parsed = typeof url === 'string' ? parse(url) : url;
|
||||
|
||||
let readTimeout, connectTimeout;
|
||||
if (isNumber(opts.readTimeout) || isNumber(opts.connectTimeout)) {
|
||||
readTimeout = isNumber(opts.readTimeout) ? Number(opts.readTimeout) : TIMEOUT;
|
||||
connectTimeout = isNumber(opts.connectTimeout) ? Number(opts.connectTimeout) : TIMEOUT;
|
||||
} else if (isNumber(opts.timeout)) {
|
||||
readTimeout = connectTimeout = Number(opts.timeout);
|
||||
} else {
|
||||
readTimeout = connectTimeout = TIMEOUT;
|
||||
}
|
||||
|
||||
const isHttps = parsed.protocol === 'https:';
|
||||
const method = (opts.method || 'GET').toUpperCase();
|
||||
const defaultAgent = isHttps ? httpsAgent : httpAgent;
|
||||
const agent = opts.agent || defaultAgent;
|
||||
|
||||
var options = {
|
||||
host: parsed.hostname || 'localhost',
|
||||
path: parsed.path || '/',
|
||||
method: method,
|
||||
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
||||
agent: agent,
|
||||
headers: opts.headers || {},
|
||||
// ssl config
|
||||
key: opts.key || '',
|
||||
cert: opts.cert || '',
|
||||
ca: opts.ca || '',
|
||||
// connect timerout
|
||||
timeout: connectTimeout
|
||||
};
|
||||
|
||||
if (isHttps && typeof opts.rejectUnauthorized !== 'undefined') {
|
||||
options.rejectUnauthorized = opts.rejectUnauthorized;
|
||||
}
|
||||
|
||||
if (opts.compression) {
|
||||
options.headers['accept-encoding'] = 'gzip,deflate';
|
||||
}
|
||||
|
||||
const httplib = isHttps ? https : http;
|
||||
|
||||
if (typeof opts.beforeRequest === 'function') {
|
||||
options = opts.beforeRequest(options);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = httplib.request(options);
|
||||
const body = opts.data;
|
||||
|
||||
var fulfilled = (response) => {
|
||||
if (debugHeader.enabled) {
|
||||
const requestHeaders = response.req._header;
|
||||
requestHeaders.split('\r\n').forEach((line) => {
|
||||
debugHeader('> %s', line);
|
||||
});
|
||||
|
||||
debugHeader('< HTTP/%s %s %s', response.httpVersion, response.statusCode, response.statusMessage);
|
||||
Object.keys(response.headers).forEach((key) => {
|
||||
debugHeader('< %s: %s', key, response.headers[key]);
|
||||
});
|
||||
}
|
||||
resolve(response);
|
||||
};
|
||||
|
||||
var rejected = (err) => {
|
||||
err.message += `${method} ${format(parsed)} failed.`;
|
||||
// clear response timer when error
|
||||
if (request.socket && request.socket[READ_TIMER]) {
|
||||
clearTimeout(request.socket[READ_TIMER]);
|
||||
}
|
||||
reject(err);
|
||||
};
|
||||
|
||||
var abort = (err) => {
|
||||
request.abort();
|
||||
rejected(err);
|
||||
};
|
||||
|
||||
const startResponseTimer = function (socket) {
|
||||
const timer = setTimeout(() => {
|
||||
if (socket[READ_TIMER]) {
|
||||
clearTimeout(socket[READ_TIMER]);
|
||||
socket[READ_TIMER] = null;
|
||||
}
|
||||
var err = new Error();
|
||||
var message = `ReadTimeout(${readTimeout})`;
|
||||
abort(append(err, 'RequestTimeout', message));
|
||||
}, readTimeout);
|
||||
// start read-timer
|
||||
socket[READ_TIME_OUT] = readTimeout;
|
||||
socket[READ_TIMER] = timer;
|
||||
socket[READ_TIMER_START_AT] = Date.now();
|
||||
// don't block the loop
|
||||
timer.unref();
|
||||
};
|
||||
|
||||
// string
|
||||
if (!body || 'string' === typeof body || body instanceof Buffer) {
|
||||
if (debugBody.enabled) {
|
||||
if (!body) {
|
||||
debugBody('<no request body>');
|
||||
} else if ('string' === typeof body) {
|
||||
debugBody(body);
|
||||
} else {
|
||||
debugBody(`Buffer <ignored>, Buffer length: ${body.length}`);
|
||||
}
|
||||
}
|
||||
request.end(body);
|
||||
} else if ('function' === typeof body.pipe) { // stream
|
||||
body.pipe(request);
|
||||
if (debugBody.enabled) {
|
||||
debugBody('<request body is a stream>');
|
||||
}
|
||||
body.once('error', (err) => {
|
||||
abort(append(err, 'HttpX', 'Stream occor error'));
|
||||
});
|
||||
}
|
||||
|
||||
request.on('timeout', () => {
|
||||
var err = new Error();
|
||||
err.name = 'RequestTimeoutError';
|
||||
err.message = `ConnectTimeout: Connect ${url} failed.`;
|
||||
request.destroy();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
request.on('response', fulfilled);
|
||||
request.on('error', rejected);
|
||||
request.once('socket', function (socket) {
|
||||
// reuse socket
|
||||
if (socket.readyState === 'opening') {
|
||||
socket.once('connect', function () {
|
||||
startResponseTimer(socket);
|
||||
});
|
||||
} else {
|
||||
startResponseTimer(socket);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.read = function (response, encoding) {
|
||||
const readable = decompress(response);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// node.js 14 use response.client
|
||||
const socket = response.socket || response.client;
|
||||
|
||||
const makeReadTimeoutError = () => {
|
||||
const req = response.req;
|
||||
var err = new Error();
|
||||
err.name = 'RequestTimeoutError';
|
||||
err.message = `ReadTimeout: ${socket[READ_TIME_OUT]}. ${req.method} ${req.path} failed.`;
|
||||
return err;
|
||||
};
|
||||
// check read-timer
|
||||
let readTimer;
|
||||
const oldReadTimer = socket[READ_TIMER];
|
||||
if (!oldReadTimer) {
|
||||
reject(makeReadTimeoutError());
|
||||
return;
|
||||
}
|
||||
const remainTime = socket[READ_TIME_OUT] - (Date.now() - socket[READ_TIMER_START_AT]);
|
||||
clearTimeout(oldReadTimer);
|
||||
if (remainTime <= 0) {
|
||||
reject(makeReadTimeoutError());
|
||||
return;
|
||||
}
|
||||
readTimer = setTimeout(function () {
|
||||
reject(makeReadTimeoutError());
|
||||
}, remainTime);
|
||||
|
||||
// start reading data
|
||||
var onError, onData, onEnd;
|
||||
var cleanup = function () {
|
||||
// cleanup
|
||||
readable.removeListener('error', onError);
|
||||
readable.removeListener('data', onData);
|
||||
readable.removeListener('end', onEnd);
|
||||
// clear read timer
|
||||
if (readTimer) {
|
||||
clearTimeout(readTimer);
|
||||
}
|
||||
};
|
||||
|
||||
const bufs = [];
|
||||
var size = 0;
|
||||
|
||||
onData = function (buf) {
|
||||
bufs.push(buf);
|
||||
size += buf.length;
|
||||
};
|
||||
|
||||
onError = function (err) {
|
||||
cleanup();
|
||||
reject(err);
|
||||
};
|
||||
|
||||
onEnd = function () {
|
||||
cleanup();
|
||||
var buff = Buffer.concat(bufs, size);
|
||||
|
||||
debugBody('');
|
||||
if (encoding) {
|
||||
const result = buff.toString(encoding);
|
||||
debugBody(result);
|
||||
return resolve(result);
|
||||
}
|
||||
|
||||
if (debugBody.enabled) {
|
||||
debugBody(buff.toString());
|
||||
}
|
||||
resolve(buff);
|
||||
};
|
||||
|
||||
readable.on('error', onError);
|
||||
readable.on('data', onData);
|
||||
readable.on('end', onEnd);
|
||||
});
|
||||
};
|
||||
|
||||
function readyToRead(readable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var onReadable, onEnd, onError;
|
||||
var cleanup = function () {
|
||||
// cleanup
|
||||
readable.removeListener('error', onError);
|
||||
readable.removeListener('end', onEnd);
|
||||
readable.removeListener('readable', onReadable);
|
||||
};
|
||||
|
||||
onReadable = function () {
|
||||
cleanup();
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
onEnd = function () {
|
||||
cleanup();
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
onError = function (err) {
|
||||
cleanup();
|
||||
reject(err);
|
||||
};
|
||||
|
||||
readable.once('readable', onReadable);
|
||||
readable.once('end', onEnd);
|
||||
readable.once('error', onError);
|
||||
});
|
||||
}
|
||||
|
||||
class Event {
|
||||
constructor(id, event, data, retry) {
|
||||
this.id = id;
|
||||
this.event = event;
|
||||
this.data = data;
|
||||
this.retry = retry;
|
||||
}
|
||||
}
|
||||
|
||||
exports.Event = Event;
|
||||
|
||||
const DATA_PREFIX = 'data:';
|
||||
const EVENT_PREFIX = 'event:';
|
||||
const ID_PREFIX = 'id:';
|
||||
const RETRY_PREFIX = 'retry:';
|
||||
|
||||
function isDigitsOnly(str) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str.charAt(i);
|
||||
if (c < '0' || c > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return str.length > 0;
|
||||
}
|
||||
|
||||
function tryGetEvents(head, chunk) {
|
||||
const all = head + chunk;
|
||||
let start = 0;
|
||||
const events = [];
|
||||
for (let i = 0; i < all.length - 1; i++) {
|
||||
const c = all[i];
|
||||
const c2 = all[i + 1];
|
||||
if (c === '\n' && c2 === '\n') {
|
||||
const part = all.substring(start, i);
|
||||
const lines = part.split('\n');
|
||||
const event = new Event();
|
||||
lines.forEach((line) => {
|
||||
if (line.startsWith(DATA_PREFIX)) {
|
||||
event.data = line.substring(DATA_PREFIX.length).trim();
|
||||
} else if (line.startsWith(EVENT_PREFIX)) {
|
||||
event.event = line.substring(EVENT_PREFIX.length).trim();
|
||||
} else if (line.startsWith(ID_PREFIX)) {
|
||||
event.id = line.substring(ID_PREFIX.length).trim();
|
||||
} else if (line.startsWith(RETRY_PREFIX)) {
|
||||
const retry = line.substring(RETRY_PREFIX.length).trim();
|
||||
if (isDigitsOnly(retry)) {
|
||||
event.retry = parseInt(retry, 10);
|
||||
}
|
||||
} else if (line.startsWith(':')) {
|
||||
// ignore the line
|
||||
}
|
||||
});
|
||||
events.push(event);
|
||||
start = i + 2;
|
||||
}
|
||||
}
|
||||
|
||||
const rest = all.substring(start);
|
||||
return [events, rest];
|
||||
}
|
||||
|
||||
/**
|
||||
* consume response and parse to event stream
|
||||
* @param {ReadableStream} response
|
||||
* @returns AsyncGenerator<Event, void, unknown>
|
||||
*/
|
||||
exports.readAsSSE = async function* (response) {
|
||||
const readable = decompress(response);
|
||||
|
||||
const socket = response.socket || response.client;
|
||||
clearTimeout(socket[READ_TIMER]);
|
||||
|
||||
let rest = '';
|
||||
|
||||
while (true) {
|
||||
const ended = await readyToRead(readable);
|
||||
if (ended) {
|
||||
return;
|
||||
}
|
||||
|
||||
let chunk;
|
||||
while (null !== (chunk = readable.read())) {
|
||||
const [ events, remain ] = tryGetEvents(rest, chunk.toString());
|
||||
rest = remain;
|
||||
if (events && events.length > 0) {
|
||||
for (const event of events) {
|
||||
yield event;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user