UNPKG

@dooboostore/simple-boot-http-server

Version:
476 lines (471 loc) 16.9 kB
// src/models/RequestResponse.ts import { Intent } from "@dooboostore/simple-boot/intent/Intent"; import { URL, URLSearchParams } from "url"; import { Buffer } 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 URL(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 URL(`${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(mime) { return (this.reqHeaderFirst("Content-Type" /* ContentType */) ?? "").toLowerCase().indexOf(mime.toLocaleLowerCase()) > -1; } reqHasAcceptHeader(mime) { return (this.reqHeaderFirst("Accept" /* Accept */) ?? "").toLowerCase().indexOf(mime.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 = Buffer.concat(data); resolve(this.reqBodyChunk); }); } else { resolve(this.reqBodyChunk ?? Buffer.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 = Buffer.from(`--${boundary}`); const crlfBuffer = Buffer.from("\r\n"); const doubleCrlfBuffer = Buffer.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 = Buffer.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; } }; export { RequestResponse, RequestResponseChain }; //# sourceMappingURL=RequestResponse.js.map