UNPKG

@dooboostore/simple-boot-http-server

Version:
1,250 lines (1,223 loc) 61.4 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; // src/SimpleBootHttpServer.ts import { SimpleApplication } from "@dooboostore/simple-boot/SimpleApplication"; // src/option/HttpServerOption.ts import { SimOption } from "@dooboostore/simple-boot/SimOption"; var HttpServerOption = class _HttpServerOption extends SimOption { static { this.DEFAULT_PORT = 8081; } static { this.DEFAULT_HOSTNAME = "127.0.0.1"; } constructor({ serverOption, listen, filters, requestEndPoints, closeEndPoints, errorEndPoints, sessionOption, globalAdvice, fileUploadTempPath, noSuchRouteEndPointMappingThrow, transactionManagerFactory } = {}, initSimOption) { super(initSimOption); this.serverOption = serverOption; this.listen = Object.assign({ port: _HttpServerOption.DEFAULT_PORT, hostname: _HttpServerOption.DEFAULT_HOSTNAME }, listen); this.filters = filters; this.requestEndPoints = requestEndPoints; this.closeEndPoints = closeEndPoints; this.errorEndPoints = errorEndPoints; this.sessionOption = Object.assign({ key: "SBSESSIONID", expiredTime: 1e3 * 60 * 30 }, sessionOption); this.globalAdvice = globalAdvice; this.fileUploadTempPath = fileUploadTempPath; this.noSuchRouteEndPointMappingThrow = noSuchRouteEndPointMappingThrow; this.transactionManagerFactory = transactionManagerFactory; } get hostname() { return this.listen.hostname; } get port() { return this.listen.port; } get protocol() { return this.isSecure ? "https" : "http"; } get address() { return `${this.protocol}://${this.hostname}:${this.port}`; } get isSecure() { return this.serverOption && "key" in this.serverOption && "cert" in this.serverOption; } }; // src/SimpleBootHttpServer.ts import { IncomingMessage, Server as HttpServer, ServerResponse } from "http"; import { Server as HttpsServer } from "https"; // src/codes/HttpHeaders.ts var HttpHeaders = /* @__PURE__ */ ((HttpHeaders2) => { HttpHeaders2["ContentLength"] = "Content-Length"; HttpHeaders2["ContentType"] = "Content-Type"; HttpHeaders2["ContentEncoding"] = "Content-Encoding"; HttpHeaders2["Accept"] = "Accept"; HttpHeaders2["Authorization"] = "Authorization"; HttpHeaders2["CacheControl"] = "Cache-Control"; HttpHeaders2["Connection"] = "Connection"; HttpHeaders2["Date"] = "Date"; HttpHeaders2["Host"] = "Host"; HttpHeaders2["Pragma"] = "Pragma"; HttpHeaders2["Trailer"] = "Trailer"; HttpHeaders2["TransferEncoding"] = "Transfer-Encoding"; HttpHeaders2["Upgrade"] = "Upgrade"; HttpHeaders2["Via"] = "Via"; HttpHeaders2["Warning"] = "Warning"; HttpHeaders2["AcceptCharset"] = "Accept-Charset"; HttpHeaders2["AcceptEncoding"] = "Accept-Encoding"; HttpHeaders2["AcceptLanguage"] = "Accept-Language"; HttpHeaders2["AccessControlAllowOrigin"] = "Access-Control-Allow-Origin"; HttpHeaders2["AccessControlAllowMethods"] = "Access-Control-Allow-Methods"; HttpHeaders2["AccessControlAllowHeaders"] = "Access-Control-Allow-Headers"; HttpHeaders2["AccessControlExposeHeaders"] = "Access-Control-Expose-Headers"; HttpHeaders2["Expect"] = "Expect"; HttpHeaders2["From"] = "From"; HttpHeaders2["MaxForwards"] = "Max-Forwards"; HttpHeaders2["Referer"] = "Referer"; HttpHeaders2["UserAgent"] = "User-Agent"; HttpHeaders2["Cookie"] = "Cookie"; HttpHeaders2["SetCookie"] = "Set-Cookie"; HttpHeaders2["Cookie2"] = "Cookie2"; HttpHeaders2["SetCookie2"] = "Set-Cookie2"; HttpHeaders2["Location"] = "Location"; HttpHeaders2["IfModifiedSince"] = "If-Modified-Since"; HttpHeaders2["IfUnmodifiedSince"] = "If-Unmodified-Since"; HttpHeaders2["IfMatch"] = "If-Match"; HttpHeaders2["IfNoneMatch"] = "If-None-Match"; HttpHeaders2["IfRange"] = "If-Range"; HttpHeaders2["Allow"] = "Allow"; HttpHeaders2["Server"] = "Server"; return HttpHeaders2; })(HttpHeaders || {}); // src/models/RequestResponse.ts import { Intent } from "@dooboostore/simple-boot/intent/Intent"; import { URL as URL2, URLSearchParams } from "url"; import { Buffer as Buffer2 } from "buffer"; // src/models/datas/body/ReqFormUrlBody.ts var ReqFormUrlBody = class { }; // src/models/datas/body/ReqJsonBody.ts var ReqJsonBody = class { }; // src/models/datas/ReqHeader.ts var ReqHeader = class { }; // src/models/RequestResponse.ts import { gzip } from "node-gzip"; import { FileUtils } from "@dooboostore/core-node/file/FileUtils"; import { ConvertUtils as CoreConvertUtils } from "@dooboostore/core/convert/ConvertUtils"; import { ConvertUtils } from "@dooboostore/core-node/convert/ConvertUtils"; var RequestResponse = class _RequestResponse { // protected sessionManager?: SessionManager; // constructor(req: IncomingMessage, res: ServerResponse); // constructor(req: RequestResponse); // constructor(req: IncomingMessage, res: ServerResponse, sessionManager?: SessionManager); // constructor(req: IncomingMessage | RequestResponse, res?: ServerResponse, sessionManager?: SessionManager) { // // this.req = req; // // this.res = res; // if (req instanceof RequestResponse) { // this.req = req.req; // this.res = req.res; // this.sessionManager = req.sessionManager; // } else { // this.req = req; // this.res = res!; // this.sessionManager = sessionManager; // } // } constructor(req, res, config) { if (req instanceof _RequestResponse) { this.req = req.req; this.res = req.res; this.config = config; } else { this.req = req; this.res = res; this.config = config; } } get reqSocket() { return this.req.socket; } get reqCookieMap() { let cookies = this.reqHeader("Cookie" /* Cookie */) ?? ""; if (Array.isArray(cookies)) { cookies = cookies.join(";"); } const map = /* @__PURE__ */ new Map(); cookies.split(";").map((it) => it.trim().split("=")).forEach((it) => { map.set(it[0], it[1]); }); return map; } reqCookieGet(key) { return this.reqCookieMap.get(key); } get reqRemoteAddress() { const ipHeader = this.req.headers["x-forwarded-for"]; let ip = this.req.socket.remoteAddress; if (Array.isArray(ipHeader)) { ip = ipHeader.join(",").split(",").shift(); } else if (typeof ipHeader === "string") { ip = ipHeader.split(",").shift(); } return ip; } get reqUrlObject() { return new URL2(this.reqUrl, `${this.reqSocket ? "https://" : "http"}${this.reqHost ?? "localhost"}`); } get reqUrlPathName() { return this.reqUrlObj().pathname ?? ""; } get reqUrl() { return this.req.url ?? ""; } reqUrlObj(config) { return new URL2(`${config?.scheme ?? "http"}://${config?.host ? config?.host : this.reqHeaderFirst("Host" /* Host */) ?? "localhost"}${this.req.url ?? ""}`); } get reqUrlSearchParamTuples() { return Array.from(this.reqUrlObj().searchParams); } get reqUrlSearchParams() { return this.reqUrlObj().searchParams; } get reqUrlSearchParamsObj() { const entries = this.reqUrlObj().searchParams; return CoreConvertUtils.toObject(entries); } get reqPathSearchParamUrl() { const reqUrlObj = this.reqUrlObj(); return reqUrlObj.pathname + (reqUrlObj.searchParams.toString() ? "?" + reqUrlObj.searchParams.toString() : ""); } get reqReadable() { return this.req.readable; } get reqIntent() { return new Intent(this.reqPathSearchParamUrl); } reqHasContentTypeHeader(mime3) { return (this.reqHeaderFirst("Content-Type" /* ContentType */) ?? "").toLowerCase().indexOf(mime3.toLocaleLowerCase()) > -1; } reqHasAcceptHeader(mime3) { return (this.reqHeaderFirst("Accept" /* Accept */) ?? "").toLowerCase().indexOf(mime3.toLocaleLowerCase()) > -1; } reqBodyData() { return new Promise((resolve, reject) => { if (this.reqReadable) { const data = []; this.req.on("data", (chunk) => data.push(chunk)); this.req.on("error", (err) => reject(err)); this.req.on("end", () => { this.reqBodyChunk = Buffer2.concat(data); resolve(this.reqBodyChunk); }); } else { resolve(this.reqBodyChunk ?? Buffer2.alloc(0)); } }); } resBodyData() { return this.resWriteChunk; } async reqBodyMultipartFormDataObject() { const m = await this.reqBodyMultipartFormData(); const formData = {}; for (const it of m) { if (it.isFile) { formData[it.name] = await FileUtils.writeFile(it.value, { originalName: it.filename }); } else { const target = formData[it.name]; if (Array.isArray(target)) { target.push(it.value); } else if (typeof target === "string") { formData[it.name] = [target, it.value]; } else { formData[it.name] = it.value; } } } ; return formData; } reqBodyMultipartFormData() { return new Promise((resolve, reject) => { const contentTypeHeader = this.req.headers["content-type"]; if (!contentTypeHeader || !contentTypeHeader.startsWith("multipart/form-data")) { return reject(new Error("Invalid Content-Type. Expected multipart/form-data.")); } const boundaryMatch = contentTypeHeader.match(/boundary=(?:"([^"]+)"|([^;]+))/i); if (!boundaryMatch) { return reject(new Error("Boundary not found in Content-Type header.")); } const boundary = boundaryMatch[1] || boundaryMatch[2]; if (!boundary) { return reject(new Error("Failed to extract boundary.")); } const boundaryBuffer = Buffer2.from(`--${boundary}`); const crlfBuffer = Buffer2.from("\r\n"); const doubleCrlfBuffer = Buffer2.from("\r\n\r\n"); const chunks = []; let totalLength = 0; this.req.on("data", (chunk) => { chunks.push(chunk); totalLength += chunk.length; }); this.req.on("error", (err) => { reject(err); }); this.req.on("end", () => { if (totalLength === 0) { return resolve([]); } const fullBuffer = Buffer2.concat(chunks, totalLength); const parsedParts = []; let currentPosition = 0; let boundaryStartIndex = fullBuffer.indexOf(boundaryBuffer, currentPosition); if (boundaryStartIndex === -1) { return reject(new Error("Initial boundary not found.")); } currentPosition = boundaryStartIndex + boundaryBuffer.length; if (fullBuffer.slice(currentPosition, currentPosition + crlfBuffer.length).equals(crlfBuffer)) { currentPosition += crlfBuffer.length; } while (currentPosition < fullBuffer.length) { const nextBoundaryIndex = fullBuffer.indexOf(boundaryBuffer, currentPosition); if (nextBoundaryIndex === -1) { break; } let partEndIndex = nextBoundaryIndex; if (partEndIndex > crlfBuffer.length && fullBuffer.slice(partEndIndex - crlfBuffer.length, partEndIndex).equals(crlfBuffer)) { partEndIndex -= crlfBuffer.length; } const partBuffer = fullBuffer.slice(currentPosition, partEndIndex); const headerBodySeparatorIndex = partBuffer.indexOf(doubleCrlfBuffer); if (headerBodySeparatorIndex === -1) { console.warn("Skipping malformed part: no header/body separator found."); currentPosition = nextBoundaryIndex + boundaryBuffer.length; if (fullBuffer.slice(currentPosition, currentPosition + crlfBuffer.length).equals(crlfBuffer)) { currentPosition += crlfBuffer.length; } if (fullBuffer.slice(currentPosition, currentPosition + 2).toString() === "--") break; continue; } const headerBuffer = partBuffer.slice(0, headerBodySeparatorIndex); const bodyBuffer = partBuffer.slice(headerBodySeparatorIndex + doubleCrlfBuffer.length); const headersStr = headerBuffer.toString("utf-8"); const headers = {}; headersStr.split("\r\n").forEach((line) => { const colonIndex = line.indexOf(":"); if (colonIndex > 0) { const key = line.substring(0, colonIndex).trim().toLowerCase(); const value = line.substring(colonIndex + 1).trim(); headers[key] = value; } }); const contentDisposition = headers["content-disposition"]; if (!contentDisposition) { console.warn("Skipping part: no Content-Disposition header."); currentPosition = nextBoundaryIndex + boundaryBuffer.length; if (fullBuffer.slice(currentPosition, currentPosition + crlfBuffer.length).equals(crlfBuffer)) { currentPosition += crlfBuffer.length; } if (fullBuffer.slice(currentPosition, currentPosition + 2).toString() === "--") break; continue; } const nameMatch = contentDisposition.match(/name="([^"]+)"/i); const filenameMatch = contentDisposition.match(/filename="([^"]+)"/i); const name = nameMatch ? nameMatch[1] : null; const filename = filenameMatch ? filenameMatch[1] : null; const contentType = headers["content-type"] || null; if (name) { const isFile = !!filename; const multipartData = { name, isFile, // filename이 있으면 파일로 간주 filename, contentType, value: isFile ? bodyBuffer : bodyBuffer.toString("utf-8") }; parsedParts.push(multipartData); } else { console.warn("Skipping part: 'name' not found in Content-Disposition."); } currentPosition = nextBoundaryIndex + boundaryBuffer.length; if (fullBuffer.slice(currentPosition, currentPosition + crlfBuffer.length).equals(crlfBuffer)) { currentPosition += crlfBuffer.length; } if (fullBuffer.slice(currentPosition, currentPosition + 2).toString() === "--") { break; } } resolve(parsedParts); }); }); } async reqBodyStringData() { const data = (await this.reqBodyData()).toString(); return data; } async reqBodyJsonData() { return JSON.parse(await this.reqBodyStringData()); } async reqBodyFormUrlData() { const data = await this.reqBodyStringData(); const formData = {}; Array.from(new URLSearchParams(data).entries()).forEach(([k, v]) => { const target = formData[k]; if (Array.isArray(target)) { target.push(v); } else if (typeof target === "string") { formData[k] = [target, v]; } else { formData[k] = v; } }); return formData; } async reqBodyReqFormUrlBody() { const data = await this.reqBodyFormUrlData(); return Object.assign(new ReqFormUrlBody(), data); } async reqBodyReqJsonBody() { const data = await this.reqBodyStringData(); return Object.assign(new ReqJsonBody(), data ? JSON.parse(data) : {}); } // reqBodyReqMultipartFormBody(): Promise<ReqMultipartFormBody> { // return this.reqBodyMultipartFormData().then(it => new ReqMultipartFormBody(it)) // } resBodyJsonData() { const data = ConvertUtils.toString(this.resBodyData()); return data ? JSON.parse(data) : null; } resBodyStringData() { return ConvertUtils.toString(this.resBodyData()); } reqMethod() { return this.req.method?.toUpperCase(); } reqHeader(key) { return this.req.headers[key.toLowerCase()]; } get reqHost() { return this.reqHeaderFirst("Host" /* Host */); } get reqHeaderObj() { const h = new ReqHeader(); Object.entries(this.req.headers).forEach(([k, v]) => { h[k] = v; }); return h; } reqHeaderFirst(key, defaultValue) { const header = this.req.headers[key.toLowerCase()]; if (header && Array.isArray(header)) { return header[0] ?? defaultValue; } else { return header ?? defaultValue; } } reqAuthorizationHeader() { return this.reqHeaderFirst("Authorization" /* Authorization */); } reqRefreshTokenHeader() { return this.reqHeaderFirst("Authorization" /* Authorization */); } // eslint-disable-next-line no-dupe-class-members resStatusCode(code) { if (code) { this.res.statusCode = code; return new RequestResponseChain(this.req, this.res, this.config, this.res.statusCode); } else { return this.res.statusCode; } } resHeader(key) { return this.res.getHeader(key.toLowerCase()); } resHeaderFirst(key, defaultValue) { const header = this.res.getHeader(key.toLowerCase()); if (header && Array.isArray(header)) { return header[0] ?? defaultValue; } else { return header ?? defaultValue; } } async reqSession() { if (this.config.sessionManager) { return (await this.config.sessionManager.session(this)).dataSet.data; } else { return Promise.reject(new Error("Not SessionManager")); } } reqSessionSet(key, value) { this.reqSession[key] = value; } reqSessionGet(key) { const session = this.reqSession; if (session) { return session[key]; } } resSetStatusCode(statusCode) { this.res.statusCode = statusCode; return this.createRequestResponseChain(this.res.statusCode); } // resEnd() { // this.res.end(); // } // eslint-disable-next-line no-undef resWrite(chunk, encoding = "utf8") { this.resWriteChunk = chunk; return this.createRequestResponseChain(this.resWriteChunk); } // eslint-disable-next-line no-undef resWriteJson(chunk, encoding = "utf8") { return this.resWrite(JSON.stringify(chunk), encoding); } resSetHeader(key, value) { return this.createRequestResponseChain(this.res.setHeader(key.toLowerCase(), value)); } resAddHeader(key, value) { const existingValue = this.res.getHeader(key.toLowerCase()); const newValue = Array.isArray(existingValue) ? existingValue.concat(value) : existingValue ? [existingValue.toString(), ...[].concat(value)] : value; return this.createRequestResponseChain(this.res.setHeader(key.toLowerCase(), newValue)); } resSetHeaders(headers) { Object.entries(headers).forEach(([key, value]) => this.resSetHeader(key, value)); return this.createRequestResponseChain(); } resAddHeaders(headers) { Object.entries(headers).forEach(([key, value]) => this.resAddHeader(key, value)); return this.createRequestResponseChain(); } // 마지막 종료될때 타는거. async resEndChunk() { const encoding = this.reqHeaderFirst("Accept-Encoding" /* AcceptEncoding */); let data = this.resWriteChunk; if (encoding?.includes("gzip")) { data = await gzip(data); this.resSetHeader("Content-Encoding" /* ContentEncoding */, "gzip"); } this.res.end(data); } async resEnd(chunk) { this.resWriteChunk = chunk ?? this.resWriteChunk; if (this.req.readable) { await this.reqBodyData(); await this.resEndChunk(); } else { await this.resEndChunk(); } } // writeContinue(callback?: () => void) { // this.res.writeContinue(callback); // return new RequestResponseChain(this.req, this.res); // } // reqWrite(chunk?: any) { // this.resWriteChunk = chunk; // // this.res.write(chunk); // return this.createRequestResponseChain(); // } resWriteHead(statusCode, headers) { return this.createRequestResponseChain(this.res.writeHead(statusCode, headers)); } resWriteHeadEnd(statusCode, headers) { this.createRequestResponseChain(this.res.writeHead(statusCode, headers)); this.res.end(); } resIsDone() { return this.res.finished || this.res.writableEnded || this.res.headersSent; } createRequestResponseChain(data) { const requestResponseChain = new RequestResponseChain(this.req, this.res, this.config, data); requestResponseChain.resWriteChunk = this.resWriteChunk; requestResponseChain.reqBodyChunk = this.reqBodyChunk; return requestResponseChain; } // res.on("readable", () => { // console.log('readable???') // }); // res.on('complete', function (details) { // var size = details.req.bytes; // console.log('complete-->', size) // }); // res.on('finish', function() { // console.log('finish??'); // }); // res.on('end', () => { // console.log('end--?') // }); }; var RequestResponseChain = class extends RequestResponse { constructor(req, res, config, result) { super(req, res, config); this.result = result; } }; // src/decorators/MethodMapping.ts import "reflect-metadata"; import { ReflectUtils } from "@dooboostore/core/reflect/ReflectUtils"; // src/codes/HttpMethod.ts var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => { HttpMethod2["GET"] = "GET"; HttpMethod2["POST"] = "POST"; HttpMethod2["PUT"] = "PUT"; HttpMethod2["DELETE"] = "DELETE"; HttpMethod2["PATCH"] = "PATCH"; HttpMethod2["HEAD"] = "HEAD"; HttpMethod2["OPTIONS"] = "OPTIONS"; HttpMethod2["TRACE"] = "TRACE"; HttpMethod2["CONNECT"] = "CONNECT"; return HttpMethod2; })(HttpMethod || {}); // src/decorators/MethodMapping.ts var UrlMappingSituationType = /* @__PURE__ */ ((UrlMappingSituationType2) => { UrlMappingSituationType2["REQ_JSON_BODY"] = "SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_JSON_BODY"; UrlMappingSituationType2["REQ_FORM_URL_BODY"] = "SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_FORM_URL_BODY"; return UrlMappingSituationType2; })(UrlMappingSituationType || {}); var MappingMetadataKey = Symbol("MappingMetadataKey"); var process2 = (config, target, propertyKey, descriptor) => { const saveMappingConfigs = ReflectUtils.getMetadata(MappingMetadataKey, target.constructor) ?? []; saveMappingConfigs.push({ propertyKey, config }); ReflectUtils.defineMetadata(MappingMetadataKey, saveMappingConfigs, target.constructor); ReflectUtils.defineMetadata(MappingMetadataKey, config, target, propertyKey); }; function UrlMapping(configOrtarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "GET" /* GET */ }, configOrtarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { process2(configOrtarget, target, propertyKey2, descriptor2); }; } } var getUrlMapping = (target, propertyKey) => { return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey); }; var getUrlMappings = (target) => { if (target !== null && void 0 !== target && typeof target === "object") { target = target.constructor; } return ReflectUtils.getMetadata(MappingMetadataKey, target); }; function GET(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "GET" /* GET */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "GET" /* GET */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getGET = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getGETS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "GET" /* GET */); }; function POST(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "POST" /* POST */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "POST" /* POST */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getPOST = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getPOSTS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "POST" /* POST */); }; function DELETE(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "DELETE" /* DELETE */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "DELETE" /* DELETE */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getDELETE = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getDELETES = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "DELETE" /* DELETE */); }; function PUT(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "PUT" /* PUT */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "PUT" /* PUT */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getPUT = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getPUTS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "PUT" /* PUT */); }; function PATCH(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "PATCH" /* PATCH */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "PATCH" /* PATCH */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getPATCH = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getPATCHS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "GET" /* GET */); }; function OPTIONS(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "OPTIONS" /* OPTIONS */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "OPTIONS" /* OPTIONS */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getOPTIONS = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getOPTIONSS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "OPTIONS" /* OPTIONS */); }; function HEAD(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "HEAD" /* HEAD */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "HEAD" /* HEAD */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getHEAD = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getHEADS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "GET" /* GET */); }; function TRACE(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "TRACE" /* TRACE */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "TRACE" /* TRACE */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getTRACE = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getTRACES = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "TRACE" /* TRACE */); }; function CONNECT(configOrTarget, propertyKey, descriptor) { if (propertyKey && descriptor) { process2({ method: "CONNECT" /* CONNECT */ }, configOrTarget, propertyKey, descriptor); } else { return (target, propertyKey2, descriptor2) => { configOrTarget.method = "CONNECT" /* CONNECT */; process2(configOrTarget, target, propertyKey2, descriptor2); }; } } var getCONNECT = (target, propertyKey) => { return getUrlMapping(target, propertyKey); }; var getCONNECTS = (target) => { return getUrlMappings(target)?.filter((it) => it.config?.method.toUpperCase() === "CONNECT" /* CONNECT */); }; // src/codes/HttpStatus.ts var HttpStatus = /* @__PURE__ */ ((HttpStatus2) => { HttpStatus2[HttpStatus2["Ok"] = 200] = "Ok"; HttpStatus2[HttpStatus2["MovedPermanently"] = 301] = "MovedPermanently"; HttpStatus2[HttpStatus2["Found"] = 302] = "Found"; HttpStatus2[HttpStatus2["SeeOther"] = 303] = "SeeOther"; HttpStatus2[HttpStatus2["NotModified"] = 304] = "NotModified"; HttpStatus2[HttpStatus2["TemporaryRedirect"] = 307] = "TemporaryRedirect"; HttpStatus2[HttpStatus2["PermanentRedirect"] = 308] = "PermanentRedirect"; HttpStatus2[HttpStatus2["BadRequest"] = 400] = "BadRequest"; HttpStatus2[HttpStatus2["Unauthorized"] = 401] = "Unauthorized"; HttpStatus2[HttpStatus2["Forbidden"] = 403] = "Forbidden"; HttpStatus2[HttpStatus2["NotFound"] = 404] = "NotFound"; HttpStatus2[HttpStatus2["InternalServerError"] = 500] = "InternalServerError"; HttpStatus2[HttpStatus2["NotImplemented"] = 501] = "NotImplemented"; HttpStatus2[HttpStatus2["BadGateway"] = 502] = "BadGateway"; HttpStatus2[HttpStatus2["ServiceUnavailable"] = 503] = "ServiceUnavailable"; HttpStatus2[HttpStatus2["GatewayTimeout"] = 504] = "GatewayTimeout"; HttpStatus2[HttpStatus2["HttpVersionNotSupported"] = 505] = "HttpVersionNotSupported"; return HttpStatus2; })(HttpStatus || {}); // src/codes/Mimes.ts var Mimes = /* @__PURE__ */ ((Mimes2) => { Mimes2["ApplicationJson"] = "application/json"; Mimes2["ApplicationOctetStream"] = "application/octet-stream"; Mimes2["ApplicationXml"] = "application/xml"; Mimes2["ApplicationJsonPatch"] = "application/json-patch+json"; Mimes2["ApplicationXWwwFormUrlencoded"] = "application/x-www-form-urlencoded"; Mimes2["TextPlain"] = "text/plain"; Mimes2["TextHtml"] = "text/html"; Mimes2["All"] = "*/*"; Mimes2["TextCss"] = "text/css"; Mimes2["TextJavascript"] = "text/javascript"; Mimes2["TextXml"] = "text/xml"; Mimes2["TextXslt"] = "text/xslt"; Mimes2["TextXmlDtd"] = "text/xml-dtd"; Mimes2["TextPlainUtf8"] = "text/plain; charset=utf-8"; Mimes2["ImagePng"] = "image/png"; Mimes2["ImageGif"] = "image/gif"; Mimes2["ImageJpeg"] = "image/jpeg"; Mimes2["ImageSvg"] = "image/svg+xml"; Mimes2["ImageBmp"] = "image/bmp"; Mimes2["ImageTiff"] = "image/tiff"; Mimes2["ImagePdf"] = "application/pdf"; Mimes2["ImageXBmp"] = "image/x-bmp"; Mimes2["ImageXpixmap"] = "image/x-xpixmap"; Mimes2["ImageXxbm"] = "image/x-xbm"; Mimes2["MultipartFormData"] = "multipart/form-data"; return Mimes2; })(Mimes || {}); // src/SimpleBootHttpServer.ts import { ExceptionHandlerSituationType, targetExceptionHandler } from "@dooboostore/simple-boot/decorators/exception/ExceptionDecorator"; import { getInject, SituationTypeContainer, SituationTypeContainers } from "@dooboostore/simple-boot/decorators/inject/Inject"; import { ReflectUtils as ReflectUtils2 } from "@dooboostore/core/reflect/ReflectUtils"; import { RouterModule } from "@dooboostore/simple-boot/route/RouterModule"; // src/models/datas/body/ReqMultipartFormBody.ts var ReqMultipartFormBody = class { constructor(field = []) { this.field = field; } }; // src/SimpleBootHttpServer.ts import { execValidationInValid, getValidIndex } from "@dooboostore/simple-boot/decorators/validate/Validation"; import { ValidException } from "@dooboostore/simple-boot/errors/ValidException"; // src/errors/HttpError.ts var HttpError = class { constructor({ status, message = "HttpError" }) { this.name = "Error"; this.message = message; this.status = status; } }; // src/SimpleBootHttpServer.ts import { getRoute } from "@dooboostore/simple-boot/decorators/route/Router"; import { URLSearchParams as URLSearchParams2 } from "url"; // src/session/SessionManager.ts import { RandomUtils } from "@dooboostore/core/random/RandomUtils"; var SessionManager = class { constructor(option) { this.option = option; this.sessions = /* @__PURE__ */ new Map(); this.sessionOption = option.sessionOption; setInterval(async () => { const uuids = await this.getSessionUUIDs(); for (const uuid of uuids) { const dataSet = await this.getSessionDataSet(uuid); if (dataSet && this.isExpired(dataSet.access)) { await this.deleteSession(uuid); } } }, this.sessionOption.expiredTime); } async getSessionUUIDs() { if (this.sessionOption.provider?.uuids) { return this.sessionOption.provider.uuids(); } else { return Array.from(this.sessions.keys()); } } async deleteSession(uuid) { if (this.sessionOption.provider?.delete) { this.sessionOption.provider.delete(uuid); } else { this.sessions.delete(uuid); } } async getSessionDataSet(uuid) { if (this.sessionOption.provider?.get) { return this.sessionOption.provider.get(uuid); } else { return this.sessions.get(uuid); } } async setSessionDataSet(uuid, dataSet) { if (this.sessionOption.provider?.set) { this.sessionOption.provider.set(uuid, dataSet); } else { this.sessions.set(uuid, dataSet); } } isExpired(access, now = Date.now()) { return now - access >= this.option.sessionOption.expiredTime; } makeUUID() { return RandomUtils.uuid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); } async session(rrOrUUID) { let uuid; if (rrOrUUID instanceof RequestResponse) { uuid = rrOrUUID.reqCookieGet(this.sessionOption.key) ?? this.makeUUID(); } else if (typeof rrOrUUID === "string") { uuid = rrOrUUID; } else { uuid = this.makeUUID(); } let dataSet = await this.getSessionDataSet(uuid); const now = Date.now(); if (dataSet) { if (this.isExpired(dataSet.access, now)) { delete dataSet.data; } dataSet.access = now; } else { dataSet = { access: now }; } await this.setSessionDataSet(uuid, dataSet); return { uuid, dataSet }; } }; // src/inject/InjectSituationType.ts var InjectSituationType; ((InjectSituationType2) => { InjectSituationType2.TransactionManager = Symbol("TransactionManager"); })(InjectSituationType || (InjectSituationType = {})); // src/SimpleBootHttpServer.ts var SimpleBootHttpServer = class extends SimpleApplication { constructor(option = new HttpServerOption()) { super(option); this.option = option; this.sessionManager = new SessionManager(this.option); } run(otherInstanceSim) { const simstanceManager = super.run(otherInstanceSim); const targets = [...this.option.closeEndPoints ?? [], ...this.option.errorEndPoints ?? [], ...this.option.requestEndPoints ?? [], ...this.option.filters ?? []]; Promise.all(targets.map((it) => typeof it === "function" ? this.simstanceManager.getOrNewSim({ target: it }) : it).map((it) => it.onInit(this))).then((it) => { this.startServer(); }); return simstanceManager; } startServer() { if (this.option.serverOption && "key" in this.option.serverOption && "cert" in this.option.serverOption) { this.server = new HttpsServer(this.option.serverOption); const httpServer = new HttpServer(); httpServer.on("request", (req, res) => { res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` }); res.end(); }); httpServer.listen(80, this.option.listen.hostname, () => { console.log("HTTP redirect server running on port 80"); }); } else if (this.option.serverOption) { this.server = new HttpServer(this.option.serverOption); } else { this.server = new HttpServer(); } this.server.on("request", async (req, res) => { const transactionManager = this.option.transactionManagerFactory ? await this.option.transactionManagerFactory() : void 0; res.on("close", async () => { if (this.option.closeEndPoints) { for (const it of this.option.closeEndPoints) { try { const execute = typeof it === "function" ? this.simstanceManager.getOrNewSim({ target: it }) : it; const endPoint = execute?.endPoint(rr, this); if (endPoint instanceof Promise) { await endPoint; } } catch (e) { } } } if (!rr.resIsDone()) { rr.resEnd(); } }); res.on("error", async () => { if (this.option.errorEndPoints) { for (const it of this.option.errorEndPoints) { try { const execute = typeof it === "function" ? this.simstanceManager.getOrNewSim({ target: it }) : it; const endPoint = execute?.endPoint(rr, this); if (endPoint instanceof Promise) { await endPoint; } } catch (e) { } } } if (!rr.resIsDone()) { rr.resEnd(); } }); const rr = new RequestResponse(req, res, { sessionManager: this.sessionManager, option: this.option }); rr.resSetHeader("Server" /* Server */, "simple-boot-http-server"); const cookie = rr.reqCookieGet(this.option.sessionOption.key); if (!cookie) { const session = await this.sessionManager.session(); rr.resSetHeader("Set-Cookie" /* SetCookie */, `${this.option.sessionOption.key}=${session.uuid}; Path=/; HttpOnly`); } const otherStorage = /* @__PURE__ */ new Map(); otherStorage.set(RequestResponse, rr); otherStorage.set(IncomingMessage, req); otherStorage.set(ServerResponse, res); try { transactionManager?.try(); if (this.option.requestEndPoints) { for (const it of this.option.requestEndPoints) { try { const execute = typeof it === "function" ? this.simstanceManager.getOrNewSim({ target: it }) : it; execute?.endPoint(rr, this); } catch (e) { } } } const filter = { carrier: /* @__PURE__ */ new Map(), filters: [] }; for (let i = 0; this.option.filters && i < this.option.filters.length; i++) { const it = this.option.filters[i]; const execute = typeof it === "function" ? this.simstanceManager.getOrNewSim({ target: it }) : it; let sw = true; if (execute?.proceedBefore) { sw = await execute.proceedBefore({ rr, app: this, carrier: filter.carrier }); filter.filters.push({ filter: execute, sw }); } if (!sw) { break; } } if (!rr.resIsDone()) { const routerModule = await super.routing(rr.reqIntent); otherStorage.set(RouterModule, routerModule); const moduleInstance = routerModule?.getModuleInstance?.(); let methods = []; if (routerModule && moduleInstance) { if (routerModule.propertyKeys) { const map = routerModule.propertyKeys?.map((it) => { return { propertyKey: it, config: getUrlMapping(moduleInstance, it) }; }); methods.push(...map ?? []); } else { methods.push(...getUrlMappings(moduleInstance).filter((it) => !getRoute(moduleInstance, it.propertyKey)) ?? []); } if (rr.reqMethod()?.toUpperCase() === "OPTIONS" /* OPTIONS */.toUpperCase()) { rr.resSetHeader("Allow" /* Allow */, methods.map((it) => it.config.method).join(", ")); rr.resStatusCode(200 /* Ok */); } methods = methods.filter((it) => it && it.propertyKey && it.config && rr.reqMethod()?.toUpperCase() === it.config.method.toUpperCase()); methods.sort((a, b) => { return (b.config?.req?.contentType?.length ?? 0) + (b.config?.req?.accept?.length ?? 0) - ((a.config?.req?.contentType?.length ?? 0) + (a.config?.req?.accept?.length ?? 0)); }); methods = methods.filter((it) => it.config?.req?.contentType ? !!it.config?.req?.contentType?.find((sit) => rr.reqHasContentTypeHeader(sit)) : true).filter((it) => it.config?.req?.accept ? !!it.config?.req?.accept?.find((sit) => rr.reqHasAcceptHeader(sit)) : true); if (methods[0]) { const it = methods[0]; const paramTypes = ReflectUtils2.getParameterTypes(moduleInstance, it.propertyKey); const injects = getInject(moduleInstance, it.propertyKey); const validIndexs = getValidIndex(moduleInstance, it.propertyKey); if (injects) { const isJson = injects.find((it2) => it2.config?.situationType === "SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_JSON_BODY" /* REQ_JSON_BODY */); const isFormUrl = injects.find((it2) => it2.config?.situationType === "SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_FORM_URL_BODY" /* REQ_FORM_URL_BODY */); const isTransactionManager = injects.find((it2) => it2.config?.situationType === InjectSituationType.TransactionManager); const siturationContainers = new SituationTypeContainers(); if (isJson) { let data2 = await rr.reqBodyJsonData(); if (isJson.type) { data2 = Object.assign(new isJson.type(), data2); } if (validIndexs.includes(isJson.index)) { const inValid = execValidationInValid(data2); if ((inValid?.length ?? 0) > 0) { throw new ValidException(inValid); } } siturationContainers.push(new SituationTypeContainer({ situationType: "SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_JSON_BODY" /* REQ_JSON_BODY */, data: data2 })); } if (isFormUrl) { let data2 = await rr.reqBodyFormUrlData(); if (isFormUrl.type) { data2 = Object.assign(new isFormUrl.type(), data2); } if (validIndexs.includes(isFormUrl.index)) { const inValid = execValidationInValid(data2); if ((inValid?.length ?? 0) > 0) { throw new ValidException(inValid); } } siturationContainers.push(new SituationTypeContainer({ situationType: "SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_FORM_URL_BODY" /* REQ_FORM_URL_BODY */, data: data2 })); } if (isTransactionManager && isTransactionManager.type && transactionManager && transactionManager.hasTransaction(isTransactionManager.type)) { let data2 = await transactionManager.getTransaction(isTransactionManager.type); if (data2) { data2 = await data2.try(isTransactionManager.config.argument); siturationContainers.push(new SituationTypeContainer({ situationType: InjectSituationType.TransactionManager, data: data2, index: isTransactionManager.index })); } } if (siturationContainers.length) { otherStorage.set(SituationTypeContainers, siturationContainers); } } for (const paramType of paramTypes) { if (paramType === ReqFormUrlBody) { otherStorage.set(ReqFormUrlBody, await rr.reqBodyReqFormUrlBody()); } else if (paramType === ReqJsonBody) { otherStorage.set(ReqJsonBody, await rr.reqBodyReqJsonBody()); } else if (paramType === URLSearchParams2) { otherStorage.set(URLSearchParams2, rr.reqUrlSearchParams); } else if (paramType === ReqMultipartFormBody) { otherStorage.set(ReqMultipartFormBody, rr.reqBodyMultipartFormData()); } else if (paramType === ReqHeader) { otherStorage.set(ReqHeader, rr.reqHeaderObj); } } let data = await this.simstanceManager.executeBindParameterSimPromise({ target: moduleInstance, targetKey: it.propertyKey }, otherStorage); if (it.config?.resolver) { const execute = typeof it.config.resolver === "function" ? this.simstanceManager.getOrNewSim({ target: it.config.resolver }) : it.config.resolver; data = await execute?.resolve?.(data, rr); } const status = it.config?.res?.status ?? 200 /* Ok */; const headers = it.config?.res?.header ?? {}; if (it.config?.res?.contentType) { headers["Content-Type" /* ContentType */] = it.config?.res?.contentType; } if ((it.config?.res?.contentType?.toLowerCase().indexOf("application/json" /* ApplicationJson */.toLowerCase()) ?? -1) > -1) { data = JSON.stringify(data); } else if (data && typeof data === "object") { data = JSON.stringify(data); } rr.resSetHeaders(headers); rr.resSetStatusCode(status); rr.resWrite(data); } } if (this.option.noSuchRouteEndPointMappingThrow && methods.length <= 0 && rr.reqMethod()?.toUpperCase() !== "OPTIONS" /* OPTIONS */) { throw this.option.noSuchRouteEndPointMappingThrow(rr); } } for (const it of filter.filters.reverse()) { if (it.filter?.proceedAfter && !await it.filter.proceedAfter({ rr, app: this, before: it.sw, carrier: filter.carrier })) { break; } } } catch (e) { transactionManager?.catch(e); rr.resStatusCode(e instanceof HttpError ? e.status : 500 /* InternalServerError */); const execute = typeof this.option.globalAdvice === "function" ? this.simstanceManager.getOrNewSim(this.option.globalAdvice) : this.option.globalAdvice; if (!execute) { rr.resEnd(); return; } if (e && typeof e === "object") { otherStorage.set(e.constructor, e); otherStorage.set(SituationTypeContainer, new SituationTypeContainer({ situationType: ExceptionHandlerSituationType.ERROR_OBJECT, data: e })); } const target = targetExceptionHandler(execute, e); if (target) { await this.simstanceManager.executeBindParameterSimPromise({ target: execute, targetKey: target.propertyKey }, otherStorage); } } finally { transactionManager?.finally(); } if (!rr.resIsDone()) { rr.resEnd(); } }); this.server.listen(this.option.listen.port, this.option.listen.hostname, this.option.listen.backlog, () => { this.option.listen.listeningListener?.(this, this.server); }); } }; // src/endpoints/CrossDomainHeaderEndPoint.ts var CrossDomainHeaderEndPoint = class { constructor(config) { this.config = config; } /* rr.resSetHeader("Access-Control-Allow-Origin", "*") rr.resSetHeader("Access-Control-Allow-Methods", "DELETE, POST, GET, OPTIONS") rr.resSetHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") */ async endPoint(rr, app) { if (this.config?.accessControlAllowOrigin) { rr.resSetHeader("Access-Control-Allow-Origin" /* AccessControlAllowOrigin */, Array.isArray(this.config.accessControlAllowOrigin) ? this.config.accessControlAllowOrigin.join(", ") : this.config.accessControlAllowOrigin); } if (this.config?.accessControlAllowMethods) { rr.resSetHeader("Access-Control-Allow-Methods" /* AccessControlAllowMethods */, Array.isArray(this.config.accessControlAllowMethods) ? this.config.accessControlAllowMethods.join(", ") : this.config.accessControlAllowMethods); } if (this.config?.accessControlAllowHeaders) { rr.resSetHeader("Access-Control-Allow-Headers" /* AccessControlAllowHeaders */, Array.isArray(this.config.accessControlAllowHeaders) ? this.config.accessControlAllowHeaders.join(", ") : this.config.accessControlAllowHeaders); } if (this.config?.accessControlExposeHeaders) { rr.resSetHeader("Access-Control-Expose-Headers" /* AccessControlExposeHeaders */, Array.isArray(this.config.accessControlExposeHeaders) ? this.config.accessControlExposeHeaders.join(", ") : this.config.accessControlExposeHeaders); } } async onInit(app) { } }; // src/endpoints/HeaderEndPoint.ts var HeaderEndPoint = class { constructor(headers) { this.headers = headers; } async endPoint(rr, app) { if (this.headers) { rr.resSetHeaders(this.headers); } } async onInit(app) { } }; // src/errors/BadRequestError.ts var BadRequestError = class extends HttpError { constructor({ status = 400 /* BadRequest */, message = "Bad Request" } = {}) { super({ status, message }); } }; // src/errors/ForbiddenError.ts var ForbiddenError = class extends HttpError { constructor({ status = 403 /* Forbidden */, message = "Forbidden" } = {}) { super({ status, message }); } }; // src/errors/InternalServerError.ts var InternalServerError = class extends HttpError { constructor({ status = 500 /* InternalServerError */, message = "Internal Server Error" } = {}) { super({ status, message })