import {Injectable, ScopedContainer, Container} from "@extollo/di" import {infer} from "@extollo/util" import {IncomingMessage} from "http" import {HTTPCookieJar} from "../kernel/HTTPCookieJar"; import {TLSSocket} from "tls"; import * as url from "url"; // FIXME - add others? export type HTTPMethod = 'post' | 'get' | 'patch' | 'put' | 'delete' | 'unknown'; export function isHTTPMethod(what: any): what is HTTPMethod { return ['post', 'get', 'patch', 'put', 'delete'].includes(what) } export interface HTTPProtocol { string: string, major: number, minor: number, } export interface HTTPSourceAddress { address: string; family: 'IPv4' | 'IPv6'; port: number; } @Injectable() export class Request extends ScopedContainer { public readonly cookies: HTTPCookieJar; public readonly url: string; public readonly fullUrl: string; public readonly method: HTTPMethod; public readonly secure: boolean; public readonly protocol: HTTPProtocol; public readonly path: string; public readonly rawQueryData: {[key: string]: string | string[] | undefined}; public readonly query: {[key: string]: any}; public readonly isXHR: boolean; public readonly address: HTTPSourceAddress; constructor( protected clientRequest: IncomingMessage ) { super(Container.getContainer()) this.secure = !!(clientRequest.connection as TLSSocket).encrypted this.cookies = new HTTPCookieJar(this) this.url = String(clientRequest.url) this.fullUrl = (this.secure ? 'https' : 'http') + `://${this.getHeader('host')}${this.url}` const method = clientRequest.method?.toLowerCase() this.method = isHTTPMethod(method) ? method : 'unknown' this.protocol = { string: clientRequest.httpVersion, major: clientRequest.httpVersionMajor, minor: clientRequest.httpVersionMinor, } this.register(Request) this.instances.push({ key: Request, value: this, }) const parts = url.parse(this.url, true) this.path = parts.pathname ?? '/' this.rawQueryData = parts.query const query: {[key: string]: any} = {} for ( const key in this.rawQueryData ) { const value = this.rawQueryData[key] if ( Array.isArray(value) ) { query[key] = value.map(x => infer(x)) } else if ( value ) { query[key] = infer(value) } else { query[key] = value } } this.query = query this.isXHR = String(this.clientRequest.headers['x-requested-with']).toLowerCase() === 'xmlhttprequest' // @ts-ignore const {address = '0.0.0.0', family = 'IPv4', port = 0} = this.clientRequest.connection.address() this.address = { address, family, port } } public async prepare() { } public getHeader(name: string) { return this.clientRequest.headers[name.toLowerCase()] } public toNative() { return this.clientRequest } public input(key: string) { if ( key in this.query ) { return this.query[key] } } // session // route // respond // body // hostname /* param json fresh/stale - cache remote ips (proxy) signedCookies accepts content type, charsets, encodings, languages is content type (wants) range header parser */ }