@dooboostore/simple-boot-http-server
Version:
back end http server frameworks
476 lines (471 loc) • 16.9 kB
JavaScript
// 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