ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Remix headers 包:基于 SuperHeaders 的类型化 HTTP 头处理与解析工具链

Remix headers 包:基于 SuperHeaders 的类型化 HTTP 头处理与解析工具链 Remix headers 包基于 SuperHeaders 的类型化 HTTP 头处理与解析工具链【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remixremix-run/headers仓库路径 packages/headers是 Remix 生态中面向 Web Fetch API 的 HTTP 头工具包提供SuperHeaders增强类与Accept、Cache-Control、Content-Type、Cookie、Set-Cookie、Range等十余种单头解析类。本文以该包的 CHANGELOG.md 为脉络结合 README.md 与 src 源码系统讲解其演进历程、核心 API、配置要点与迁移路径读完你可以在服务端请求/响应处理、内容协商、缓存控制、分片下载等场景中直接落地使用。一、包定位与安装1.1 它解决什么问题手写 HTTP 头解析容易出错Accept: text/html, text/*;q0.9的权重计算、Set-Cookie的多值语义、Content-Disposition中 RFC 8187 的filename*解码、Range的区间归一化……remix-run/headers把这些常见头的解析、修改、序列化封装成类型安全的类同时通过SuperHeaders保持与原生Headers的兼容。1.2 安装与子路径导出根据 package.json该包为 ESM-onlyv0.14.0 起移除了 CommonJS 构建TypeScript 版本要求 ≥ 5.7v0.13.0 起。安装方式npm i remix由于 npm 包名remix就是主包你可以直接导入import SuperHeaders from remix/headers // 默认导出 import { ContentType, SetCookie } from remix/headers需要强调当前仓库以源码直接分发——files字段包含src目录排除测试文件exports中的子路径直接指向./src/lib/*.ts发布时再映射到dist/lib/*.js见 package.json 的publishConfig。因此“转到定义”会直接跳到真实源码例如Accept类位于 src/lib/accept.ts。如果只需要某一个解析器可以按子路径导入避免拉入整个 barrel 与SuperHeadersimport { ContentType } from remix/headers/content-type import { SetCookie } from remix/headers/set-cookie完整的子路径清单v0.21.1 的 package.json包括accept、accept-encoding、accept-language、cache-control、content-disposition、content-range、content-type、cookie、if-match、if-none-match、if-range、range、raw-headers、set-cookie、vary。二、SuperHeaders带惰性类型化访问器的增强 Headers2.1 设计与演进SuperHeaders是 v0.20.0 重新引入的默认与具名导出此前曾在 v0.19.0 被移除。它继承原生Headers类恢复被 #10911 移除的惰性、类型化属性访问器同时保持原生存储同步因此可以直接传给Response等平台 API。从 src/lib/super-headers.ts 源码可以看到其实现方式在类的static {}块中遍历HeaderDescriptors包含 object/string/number/date/set-cookie 五种描述符用Object.defineProperty在原型上注册全部 getter/setter。这解释了为什么headers.contentType这种写法可以工作——它只是原生Headers之上的语法糖。2.2 基本用法import SuperHeaders from remix/headers let headers new SuperHeaders(request.headers) headers.contentType { mediaType: text/html, charset: utf-8 } headers.cacheControl { public: true, maxAge: 3600 } headers.setCookie { name: session, value: abc, httpOnly: true } headers.contentType.charset iso-8859-1 headers.cacheControl.maxAge 60 headers.setCookie.push({ name: theme, value: dark, path: / }) return new Response(html, { headers })因为它是真实的Headers子类可以无缝接入平台 APIlet headers new SuperHeaders({ contentType: text/plain }) headers instanceof globalThis.Headers // true new Response(Hello, { headers }).headers.get(Content-Type) // text/plain惰性解析体现在“读取时才解析”headers.get(Content-Type)不做类型化解析而headers.contentType.mediaType才触发惰性解析。2.3 访问器覆盖范围v0.20.0 的 CHANGELOG 完整列出了支持的全部访问器涵盖Accept系Accept、Accept-Charset、Accept-Encoding、Accept-Language、Accept-Patch、Accept-Post、Accept-Ranges、Access-Control-*CORS 全系列、Authorization、Cache-Control、Content-*Content-Disposition、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-Range、Content-Security-Policy、Content-Type、Cookie/Set-Cookie、日期类Date、Expires、Last-Modified、If-Modified-Since、If-Unmodified-Since、条件请求类If-Match、If-None-Match、If-Range、Range、Vary、ETag、Host、Location、Origin、Referer、User-Agent、X-*系列等共 60 个属性。从 super-headers.ts 的源码看这些访问器按语义分为四类object 型返回类型化对象accept、acceptEncoding、acceptLanguage、cacheControl、contentDisposition、contentRange、contentType、cookie、ifMatch、ifNoneMatch、ifRange、range、varystring 型多数支持数组初始化、逗号拼接authorization、etag带quoteEtag自动加引号、host、location等number 型读取时parseIntage、contentLength、maxForwards、accessControlMaxAge、upgradeInsecureRequestsdate 型读取时new Date、写入时toUTCString()date、expires、ifModifiedSince、ifUnmodifiedSince、lastModified。2.4 apply()header-aware 合并语义v0.21.0 新增SuperHeaders#apply(init)用于把SuperHeadersInit应用到已有实例采用“header-aware”行为替换单值头同时保留Cookie、Set-Cookie、Vary等可叠加头见 #11398。let headers new SuperHeaders({ contentType: text/html, setCookie: { name: session, value: abc }, vary: Accept-Encoding, }) headers.apply({ contentType: application/json, setCookie: { name: theme, value: dark }, vary: [Accept-Encoding, Accept-Language], }) headers.get(Content-Type) // application/json headers.get(Vary) // accept-encoding, accept-language headers.getSetCookie() // [sessionabc, themedark]源码层面apply()的实现位于 super-headers.ts 的#applyHeaderValueSet-Cookie采用追加appendCookie与Vary会把新旧值解析后合并再写回而Accept/Accept-Encoding/Accept-Language/Cache-Control/If-Match/If-None-Match以及所有 list 型 string 头走追加路径其余单值头直接set覆盖。注意apply()与构造函数一样不接受原始 header 字符串会抛TypeError原始串请用parse()。2.5 惰性对象头与缓存失效v0.20.0 恢复的属性访问器行为背后是 super-headers.ts 中的缓存机制#cache对象头、#setCookieCacheSet-Cookie 列表与#revisions版本号配合append/delete/set覆写原生方法后调用#invalidate使缓存失效。对象头值通过Proxy观察变更observeMutations你在headers.contentType.charset iso-8859-1后无需手动写回原生存储会自动同步。同时get()对未初始化自定义头统一返回nullv0.9.0 起对齐原生Headers接口。三、单头解析类from() / toString() 的往返安全每个受支持的头都实现HeaderValue接口见 src/lib/header-value.ts通过静态from()解析、实例方法toString()序列化保证“解析-序列化”往返安全。所有类都同时接受字符串、初始化对象init与null三种输入。v0.19.0 是一个重要的破坏性变更节点移除了Headers/SuperHeaders类及默认导出改为每个头类的静态from()方法。迁移示例如下// Before: import SuperHeaders from remix-run/headers let headers new SuperHeaders(request.headers) let mediaType headers.contentType.mediaType // After: import { ContentType } from remix-run/headers let contentType ContentType.from(request.headers.get(Content-Type)) let mediaType contentType.mediaType注意v0.19.0 的破坏性变更在 v0.20.0 被部分逆转——SuperHeaders重新成为默认导出。当前 v0.21.1 同时提供SuperHeaders默认/具名导出与各头类的.from()。3.1 内容协商Accept / Accept-Encoding / Accept-Language三者均实现Mapkey, quality语义提供accepts()、getWeight()、getPreferred()方法v0.9.0 引入import { Accept, AcceptLanguage, AcceptEncoding } from remix/headers let accept new Accept({ text/html: 1, text/*: 0.9 }) accept.accepts(text/html) // true accept.accepts(text/plain) // truetext/* 匹配 accept.accepts(image/jpeg) // false accept.getPreferred([text/html, text/plain]) // text/html let lang new AcceptLanguage({ en-US: 1, en: 0.9 }) lang.accepts(en-GB) // trueen 匹配 lang.getWeight(en-GB) // 0.9 let enc new AcceptEncoding({ gzip: 1, deflate: 0.9 }) enc.accepts(identity) // truev0.18.0 起getPreferred()变为泛型方法保留输入数组的联合类型。源码位于 accept.ts、accept-encoding.ts、accept-language.ts。3.2 Cache-Controlimport { CacheControl } from remix/headers let cacheControl CacheControl.from(response.headers.get(Cache-Control)) cacheControl.public // true cacheControl.maxAge // 3600 cacheControl.sMaxage // 7200 cacheControl.noCache // undefined cacheControl.mustRevalidate // undefined cacheControl.immutable // undefined cacheControl.maxAge 7200 cacheControl.immutable true headers.set(Cache-Control, cacheControl)构造方式new CacheControl(public, max-age3600)或new CacheControl({ public: true, maxAge: 3600 })。实现见 src/lib/cache-control.ts。3.3 Content-Typeimport { ContentType } from remix/headers let contentType ContentType.from(request.headers.get(Content-Type)) contentType.mediaType // text/html contentType.charset // utf-8 contentType.boundary // multipart 边界 contentType.charset iso-8859-1 headers.set(Content-Type, contentType)实现细节见 src/lib/content-type.tsfrom()内部用parseParams解析参数toString()仅当mediaType存在时输出charset/boundary会用quote()正确加引号。空Content-Type序列化为空字符串。3.4 Content-Disposition 与 RFC 8187 文件名解码import { ContentDisposition } from remix/headers let cd ContentDisposition.from(response.headers.get(Content-Disposition)) cd.type // attachment cd.filename // example.pdf cd.filenameSplat // UTF-8%E4%BE%8B%E5%AD%90.pdf cd.preferredFilename // 例子.pdf从 filename* 解码v0.20.0 修复了 RFC 8187filename*解码时保留字面字符的问题此前可能被误解码为空格。实现见 src/lib/content-disposition.ts。3.5 Cookie保留重复名称的有序列表v0.21.0 修复了Cookie/SuperHeaders.cookie对 path 或 domain 不同造成的同名 cookie 顺序保留问题见 #11423import { Cookie } from remix/headers let cookie Cookie.from(request.headers.get(Cookie)) cookie.get(session_id) // abc123返回第一个匹配值 cookie.getAll(session_id) // [abc123]读取全部匹配值 cookie.append(session_id, def456) // 追加同名 cookie cookie.set(theme, light) cookie.delete(session_id) headers.set(Cookie, cookie)构造方式new Cookie(session_idabc123; themedark)、new Cookie({ session_id: abc123 })或new Cookie([[session_id, abc123]])。v0.10.0 起names/values变为返回string[]的 getter不再返回IterableIteratorforEach回调签名改为(name, value, cookie)delete()返回void。3.6 Set-Cookie完整 CookiePropertiesv0.15.0 导出CookieProperties类型并支持Partitioned属性v0.17.1 修复Max-Age0未出现在序列化结果中的 bugv0.17.2 将secure类型从字面量true放宽为boolean与httpOnly、partitioned保持一致见 set-cookie.tsimport { SetCookie } from remix/headers let setCookie SetCookie.from(response.headers.get(Set-Cookie)) setCookie.name // session_id setCookie.value // abc setCookie.path // / setCookie.httpOnly // true setCookie.secure // true setCookie.sameSite // Strict | Lax | None | undefined setCookie.maxAge // 秒 setCookie.expires // Date setCookie.domain // 域名 setCookie.partitioned // CHIPS 分区 Cookie setCookie.maxAge 3600 headers.set(Set-Cookie, setCookie) // 直接构造 new SetCookie(session_idabc; Path/; HttpOnly; Secure) new SetCookie({ name: session_id, value: abc, path: /, httpOnly: true, secure: true })CookieProperties的完整字段domain、expires、httpOnly、maxAge、partitioned、path、sameSite、secure在 set-cookie.ts 中有逐项 JSDoc 注释。3.7 Range / Content-Range分片下载支持v0.17.0 新增Range与Content-Range支持源码见 range.ts 与 content-range.tsimport { Range, ContentRange } from remix/headers // Range let range new Range({ unit: bytes, ranges: [{ start: 0, end: 999 }] }) range.toString() // bytes0-999 let parsed new Range(bytes0-999,2000-2999) parsed.ranges // [{ start: 0, end: 999 }, { start: 2000, end: 2999 }] parsed.isSatisfiable? // 注意v0.17.0 文档中为 isSatisfiable // 当前源码 [range.ts] 中对应方法名为 canSatisfy(resourceSize) parsed.canSatisfy(5000) // true new Range(bytes0-).normalize(5000) // [{ start: 0, end: 4999 }] // 后缀区间最后 N 字节 new Range(bytes-500).normalize(2000) // [{ start: 1500, end: 1999 }] // Content-Range let cr new ContentRange({ unit: bytes, start: 0, end: 999, size: 5000 }) cr.toString() // bytes 0-999/5000 let parsedCR new ContentRange(bytes 200-1000/67589) parsedCR.start // 200 parsedCR.end // 1000 parsedCR.size // 67589 // 未满足的区间 ContentRange.from(bytes */67589).start // nullRange的normalize(size)会把三种写法0-99、100-、-500统一解析为具体的{start, end}闭区间并做边界裁剪canSatisfy()校验格式合法性至少一个边界、start end与资源大小是否可满足不可满足时normalize()返回空数组。3.8 If-Match / If-None-Match条件请求与弱 ETag 语义v0.17.0 新增If-Matchv0.10.0 新增If-None-Match用于条件 GET 返回 304。两者实现Setetag关键区别在于强弱比较语义import { IfMatch, IfNoneMatch } from remix/headers // If-Match仅强比较弱 ETag 永不匹配 let im new IfMatch([abc123, def456]) im.has(abc123) // true im.matches(abc123) // true im.matches(W/abc123) // false弱 ETag 永不匹配 // If-None-Match支持弱比较 let inm IfNoneMatch.from(W/67ab43) inm.matches(W/67ab43) // true条件 GET 实战来自 v0.10.0 CHANGELOGimport { SuperHeaders } from remix/headers function requestHandler(request: Request): PromiseResponse { let response await callDownstreamService(request) if (request.method GET response.headers.has(ETag)) { let headers new SuperHeaders(request.headers) if (headers.ifNoneMatch.matches(response.headers.get(ETag))) { return new Response(null, { status: 304 }) } } return response }3.9 If-RangeETag 或 Last-Modified 双模式v0.17.0 新增支持 ETag 与 HTTP 日期两种判定弱 ETag 同样不匹配import { IfRange } from remix/headers new IfRange(abc123).matches({ etag: abc123 }) // true new IfRange(abc123).matches({ etag: W/abc123 }) // false new IfRange(new Date(2025-10-21T07:28:00Z)).matches({ lastModified: new Date(2025-10-21T07:28:00Z), }) // true // 空值返回空实例视为无条件放行 IfRange.from(null).matches({ etag: any }) // true3.10 Vary大小写不敏感的集合v0.18.0 新增import { Vary } from remix/headers let header new Vary(Accept-Encoding) header.add(Accept-Language) header.headerNames // [accept-encoding, accept-language] header.has(Accept-Encoding) // true大小写不敏感 header.toString() // accept-encoding, accept-language // 数组/对象构造 new Vary([Accept-Encoding, Accept-Language]) new Vary({ headerNames: [Accept-Encoding, Accept-Language] })实现见 src/lib/vary.ts。四、原始头工具parse() 与 stringify()v0.19.0 新增的原始头工具用于在原生Headers与原始 HTTP 头字符串之间转换实现见 src/lib/raw-headers.tsimport { parse, stringify } from remix/headers let headers parse(Content-Type: text/html\r\nCache-Control: no-cache) headers.get(Content-Type) // text/html headers.get(Cache-Control) // no-cache stringify(headers) // Content-Type: text/html\r\nCache-Control: no-cache底层实现parse()按\r\n分行用/^([^:]):(.*)/正则提取名称与值stringify()遍历Headers并用 header-names.ts 的canonicalHeaderName把名称规范化为驼峰标准形式。五、迁移与破坏性变更速查5.1 各版本破坏性变更汇总v0.19.0移除Headers/SuperHeaders类与默认导出 → 使用各头类的from()新增parse()/stringify()。v0.20.0 又恢复了SuperHeaders。v0.14.0移除 CommonJS 构建改为 ESM-only。CommonJS 项目需用动态import()。v0.13.0TypeScript 版本要求 ≥ 5.7。v0.10.0Cookie#names()/values()变为 getter返回string[]forEach()回调签名改为(name, value, cookie)delete()返回void。v0.9.0set()/append()第二个参数只能传字符串对象值请用 setterget()未初始化的自定义头返回null而非undefinedAcceptLanguage不再允许undefined权重值setter 接受null/undefined等同于delete()日期头支持数字毫秒时间戳新增Accept、Accept-Encoding及accept、etag、host、location等访问器。5.2 v0.19.0 迁移示例// 解析原始头字符串原 new SuperHeaders(rawString) 的替代 import { parse } from remix/headers let headers parse(Content-Type: text/html\r\nCache-Control: no-cache) // 序列化回原始格式原 headers.toString() 的替代 import { stringify } from remix/headers let h new Headers() h.set(Content-Type, text/html) stringify(h) // Content-Type: text/html六、仓库中的消费方与测试验证该包不是孤立工具而是 Remix 生态的公共基础件。CHANGELOG v0.21.1 提到将内部 header 解析消费者含 multipart 解析器迁移到子路径导入——在仓库中可以看到 packages/multipart-parser、packages/fetch-proxy、packages/session 等包都依赖remix-run/headers的能力。每个解析类都配有完整的单元测试位于 packages/headers/src/lib 下的*.test.ts例如 accept.test.ts、cookie.test.ts、range.test.ts、super-headers.test.ts。测试覆盖了本文章节中提到的绝大多数行为同名 cookie 保留、弱 ETag 不匹配、Max-Age0序列化、字符解码、apply()的 header-aware 合并等是理解边界行为的第一手资料。七、版本演进路线图从 CHANGELOG 可以梳理出该包的能力演进主线当前版本 v0.21.1版本里程碑v0.5.0支持对象初始化new Headers({ contentType: {...} })更名为remix-run/headersv0.7.0新增Accept-Languagev0.9.0收紧类型安全对齐原生Headers新增Accept、Accept-Encodingv0.10.0Cookie改进新增If-None-Matchv0.14.0ESM-only移除 CommonJSv0.17.0新增Range、Content-Range、If-Match、If-Range、Allowv0.18.0新增VarygetPreferred()泛型化v0.19.0破坏性变更改用各头类from()新增parse()/stringify()v0.20.0恢复SuperHeaders默认/具名导出修复 RFC 8187解码v0.21.0新增apply()Cookie同名保留修复显式公开 API 返回类型v0.21.1子路径导出内部消费者迁移说明v0.17.0 的 CHANGELOG 示例中使用了isSatisfiable而当前仓库 range.ts 中实际方法名为canSatisfy使用时应以源码为准。八、最佳实践建议服务端请求/响应处理在 Remix loaders/actions 或任何 Fetch 服务端如 node-fetch-server中用new SuperHeaders(request.headers)获取类型化访问返回时直接作为Response的headers选项。内容协商用Accept/AcceptLanguage的getPreferred()做响应式协商注意 v0.18.0 起的泛型返回可保留输入数组的联合类型。缓存策略用CacheControl对象化读写public/max-age/immutable/s-maxage避免字符串拼接出错。分片下载服务端用Range/ContentRange处理bytes...请求并回206 Partial Content用canSatisfy/normalize统一处理三种区间写法。条件请求用If-None-Match做 304 短路支持弱比较If-Match用于写操作的乐观锁仅强比较。Cookie 管理用Cookie保留同名多值顺序用Set-Cookie完整控制HttpOnly、Secure、SameSite、Partitioned等属性。子路径按需导入只需单个解析器时从remix/headers/content-type等子路径导入减小打包体积。九、相关资源包入口与导出packages/headers/src/index.ts核心实现src/lib/super-headers.ts原始头工具src/lib/raw-headers.ts使用文档packages/headers/README.md依赖它的相关包packages/multipart-parser、packages/fetch-proxy、packages/node-fetch-server【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表