UNPKG

@tsed/platform-http

Version:
301 lines (300 loc) 8.23 kB
import { isArray, isBoolean, isNumber, isStream, isString } from "@tsed/core"; import { injectable, lazyInject, ProviderScope } from "@tsed/di"; import { getStatusMessage } from "@tsed/schema"; import encodeUrl from "encodeurl"; /** * Platform Response abstraction layer. * @platform */ export class PlatformResponse { constructor($ctx) { this.$ctx = $ctx; } /** * The current @@PlatformRequest@@. */ get request() { return this.$ctx.request; } get raw() { return this.$ctx.event.response; } /** * Get the current statusCode */ get statusCode() { return this.raw.statusCode; } /** * An object that contains response local variables scoped to the request, and therefore available only to the view(s) rendered during that request / response cycle (if any). Otherwise, this property is identical to app.locals. * * This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on. */ get locals() { return this.raw.locals; } /** * Return the original response framework instance */ get response() { return this.getResponse(); } /** * Return the original response node.js instance */ get res() { return this.getRes(); } /** * Returns the HTTP response header specified by field. The match is case-insensitive. * * ```typescript * response.get('Content-Type') // => "text/plain" * ``` * * @param name */ get(name) { return this.raw.get(name); } getHeaders() { return this.raw.getHeaders(); } /** * Return the Framework response object (express, koa, etc...) */ getResponse() { return this.raw; } /** * Return the Node.js response object */ getRes() { return this.raw; } hasStatus() { return this.statusCode !== 200; } /** * Sets the HTTP status for the response. * * @param status */ status(status) { this.raw.status(status); return this; } /** * Set header `field` to `val`, or pass * an object of header fields. * * Examples: * ```typescript * response.setHeaders({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); * ``` * * Aliased as `res.header()`. */ setHeaders(headers) { // apply headers Object.entries(headers).forEach(([key, item]) => { this.setHeader(key, item); }); return this; } setHeader(key, item) { this.raw.set(key, this.formatHeader(key, item)); return this; } /** * Set `Content-Type` response header with `type` through `mime.lookup()` * when it does not contain "/", or set the Content-Type to `type` otherwise. * * Examples: * * res.type('.html'); * res.type('html'); * res.type('json'); * res.type('application/json'); * res.type('png'); */ contentType(contentType) { this.raw.contentType(contentType); return this; } contentLength(length) { this.setHeader("Content-Length", length); return this; } getContentLength() { if (this.get("Content-Length")) { return parseInt(this.get("Content-Length"), 10) || 0; } } getContentType() { return (this.get("Content-Type") || "").split(";")[0]; } /** * Sets the HTTP response Content-Disposition header field to “attachment”. * If a filename is given, then it sets the Content-Type based on the extension name via res.type(), and sets the Content-Disposition “filename=” parameter. * * ```typescript * res.attachment() * // Content-Disposition: attachment * * res.attachment('path/to/logo.png') * // Content-Disposition: attachment; filename="logo.png" * // Content-Type: image/png * ``` * * @param filename */ attachment(filename) { this.raw.attachment(filename); return this; } /** * Redirects to the URL derived from the specified path, with specified status, a positive integer that corresponds to an [HTTP status code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html). * If not specified, status defaults to `302 Found`. * * @param status * @param url */ redirect(status, url) { status = status || 302; this.location(url); // Set location header url = this.get("Location"); const txt = `${getStatusMessage(status)}. Redirecting to ${url}`; this.status(status); if (this.request.method === "HEAD") { this.end(); } else { this.setHeader("Content-Length", Buffer.byteLength(txt)).end(txt); } return this; } /** * Sets the response Location HTTP header to the specified path parameter. * * @param location */ location(location) { this.setHeader("Location", location); return this; } /** * Stream the given data. * * @param data */ stream(data) { data.pipe(this.raw); return this; } /** * Renders a view and sends the rendered HTML string to the client. * * @param path * @param options */ async render(path, options = {}) { const platformViews = await lazyInject(() => import("@tsed/platform-views")); return platformViews.render(path, { ...this.locals, ...options }); } /** * Send any data to your consumer. * * This method accept a ReadableStream, a plain object, boolean, string, number, null and undefined data. * It chooses the better way to send the data. * * @param data */ body(data) { this.data = data; if (data === undefined) { this.end(); return this; } if (isStream(data)) { this.stream(data); return this; } if (Buffer.isBuffer(data)) { this.buffer(data); return this; } if (isBoolean(data) || isNumber(data) || isString(data) || data === null) { if (data === null) { this.status(204); } this.end(data); return this; } this.json(data); return this; } getBody() { return this.data; } /** * Add a listener to handler the end of the request/response. * @param cb */ onEnd(cb) { const res = this.getRes(); res.on("finish", cb); return this; } isDone() { if (this.$ctx.isFinished()) { return true; } const res = this.getRes(); return Boolean(this.isHeadersSent() || res.writableEnded || res.writableFinished); } isHeadersSent() { return this.getRes().headersSent; } cookie(name, value, opts) { if (value === null) { this.raw.clearCookie(name, opts); return this; } this.raw.cookie(name, value, opts); const cookie = this.raw.get("set-cookie"); if (!isArray(value)) { this.raw.set("set-cookie", [].concat(cookie)); } return this; } formatHeader(key, item) { if (key.toLowerCase() === "location") { // "back" is an alias for the referrer if (item === "back") { item = this.request.get("Referrer") || "/"; } item = encodeUrl(String(item)); } return item; } json(data) { this.raw.json(data); return this; } buffer(data) { if (!this.getContentType()) { this.contentType("application/octet-stream"); } this.contentLength(data.length); this.end(data); } end(data) { this.raw.send(data); } } injectable(PlatformResponse).scope(ProviderScope.INSTANCE);