@remix-run/headers
v0.19.0
Published
A toolkit for working with HTTP headers in JavaScript
Readme
headers
Utilities for parsing, manipulating and stringifying HTTP header values.
HTTP headers contain critical information—from content negotiation and caching directives to authentication tokens and file metadata. While the native Headers API provides a basic string-based interface, it leaves the complexities of parsing specific header formats entirely up to you.
Installation
npm install @remix-run/headersIndividual Header Utilities
Each supported header has a class that represents the header value. Use the static from() method to parse header values. Each class has a toString() method that returns the header value as a string, which you can either call manually, or will be called automatically when the header class is used in a context that expects a string.
The following headers are currently supported:
- Accept
- Accept-Encoding
- Accept-Language
- Cache-Control
- Content-Disposition
- Content-Range
- Content-Type
- Cookie
- If-Match
- If-None-Match
- If-Range
- Range
- Set-Cookie
- Vary
Accept
Parse, manipulate and stringify Accept headers.
Implements Map<mediaType, quality>.
import { Accept } from '@remix-run/headers'
// Parse from headers
let accept = Accept.from(request.headers.get('accept'))
accept.mediaTypes // ['text/html', 'text/*']
accept.weights // [1, 0.9]
accept.accepts('text/html') // true
accept.accepts('text/plain') // true (matches text/*)
accept.accepts('image/jpeg') // false
accept.getWeight('text/plain') // 1 (matches text/*)
accept.getPreferred(['text/html', 'text/plain']) // 'text/html'
// Iterate
for (let [mediaType, quality] of accept) {
// ...
}
// Modify and set header
accept.set('application/json', 0.8)
accept.delete('text/*')
headers.set('Accept', accept)
// Construct directly
new Accept('text/html, text/*;q=0.9')
new Accept({ 'text/html': 1, 'text/*': 0.9 })
new Accept(['text/html', ['text/*', 0.9]])
// Use class for type safety when setting Headers values
// via Accept's `.toString()` method
let headers = new Headers({
Accept: new Accept({ 'text/html': 1, 'application/json': 0.8 }),
})
headers.set('Accept', new Accept({ 'text/html': 1, 'application/json': 0.8 }))Accept-Encoding
Parse, manipulate and stringify Accept-Encoding headers.
Implements Map<encoding, quality>.
import { AcceptEncoding } from '@remix-run/headers'
// Parse from headers
let acceptEncoding = AcceptEncoding.from(request.headers.get('accept-encoding'))
acceptEncoding.encodings // ['gzip', 'deflate']
acceptEncoding.weights // [1, 0.8]
acceptEncoding.accepts('gzip') // true
acceptEncoding.accepts('br') // false
acceptEncoding.getWeight('gzip') // 1
acceptEncoding.getPreferred(['gzip', 'deflate', 'br']) // 'gzip'
// Modify and set header
acceptEncoding.set('br', 1)
acceptEncoding.delete('deflate')
headers.set('Accept-Encoding', acceptEncoding)
// Construct directly
new AcceptEncoding('gzip, deflate;q=0.8')
new AcceptEncoding({ gzip: 1, deflate: 0.8 })
// Use class for type safety when setting Headers values
// via AcceptEncoding's `.toString()` method
let headers = new Headers({
'Accept-Encoding': new AcceptEncoding({ gzip: 1, br: 0.9 }),
})
headers.set('Accept-Encoding', new AcceptEncoding({ gzip: 1, br: 0.9 }))Accept-Language
Parse, manipulate and stringify Accept-Language headers.
Implements Map<language, quality>.
import { AcceptLanguage } from '@remix-run/headers'
// Parse from headers
let acceptLanguage = AcceptLanguage.from(request.headers.get('accept-language'))
acceptLanguage.languages // ['en-us', 'en']
acceptLanguage.weights // [1, 0.9]
acceptLanguage.accepts('en-US') // true
acceptLanguage.accepts('en-GB') // true (matches en)
acceptLanguage.getWeight('en-GB') // 1 (matches en)
acceptLanguage.getPreferred(['en-US', 'en-GB', 'fr']) // 'en-US'
// Modify and set header
acceptLanguage.set('fr', 0.5)
acceptLanguage.delete('en')
headers.set('Accept-Language', acceptLanguage)
// Construct directly
new AcceptLanguage('en-US, en;q=0.9')
new AcceptLanguage({ 'en-US': 1, en: 0.9 })
// Use class for type safety when setting Headers values
// via AcceptLanguage's `.toString()` method
let headers = new Headers({
'Accept-Language': new AcceptLanguage({ 'en-US': 1, fr: 0.5 }),
})
headers.set('Accept-Language', new AcceptLanguage({ 'en-US': 1, fr: 0.5 }))Cache-Control
Parse, manipulate and stringify Cache-Control headers.
import { CacheControl } from '@remix-run/headers'
// Parse from headers
let cacheControl = CacheControl.from(response.headers.get('cache-control'))
cacheControl.public // true
cacheControl.maxAge // 3600
cacheControl.sMaxage // 7200
cacheControl.noCache // undefined
cacheControl.noStore // undefined
cacheControl.noTransform // undefined
cacheControl.mustRevalidate // undefined
cacheControl.immutable // undefined
// Modify and set header
cacheControl.maxAge = 7200
cacheControl.immutable = true
headers.set('Cache-Control', cacheControl)
// Construct directly
new CacheControl('public, max-age=3600')
new CacheControl({ public: true, maxAge: 3600 })
// Use class for type safety when setting Headers values
// via CacheControl's `.toString()` method
let headers = new Headers({
'Cache-Control': new CacheControl({ public: true, maxAge: 3600 }),
})
headers.set('Cache-Control', new CacheControl({ public: true, maxAge: 3600 }))Content-Disposition
Parse, manipulate and stringify Content-Disposition headers.
import { ContentDisposition } from '@remix-run/headers'
// Parse from headers
let contentDisposition = ContentDisposition.from(response.headers.get('content-disposition'))
contentDisposition.type // 'attachment'
contentDisposition.filename // 'example.pdf'
contentDisposition.filenameSplat // "UTF-8''%E4%BE%8B%E5%AD%90.pdf"
contentDisposition.preferredFilename // '例子.pdf' (decoded from filename*)
// Modify and set header
contentDisposition.filename = 'download.pdf'
headers.set('Content-Disposition', contentDisposition)
// Construct directly
new ContentDisposition('attachment; filename="example.pdf"')
new ContentDisposition({ type: 'attachment', filename: 'example.pdf' })
// Use class for type safety when setting Headers values
// via ContentDisposition's `.toString()` method
let headers = new Headers({
'Content-Disposition': new ContentDisposition({ type: 'attachment', filename: 'example.pdf' }),
})
headers.set(
'Content-Disposition',
new ContentDisposition({ type: 'attachment', filename: 'example.pdf' }),
)Content-Range
Parse, manipulate and stringify Content-Range headers.
import { ContentRange } from '@remix-run/headers'
// Parse from headers
let contentRange = ContentRange.from(response.headers.get('content-range'))
contentRange.unit // "bytes"
contentRange.start // 200
contentRange.end // 1000
contentRange.size // 67589
// Unsatisfied range
let unsatisfied = ContentRange.from('bytes */67589')
unsatisfied.start // null
unsatisfied.end // null
unsatisfied.size // 67589
// Construct directly
new ContentRange({ unit: 'bytes', start: 0, end: 499, size: 1000 })
// Use class for type safety when setting Headers values
// via ContentRange's `.toString()` method
let headers = new Headers({
'Content-Range': new ContentRange({ unit: 'bytes', start: 0, end: 499, size: 1000 }),
})
headers.set('Content-Range', new ContentRange({ unit: 'bytes', start: 0, end: 499, size: 1000 }))Content-Type
Parse, manipulate and stringify Content-Type headers.
import { ContentType } from '@remix-run/headers'
// Parse from headers
let contentType = ContentType.from(request.headers.get('content-type'))
contentType.mediaType // "text/html"
contentType.charset // "utf-8"
contentType.boundary // undefined (or boundary string for multipart)
// Modify and set header
contentType.charset = 'iso-8859-1'
headers.set('Content-Type', contentType)
// Construct directly
new ContentType('text/html; charset=utf-8')
new ContentType({ mediaType: 'text/html', charset: 'utf-8' })
// Use class for type safety when setting Headers values
// via ContentType's `.toString()` method
let headers = new Headers({
'Content-Type': new ContentType({ mediaType: 'text/html', charset: 'utf-8' }),
})
headers.set('Content-Type', new ContentType({ mediaType: 'text/html', charset: 'utf-8' }))Cookie
Parse, manipulate and stringify Cookie headers.
Implements Map<name, value>.
import { Cookie } from '@remix-run/headers'
// Parse from headers
let cookie = Cookie.from(request.headers.get('cookie'))
cookie.get('session_id') // 'abc123'
cookie.get('theme') // 'dark'
cookie.has('session_id') // true
cookie.size // 2
// Iterate
for (let [name, value] of cookie) {
// ...
}
// Modify and set header
cookie.set('theme', 'light')
cookie.delete('session_id')
headers.set('Cookie', cookie)
// Construct directly
new Cookie('session_id=abc123; theme=dark')
new Cookie({ session_id: 'abc123', theme: 'dark' })
new Cookie([
['session_id', 'abc123'],
['theme', 'dark'],
])
// Use class for type safety when setting Headers values
// via Cookie's `.toString()` method
let headers = new Headers({
Cookie: new Cookie({ session_id: 'abc123', theme: 'dark' }),
})
headers.set('Cookie', new Cookie({ session_id: 'abc123', theme: 'dark' }))If-Match
Parse, manipulate and stringify If-Match headers.
Implements Set<etag>.
import { IfMatch } from '@remix-run/headers'
// Parse from headers
let ifMatch = IfMatch.from(request.headers.get('if-match'))
ifMatch.tags // ['"67ab43"', '"54ed21"']
ifMatch.has('"67ab43"') // true
ifMatch.matches('"67ab43"') // true (checks precondition)
ifMatch.matches('"abc123"') // false
// Note: Uses strong comparison only (weak ETags never match)
let weak = IfMatch.from('W/"67ab43"')
weak.matches('W/"67ab43"') // false
// Modify and set header
ifMatch.add('"newetag"')
ifMatch.delete('"67ab43"')
headers.set('If-Match', ifMatch)
// Construct directly
new IfMatch(['abc123', 'def456'])
// Use class for type safety when setting Headers values
// via IfMatch's `.toString()` method
let headers = new Headers({
'If-Match': new IfMatch(['"abc123"', '"def456"']),
})
headers.set('If-Match', new IfMatch(['"abc123"', '"def456"']))If-None-Match
Parse, manipulate and stringify If-None-Match headers.
Implements Set<etag>.
import { IfNoneMatch } from '@remix-run/headers'
// Parse from headers
let ifNoneMatch = IfNoneMatch.from(request.headers.get('if-none-match'))
ifNoneMatch.tags // ['"67ab43"', '"54ed21"']
ifNoneMatch.has('"67ab43"') // true
ifNoneMatch.matches('"67ab43"') // true
// Supports weak comparison (unlike If-Match)
let weak = IfNoneMatch.from('W/"67ab43"')
weak.matches('W/"67ab43"') // true
// Modify and set header
ifNoneMatch.add('"newetag"')
ifNoneMatch.delete('"67ab43"')
headers.set('If-None-Match', ifNoneMatch)
// Construct directly
new IfNoneMatch(['abc123'])
// Use class for type safety when setting Headers values
// via IfNoneMatch's `.toString()` method
let headers = new Headers({
'If-None-Match': new IfNoneMatch(['"abc123"']),
})
headers.set('If-None-Match', new IfNoneMatch(['"abc123"']))If-Range
Parse, manipulate and stringify If-Range headers.
import { IfRange } from '@remix-run/headers'
// Parse from headers
let ifRange = IfRange.from(request.headers.get('if-range'))
// With HTTP date
ifRange.matches({ lastModified: 1609459200000 }) // true
ifRange.matches({ lastModified: new Date('2021-01-01') }) // true
// With ETag
let etagHeader = IfRange.from('"67ab43"')
etagHeader.matches({ etag: '"67ab43"' }) // true
// Empty/null returns empty instance (range proceeds unconditionally)
let empty = IfRange.from(null)
empty.matches({ etag: '"any"' }) // true
// Construct directly
new IfRange('"abc123"')
// Use class for type safety when setting Headers values
// via IfRange's `.toString()` method
let headers = new Headers({
'If-Range': new IfRange('"abc123"'),
})
headers.set('If-Range', new IfRange('"abc123"'))Range
Parse, manipulate and stringify Range headers.
import { Range } from '@remix-run/headers'
// Parse from headers
let range = Range.from(request.headers.get('range'))
range.unit // "bytes"
range.ranges // [{ start: 200, end: 1000 }]
range.canSatisfy(2000) // true
range.canSatisfy(500) // false
range.normalize(2000) // [{ start: 200, end: 1000 }]
// Multiple ranges
let multi = Range.from('bytes=0-499, 1000-1499')
multi.ranges.length // 2
// Suffix range (last N bytes)
let suffix = Range.from('bytes=-500')
suffix.normalize(2000) // [{ start: 1500, end: 1999 }]
// Construct directly
new Range({ unit: 'bytes', ranges: [{ start: 0, end: 999 }] })
// Use class for type safety when setting Headers values
// via Range's `.toString()` method
let headers = new Headers({
Range: new Range({ unit: 'bytes', ranges: [{ start: 0, end: 999 }] }),
})
headers.set('Range', new Range({ unit: 'bytes', ranges: [{ start: 0, end: 999 }] }))Set-Cookie
Parse, manipulate and stringify Set-Cookie headers.
import { SetCookie } from '@remix-run/headers'
// Parse from 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.domain // undefined
setCookie.maxAge // undefined
setCookie.expires // undefined
setCookie.sameSite // undefined
// Modify and set header
setCookie.maxAge = 3600
setCookie.sameSite = 'Strict'
headers.set('Set-Cookie', setCookie)
// Construct directly
new SetCookie('session_id=abc; Path=/; HttpOnly; Secure')
new SetCookie({
name: 'session_id',
value: 'abc',
path: '/',
httpOnly: true,
secure: true,
})
// Use class for type safety when setting Headers values
// via SetCookie's `.toString()` method
let headers = new Headers({
'Set-Cookie': new SetCookie({ name: 'session_id', value: 'abc', httpOnly: true }),
})
headers.set('Set-Cookie', new SetCookie({ name: 'session_id', value: 'abc', httpOnly: true }))Vary
Parse, manipulate and stringify Vary headers.
Implements Set<headerName>.
import { Vary } from '@remix-run/headers'
// Parse from headers
let vary = Vary.from(response.headers.get('vary'))
vary.headerNames // ['accept-encoding', 'accept-language']
vary.has('Accept-Encoding') // true (case-insensitive)
vary.size // 2
// Modify and set header
vary.add('User-Agent')
vary.delete('Accept-Language')
headers.set('Vary', vary)
// Construct directly
new Vary('Accept-Encoding, Accept-Language')
new Vary(['Accept-Encoding', 'Accept-Language'])
new Vary({ headerNames: ['Accept-Encoding', 'Accept-Language'] })
// Use class for type safety when setting Headers values
// via Vary's `.toString()` method
let headers = new Headers({
Vary: new Vary(['Accept-Encoding', 'Accept-Language']),
})
headers.set('Vary', new Vary(['Accept-Encoding', 'Accept-Language']))Raw Headers
Parse and stringify raw HTTP header strings.
import { parse, stringify } from '@remix-run/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'Related Packages
fetch-proxy- Build HTTP proxy servers using the web fetch APInode-fetch-server- Build HTTP servers on Node.js using the web fetch API
License
See LICENSE
