reserve
Version:
Lightweight http server statically configurable using regular expressions
372 lines (299 loc) • 10.1 kB
TypeScript
import { IncomingMessage, ServerResponse, Server as HttpServer } from 'http'
import { Server as HttpsServer } from 'https'
import { Http2Server } from 'http2'
declare module 'reserve' {
type RedirectResponse =
| void
/** Ends the response with corresponding status code */
| number
/** Triggers an internal redirect */
| string
class Request extends IncomingMessage {
setForgedUrl: (url: string) => void
}
class Response extends ServerResponse {
constructor(req?: Request)
waitForFinish: () => Promise<void>
isInitial: () => boolean
setAsynchronous: () => void
}
type IfMatcher = (request: IncomingMessage, url: string, match: RegExpMatchArray) => boolean | RedirectResponse
interface BaseMapping {
match?: string | RegExp
method?: string
'invert-match'?: boolean
'if-match'?: IfMatcher
'exclude-from-holding-list'?: boolean
cwd?: string
}
type ExternalModule = string
// region custom
type CustomRedirectResponse =
| RedirectResponse
/** Handles response through send */
| [ ReadableStream | string | object ]
/** Handles response through send */
| [ ReadableStream | string | object, SendOptions ]
interface CustomMapping extends BaseMapping {
custom:
| ExternalModule
| [string]
| [object]
| [string, SendOptions]
| [object, SendOptions]
| ((request: IncomingMessage, response: ServerResponse, ...capturedGroups: string[]) => CustomRedirectResponse | Promise<CustomRedirectResponse>)
}
// endregion custom
// region file
interface ReadStreamOptions {
start: number
end: number
}
interface CustomFileSystemStat {
isDirectory: () => boolean
size: number
mtime: Date
}
interface CustomFileSystem {
readdir: (folderPath: string) => Promise<string[]>
stat: (filePath: string) => Promise<CustomFileSystemStat>
createReadStream: (filePath: string, options?: ReadStreamOptions) => Promise<ReadableStream>
}
interface PunycacheOptions {
ttl?: number
max?: number
policy?: 'lru' | 'lfu'
}
interface FileMapping extends BaseMapping {
file: string
'mime-types'?: { [key: string]: string }
'caching-strategy'?: 'modified' | number
'custom-file-system'?: ExternalModule | CustomFileSystem
'static'?: boolean | PunycacheOptions
}
// endregion file
// region status
interface StatusMapping extends BaseMapping {
status: number
headers?: { [key: string]: string }
}
// endregion status
// region url
type Headers = { [key: string]: string | string[] }
interface RequestSummary {
method: string
url: string
headers: Headers
}
interface ForwardRequestContext {
configuration: IConfiguration
context: object
mapping: UrlMapping
match: RegExpMatchArray
request: RequestSummary
incoming: IncomingMessage
}
interface ForwardResponseContext {
configuration: IConfiguration
context: object
mapping: UrlMapping
match: RegExpMatchArray
request: RequestSummary
statusCode: number
headers: Headers
}
interface UrlMapping extends BaseMapping {
url: string
'unsecure-cookies'?: boolean
'forward-request'?: ExternalModule | ((context: ForwardRequestContext) => Promise<void>)
'forward-response'?: ExternalModule | ((context: ForwardResponseContext) => Promise<RedirectResponse>)
'ignore-unverifiable-certificate'?: boolean
'absolute-location'?: boolean
}
// endregion url
// region use
interface UseMapping extends BaseMapping {
use: ExternalModule | ((options?: object) => ((request: IncomingMessage, response: ServerResponse, next: (err: Error) => void) => void))
options?: object
}
// endregion use
// region helpers
function log (server: Server, verbose?: boolean): Server
function interpolate (match: RegExpMatchArray, pattern: string): string
function interpolate (match: RegExpMatchArray, pattern: object): object
interface BodyOptions {
ignoreContentLength?: boolean
}
type BodyResult = Promise<Buffer | string | object> & {
buffer: () => Promise<Buffer>
text: () => Promise<string>
json: () => Promise<any>
}
function body (request: IncomingMessage, options?: BodyOptions): BodyResult
interface SendOptions {
statusCode?: number /* defaulted to 200 */
headers?: Headers
noBody?: boolean /* do not send body */
}
function send (response: ServerResponse, data: ReadableStream, options?: SendOptions): Promise<void>
function send (response: ServerResponse, data?: string | object, options?: SendOptions): void
function capture (response: ServerResponse, stream: WritableStream): Promise<void>
interface PunycacheOptions {
ttl?: number
max?: number
policy?: 'lru' | 'lfu'
}
interface PunycacheCache {
set (key: string, value: any): void
get (key: string): any
del (key: string): void
keys (): string[]
}
function punycache (options?: PunycacheOptions): PunycacheCache
// endregion helpers
interface SSLSettings {
cert: string
key: string
}
interface PropertySchema {
type?: string
types?: string[]
defaultValue?: boolean | number | string | object | Function
}
interface RedirectContext {
configuration: IConfiguration
mapping: BaseMapping
match: RegExpMatchArray
redirect: string
request: IncomingMessage
response: ServerResponse
}
interface Handler {
readonly schema?: { [key: string]: string | string[] | PropertySchema }
readonly method?: string
readonly validate?: (mapping: BaseMapping, configuration: IConfiguration) => void
readonly redirect: (context: RedirectContext) => Promise<RedirectResponse>
}
type Handlers = { [key: string]: Handler }
type Listener = ExternalModule | ServerListener
type Mapping = BaseMapping | CustomMapping | FileMapping | StatusMapping | UrlMapping | UseMapping
interface Configuration {
hostname?: string
port?: number
'max-redirect'?: number
ssl?: SSLSettings
http2?: boolean
httpOptions?: object
handlers?: Handlers
listeners?: Listener[]
mappings: Mapping[]
extend?: string
}
function read (filename: string): Promise<Configuration>
function check (configuration: Configuration): Promise<Configuration>
interface IConfiguration {
readonly handlers: { [key: string]: Handler }
readonly mappings: Mapping[]
readonly http2: boolean
readonly protocol: string
setMappings: (mappings: Mapping[], request: IncomingMessage, timeout?: number) => Promise<void>
dispatch: (request: IncomingMessage, response: ServerResponse) => Promise<void>
}
type ServerEventName =
| 'created'
| 'ready'
| 'incoming'
| 'error'
| 'redirecting'
| 'redirected'
| 'aborted'
| 'closed'
type ServerEventCommon = {
method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'TRACE' | 'PATCH' | string
incomingUrl: string // before normalization
url: string // after normalization
headers: Headers
start: Date
id: number
internal: boolean
}
type ServerEventCreated ={
eventName: 'created'
server: HttpServer | HttpsServer | Http2Server
configuration: IConfiguration
};
type ServerEventReady = {
eventName: 'ready'
url: string
port: number
http2 : boolean
}
type ServerEventIncoming = {
eventName: 'incoming'
} & ServerEventCommon
type ServerEventError = {
eventName: 'error'
error: any
} & ServerEventCommon
type ServerEventRedirecting = {
eventName: 'redirecting'
type: string
redirect: string | number
} & ServerEventCommon
type ServerEventRedirected = {
eventName: 'redirected'
end: Date
timeSpent: number
statusCode: number
} & ServerEventCommon
type ServerEventAborted = {
eventName: 'aborted'
} & ServerEventCommon
type ServerEventClosed = {
eventName: 'closed'
} & ServerEventCommon
type ServerEvent<eventName = unknown> = eventName extends 'created'
? ServerEventCreated
: eventName extends 'ready'
? ServerEventReady
: eventName extends 'incoming'
? ServerEventIncoming
: eventName extends 'error'
? ServerEventError
: eventName extends 'redirecting'
? ServerEventRedirecting
: eventName extends 'redirected'
? ServerEventRedirected
: eventName extends 'aborted'
? ServerEventAborted
: eventName extends 'closed'
? ServerEventClosed
: ServerEventCreated | ServerEventReady | ServerEventIncoming | ServerEventError | ServerEventRedirecting | ServerEventRedirected | ServerEventAborted | ServerEventClosed
type ServerListener<EventName = unknown> = (event: ServerEvent<EventName>) => void
interface Server {
on: <ServerEventName extends string>(eventName: ServerEventName, listener: ServerListener<ServerEventName>) => Server
close: () => Promise<void>
}
function serve (configuration: Configuration): Server
interface MockedResponse extends ServerResponse {
toString: () => string
waitForFinish: () => Promise<void>
isInitial: () => boolean
setAsynchronous: () => void
}
type MockedRequestDefinition = {
method?: string,
url: string,
headers?: Headers,
body?: string,
properties?: object
}
interface MockServer extends Server {
request: ((method: string, url: string) => Promise<MockedResponse>) &
((method: string, url: string, headers: Headers) => Promise<MockedResponse>) &
((method: string, url: string, headers: Headers, body: string) => Promise<MockedResponse>) &
((method: string, url: string, headers: Headers, body: string, properties: object) => Promise<MockedResponse>) &
((definition: MockedRequestDefinition) => Promise<MockedResponse>)
}
function mock (configuration: Configuration, mockedHandlers?: Handlers): Promise<MockServer>
}