UNPKG

@webda/core

Version:

Expose API with Lambda

153 lines 4.82 kB
import * as fs from "fs"; import * as mime from "mime-types"; import * as path from "path"; import { WebdaError } from "../errors.js"; import { Service, ServiceParameters } from "./service.js"; /** * ResourceService parameters */ export class ResourceServiceParameters extends ServiceParameters { constructor(params) { super(params); this.url ?? (this.url = "resources"); this.rootRedirect ?? (this.rootRedirect = false); if (!this.url.startsWith("/")) { this.url = "/" + this.url; } if (!this.url.endsWith("/")) { this.url += "/"; } this.folder ?? (this.folder = "." + this.url); if (!this.folder.endsWith("/")) { this.folder += "/"; } this.index ?? (this.index = "index.html"); this.indexFallback ?? (this.indexFallback = true); } } /** * This service expose a folder as web * * It is the same as `static` on `express` * * @category CoreServices * @WebdaModda */ export default class ResourceService extends Service { /** * Load the parameters for a service */ loadParameters(params) { return new ResourceServiceParameters(params); } /** * Resolve resource folder */ computeParameters() { super.computeParameters(); this._resolved = path.resolve(this.parameters.folder); this.fileOnly = !fs.lstatSync(this._resolved).isDirectory(); } /** * Init the routes */ initRoutes() { this.addRoute(this.parameters.url, ["GET"], this._serve, { get: { description: "Get resources", summary: "Get file", operationId: "getResource", responses: { "200": { description: "" }, "401": { description: "Illegal resource" }, "404": { description: "File not found" } } } }); this.addRoute(this.parameters.url + "{+resource}", ["GET"], this._serve, { get: { description: "Get resources", summary: "Get file", operationId: "getResources", responses: { "200": { description: "" }, "401": { description: "Illegal resource" }, "404": { description: "File not found" } } } }, true); if (this.parameters.rootRedirect) { this.addRoute("/", ["GET"], this._redirect, { get: { description: "Redirect / to the exposed url", summary: "Serve resource", operationId: "redirectRoottoResources", responses: { "302": { description: "" } } } }); } } /** * Handle / request and redirect to the resources folder * * @param ctx */ _redirect(ctx) { ctx.redirect(ctx.getHttpContext().getAbsoluteUrl(this.parameters.url)); } /** * Serve the folder by itself, doing the mime detection * * @param ctx */ _serve(ctx) { let file = this._resolved; // If resource is not a file if (!this.fileOnly) { file = path.join(this._resolved, ctx.parameter("resource") || this.parameters.index); } // Avoid path transversal if (!path.resolve(file).startsWith(this._resolved)) { throw new WebdaError.Unauthorized(file); } if (!fs.existsSync(file)) { file = path.join(this._resolved, this.parameters.index); // Catch All for SPA if (!(this.parameters.indexFallback && fs.existsSync(file))) { throw new WebdaError.NotFound(file); } } let mimetype = mime.lookup(file) || "application/octet-stream"; if (mimetype.startsWith("text/")) { mimetype += "; charset=UTF-8"; } ctx.writeHead(200, { "content-type": mimetype, "content-length": fs.lstatSync(file).size }); const stream = fs.createReadStream(file); return new Promise((resolve, reject) => { stream.on("error", reject); stream.on("end", resolve); stream.pipe(ctx.getStream()); }); } } export { ResourceService }; //# sourceMappingURL=resource.js.map