UNPKG

@curveball/core

Version:

Curveball is a framework writting in Typescript for Node.js

191 lines 6.13 kB
import * as http from 'node:http'; import { promisify } from 'node:util'; import { isHttp2Response } from './http-utils.js'; import push from './push.js'; import NodeHeaders from './response-headers.js'; import { Context, MemoryRequest, MemoryResponse, invokeMiddlewares, headerHelpers, } from '@curveball/kernel'; export class NodeResponse { inner; bodyValue; explicitStatus; constructor(inner, origin) { // The default response status is 404. this.inner = inner; // @ts-expect-error Typescript doesn't like null here because it might be // incompatible with T, but we're ignoring it as it's a good default. this.body = null; this.status = 404; this.explicitStatus = false; this.origin = origin; } /** * List of HTTP Headers */ get headers() { return new NodeHeaders(this.inner); } /** * HTTP status code. */ get status() { return this.inner.statusCode; } /** * Updates the HTTP status code for this response. */ set status(value) { this.explicitStatus = true; this.inner.statusCode = value; } /** * Updates the response body. */ set body(value) { if (!this.explicitStatus) { // If no status was set earlier, we set it to 200. this.inner.statusCode = 200; } this.bodyValue = value; } /** * Returns the response body. */ get body() { return this.bodyValue; } /** * Sends an informational response before the real response. * * This can be used to for example send a `100 Continue` or `103 Early Hints` * response. */ async sendInformational(status, headers) { let outHeaders = {}; if (typeof headers !== 'undefined') { if (headers.getAll !== undefined) { outHeaders = headers.getAll(); } else { outHeaders = headers; } } /** * It's a HTTP2 connection. */ if (isHttp2Response(this.inner)) { this.inner.stream.additionalHeaders({ ':status': status, ...outHeaders }); } else { const rawHeaders = []; for (const headerName of Object.keys(outHeaders)) { const headerValue = outHeaders[headerName]; if (Array.isArray(headerValue)) { for (const headerVal of headerValue) { rawHeaders.push(`${headerName}: ${headerVal}\r\n`); } } else { rawHeaders.push(`${headerName}: ${headerValue}\r\n`); } } // @ts-expect-error let's ignore this const writeRaw = promisify(this.inner._writeRaw.bind(this.inner)); const message = `HTTP/1.1 ${status} ${http.STATUS_CODES[status]}\r\n${rawHeaders.join('')}\r\n`; await writeRaw(message, 'ascii'); } } /** * Sends a HTTP/2 push. * * The passed middleware will be called with a new Context object specific * for pushes. */ async push(callback) { if (!isHttp2Response(this.inner)) { // Not HTTP2 return; } const stream = this.inner.stream; if (!stream.pushAllowed) { // Client doesn't want pushes return; } const pushCtx = new Context(new MemoryRequest('GET', '|||DELIBERATELY_INVALID|||', this.origin), new MemoryResponse(this.origin)); await invokeMiddlewares(pushCtx, [callback]); if (pushCtx.request.requestTarget === '|||DELIBERATELY_INVALID|||') { throw new Error('The "path" must be set in the push context\'s request'); } return push(stream, pushCtx); } /** * Returns the value of the Content-Type header, with any additional * parameters such as charset= removed. * * If there was no Content-Type header, an empty string will be returned. */ get type() { const type = this.headers.get('content-type'); if (!type) { return ''; } return type.split(';')[0]; } /** * Shortcut for setting the Content-Type header. */ set type(value) { this.headers.set('content-type', value); } /** * This method will return true or false if a Request or Response has a * Content-Type header that matches the argument. * * For example, if the Content-Type header has the value: application/hal+json, * then the arguments will all return true: * * * application/hal+json * * application/json * * hal+json * * json * * application/* */ is(type) { return headerHelpers.is(this, type); } /** * redirect redirects the response with an optionally provided HTTP status * code in the first position to the location provided in address. If no status * is provided, 303 See Other is used. * * @param {(string|number)} addrOrStatus if passed a string, the string will * be used to set the Location header of the response object and the default status * of 303 See Other will be used. If a number, an addressed must be passed in the second * argument. * @param {string} address If addrOrStatus is passed a status code, this value is * set as the value of the response's Location header. */ redirect(addrOrStatus, address = '') { let status = 303; let addr; if (typeof (addrOrStatus) === 'number') { status = addrOrStatus; addr = address; } else { addr = addrOrStatus; } this.status = status; this.headers.set('Location', addr); } /** * Public base URL * * This will be used to determine the absoluteUrl */ origin; } export default NodeResponse; //# sourceMappingURL=response.js.map