mirror of
https://github.com/certd/certd.git
synced 2026-05-16 05: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;
|
export const HttpCommonError = HttpError;
|
||||||
|
|
||||||
let defaultAgents = createAgent();
|
let defaultAgents = createAgent();
|
||||||
|
let defaultHeaders: Record<string, string> = {};
|
||||||
|
|
||||||
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
||||||
logger.info("setGlobalProxy:", opts);
|
logger.info("setGlobalProxy:", opts);
|
||||||
@@ -92,6 +93,15 @@ export function getGlobalAgents() {
|
|||||||
return defaultAgents;
|
return defaultAgents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setGlobalHeaders(headers: Record<string, string> = {}) {
|
||||||
|
logger.info("setGlobalHeaders:", Object.keys(headers));
|
||||||
|
defaultHeaders = { ...headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGlobalHeaders() {
|
||||||
|
return defaultHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 创建请求实例
|
* @description 创建请求实例
|
||||||
*/
|
*/
|
||||||
@@ -148,6 +158,12 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
config.httpsAgent = agents.httpsAgent;
|
config.httpsAgent = agents.httpsAgent;
|
||||||
config.httpAgent = agents.httpAgent;
|
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({
|
// const agent = new https.Agent({
|
||||||
// rejectUnauthorized: false // 允许自签名证书
|
// rejectUnauthorized: false // 允许自签名证书
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export class SysPrivateSettings extends BaseSettings {
|
|||||||
|
|
||||||
httpsProxy? = '';
|
httpsProxy? = '';
|
||||||
httpProxy? = '';
|
httpProxy? = '';
|
||||||
|
commonHeaders?: string = '';
|
||||||
|
|
||||||
reverseProxies?: Record<string, 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 { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
||||||
|
|
||||||
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
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 { isPlus } from '@certd/plus-core';
|
||||||
import * as dns from 'node:dns';
|
import * as dns from 'node:dns';
|
||||||
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
||||||
@@ -167,6 +167,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
httpsProxy: privateSetting.httpsProxy,
|
httpsProxy: privateSetting.httpsProxy,
|
||||||
};
|
};
|
||||||
setGlobalProxy(opts);
|
setGlobalProxy(opts);
|
||||||
|
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
|
||||||
|
|
||||||
if (privateSetting.dnsResultOrder) {
|
if (privateSetting.dnsResultOrder) {
|
||||||
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
||||||
@@ -185,12 +186,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnvironmentVars(vars: string) {
|
parseKeyValueText(text: string) {
|
||||||
const envVars = {}
|
const values = {};
|
||||||
if (typeof vars !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
vars = ""
|
text = "";
|
||||||
}
|
}
|
||||||
vars.split('\n').forEach(line => {
|
text.split('\n').forEach(line => {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
if (!line || line.startsWith('#')) {
|
if (!line || line.startsWith('#')) {
|
||||||
return
|
return
|
||||||
@@ -204,11 +205,18 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
return
|
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) {
|
if (key && value) {
|
||||||
envVars[key.trim()] = value.trim();
|
values[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvironmentVars(vars: string) {
|
||||||
|
const envVars = this.parseKeyValueText(vars);
|
||||||
//先删除旧环境变量
|
//先删除旧环境变量
|
||||||
if (lastSaveEnvVars) {
|
if (lastSaveEnvVars) {
|
||||||
for (const key in lastSaveEnvVars) {
|
for (const key in lastSaveEnvVars) {
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export default {
|
|||||||
reverseProxyEmpty: "No reverse proxy list configured",
|
reverseProxyEmpty: "No reverse proxy list configured",
|
||||||
environmentVars: "Environment Variables",
|
environmentVars: "Environment Variables",
|
||||||
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
|
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",
|
bindUrl: "Bind URL",
|
||||||
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export default {
|
|||||||
reverseProxyEmpty: "未配置反向代理",
|
reverseProxyEmpty: "未配置反向代理",
|
||||||
environmentVars: "环境变量",
|
environmentVars: "环境变量",
|
||||||
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
||||||
|
commonHeaders: "公共请求头",
|
||||||
|
commonHeadersHelper: "服务端发起 HTTP 请求时自动附加的公共请求头,每行一个,格式:KEY=VALUE;请求中已设置同名 Header 时不会覆盖\n注意: 不要将token等敏感内容放在此处,仅限个人和公司内部使用,商业版不要设置",
|
||||||
bindUrl: "绑定URL",
|
bindUrl: "绑定URL",
|
||||||
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export type SuiteSetting = {
|
|||||||
export type SysPrivateSetting = {
|
export type SysPrivateSetting = {
|
||||||
httpProxy?: string;
|
httpProxy?: string;
|
||||||
httpsProxy?: string;
|
httpsProxy?: string;
|
||||||
|
commonHeaders?: string;
|
||||||
reverseProxies?: any;
|
reverseProxies?: any;
|
||||||
dnsResultOrder?: string;
|
dnsResultOrder?: string;
|
||||||
commonCnameEnabled?: boolean;
|
commonCnameEnabled?: boolean;
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
||||||
</a-form-item>
|
</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-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
||||||
<a-select v-model:value="formState.private.dnsResultOrder">
|
<a-select v-model:value="formState.private.dnsResultOrder">
|
||||||
<a-select-option value="verbatim">{{ t("certd.default") }}</a-select-option>
|
<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_CONNECT_TIMEOUT=16000 #连接超时,单位毫秒
|
||||||
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
||||||
);
|
);
|
||||||
|
const commonHeadersExample = ref(
|
||||||
|
`User-Agent=certd
|
||||||
|
X-Custom-Header=value`
|
||||||
|
);
|
||||||
|
|
||||||
const formState = reactive<Partial<SysSettings>>({
|
const formState = reactive<Partial<SysSettings>>({
|
||||||
public: {},
|
public: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user