You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
112 lines
3.3 KiB
112 lines
3.3 KiB
3 years ago
|
import {Request} from "./Request";
|
||
|
import {ErrorWithContext, HTTPStatus} from "@extollo/util"
|
||
|
import {ServerResponse} from "http"
|
||
|
|
||
|
export class HeadersAlreadySentError extends ErrorWithContext {
|
||
|
constructor(response: Response, headerName?: string) {
|
||
|
super(`Cannot modify or re-send headers for this request as they have already been sent.`);
|
||
|
this.context = { headerName }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class ResponseAlreadySentError extends ErrorWithContext {
|
||
|
constructor(response: Response) {
|
||
|
super(`Cannot modify or re-send response as it has already ended.`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class Response {
|
||
|
private headers: {[key: string]: string | string[]} = {}
|
||
|
private _sentHeaders: boolean = false
|
||
|
private _responseEnded: boolean = false
|
||
|
private _status: HTTPStatus = HTTPStatus.OK
|
||
|
public body: any
|
||
|
|
||
|
constructor(
|
||
|
public readonly request: Request,
|
||
|
protected readonly serverResponse: ServerResponse,
|
||
|
) { }
|
||
|
|
||
|
public getStatus() {
|
||
|
return this._status
|
||
|
}
|
||
|
|
||
|
public setStatus(status: HTTPStatus) {
|
||
|
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this, 'status')
|
||
|
this._status = status
|
||
|
}
|
||
|
|
||
|
public get cookies() {
|
||
|
return this.request.cookies
|
||
|
}
|
||
|
|
||
|
public getHeader(name: string): string | string[] | undefined {
|
||
|
return this.headers[name]
|
||
|
}
|
||
|
|
||
|
public setHeader(name: string, value: string | string[]) {
|
||
|
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this, name)
|
||
|
this.headers[name] = value
|
||
|
return this
|
||
|
}
|
||
|
|
||
|
public setHeaders(data: {[name: string]: string | string[]}) {
|
||
|
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this)
|
||
|
this.headers = {...this.headers, ...data}
|
||
|
return this
|
||
|
}
|
||
|
|
||
|
public appendHeader(name: string, value: string | string[]) {
|
||
|
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this, name)
|
||
|
if ( !Array.isArray(value) ) value = [value]
|
||
|
let existing = this.headers[name] ?? []
|
||
|
if ( !Array.isArray(existing) ) existing = [existing]
|
||
|
|
||
|
existing = [...existing, ...value]
|
||
|
if ( existing.length === 1 ) existing = existing[0]
|
||
|
this.headers[name] = existing
|
||
|
}
|
||
|
|
||
|
public sendHeaders() {
|
||
|
const headers = {} as any
|
||
|
|
||
|
const setCookieHeaders = this.cookies.getSetCookieHeaders()
|
||
|
if ( setCookieHeaders.length ) headers['Set-Cookie'] = setCookieHeaders
|
||
|
|
||
|
for ( const key in this.headers ) {
|
||
|
if ( !this.headers.hasOwnProperty(key) ) continue
|
||
|
headers[key] = this.headers[key]
|
||
|
}
|
||
|
|
||
|
this.serverResponse.writeHead(this._status, headers)
|
||
|
this._sentHeaders = true
|
||
|
}
|
||
|
|
||
|
public hasSentHeaders() {
|
||
|
return this._sentHeaders
|
||
|
}
|
||
|
|
||
|
public async write(data: any) {
|
||
|
return new Promise<void>((res, rej) => {
|
||
|
if ( !this._sentHeaders ) this.sendHeaders()
|
||
|
this.serverResponse.write(data, error => {
|
||
|
if ( error ) rej(error)
|
||
|
else res()
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
public async send(data?: any) {
|
||
|
await this.write(data ?? this.body ?? '')
|
||
|
this.end()
|
||
|
}
|
||
|
|
||
|
public end() {
|
||
|
if ( this._responseEnded ) throw new ResponseAlreadySentError(this)
|
||
|
this._sentHeaders = true
|
||
|
this.serverResponse.end()
|
||
|
}
|
||
|
|
||
|
// location?
|
||
|
}
|