axios AxiosHeaders 请求头方法全集:从 set/get 到 normalize 的源码级实践指南
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
本篇基于 axios 官方文档《Métodos de encabezados》(header-methods.md)展开,系统讲解AxiosHeaders类的全部核心方法——构造、set、get、has、delete、clear、normalize、concat、toJSON、toString、静态from以及内建快捷访问器。读完后你将掌握:如何以比直接操作普通对象更安全、更可控的方式管理 HTTP 请求头,以及每个方法在 lib/core/AxiosHeaders.js 中的底层实现逻辑与典型调用链。
为什么需要 AxiosHeaders:先看懂构造器
自引入AxiosHeaders类以来,axios 提供了一组专门用于操作请求头的方法。相比直接摆弄一个普通对象,AxiosHeaders在键名大小写归一、重复键合并、头部值清洗等方面做了统一处理,是"更便捷的设置、获取与删除请求头方式"的载体。
类定义在 lib/core/AxiosHeaders.js,构造器签名如下:
constructor(headers?: RawAxiosHeaders | AxiosHeaders | string);它接受一个可选参数用于初始化实例:可以是任意数量的头对象(键名大小写不敏感),也可以是一段以换行符分隔的原始头部文本。源码中构造器只做了一件事——把参数交给set:
constructor(headers) { headers && this.set(headers); }这解释了为什么构造函数、set单键值、set对象、set原始字符串三种用法的行为完全一致。传入原始字符串时,set内部会走 parseHeaders.js 的解析逻辑(按行切分、冒号左侧为键并转小写、右侧为值),例如:
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: '*/*' // }值得注意的是,parseHeaders内置了一张ignoreDuplicateOf列表(lib/helpers/parseHeaders.js#L7-L25),涵盖host、content-type、user-agent等 Node.js HTTP 模块会忽略重复项的头部——解析原始文本时这些键只保留第一个值,而set-cookie会被收集为数组,其余重复键以', '拼接。这一行为是从原始报文解析场景出发的刻意设计。
set:三种输入形态与 rewrite 覆盖语义
set是写入请求头的主入口,支持三种调用形态以及一个控制覆盖行为的可选参数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控制覆盖行为,语义如下:
| rewrite 取值 | 行为 |
|---|---|
false | 若该头已有值(非undefined)则不覆盖 |
undefined(默认) | 会覆盖,除非现有值被显式设置为false |
true | 无条件覆盖 |
也可以传入用户自定义函数,由它决定是否覆盖——函数接收当前值、头名称以及头对象本身。另外,空名称或纯空格名称会被直接忽略。
源码中的判定逻辑非常直观(lib/core/AxiosHeaders.js#L206-L222):
function setHeader(_value, _header, _rewrite) { const lHeader = normalizeHeader(_header); // trim + toLowerCase if (!lHeader) { return; // 空名或纯空格 -> 忽略 } const key = utils.findKey(self, lHeader); // 查找已存在的同名键(保留原大小写) if ( !key || self[key] === undefined || _rewrite === true || (_rewrite === undefined && self[key] !== false) ) { self[key || _header] = normalizeValue(_value); } }这里有两个关键细节:
- 大小写不敏感的键查找:
normalizeHeader对名称做trim().toLowerCase(),而实际写入时用utils.findKey找到实例上已存在的原始键名,因此Content-Type与content-type指向同一个键。 false是"占位"语义:把某头显式设为false后,默认写入不再覆盖它,只有rewrite === true才能改回——单元测试 tests/unit/axiosHeaders.test.js#L48-L77 专门验证了这两条规则。
接受 Map 等可迭代键值对
set的第三个形态接受可迭代的键值对序列,如Map:
const headers = new AxiosHeaders(); headers.set( new Map([ ['X-Trace-Id', 'abc123'], ['Accept', 'application/json'], ]) );从源码结构看,可迭代分支会先把迭代结果归拢到一个 null-proto 对象,迭代项不是二元数组时会抛出TypeError('Object iterator must return a key-value pair')(lib/core/AxiosHeaders.js#L232-L251)。测试中还有针对性验证:即便Object.prototype被注入同名键或污染了Symbol.iterator,也不会把原型链上的值混入迭代来源(tests/unit/axiosHeaders.test.js#L87-L129)。
保留特定头部的大小写
AxiosHeaders保留第一个匹配键的大小写形式。你可以利用这一点:先用undefined预设一个键名,之后再写入值,从而固定某个头部的大小写。这一技巧在对接对头部大小写行为不规范的服务器时很有用,完整的defaults与axios.create用法见 保留特定请求头大小写。
写入值的阶段还会经过normalizeValue与 sanitizeHeaderValue:字符串值会被剥离 C0 控制字符与 DEL(0x00–0x08、0x0A–0x1F、0x7F)并去掉两端空格/水平制表符,数组值递归处理;false与null原样保留。
get:匹配器与解析器(含 parseParameters)
get用于读取头部值,可传入头名称、可选的 matcher 或 parser;matcher 的默认值为true,parser 可以是用于从头部值中提取内容的正则:
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对照源码(lib/core/AxiosHeaders.js#L259-L287)可以看到分派顺序:
header先经normalizeHeader归一,再用utils.findKey找到实际键名——所以取值不区分大小写;- 无 parser 直接返回值;
parser === true走parseTokens,用正则/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g做轻量分词(注意它对带引号的内容不做处理);- 函数 parser 以该实例为
this调用;正则 parser 执行exec; - 其它类型直接抛
TypeError('parser must be boolean|regexp|function')。
parseParameters:更严格的 HTTP 参数解析器
AxiosHeaders.parseParameters是可选的解析器,针对 HTTP 归一化参数值:返回 null-proto 对象,参数名一律转小写;剥离带引号字符串两端的引号,解码转义的\"与\\,并保留引号内的逗号/分号;对不带引号的值只去掉 RFC 定义的可选空白(空格与水平制表符)。
其实现是一个手写的字符级状态机(lib/core/AxiosHeaders.js#L92-L149):按"切换引号状态、按0x5c记录转义、在引号外遇到,或;时切出参数片段;参数名需匹配parameterNameRE(/^[!#$%&'*+\-.^_|~0-9A-Za-z]+$/`)才会被收录。
一个安全细节值得注意:解析器会跳过__proto__、constructor、prototype这三个"危险键名",避免通过构造恶意头部值向解析结果注入原型污染面(lib/core/AxiosHeaders.js#L115-L121)。而传true时仍走上面提到的parseTokens旧分词器,以保持向后兼容。
has:存在性判断
has(header: string, matcher?: AxiosHeaderMatcher): boolean;当且仅当头已定义(值不是undefined)时返回true。源码中的判定(lib/core/AxiosHeaders.js#L289-L303)是key && this[key] !== undefined && (!matcher || matchHeaderValue(...))——所以把某头显式设为false后has依然返回true,只有undefined才视为"不存在"。可选的 matcher 可进一步按值过滤。
delete 与 clear:删除与清空
delete(header: string | string[], matcher?: AxiosHeaderMatcher): boolean;delete支持单个头名称或名称数组,可按值匹配器筛选;至少删除一个头时返回true。
clear(matcher?: AxiosHeaderMatcher): boolean;clear无参时清空全部头部;传入 matcher 时只删除匹配项——且此时 matcher 是拿头名称(而非值)做比较。这一点在源码里体现得很明确:clear调用matchHeaderValue时传入了第五个参数isHeaderNameFilter = true,使字符串/正则过滤器作用在键名上(lib/core/AxiosHeaders.js#L332-L346)。
匹配器支持三种形式:字符串(子串包含)、正则(test)、函数,统一由 lib/core/AxiosHeaders.js#L153-L171 的matchHeaderValue处理。
normalize:合并重复键,适配器与拦截器的收尾动作
如果你直接以属性方式修改过头部对象(如headers.Foo = '2'),可能出现同名但大小写不同的多个键。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以便链式调用。实现上(lib/core/AxiosHeaders.js#L348-L373)它遍历自身,对已出现过的归一化名称直接合并到已有键上,否则按format决定是否做formatHeader(trim().toLowerCase()后按单词首字母大写)重命名。
"拦截器后调用"这一点可以在仓库里直接找到证据:XHR、HTTP、fetch 三个适配器以及transformData都会先取头再normalize:
- lib/adapters/xhr.js#L21:
const requestHeaders = AxiosHeaders.from(_config.headers).normalize(); - lib/adapters/http.js#L766:
const headers = AxiosHeaders.from(config.headers).normalize(); - lib/adapters/fetch.js#L456:发送前执行
headers.normalize()再转 ByteString 头部对象 - lib/core/transformData.js#L22-L25:
transformRequest回调执行前后各normalize()一次
也就是说,normalize是 axios 请求管线里保证头部键唯一性的"守门员",而不是可选项。
concat:组合头部并固定大小写预设
concat把当前实例与若干目标组合成新的AxiosHeaders实例:目标是字符串时按原始 HTTP 头格式解析,目标是AxiosHeaders实例时直接合并。这在需要"预置大小写 + 后填值"的组合场景特别有用:
const headers = AxiosHeaders.concat( { 'content-type': undefined }, { 'Content-Type': 'application/octet-stream' } );concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string | undefined | null>): AxiosHeaders;静态实现非常简洁:new this(first)后对每个目标依次set(lib/core/AxiosHeaders.js#L418-L424),因此目标中已有的键默认不会覆盖前者——配合undefined预设即可固定content-type的小写形式。
toJSON 与 toString:序列化的两种形态
toJSON把内部所有头值解析为一个 null-proto 新对象,跳过null与false的值;asStrings传true时把数组值用逗号连接为字符串:
toJSON(asStrings: true): Record<string, string>; toJSON(asStrings?: false): Record<string, string | string[]>;toString则返回不带 CRLF 的原始 HTTP 头文本块,每行一个name: value对(lib/core/AxiosHeaders.js#L395-L399),底层同样基于toJSON:
toString(): string;另外从源码可以看到实例实现了[Symbol.iterator](迭代toJSON()的 entries)和[Symbol.toStringTag] = 'AxiosHeaders',可以直接for...of遍历或用instanceof识别类型。
静态 from:幂等的类型归一
from(thing?: AxiosHeaders | RawAxiosHeaders | string): AxiosHeaders;若传入的已经是AxiosHeaders实例则原样返回,否则用其构造新实例。源码就一行:return thing instanceof this ? thing : new this(thing);(lib/core/AxiosHeaders.js#L410-L412)。前面提到的三个适配器正是靠AxiosHeaders.from(config.headers).normalize()把配置里可能是普通对象、字符串或AxiosHeaders的头部统一归一。
快捷访问器:setContentType 一族
类上预置了以下快捷方法(每个头部各有 get/set/has 三个):
setContentType、getContentType、hasContentTypesetContentLength、getContentLength、hasContentLengthsetAccept、getAccept、hasAcceptsetUserAgent、getUserAgent、hasUserAgentsetContentEncoding、getContentEncoding、hasContentEncoding
它们由AxiosHeaders.accessor静态方法批量生成:以Content-Type、Content-Length、Accept、Accept-Encoding、User-Agent、Authorization六个头部为名单调用accessor(lib/core/AxiosHeaders.js#L452-L459),内部通过buildAccessors在原型上用toCamelCase生成如getContentEncoding这样的方法,转调this.get/set/has.call(this, header, ...)(lib/core/AxiosHeaders.js#L182-L196)。
两个源码层面的工程细节:
- 访问器属性描述符刻意设为 null-proto(
__proto__: null),防止被污染的Object.prototype.get在写入途中把数据描述符变成访问器描述符——这是对原型污染攻击面的防御性写法; accessor是公开的静态方法,你也可以为任意自定义头(如X-Trace-Id)动态生成访问器,tests/unit/axiosHeaders.test.js#L599-L628 中有headers.constructor.accessor('foo')的验证用例。
此外源码里还有一个文档未展开的细节:reduceDescriptors把set/get/...等原型方法做了"保留名"热修(lib/core/AxiosHeaders.js#L461-L470),避免把名为set、get的头部当作属性赋值时覆盖掉同名方法。
小结:方法选择速查
| 场景 | 推荐方法 | 关键依据 |
|---|---|---|
| 初始化 / 批量写入 | 构造器、set(对象、字符串、Map) | lib/core/AxiosHeaders.js#L198-L257 |
| 条件覆盖 | set(name, value, rewrite) | 默认不覆盖false占位值 |
| 提取参数(如 boundary) | get(name, AxiosHeaders.parseParameters) | 处理引号、转义与原型污染防护 |
| 存在性判断 | has | false也算"已定义" |
| 精准删除 | delete/clear(matcher) | clear的 matcher 作用于键名 |
| 消除重复键 | normalize(format?) | 适配器与 transformData 管线自动调用 |
| 组合 + 大小写预设 | AxiosHeaders.concat、from | 固定头部大小写 |
| 序列化 | toJSON(asStrings?)/toString | null-proto、跳过false |
| 常用头简写 | getContentType等访问器 | accessor可扩展自定义头 |
AxiosHeaders的设计思路可以概括为:对外提供"大小写不敏感、值自动清洗、重复键可归一"的统一头容器,对内通过normalize保证适配器拿到的总是干净的头部集合。理解了set的 rewrite 语义、parseParameters的引号状态机和normalize在请求管线中的自动触发,你就能在拦截器、实例默认值和请求级配置三处安全地操控请求头。
相关文档:headers 总览(大小写保留与拦截器设置)、api-reference、单元测试 tests/unit/axiosHeaders.test.js。
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考