mirror of
https://github.com/certd/certd.git
synced 2026-05-14 12:07:32 +08:00
perf(network): 新增全局公共http请求 headers设置
1. 新增公共请求头配置项,支持在系统设置中配置全局请求头 2. 实现请求头解析工具方法,支持多行KEY=VALUE格式配置 3. 在请求发起时自动附加全局公共请求头,且不会覆盖请求中已存在的同名Header 4. 添加多语言配置与前端表单组件,完善配置界面 5. 新增单元测试验证全局请求头合并逻辑
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { expect } from "chai";
|
||||
import { createAxiosService, HttpClient, setGlobalHeaders } from "./util.request.js";
|
||||
import { ILogger } from "./util.log.js";
|
||||
|
||||
const testLogger = {
|
||||
info() {},
|
||||
error() {},
|
||||
} as unknown as ILogger;
|
||||
|
||||
describe("util.request", () => {
|
||||
afterEach(() => {
|
||||
setGlobalHeaders({});
|
||||
});
|
||||
|
||||
it("should merge global headers without overriding request headers", async () => {
|
||||
setGlobalHeaders({
|
||||
"X-Common": "common",
|
||||
"X-Override": "global",
|
||||
});
|
||||
|
||||
const http = createAxiosService({ logger: testLogger }) as HttpClient;
|
||||
const res = await http.request({
|
||||
url: "http://example.com",
|
||||
method: "get",
|
||||
logReq: false,
|
||||
logRes: false,
|
||||
headers: {
|
||||
"X-Override": "request",
|
||||
"X-Request": "request",
|
||||
},
|
||||
adapter: async config => {
|
||||
const headers = config.headers;
|
||||
return {
|
||||
config,
|
||||
data: {
|
||||
common: headers.get("X-Common"),
|
||||
override: headers.get("X-Override"),
|
||||
request: headers.get("X-Request"),
|
||||
},
|
||||
headers: {},
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
expect(res).to.deep.equal({
|
||||
common: "common",
|
||||
override: "request",
|
||||
request: "request",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -82,6 +82,7 @@ export class HttpError extends Error {
|
||||
export const HttpCommonError = HttpError;
|
||||
|
||||
let defaultAgents = createAgent();
|
||||
let defaultHeaders: Record<string, string> = {};
|
||||
|
||||
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
||||
logger.info("setGlobalProxy:", opts);
|
||||
@@ -92,6 +93,15 @@ export function getGlobalAgents() {
|
||||
return defaultAgents;
|
||||
}
|
||||
|
||||
export function setGlobalHeaders(headers: Record<string, string> = {}) {
|
||||
logger.info("setGlobalHeaders:", Object.keys(headers));
|
||||
defaultHeaders = { ...headers };
|
||||
}
|
||||
|
||||
export function getGlobalHeaders() {
|
||||
return defaultHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建请求实例
|
||||
*/
|
||||
@@ -148,6 +158,12 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
||||
config.httpsAgent = agents.httpsAgent;
|
||||
config.httpAgent = agents.httpAgent;
|
||||
|
||||
if (Object.keys(defaultHeaders).length > 0) {
|
||||
const headers = AxiosHeaders.from(defaultHeaders);
|
||||
headers.set(config.headers || {});
|
||||
config.headers = headers;
|
||||
}
|
||||
|
||||
// const agent = new https.Agent({
|
||||
// rejectUnauthorized: false // 允许自签名证书
|
||||
// });
|
||||
|
||||
@@ -80,6 +80,7 @@ export class SysPrivateSettings extends BaseSettings {
|
||||
|
||||
httpsProxy? = '';
|
||||
httpProxy? = '';
|
||||
commonHeaders?: string = '';
|
||||
|
||||
reverseProxies?: Record<string, string> = {};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SysSettingsEntity } from '../entity/sys-settings.js';
|
||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
||||
|
||||
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
||||
import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic';
|
||||
import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from '@certd/basic';
|
||||
import { isPlus } from '@certd/plus-core';
|
||||
import * as dns from 'node:dns';
|
||||
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
||||
@@ -167,6 +167,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
httpsProxy: privateSetting.httpsProxy,
|
||||
};
|
||||
setGlobalProxy(opts);
|
||||
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
|
||||
|
||||
if (privateSetting.dnsResultOrder) {
|
||||
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
||||
@@ -185,12 +186,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
|
||||
}
|
||||
|
||||
setEnvironmentVars(vars: string) {
|
||||
const envVars = {}
|
||||
if (typeof vars !== 'string') {
|
||||
vars = ""
|
||||
parseKeyValueText(text: string) {
|
||||
const values = {};
|
||||
if (typeof text !== 'string') {
|
||||
text = "";
|
||||
}
|
||||
vars.split('\n').forEach(line => {
|
||||
text.split('\n').forEach(line => {
|
||||
line = line.trim();
|
||||
if (!line || line.startsWith('#')) {
|
||||
return
|
||||
@@ -204,11 +205,18 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
return
|
||||
}
|
||||
|
||||
const [key, value] = line.split('=');
|
||||
const eqIndex = line.indexOf('=');
|
||||
const key = line.substring(0, eqIndex).trim();
|
||||
const value = line.substring(eqIndex + 1).trim();
|
||||
if (key && value) {
|
||||
envVars[key.trim()] = value.trim();
|
||||
values[key] = value;
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
setEnvironmentVars(vars: string) {
|
||||
const envVars = this.parseKeyValueText(vars);
|
||||
//先删除旧环境变量
|
||||
if (lastSaveEnvVars) {
|
||||
for (const key in lastSaveEnvVars) {
|
||||
|
||||
@@ -91,6 +91,8 @@ export default {
|
||||
reverseProxyEmpty: "No reverse proxy list configured",
|
||||
environmentVars: "Environment Variables",
|
||||
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
|
||||
commonHeaders: "Common Headers",
|
||||
commonHeadersHelper: "Common headers automatically added to server-side HTTP requests, one per line, format: KEY=VALUE. Existing request headers with the same name are not overwritten.",
|
||||
|
||||
bindUrl: "Bind URL",
|
||||
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
||||
|
||||
@@ -89,6 +89,8 @@ export default {
|
||||
reverseProxyEmpty: "未配置反向代理",
|
||||
environmentVars: "环境变量",
|
||||
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
||||
commonHeaders: "公共请求头",
|
||||
commonHeadersHelper: "服务端发起 HTTP 请求时自动附加的公共请求头,每行一个,格式:KEY=VALUE;请求中已设置同名 Header 时不会覆盖\n注意: 不要将token等敏感内容放在此处,仅限个人和公司内部使用,商业版不要设置",
|
||||
bindUrl: "绑定URL",
|
||||
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
||||
},
|
||||
|
||||
@@ -99,6 +99,7 @@ export type SuiteSetting = {
|
||||
export type SysPrivateSetting = {
|
||||
httpProxy?: string;
|
||||
httpsProxy?: string;
|
||||
commonHeaders?: string;
|
||||
reverseProxies?: any;
|
||||
dnsResultOrder?: string;
|
||||
commonCnameEnabled?: boolean;
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('certd.sys.setting.commonHeaders')" :name="['private', 'commonHeaders']">
|
||||
<a-textarea v-model:value="formState.private.commonHeaders" :placeholder="commonHeadersExample" rows="4" />
|
||||
<div class="helper">{{ t("certd.sys.setting.commonHeadersHelper") }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
||||
<a-select v-model:value="formState.private.dnsResultOrder">
|
||||
<a-select-option value="verbatim">{{ t("certd.default") }}</a-select-option>
|
||||
@@ -64,6 +69,10 @@ const environmentVarsExample = ref(
|
||||
`ALIYUN_CLIENT_CONNECT_TIMEOUT=16000 #连接超时,单位毫秒
|
||||
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
||||
);
|
||||
const commonHeadersExample = ref(
|
||||
`User-Agent=certd
|
||||
X-Custom-Header=value`
|
||||
);
|
||||
|
||||
const formState = reactive<Partial<SysSettings>>({
|
||||
public: {},
|
||||
|
||||
Reference in New Issue
Block a user