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.
lib/src/http/lifecycle/Response.ts

117 lines
3.6 KiB

import {Request} from "./Request";
import {ErrorWithContext, HTTPStatus, BehaviorSubject} 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: string = ''
public readonly sending$: BehaviorSubject<Response> = new BehaviorSubject<Response>()
public readonly sent$: BehaviorSubject<Response> = new BehaviorSubject<Response>()
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() {
await this.sending$.next(this)
this.setHeader('Content-Length', String(this.body?.length ?? 0))
await this.write(this.body ?? '')
this.end()
await this.sent$.next(this)
}
public end() {
if ( this._responseEnded ) throw new ResponseAlreadySentError(this)
this._sentHeaders = true
this.serverResponse.end()
}
// location?
}