@tsed/platform-http
Version:
A TypeScript Framework on top of Express
301 lines (300 loc) • 8.23 kB
JavaScript
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);