API
Six functions, all exported from the package root and from their own subpaths. Types
are in index.d.ts. Where the shipped types and the behaviour disagree, this page
says so.
| Export | Subpath | Direction |
|---|---|---|
toWebRequest |
@johnhenry/webwire/to-web-request |
server IncomingMessage → Request |
toWebResponse |
@johnhenry/webwire/to-web-response |
client IncomingMessage → Response |
writeWebResponse |
@johnhenry/webwire/write-web-response |
Response → ServerResponse |
toNodeRequestOptions |
@johnhenry/webwire/to-node-request-options |
Request or URL → http.request() options |
setTrailers, getTrailers |
@johnhenry/webwire/trailers |
attach and read Response trailers |
toWebRequest(req, options?)
Section titled “toWebRequest(req, options?)”function toWebRequest( req: import("http").IncomingMessage, options?: { attachRaw?: boolean; hostHeaders?: string[] },): Request;Converts the req of server.on("request", (req, res) => ...) into a Web Request.
Synchronous.
Options
attachRaw(boolean, defaultfalse): attachreqto theRequestas a non-enumerable.rawproperty, so middleware can reach the socket (for WebSocket upgrades, say). It does not show up inObject.keys().hostHeaders(string[], default["host"]): header names to read the host from, in priority order. The first one present with a non-empty value wins; if none is present the host islocalhost. A reverse proxy usually wants["x-forwarded-host", "host"]. Names are matched case-insensitively (they are lower-cased internally, because Node lower-cases incoming header names). That is 0.0.1 behaviour; in 0.0.0 you must write them lower-case (see Limitations).
How the Request is built
- The URL is
new URL(req.url, scheme + "://" + host). From 0.0.1 the scheme ishttpswhenreq.socket.encryptedis true andhttpotherwise; 0.0.0 always usedhttp.X-Forwarded-Protois never consulted. An absolute-form request target (GET http://other/x) or a//host/pathtarget replaces the host. - The method and all headers are copied from
req. - For any method other than
GETandHEAD, the body isreqitself (withduplex: "half"), so it is streamed, not buffered, and reading it consumesreq.GETandHEADrequests haverequest.body === null, even if the client sent a body.
Errors
- If
req.urldoes not form a valid URL, throws anErrorwithmessageInvalid request URL: <req.url>,.status = 400and.causeset to the originalTypeError. - Anything the
Requestconstructor rejects is not wrapped. For example, aCONNECTorTRACErequest throws a plainTypeError('CONNECT' HTTP method is unsupported), with no.status.
toWebResponse(nodeRes, body?)
Section titled “toWebResponse(nodeRes, body?)”function toWebResponse( nodeRes: import("http").IncomingMessage, body?: BodyInit | null,): Response;Converts the response Node gives you from an outbound http.request() (the res in
http.request(url, (res) => ...)) into a Web Response. Synchronous.
It copies statusCode, statusMessage (as statusText) and the headers, and wraps
whatever you pass as body. It never reads nodeRes. Buffer it, stream it, transform
it, or omit it, and pass the result in. An omitted or null body gives a bodyless
Response.
Header handling: array-valued headers (set-cookie is the one Node gives you as an
array) are appended one by one, so response.headers.getSetCookie() returns every
cookie; other headers are set as given.
Errors: none of its own, but the Response constructor is strict and its errors
pass straight through:
- In 0.0.0, a
204or304status with any body, including an emptyBuffer, threwTypeError. From 0.0.1 the body is ignored (passed asnull) for 204, 205 and 304. - A status outside 200 to 599 (a
1xx) throwsRangeError. - A body the
Responseconstructor does not accept throwsTypeError. A NodeReadableis accepted (it is an async iterable); a webReadableStreammust yieldUint8Arraychunks, not strings.
writeWebResponse(response, res, options?)
Section titled “writeWebResponse(response, res, options?)”function writeWebResponse( response: Response | { status: 101 }, res: import("http").ServerResponse, options?: { onError?: (error: Error) => void },): Promise<void>;Writes a Response out through a ServerResponse: status, headers, body, trailers,
then res.end(). Resolves when the response has been written. Call it once per res.
Status. res.statusCode is set from response.status. From 0.0.1
a non-empty response.statusText is also written as the reason phrase; 0.0.0 never
wrote it, and an empty one always uses Node’s default phrase for the code.
Headers. Same-named headers are collected and passed to res.setHeader() as an
array, so repeated Set-Cookie headers go out as separate header lines instead of one
comma-joined line.
Body. For a real Response, response.body is a ReadableStream (or null),
which is piped to res with backpressure. The function also accepts duck-typed
objects whose body is a string, a Uint8Array, a Node Readable (anything with
.pipe()), or anything else (written via String()). Those shapes only come up when
you pass your own object instead of a Response.
Trailers. After the body is written, if getTrailers(response) returned a promise,
it is awaited, converted with new Headers(...), and sent with res.addTrailers()
before res.end(). See setTrailers().
status === 101. Returns immediately without touching res. A real Response
cannot have that status (the constructor throws outside 200 to 599), so pass a duck-typed
Object.freeze({ status: 101 }) to signal “a WebSocket upgrade, handled elsewhere”.
Options
onError(error): called, not thrown, when writing a streamed body fails after headers are sent: the source stream errors, the client disconnects mid-body (ERR_STREAM_PREMATURE_CLOSE), or a trailers promise rejects. Defaults toconsole.error("Error streaming response body:", err). AfteronErrorruns the connection is destroyed withres.destroy(err)and the returned promise still resolves.
Errors. Failures before the body starts reject the promise: res.setHeader()
throws for an invalid header name or value (for example ERR_INVALID_HTTP_TOKEN).
For a stream body, nothing is thrown afterwards; use onError.
toNodeRequestOptions(requestOrUrl, options?)
Section titled “toNodeRequestOptions(requestOrUrl, options?)”function toNodeRequestOptions( requestOrUrl: Request | string | URL, options?: { method?: string; headers?: HeadersInit },): { url: URL; isHTTPS: boolean; requestOptions: { hostname: string; port: number; path: string; method: string; headers: Record<string, string | string[]>; };};Converts a Request, or a bare URL plus { method, headers }, into the pieces for an
outbound http.request() / https.request() call. Synchronous. It returns plain data,
not a live request, so you choose when and how to issue it.
- If
requestOrUrlis aRequest(checked withinstanceof Request), its URL, method and headers are used andoptionsis ignored. - Otherwise it is passed to
new URL(), withoptions.method(default"GET") andoptions.headers(anyHeadersInit).
Return value
url: the parsedURL.isHTTPS:url.protocol === "https:". Any other protocol (ws:,ftp:) isfalse.requestOptions.hostname:url.hostnamewith IPv6 brackets stripped ([::1]becomes::1). In 0.0.0 the brackets were kept andhttp.request()failed withENOTFOUND.requestOptions.port: always a number: the URL’s explicit port, else443or80. In 0.0.0 an explicit port was the string"8080", althoughindex.d.tstyped it asnumber.requestOptions.path: pathname plus search. The hash is dropped.requestOptions.method: as given. It is not upper-cased when you pass lower-case tooptions.method.requestOptions.headers: a plain object with lower-case keys. AHeadersinstance is not accepted byhttp.request()as it is, which is why this conversion exists. Repeatedset-cookieheaders are an array (one element per header) from 0.0.1; 0.0.0 collapsed them.
Errors: new URL() throws TypeError (ERR_INVALID_URL) for a relative or
unparseable URL. Nothing is validated beyond that.
What it does not carry: the request body, URL credentials (user:pass@), and
the abort signal. See Calling out.
setTrailers(response, trailers) / getTrailers(response)
Section titled “setTrailers(response, trailers) / getTrailers(response)”function setTrailers( response: Response, trailers: HeadersInit | Promise<HeadersInit>,): void;
function getTrailers(response: Response): Promise<HeadersInit> | undefined;HTTP trailers are not part of the Fetch model: a Response has no trailers.
setTrailers() attaches some to a Response you are about to return, held in a
module-level WeakMap keyed by that Response object. getTrailers() returns the
(always promise-wrapped) value, or undefined if none were set. writeWebResponse()
is the consumer.
Pass a Promise when the trailer is computed from the streamed body, such as a running
checksum or Server-Timing. It is awaited only after the body has finished. Calling
setTrailers() again on the same Response replaces the earlier value.
Trailers only travel on chunked responses; see Limitations.