2024-03-29 18:43:36 +00:00
|
|
|
import http, { IncomingMessage, ServerResponse, Server } from "node:http";
|
2023-11-13 11:43:37 -05:00
|
|
|
import { inject, injectAll, injectable } from "tsyringe";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-10-19 17:21:17 +00:00
|
|
|
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
|
|
|
|
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
|
|
|
|
import { HttpServerHelper } from "@spt-aki/helpers/HttpServerHelper";
|
|
|
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
|
|
|
import { IHttpConfig } from "@spt-aki/models/spt/config/IHttpConfig";
|
|
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
|
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|
|
|
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
2023-11-13 11:12:51 -05:00
|
|
|
import { WebSocketServer } from "@spt-aki/servers/WebSocketServer";
|
2023-11-13 11:43:37 -05:00
|
|
|
import { IHttpListener } from "@spt-aki/servers/http/IHttpListener";
|
2023-10-19 17:21:17 +00:00
|
|
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
@injectable()
|
|
|
|
export class HttpServer
|
|
|
|
{
|
2023-03-25 15:03:20 +00:00
|
|
|
protected httpConfig: IHttpConfig;
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
constructor(
|
|
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
|
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
|
|
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
|
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
|
|
@injectAll("HttpListener") protected httpListeners: IHttpListener[],
|
|
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
|
|
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
2023-11-13 11:12:51 -05:00
|
|
|
@inject("WebSocketServer") protected webSocketServer: WebSocketServer,
|
2023-03-03 15:23:46 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
|
|
|
}
|
|
|
|
|
2023-03-25 15:03:20 +00:00
|
|
|
/**
|
|
|
|
* Handle server loading event
|
|
|
|
*/
|
2023-03-03 15:23:46 +00:00
|
|
|
public load(): void
|
|
|
|
{
|
|
|
|
/* create server */
|
2024-03-29 18:43:36 +00:00
|
|
|
const httpServer: Server = http.createServer();
|
|
|
|
|
|
|
|
httpServer.on("request", (req, res) =>
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
this.handleRequest(req, res);
|
|
|
|
});
|
|
|
|
|
2023-03-25 15:03:20 +00:00
|
|
|
/* Config server to listen on a port */
|
2023-03-03 15:23:46 +00:00
|
|
|
httpServer.listen(this.httpConfig.port, this.httpConfig.ip, () =>
|
|
|
|
{
|
2023-11-13 11:12:51 -05:00
|
|
|
this.logger.success(
|
|
|
|
this.localisationService.getText("started_webserver_success", this.httpServerHelper.getBackendUrl()),
|
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
httpServer.on("error", (e: any) =>
|
|
|
|
{
|
|
|
|
/* server is already running or program using privileged port without root */
|
|
|
|
if (process.platform === "linux" && !(process.getuid && process.getuid() === 0) && e.port < 1024)
|
|
|
|
{
|
|
|
|
this.logger.error(this.localisationService.getText("linux_use_priviledged_port_non_root"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this.logger.error(this.localisationService.getText("port_already_in_use", e.port));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Setting up websocket
|
|
|
|
this.webSocketServer.setupWebSocket(httpServer);
|
|
|
|
}
|
|
|
|
|
2023-03-25 15:03:20 +00:00
|
|
|
protected handleRequest(req: IncomingMessage, resp: ServerResponse): void
|
|
|
|
{
|
|
|
|
// Pull sessionId out of cookies and store inside app context
|
2023-11-13 11:51:02 -05:00
|
|
|
const sessionId = this.getCookies(req).PHPSESSID;
|
2023-03-25 15:03:20 +00:00
|
|
|
this.applicationContext.addValue(ContextVariableType.SESSION_ID, sessionId);
|
|
|
|
|
2023-11-13 11:12:51 -05:00
|
|
|
if (this.httpConfig.logRequests)
|
2023-03-25 15:03:20 +00:00
|
|
|
{
|
2024-03-23 09:41:36 +00:00
|
|
|
// TODO: Extend to include 192.168 / 10.10 ranges or check subnet
|
|
|
|
const isLocalRequest = req.socket.remoteAddress.startsWith("127.0.0");
|
|
|
|
if (isLocalRequest)
|
|
|
|
{
|
|
|
|
this.logger.info(this.localisationService.getText("client_request", req.url));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this.logger.info(this.localisationService.getText("client_request_ip", {
|
|
|
|
ip: req.socket.remoteAddress,
|
|
|
|
url: req.url.replaceAll("/", "\\"), // Localisation service escapes `/` into hex code `/`
|
|
|
|
}));
|
|
|
|
}
|
2023-03-25 15:03:20 +00:00
|
|
|
}
|
2023-11-13 11:12:51 -05:00
|
|
|
|
2023-03-25 15:03:20 +00:00
|
|
|
for (const listener of this.httpListeners)
|
|
|
|
{
|
|
|
|
if (listener.canHandle(sessionId, req))
|
|
|
|
{
|
|
|
|
listener.handle(sessionId, req, resp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 18:43:36 +00:00
|
|
|
protected getCookies(req: IncomingMessage): Record<string, string>
|
2023-03-25 15:03:20 +00:00
|
|
|
{
|
|
|
|
const found: Record<string, string> = {};
|
|
|
|
const cookies = req.headers.cookie;
|
|
|
|
|
|
|
|
if (cookies)
|
|
|
|
{
|
|
|
|
for (const cookie of cookies.split(";"))
|
|
|
|
{
|
|
|
|
const parts = cookie.split("=");
|
|
|
|
|
|
|
|
found[parts.shift().trim()] = decodeURI(parts.join("="));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
2023-11-13 11:12:51 -05:00
|
|
|
}
|