axios AxiosHeaders 头方法详解:set、get、normalize、concat 等头部操作 API 的源码级指南
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
axios 通过AxiosHeaders类提供了一整套结构化的请求/响应头操作 API,用于以更规范的方式设置、读取、删除和合并 HTTP 头。本篇技术指南以 axios 官方文档中的 Header methods 章节为主体,结合lib/core/AxiosHeaders.js的完整实现与单元测试,逐个方法讲解其签名、语义、边界行为(如 rewrite 覆盖策略、matcher 匹配规则、大小写保留机制),并给出在拦截器、适配器内部实际调用这些方法的位置,帮助读者从"会用 API"进阶到"理解其底层机制与坑点"。
AxiosHeaders 类概览
自 axios 引入独立的AxiosHeaders类后,头信息不再只是一个普通的键值对象,而是一个带方法、支持迭代、大小写不敏感的对象。直接操作头对象(如headers['Content-Type'] = '...')会绕过值归一化与大小写管理,而通过AxiosHeaders的方法操作可以保证:
- 所有键在内部统一做小写比较(
normalizeHeader实现于 lib/core/AxiosHeaders.js),但保留首次写入时的原始大小写(通过utils.findKey做 caseless 查找,见 lib/utils.js); - 所有值经过
normalizeValue清洗(false与null表示"显式删除该头",字符串会被 sanitizeHeaderValue 去除控制字符,数组递归处理); - 类方法被
utils.freezeMethods冻结(lib/core/AxiosHeaders.js),防止运行时被覆盖。
下文按官方文档脉络依次展开每个方法。
构造函数new AxiosHeaders(headers?)
AxiosHeaders构造函数接受一个可选的头部源用于初始化实例,可以是任意数量的头部对象、AxiosHeaders实例,或按换行符分隔的原始头字符串:
constructor(headers?: RawAxiosHeaders | AxiosHeaders | string);传入原始字符串时,axios 会将其解析为逐行的name: value对后加入实例:
const headers = new AxiosHeaders(` Host: www.bing.com User-Agent: curl/7.54.0 Accept: */*`); console.log(headers); // Object [AxiosHeaders] { // host: 'www.bing.com', // 'user-agent': 'curl/7.54.0', // accept: '*/*' // }从源码结构看,构造函数本体只有一行headers && this.set(headers)(lib/core/AxiosHeaders.js),即构造函数完全复用set的解析逻辑,因此set支持的所有输入形态(对象、字符串、可迭代键值对)在构造时同样可用。其中字符串分支会先经 isValidHeaderName 判断"这更像头名还是原始头块":若整个字符串不是合法头名,则走 parseHeaders 按行解析。测试用例 tests/unit/axiosHeaders.test.js 验证了对象形态构造时数字值会被字符串化(headers.get('x') === '1')。
set:设置头部与 rewrite 覆盖策略
set用于在AxiosHeaders实例上设置头部。它可以接受"单个头名 + 值"、包含多个头的对象,或换行分隔的原始头字符串,并支持可选的rewrite参数控制覆盖行为:
set(headerName, value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher); set(headerName, value, rewrite?: (this: AxiosHeaders, value: string, name: string) => boolean); set(headers?: RawAxiosHeaders | AxiosHeaders | string, rewrite?: boolean); set(headers?: Iterable<[string, AxiosHeaderValue]>, rewrite?: boolean);rewrite参数控制覆盖语义,取值含义如下(与实现 setHeader 中的判断条件一一对应):
| rewrite 取值 | 行为 | 源码条件 |
|---|---|---|
false | 若该头已有值(非undefined)则不覆盖 | 条件不满足_rewrite === true且非 undefined 分支 |
undefined(默认) | 覆盖,除非该头当前值被显式设为false | _rewrite === undefined && self[key] !== false |
true | 无论如何都覆盖 | _rewrite === true |
| 自定义函数 | 由函数决定当前值是否应被覆盖,函数接收(value, name, headers) | 经 matchHeaderValue 以filter.call(this, value, header)执行 |
其中"值为false表示禁止覆盖"的设计与toJSON中跳过false值的行为(false等价于"该头将被删除/不发送")是配套语义。这一点有专门的测试覆盖:先set('foo', 'value1')后set('foo', 'value2', false)不会改写值,而默认set('foo', 'value2')会改写(tests/unit/axiosHeaders.test.js);若头被显式设为false,默认set也不会覆盖,只有rewrite: true才能强行写入(tests/unit/axiosHeaders.test.js)。
其他值得注意的行为:
- 空名称被忽略:空字符串或仅由空白组成的头名在
normalizeHeader后为假值,直接返回(lib/core/AxiosHeaders.js)。 - 可迭代键值对被接受:如
Map或任何安全的 key/value 迭代器。多个同名键会被合并为数组:
const headers = new AxiosHeaders(); headers.set( new Map([ ['X-Trace-Id', 'abc123'], ['Accept', 'application/json'], ]) );对应源码中迭代分支会把重复键聚合为[dest, entry[1]]数组(lib/core/AxiosHeaders.js)。
- 大小写保留:
AxiosHeaders保留它看到的第一个匹配键的大小写。可以先把键以undefined值"占位",之后再设置值,从而锁定特定大小写,详见官方文档 保留特定头的大小写。 - 原型污染防护:
set的可迭代分支只信任对象自有的Symbol.iterator,被污染的Object.prototype[Symbol.iterator]不会被消费;该场景有专门的回归测试(tests/unit/axiosHeaders.test.js)。set方法返回this,支持链式调用。
get:读取头部与三种解析器
get用于读取头值,第二个参数可以是可选的 matcher 或解析器,matcher 默认为true;解析器可以是用于从头值中提取信息的正则表达式:
get(headerName: string, parser: typeof AxiosHeaders.parseParameters): AxiosHeaderParameters; get(headerName: string, parser: RegExp): RegExpExecArray | null; get(headerName: string, matcher?: true | AxiosHeaderParser): AxiosHeaderValue;官方文档给出的典型用法覆盖了全部解析器形态:
const headers = new AxiosHeaders({ 'Content-Type': 'multipart/form-data; boundary=Asrf456BGe4h', }); console.log(headers.get('Content-Type')); // multipart/form-data; boundary=Asrf456BGe4h console.log(headers.get('Content-Type', true)); // 按 \s,;= 分隔符解析键值对: // [Object: null prototype] { // 'multipart/form-data': undefined, // boundary: 'Asrf456BGe4h' // } const quotedHeaders = new AxiosHeaders({ 'Content-Type': 'multipart/form-data; boundary="a,b"', }); console.log({ ...quotedHeaders.get('Content-Type', AxiosHeaders.parseParameters), }); // { boundary: 'a,b' } console.log( headers.get('Content-Type', (value, name, headers) => { return String(value).replace(/a/g, 'ZZZ'); }) ); // multipZZZrt/form-dZZZtZZZ; boundZZZry=Asrf456BGe4h console.log(headers.get('Content-Type', /boundary=(\w+)/)?.[0]); // boundary=Asrf456BGe4h四种分支在源码 get 实现 中清晰对应:
- 不传解析器:原样返回存储值;
parser === true:走parseTokens(lib/core/AxiosHeaders.js),用正则/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g把值切分成 null-prototype 键值表;- 函数解析器:以
this(当前AxiosHeaders实例)调用,参数为(value, key); - 正则解析器:执行
parser.exec(value),返回RegExpExecArray | null。
传入其他类型会抛出TypeError('parser must be boolean|regexp|function')。
AxiosHeaders.parseParameters:规范化的参数解析器
AxiosHeaders.parseParameters是一个**选择性启用(opt-in)**的解析器,用于解析 HTTP 参数值(如Content-Type中的参数部分),返回参数名为小写、无原型(null-prototype)的映射对象。其行为要点:
- 剥离引号字符串的分隔符,解码被转义的
"(DQUOTE)与反斜杠序列(实现见 decodeQuotedString); - 引号内部的逗号或分号保留在值中(例如
boundary="a,b"解析为boundary: 'a,b'); - 对未加引号的值,只去除其两侧符合 RFC 定义的可选空白(空格与水平制表符),实现见 trimOWS;
- 省略危险的对象物化键:
__proto__、constructor、prototype三个名称会被直接跳过(lib/core/AxiosHeaders.js),避免原型污染; - 传
true则继续使用历史 tokenizer(parseTokens),保持既有输出以兼容旧行为。
has、delete 与 clear:存在性检查与删除
has
检查某头部是否存在于实例中,可附带 matcher:
has(header: string, matcher?: AxiosHeaderMatcher): boolean;返回true的条件是:键存在、值不为undefined(false值也算"已设置"),且 matcher 通过(若有)。源码见 has 实现。
delete
删除实例上的某个头部,接受单个头名(或字符串数组)与可选 matcher,matcher 此时用于匹配头的值:
delete(header: string | string[], matcher?: AxiosHeaderMatcher): boolean;返回true表示至少删除了一个头。实现见 delete 实现:传入数组时会逐个deleteHeader,只有 matcher 与值匹配才真正delete对应键。
matcher 支持三种形式(统一由 matchHeaderValue 处理):函数(filter.call(this, value, header))、字符串(判断值是否包含该子串)、正则(filter.test(value))。
clear
不传参数时清空实例中的所有头部;传入 matcher 时只删除匹配的头部,且此时 matcher 用于匹配头的名称而非值——这是与delete的关键区别(源码在 matchHeaderValue 中以isHeaderNameFilter标志将value替换为头名再匹配):
clear(matcher?: AxiosHeaderMatcher): boolean;返回true表示至少清除了一个头。实现见 clear 实现,它通过Object.keys(this)遍历自身自有属性并逐个删除。
normalize:合并大小写重复键
如果头对象被直接修改过(绕过set方法),可能出现同名但大小写不同的重复键。normalize方法把这些重复键合并为一个;axios 在每次拦截器调用后内部都会执行它,format设为true时会把名称转为小写并首字母大写(cOntEnt-type=>Content-Type),false则保留原格式:
const headers = new AxiosHeaders({ foo: '1', }); headers.Foo = '2'; headers.FOO = '3'; console.log(headers.toJSON()); // [Object: null prototype] { foo: '1', Foo: '2', FOO: '3' } console.log(headers.normalize().toJSON()); // [Object: null prototype] { foo: '3' } console.log(headers.normalize(true).toJSON()); // [Object: null prototype] { Foo: '3' }返回this以支持链式调用。
实现见 normalize 实现:它遍历所有键,用utils.findKey在新表中查找已归一化的键,若已存在则把值写入先出现的那个键并删除当前键(因此示例中foo: '3'保留了首个键foo的名称、取最后处理的值);formatHeader(lib/core/AxiosHeaders.js)负责"小写 + 按-分段首字母大写"的格式化。
在 axios 请求链路中,normalize的实际调用点可验证这一"拦截器之后内部调用"的说法:
- 数据转换阶段:lib/core/transformData.js 在请求/响应转换器前后分别调用
headers.normalize(); - 浏览器 XHR 适配器:lib/adapters/xhr.js 用
AxiosHeaders.from(_config.headers).normalize()取出requestHeaders; - Node http 适配器:lib/adapters/http.js 同样经
AxiosHeaders.from(config.headers).normalize()归一化后再写网; - fetch 适配器:lib/adapters/fetch.js 在
headers.normalize()后转为ByteString头对象。
这意味着在拦截器中直接以任意大小写修改config.headers是安全的——最终发往网络层的头部都会先经过归一化合并。
concat:合并多个头部源
concat将实例与若干目标合并为一个新的AxiosHeaders实例。目标是字符串时按原始 HTTP 头解析;是AxiosHeaders实例或普通对象时直接合并。它特别适合组合头部时预置大小写(case preset):
const headers = AxiosHeaders.concat( { 'content-type': undefined }, { 'Content-Type': 'application/octet-stream' } );第一个目标以undefined值占位content-type,使实例"记住"了该键的原始小写形式;第二个目标写入真实值后,由于set的默认语义"值为undefined时可以覆盖",最终键名保留为content-type而值已是application/octet-stream。
concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string | undefined | null>): AxiosHeaders;返回一个新的AxiosHeaders实例。实现上,实例方法concat只是委托给静态方法(lib/core/AxiosHeaders.js),而 static concat 先用第一个目标构造新实例,再对每个后续目标调用computed.set(target)。由于set本身接受字符串/对象/实例,静态concat也能作为独立工厂函数使用。
toJSON、toString 与可迭代
toJSON
把内部所有头值解析为一个 null-prototype 对象;asStrings设为true时,数组值会被解析为逗号分隔的字符串:
toJSON(asStrings: true): Record<string, string>; toJSON(asStrings?: false): Record<string, string | string[]>;实现见 toJSON:null与false值会被跳过——这正是"值设为false表示不发送该头"的机制落点。
toString
返回不带 CRLF 的 HTTP 头块,每行一对name: value:
toString(): string;实现见 toString,本质是对toJSON()的条目做header + ': ' + value后以换行连接,可直接用于调试输出。
迭代器
AxiosHeaders实现了Symbol.iterator(lib/core/AxiosHeaders.js),因此for...of、展开运算符与Object.entries风格的遍历都可以直接使用,例如文档示例中的{ ...quotedHeaders.get('Content-Type', AxiosHeaders.parseParameters) }即依赖无原型对象的可展开性。
from:幂等的实例化
from返回基于传入原始头创建的新AxiosHeaders实例;若传入的已经是AxiosHeaders实例,则原样返回该对象(不产生拷贝):
from(thing?: AxiosHeaders | RawAxiosHeaders | string): AxiosHeaders;实现只有两行(static from):thing instanceof this ? thing : new this(thing)。前面提到适配器与transformData都通过AxiosHeaders.from(config.headers).normalize()进入归一化流程,from的幂等性保证了"配置里放普通对象或AxiosHeaders实例"两种方式都能安全处理。
Shortcuts:访问器快捷方法
以下快捷方法开箱即用:
setContentType、getContentType、hasContentTypesetContentLength、getContentLength、hasContentLengthsetAccept、getAccept、hasAcceptsetUserAgent、getUserAgent、hasUserAgentsetContentEncoding、getContentEncoding、hasContentEncoding
源码机制:accessor 注册与保留名热修复
这些快捷方法并非手写,而是由static accessor动态生成。lib/core/AxiosHeaders.js 末尾一次性注册了六个头名:Content-Type、Content-Length、Accept、Accept-Encoding、User-Agent、Authorization;buildAccessors 对每个头名把名字转为驼峰(如content-type=>ContentType),然后在原型上定义getXxx/setXxx/hasXxx三个方法,内部分别转发到this.get/set/has.call(this, header, ...)。因此headers.setContentType('application/json')等价于headers.set('Content-Type', 'application/json'),hasContentType()等价于headers.has('Content-Type')。
两个值得注意的安全细节:
- null-proto 描述符:
buildAccessors定义的属性描述符带__proto__: null,注释明确说明这是为了防御"被污染的Object.prototype.get在途中把数据描述符变成访问器描述符"的原型污染手法(lib/core/AxiosHeaders.js)。 - 保留名热修复:
utils.reduceDescriptors把原型上每个方法名映射为首字母大写属性(set=>Set、delete=>Delete等,lib/core/AxiosHeaders.js)。这样直接给实例赋headers.delete = 'x'这类"头名与方法名撞车"的操作时,实际写入的是this['Delete']数据属性,而不会把原型上的delete方法本身覆盖掉。
另外,源码中还提供了文档未单列的辅助方法getSetCookie()(lib/core/AxiosHeaders.js):把set-cookie头的值统一规整为数组(set-cookie是典型的多值头,axios 在响应头解析中会将其存为数组),可配合getSetCookie在多值场景下使用。
实战建议与验证入口
综合文档与源码,几条落地建议:
- 统一走方法操作:始终用
set/get/has/delete/clear而非直接键赋值,以获得值清洗、false删除语义与大小写管理;确需直接赋值后(如拦截器里改config.headers),依赖 axios 内部的normalize()自动兜底即可(见上文各适配器调用点)。 - 需要锁定头大小写时:先用
undefined占位键(或AxiosHeaders.concat的 case preset 技巧),再写入真实值。 - 解析
Content-Type参数(如 multipart boundary)时:优先使用get(name, AxiosHeaders.parseParameters)而非手写正则,获得引号、转义与危险键过滤的正确处理。 - 验证行为:本文引用的边界行为均可在 tests/unit/axiosHeaders.test.js 中找到对应断言,如 rewrite 三态、
false值保护、Map迭代与原型污染防护;仓库内可用npx vitest run tests/unit/axiosHeaders.test.js运行该测试文件复核(仅读取与运行,不修改仓库内容)。
关键文件索引:
| 内容 | 路径 |
|---|---|
| AxiosHeaders 完整实现 | lib/core/AxiosHeaders.js |
| 原始头字符串解析 | lib/helpers/parseHeaders.js |
| 值清洗 | lib/helpers/sanitizeHeaderValue.js |
| caseless 键查找 findKey | lib/utils.js |
| 数据转换中的 normalize 调用 | lib/core/transformData.js |
| 单元测试 | tests/unit/axiosHeaders.test.js |
| 头部总览与大小写保留文档 | docs/pages/advanced/headers.md |
| 英文原版 header-methods 文档 | docs/pages/advanced/header-methods.md |
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考