UNPKG

rjweb-server

Version:

Easy and Robust Way to create a Web Server with Many Easy-to-use Features in NodeJS

328 lines (327 loc) 15.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const index_1 = require("../index"); const http_1 = require("http"); const writeHeaders_1 = __importDefault(require("../functions/writeHeaders")); const getCompressMethod_1 = __importDefault(require("../functions/getCompressMethod")); const utils_1 = require("@rjweb/utils"); const fileExists_1 = require("../functions/fileExists"); /** * Handler for HTTP Requests * @since 9.0.0 */ async function handle(context, req, server, middlewares, customContext) { if (context.global.options.version) req.header('rjweb-server', index_1.version); req.header('date', new Date().toUTCString()); context.response.headers.set('content-type', 'text/plain'); context.response.headers.set('accept-ranges', 'none'); if (req.aborted().aborted) return context.abort(); if (context.url.method === 'GET') { context.body.raw = Buffer.allocUnsafe(0); } const ctr = new context.global.classContexts.HttpRequest(context, req, req.aborted()); Object.assign(ctr["@"], customContext); if (context.global.options.proxy.enabled) { context.response.headers.set('proxy-authenticate', `Basic realm="Access rjweb-server@${index_1.version}"`); if (context.global.options.proxy.force) { if (!context.headers.has('proxy-authorization')) { await req .status(ctr.$status.PROXY_AUTHENTICATION_REQUIRED, http_1.STATUS_CODES[ctr.$status.PROXY_AUTHENTICATION_REQUIRED] || 'Unknown') .write(context.global.cache.arrayBufferTexts.proxy_auth_required); return; } } if (!context.global.options.proxy.credentials.authenticate) context.ip.isProxied = true; else if (context.headers.has('proxy-authorization')) { let authcheck = context.global.cache.proxyCredentials; if (!authcheck) { authcheck = Buffer.from(`${context.global.options.proxy.credentials.username}:${context.global.options.proxy.credentials.password}`).toString('base64'); context.global.cache.proxyCredentials = authcheck; } if (context.headers.get('proxy-authorization') !== 'Basic '.concat(authcheck)) { await req .status(ctr.$status.UNAUTHORIZED, http_1.STATUS_CODES[ctr.$status.UNAUTHORIZED] || 'Unknown') .write(context.global.cache.arrayBufferTexts.proxy_auth_invalid); return; } else context.ip.isProxied = true; } const oldIp = context.ip.value; if (context.ip.isProxied) { context.ip.value = context.headers.get(context.global.options.proxy.header, context.ip.value).split(',')[0].trim(); } if (context.ip.isProxied && context.global.options.proxy.ips.validate) { if (context.global.options.proxy.ips.list.some((ip) => ip instanceof utils_1.network.IPAddress ? ip.equals(ctr.client.ip) : ip.includes(ctr.client.ip))) { if (context.global.options.proxy.ips.mode === 'blacklist') { if (context.global.options.proxy.force) { await req .status(ctr.$status.PROXY_AUTHENTICATION_REQUIRED, http_1.STATUS_CODES[ctr.$status.PROXY_AUTHENTICATION_REQUIRED] || 'Unknown') .write(context.global.cache.arrayBufferTexts.proxy_auth_required); return; } else { context.ip.isProxied = false; context.ip.value = oldIp; Object.assign(ctr.client, { ip: new utils_1.network.IPAddress(oldIp) }); } } } } } if (context.global.httpHandler) { try { await Promise.resolve(context.global.httpHandler(ctr, () => context.endFn = true)); } catch (err) { context.handleError(err, 'http.handle.global.httpHandler'); } if (req.aborted().aborted) return context.abort(); } const split = context.url.path.split('/'); for (const route of context.global.routes[context.type]) { if (route.matches(context.url.method, context.params, context.url.path, split)) { context.route = route; break; } } for (let i = 0; i < middlewares.length; i++) { const middleware = middlewares[i]; if (req.aborted().aborted) return context.abort(); if (middleware.callbacks.httpRequest) { try { await Promise.resolve(middleware.callbacks.httpRequest(middleware.config, server, context, ctr, () => context.endFn = true)); } catch (err) { context.handleError(err, `http.handle.middleware.${i}.httpRequest`); } if (context.endFn) break; } } if (context.route?.ratelimit && context.route.ratelimit.maxHits !== Infinity && context.route.ratelimit.timeWindow !== Infinity) { let data = context.global.rateLimits.get(`http+${ctr.client.ip}-${context.route.ratelimit.sortTo}`, { hits: 0, end: Date.now() + context.route.ratelimit.timeWindow }); if (data.hits + 1 > context.route.ratelimit.maxHits && data.end > Date.now()) { if (data.hits === context.route.ratelimit.maxHits) data.end += context.route.ratelimit.penalty; context.endFn = true; if (context.global.rateLimitHandlers.httpRequest) { try { await Promise.resolve(context.global.rateLimitHandlers.httpRequest(ctr)); } catch (err) { context.handleError(err, 'http.handle.rateLimitHandlers.httpRequest'); } } else { context.response.status = 429; context.response.statusText = 'Too Many Requests'; context.response.content = context.global.cache.arrayBufferTexts.rate_limit_exceeded; } } else if (data.end < Date.now()) { context.global.rateLimits.delete(`http+${ctr.client.ip}-${context.route.ratelimit.sortTo}`); data = { hits: 0, end: Date.now() + context.route.ratelimit.timeWindow }; } context.response.headers.set('x-ratelimit-limit', context.route.ratelimit.maxHits.toString()); context.response.headers.set('x-ratelimit-remaining', (context.route.ratelimit.maxHits - (data.hits + 1)).toString()); context.response.headers.set('x-ratelimit-reset', Math.floor(data.end / 1000).toString()); context.response.headers.set('x-ratelimit-policy', `${context.route.ratelimit.maxHits};w=${Math.floor(context.route.ratelimit.timeWindow / 1000)}`); context.global.rateLimits.set(`http+${ctr.client.ip}-${context.route.ratelimit.sortTo}`, { ...data, hits: data.hits + 1 }); } if (!context.endFn && !context.route && context.url.method === 'GET') { const cached = context.global.cache.staticFiles.get(context.url.path); if (cached) { context.file = cached[0]; context.route = cached[1]; } if (!context.route) for (const route of context.global.routes.static) { if (typeof route.urlData.value !== 'string') continue; if (!context.url.path.startsWith(route.urlData.value)) continue; const path = context.url.path.slice(route.urlData.value.length); if (!route.data.stripHtmlEnding) { const file = `${route.data.folder}/${path}`; if (await (0, fileExists_1.fileExists)(file)) { context.file = file; context.route = route; context.global.cache.staticFiles.set(context.url.path, [file, route]); break; } } else { { // path/index.html const file = `${route.data.folder}/${path}/index.html`; if (await (0, fileExists_1.fileExists)(file)) { context.file = file; context.route = route; context.global.cache.staticFiles.set(context.url.path, [file, route]); break; } } { // path.html const file = `${route.data.folder}/${path}.html`; if (await (0, fileExists_1.fileExists)(file)) { context.file = file; context.route = route; context.global.cache.staticFiles.set(context.url.path, [file, route]); break; } } { // path const file = `${route.data.folder}/${path}`; if (await (0, fileExists_1.fileExists)(file)) { context.file = file; context.route = route; context.global.cache.staticFiles.set(context.url.path, [file, route]); break; } } } } } if (req.aborted().aborted) return context.abort(); if (context.route && !context.endFn) { for (let i = 0; i < context.route.validators.length; i++) { const validator = context.route.validators[i]; for (let j = 0; j < validator.listeners.httpRequest.length; j++) { if (req.aborted().aborted) return; const validate = validator.listeners.httpRequest[j]; try { await Promise.resolve(validate(ctr, () => context.endFn = true, validator.data)); } catch (err) { context.handleError(err, `ws.handle.validator.${i}.listeners.${j}.httpRequest`); } } } } if (!context.endFn) { if (req.aborted().aborted) return context.abort(); switch (context.route?.type) { case "http": { if (context.route.data.onRawBody) { await Promise.resolve(context.awaitBody(ctr, false)); } if (context.route?.data.onRequest) try { await Promise.resolve(context.route.data.onRequest(ctr)); } catch (err) { context.handleError(err, 'http.handle.onRequest'); } break; } case "ws": { if (context.route.data.onUpgrade) try { await Promise.resolve(context.route.data.onUpgrade(ctr, () => context.endFn = true)); } catch (err) { context.handleError(err, 'http.handle.onUpgrade'); } context.response.status = 101; context.response.statusText = 'Switching Protocols'; if (!context.endFn && !context.error) { context.setExecuteSelf(async () => { await (0, writeHeaders_1.default)(null, context, req); context.body.chunks.length = 0; context.body.raw = Buffer.allocUnsafe(0); const success = req .status(101, 'Switching Protocols') .upgrade({ custom: ctr["@"], aborter: new AbortController(), context }); if (!success) { context.handleError(new Error('Failed to Upgrade Connection'), 'http.handle.upgrade'); return true; } return false; }); } break; } case "static": { if (context.file) { ctr.printFile(context.file); } else { context.global.logger.debug(`Static Route without File: ${context.url.path}`); } break; } case undefined: { if (context.global.notFoundHandler) { try { await Promise.resolve(context.global.notFoundHandler(ctr)); } catch (err) { context.handleError(err, 'http.handle.notFoundHandler'); } } else { context.response.status = 404; context.response.statusText = null; context.response.content = context.global.cache.arrayBufferTexts.route_not_found; } break; } } } if (req.aborted().aborted) return context.abort(); if (context.error || await Promise.resolve(context['executeSelf']())) { if (context.error) { if (context.global.errorHandlers.httpRequest) { try { await Promise.resolve(context.global.errorHandlers.httpRequest(ctr, context.error)); } catch { } } else { context.response.status = 500; context.response.statusText = null; context.response.content = `An Error has occured:\n${context.error.toString()}`; context.global.logger.error(`An Error has occured on ${context.url.method} ${context.url.href}\n${context.error.toString()}`); } } const content = await (0, index_1.parseContent)(context.response.content, context.response.prettify, context.global.logger); if (req.aborted().aborted) return context.abort(); for (const [key, value] of Object.entries(content.headers)) { context.response.headers.set(key, value); } const continueWrites = await (0, writeHeaders_1.default)(content.content, context, req); if (!continueWrites) return; await req .compress((0, getCompressMethod_1.default)(true, context.headers.get('accept-encoding', ''), content.content.byteLength, context.ip.isProxied, context.global)) .status(context.response.status, context.response.statusText || http_1.STATUS_CODES[context.response.status] || 'Unknown') .write(content.content); } return context.abort(ctr); } exports.default = handle;