mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-22 00:11:02 -05:00
Made utility methods to work with HTTP headers
This commit is contained in:
parent
0def4d2bde
commit
fd361a63f5
1 changed files with 270 additions and 0 deletions
270
src/utils/net/headers.ts
Normal file
270
src/utils/net/headers.ts
Normal file
|
@ -0,0 +1,270 @@
|
|||
import { asArray, asArrayLike, isIterable, isMap, isMultiMap } from "@/utils/collections";
|
||||
import { statSync } from "node:fs";
|
||||
import { Blob } from "./blob";
|
||||
import { HttpRequestBody, isStreamableHttpRequestBody } from "./http-request-body";
|
||||
|
||||
/**
|
||||
* Represents HTTP headers collection.
|
||||
*/
|
||||
export type Headers = Record<string, string> | Iterable<Iterable<string>>;
|
||||
|
||||
/**
|
||||
* A separator used to concatenate multiple header values.
|
||||
*/
|
||||
const HEADER_SEPARATOR = ", ";
|
||||
|
||||
/**
|
||||
* Checks if a header exists in the given headers collection.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param header - The header to look for.
|
||||
*
|
||||
* @returns `true` if the header exists; otherwise, `false`.
|
||||
*/
|
||||
export function hasHeader(headers: Headers, header: string): boolean {
|
||||
return getHeader(headers, header) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a header from the given headers collection.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param header - The header to look for.
|
||||
*
|
||||
* @returns The value of the header, or `undefined` if the header does not exist.
|
||||
*/
|
||||
export function getHeader(headers: Headers, header: string): string | undefined {
|
||||
if (!headers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isMap<string, string>(headers)) {
|
||||
return headers.get(header);
|
||||
}
|
||||
|
||||
if (isIterable<string>(headers)) {
|
||||
const arrayLikeHeaders = asArrayLike(headers);
|
||||
return arrayLikeHeaders.find(x => asArrayLike(x).at(0) === header);
|
||||
}
|
||||
|
||||
return headers[header];
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a header value to the given headers collection.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param header - The header to append.
|
||||
* @param value - The value of the header to append.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function appendHeader(headers: Headers, header: string, value: string): Headers {
|
||||
if (isMultiMap(headers)) {
|
||||
headers.append(header, value);
|
||||
return headers;
|
||||
}
|
||||
|
||||
const currentValue = getHeader(headers, header);
|
||||
const concatenatedValue = currentValue ? `${currentValue}${HEADER_SEPARATOR}${value}` : value;
|
||||
return setHeader(headers, header, concatenatedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends multiple headers to the given headers collection.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param newHeaders - The headers to append.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function appendHeaders(headers: Headers, newHeaders: Headers): Headers {
|
||||
return mergeHeaders(headers, newHeaders, appendHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a header value in the given headers collection, overwriting any existing value.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param header - The header to set.
|
||||
* @param value - The value of the header to set.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function setHeader(headers: Headers, header: string, value: string): Headers {
|
||||
if (value === undefined || value === null) {
|
||||
return deleteHeader(headers, header);
|
||||
}
|
||||
|
||||
if (isMap(headers)) {
|
||||
headers.set(header, value);
|
||||
return headers;
|
||||
}
|
||||
|
||||
if (isIterable(headers)) {
|
||||
const arrayLikeHeaders = asArray(headers);
|
||||
const headerIndex = arrayLikeHeaders.findIndex(x => asArrayLike(x).at(0) === header);
|
||||
if (headerIndex >= 0) {
|
||||
arrayLikeHeaders[headerIndex][1] = value;
|
||||
} else {
|
||||
arrayLikeHeaders.push([header, value]);
|
||||
}
|
||||
return arrayLikeHeaders;
|
||||
}
|
||||
|
||||
headers = headers || {};
|
||||
headers[header] = value;
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple headers in the given headers collection, overwriting any existing values.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param newHeaders - The headers to set.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function setHeaders(headers: Headers, newHeaders: Headers): Headers {
|
||||
return mergeHeaders(headers, newHeaders, setHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a header value in the given headers collection only if the header does not already exist.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param header - The header to set.
|
||||
* @param defaultValue - The default value of the header to set.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function setDefaultHeader(headers: Headers, header: string, defaultValue: string): Headers {
|
||||
return hasHeader(headers, header) ? headers : setHeader(headers, header, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple default headers in the given headers collection, only if the headers do not already exist.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param defaultHeaders - The default headers to set.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function setDefaultHeaders(headers: Headers, defaultHeaders: Headers): Headers {
|
||||
return mergeHeaders(headers, defaultHeaders, setDefaultHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a header value from the given headers collection.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param header - The header to delete.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function deleteHeader(headers: Headers, header: string): Headers {
|
||||
if (isMap(headers)) {
|
||||
headers.delete(header);
|
||||
return headers;
|
||||
}
|
||||
|
||||
if (isIterable(headers)) {
|
||||
return asArrayLike(headers).filter(x => asArrayLike(x).at(0) !== header);
|
||||
}
|
||||
|
||||
delete headers?.[header];
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple header values from the given headers collection.
|
||||
*
|
||||
* @param headers - The headers collection.
|
||||
* @param headersToDelete - The headers to delete.
|
||||
*
|
||||
* @returns The updated headers collection.
|
||||
*/
|
||||
export function deleteHeaders(headers: Headers, headersToDelete: Iterable<string>): Headers {
|
||||
for (const header of headersToDelete) {
|
||||
headers = deleteHeader(headers, header);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the provided headers collection, preserving the key-value pairs of the original.
|
||||
*
|
||||
* If the headers object is an instance of a `Map`, a new instance of
|
||||
* the same type is created and the key-value pairs are copied over.
|
||||
*
|
||||
* If the headers collection is iterable, the key-value pairs are returned as an array.
|
||||
*
|
||||
* @param headers - The headers collection to be cloned.
|
||||
*
|
||||
* @returns A new headers collection containing the key-value pairs of the original headers collection,
|
||||
* or `undefined` if the provided headers collection is `undefined` or `null`.
|
||||
*/
|
||||
export function cloneHeaders(headers: Headers): Headers {
|
||||
if (headers?.constructor && (isMap(headers) || !isIterable(headers))) {
|
||||
return setHeaders(new (headers.constructor as new() => Headers)(), headers);
|
||||
}
|
||||
|
||||
if (isIterable(headers)) {
|
||||
return [...headers];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two headers collections using the specified merger function.
|
||||
*
|
||||
* @param left - The left headers collection.
|
||||
* @param right - The right headers collection.
|
||||
* @param merger - The function that merges headers.
|
||||
*
|
||||
* @returns The merged headers collection.
|
||||
*/
|
||||
function mergeHeaders(left: Headers, right: Headers, merger: (headers: Headers, header: string, value: string) => Headers): Headers {
|
||||
const defaultHeadersIterable = isIterable(right) ? right : Object.entries(right || {});
|
||||
for (const headerEntry of defaultHeadersIterable) {
|
||||
const [header, value] = asArray(headerEntry);
|
||||
left = merger(left, header, value);
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers the appropriate headers for a given HTTP request body.
|
||||
*
|
||||
* @param body - The HTTP request body to infer headers from.
|
||||
*
|
||||
* @returns A collection of the inferred headers.
|
||||
*/
|
||||
export function inferHttpRequestBodyHeaders(body: HttpRequestBody): Headers {
|
||||
const headers = { } as Headers;
|
||||
if (!isStreamableHttpRequestBody(body)) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
const type = "application/octet-stream";
|
||||
const length =
|
||||
// `body` is a `Blob`
|
||||
typeof (body as Blob).size === "number" ? (body as Blob).size :
|
||||
|
||||
// `body` is a `Buffer`
|
||||
typeof (body as Buffer).byteLength === "number" ? (body as Buffer).byteLength :
|
||||
|
||||
// `body` is a `ReadableStream`, which was created from a `Buffer`
|
||||
Buffer.isBuffer(body["path"]) ? (body["path"] as Buffer).byteLength :
|
||||
|
||||
// `body` is a `ReadableStream`, which was created from a file path
|
||||
typeof body["path"] === "string" || body["path"]?.[Symbol.toStringTag] === "URL" ? statSync(body["path"]).size :
|
||||
|
||||
// `length` is unknown
|
||||
undefined;
|
||||
|
||||
headers["Content-Type"] = type;
|
||||
headers["Content-Length"] = length;
|
||||
return headers;
|
||||
}
|
Loading…
Reference in a new issue