UNPKG

mockttp-mvs

Version:

Mock HTTP server for testing HTTP clients and stubbing webservices

663 lines (662 loc) 26.2 kB
/// <reference types="node" /> import type * as net from 'net'; import { Readable } from 'stream'; import { Headers, CompletedRequest, CompletedBody, Explainable, RawHeaders } from "../../types"; import { MaybePromise, Replace } from '../../util/type-utils'; import { Serializable, ClientServerChannel, SerializedProxyConfig } from "../../serialization/serialization"; import { ProxyConfig } from '../proxy-config'; export interface RequestHandlerDefinition extends Explainable, Serializable { type: keyof typeof HandlerDefinitionLookup; } export declare type SerializedBuffer = { type: 'Buffer'; data: number[]; }; /** * Can be returned from callbacks to override parts of a request. * * All fields are optional, and omitted values will default to the original * request value. */ export interface CallbackRequestResult { /** * A replacement HTTP method, capitalized. */ method?: string; /** * The full URL to send the request to. If set, this will redirect * the request and automatically update the Host header accordingly, * unless you also provide a `headers` value that includes a Host * header, in which case that will take used as-is. */ url?: string; /** * The replacement HTTP headers, as an object of string keys and either * single string or array of string values. */ headers?: Headers; /** * A string or buffer, which replaces the request body if set. This will * be automatically content-encoded to match the Content-Encoding defined * in your request headers. * * If this is set, the Content-Length header will be automatically updated * accordingly to match, unless you also provide a `headers` value that * includes a Content-Length header, in which case that will take used * as-is. * * You should only return one body field: either `body`, `rawBody` or * `json`. */ body?: string | Buffer | Uint8Array; /** * A buffer, which replaces the request body if set, which is sent exactly * as is, and is not automatically encoded. * * If this is set, the Content-Length header will be automatically updated * accordingly to match, unless you also provide a `headers` value that * includes a Content-Length header, in which case that will take used * as-is. * * You should only return one body field: either `body`, `rawBody` or * `json`. */ rawBody?: Buffer | Uint8Array; /** * A JSON value, which will be stringified and send as a JSON-encoded * request body. This will be automatically content-encoded to match * the Content-Encoding defined in your request headers. * * If this is set, the Content-Length header will be automatically updated * accordingly to match, unless you also provide a `headers` value that * includes a Content-Length header, in which case that will take used * as-is. * * You should only return one body field: either `body`, `rawBody` or * `json`. */ json?: any; /** * A response: either a response object defining the fields of a response * or the string 'close' to immediately close the connection. * * See {@link CallbackResponseMessageResult} for the possible fields that can * be set to define the response. * * If set, the request will not be forwarded at all, and this will be used * as the response to immediately return to the client (or for 'close', this * will immediately close the connection to the client). */ response?: CallbackResponseResult; } export declare type CallbackResponseResult = CallbackResponseMessageResult | 'close' | 'reset'; /** * Can be returned from callbacks to define parts of a response, or * override parts when given an existing repsonse. * * All fields are optional, and omitted values will default to the original * response value or a default value. */ export interface CallbackResponseMessageResult { /** * The response status code as a number. * * Defaults to 200 if not set. */ statusCode?: number; /** * Supported only for backward compatibility. * * @deprecated Use statusCode instead. */ status?: number; /** * The response status message, as a string. This is ignored for * HTTP/2 responses. * * Defaults to the default status message for the status code if not set. */ statusMessage?: string; /** * The replacement HTTP headers, as an object of string keys and either * single string or array of string values. * * Defaults to a minimum set of standard required headers if not set. */ headers?: Headers; /** * A string or buffer, which replaces the response body if set. This will * be automatically encoded to match the Content-Encoding defined in your * response headers. * * If this is set, the Content-Length header will be automatically updated * accordingly to match, unless you also provide a `headers` value that * includes a Content-Length header, in which case that will take used * as-is. * * Defaults to empty. * * You should only return one body field: either `body`, `rawBody` or * `json`. */ body?: string | Buffer | Uint8Array; /** * A buffer, which replaces the response body if set, which is sent exactly * as is, and is not automatically encoded. * * If this is set, the Content-Length header will be automatically updated * accordingly to match, unless you also provide a `headers` value that * includes a Content-Length header, in which case that will take used * as-is. * * You should only return one body field: either `body`, `rawBody` or * `json`. */ rawBody?: Buffer | Uint8Array; /** * A JSON value, which will be stringified and send as a JSON-encoded * request body. This will be automatically content-encoded to match the * Content-Encoding defined in your response headers. * * If this is set, the Content-Length header will be automatically updated * accordingly to match, unless you also provide a `headers` value that * includes a Content-Length header, in which case that will take used * as-is. * * You should only return one body field: either `body`, `rawBody` or * `json`. */ json?: any; } export declare class SimpleHandlerDefinition extends Serializable implements RequestHandlerDefinition { status: number; statusMessage?: string | undefined; data?: string | Uint8Array | Buffer | SerializedBuffer | undefined; headers?: Headers | undefined; readonly type = "simple"; constructor(status: number, statusMessage?: string | undefined, data?: string | Uint8Array | Buffer | SerializedBuffer | undefined, headers?: Headers | undefined); explain(): string; } /** * @internal */ export interface SerializedCallbackHandlerData { type: string; name?: string; version?: number; } /** * @internal */ export interface CallbackRequestMessage { args: [ Replace<CompletedRequest, { body: string; }> | CompletedRequest ]; } export declare class CallbackHandlerDefinition extends Serializable implements RequestHandlerDefinition { callback: (request: CompletedRequest) => MaybePromise<CallbackResponseResult>; readonly type = "callback"; constructor(callback: (request: CompletedRequest) => MaybePromise<CallbackResponseResult>); explain(): string; /** * @internal */ serialize(channel: ClientServerChannel): SerializedCallbackHandlerData; } /** * @internal */ export interface SerializedStreamHandlerData { type: string; status: number; headers?: Headers; } export declare class StreamHandlerDefinition extends Serializable implements RequestHandlerDefinition { status: number; stream: Readable & { done?: true; }; headers?: Headers | undefined; readonly type = "stream"; constructor(status: number, stream: Readable & { done?: true; }, headers?: Headers | undefined); explain(): string; /** * @internal */ serialize(channel: ClientServerChannel): SerializedStreamHandlerData; } export declare class FileHandlerDefinition extends Serializable implements RequestHandlerDefinition { status: number; statusMessage: string | undefined; filePath: string; headers?: Headers | undefined; readonly type = "file"; constructor(status: number, statusMessage: string | undefined, filePath: string, headers?: Headers | undefined); explain(): string; } export interface PassThroughResponse { id: string; statusCode: number; statusMessage?: string; headers: Headers; rawHeaders: RawHeaders; body: CompletedBody; } export interface ForwardingOptions { targetHost: string; updateHostHeader?: true | false | string; } export interface PassThroughLookupOptions { /** * The maximum time to cache a DNS response. Up to this limit, * responses will be cached according to their own TTL. Defaults * to Infinity. */ maxTtl?: number; /** * How long to cache a DNS ENODATA or ENOTFOUND response. Defaults * to 0.15. */ errorTtl?: number; /** * The primary servers to use. DNS queries will be resolved against * these servers first. If no data is available, queries will fall * back to dns.lookup, and use the OS's default DNS servers. * * This defaults to dns.getServers(). */ servers?: string[]; } export interface PassThroughHandlerOptions { /** * The forwarding configuration for the passthrough rule. * This generally shouldn't be used explicitly unless you're * building rule data by hand. Instead, call `thenPassThrough` * to send data directly or `thenForwardTo` with options to * configure traffic forwarding. */ forwarding?: ForwardingOptions; /** * A list of hostnames for which server certificate and TLS version errors * should be ignored (none, by default). * * If set to 'true', HTTPS errors will be ignored for all hosts. WARNING: * Use this at your own risk. Setting this to `true` can open your * application to MITM attacks and should never be used over any network * that is not completed trusted end-to-end. */ ignoreHostHttpsErrors?: string[] | boolean; /** * An array of additional certificates, which should be trusted as certificate * authorities for upstream hosts, in addition to Node.js's built-in certificate * authorities. * * Each certificate should be an object with either a `cert` key and a string * or buffer value containing the PEM certificate, or a `certPath` key and a * string value containing the local path to the PEM certificate. */ trustAdditionalCAs?: Array<{ cert: string | Buffer; } | { certPath: string; }>; /** * A mapping of hosts to client certificates to use, in the form of * `{ key, cert }` objects (none, by default) */ clientCertificateHostMap?: { [host: string]: { pfx: Buffer; passphrase?: string; }; }; /** * Upstream proxy configuration: pass through requests via this proxy. * * If this is undefined, no proxy will be used. To configure a proxy * provide either: * - a ProxySettings object * - a callback which will be called with an object containing the * hostname, and must return a ProxySettings object or undefined. * - an array of ProxySettings or callbacks. The array will be * processed in order, and the first not-undefined ProxySettings * found will be used. * * When using a remote client, this parameter or individual array * values may be passed by reference, using the name of a rule * parameter configured in the admin server. */ proxyConfig?: ProxyConfig; /** * Custom DNS options, to allow configuration of the resolver used * when forwarding requests upstream. Passing any option switches * from using node's default dns.lookup function to using the * cacheable-lookup module, which will cache responses. */ lookupOptions?: PassThroughLookupOptions; /** * Whether to simulate connection errors back to the client. * * By default (in most cases - see below) when an upstream request fails * outright a 502 "Bad Gateway" response is sent to the downstream client, * explicitly indicating the failure and containing the error that caused * the issue in the response body. * * Only in the case of upstream connection reset errors is a connection reset * normally sent back downstream to existing clients (this behaviour exists * for backward compatibility, and will change to match other error behaviour * in a future version). * * When this option is set to `true`, low-level connection failures will * always trigger a downstream connection close/reset, rather than a 502 * response. * * This includes DNS failures, TLS connection errors, TCP connection resets, * etc (but not HTTP non-200 responses, which are still proxied as normal). * This is less convenient for debugging in a testing environment or when * using a proxy intentionally, but can be more accurate when trying to * transparently proxy network traffic, errors and all. */ simulateConnectionErrors?: boolean; /** * A set of data to automatically transform a request. This includes properties * to support many transformation common use cases. * * For advanced cases, a custom callback using beforeRequest can be used instead. * Using this field however where possible is typically simpler, more declarative, * and can be more performant. The two options are mutually exclusive: you cannot * use both transformRequest and a beforeRequest callback. * * Only one transformation for each target (method, headers & body) can be * specified. If more than one is specified then an error will be thrown when the * rule is registered. */ transformRequest?: RequestTransform; /** * A set of data to automatically transform a response. This includes properties * to support many transformation common use cases. * * For advanced cases, a custom callback using beforeResponse can be used instead. * Using this field however where possible is typically simpler, more declarative, * and can be more performant. The two options are mutually exclusive: you cannot * use both transformResponse and a beforeResponse callback. * * Only one transformation for each target (status, headers & body) can be * specified. If more than one is specified then an error will be thrown when the * rule is registered. */ transformResponse?: ResponseTransform; /** * A callback that will be passed the full request before it is passed through, * and which returns an object that defines how the the request content should * be transformed before it's passed to the upstream server. * * The callback can return an object to define how the request should be changed. * All fields on the object are optional, and returning undefined is equivalent * to returning an empty object (transforming nothing). * * See {@link CallbackRequestResult} for the possible fields that can be set. */ beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult | void> | void; /** * A callback that will be passed the full response before it is passed through, * and which returns a value that defines how the the response content should * be transformed before it's returned to the client. * * The callback can either return an object to define how the response should be * changed, or the string 'close' to immediately close the underlying connection. * * All fields on the object are optional, and returning undefined is equivalent * to returning an empty object (transforming nothing). * * See {@link CallbackResponseMessageResult} for the possible fields that can be set. */ beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void> | void; } export interface RequestTransform { /** * A replacement HTTP method. Case insensitive. */ replaceMethod?: string; /** * A headers object which will be merged with the real request headers to add or * replace values. Headers with undefined values will be removed. */ updateHeaders?: Headers; /** * A headers object which will completely replace the real request headers. */ replaceHeaders?: Headers; /** * A string or buffer that replaces the request body entirely. * * If this is specified, the upstream request will not wait for the original request * body, so this may make responses faster than they would be otherwise given large * request bodies or slow/streaming clients. */ replaceBody?: string | Uint8Array | Buffer; /** * The path to a file, which will be used to replace the request body entirely. The * file will be re-read for each request, so the body will always reflect the latest * file contents. * * If this is specified, the upstream request will not wait for the original request * body, so this may make responses faster than they would be otherwise given large * request bodies or slow/streaming clients. */ replaceBodyFromFile?: string; /** * A JSON object which will be merged with the real request body. Undefined values * will be removed. Any requests which are received with an invalid JSON body that * match this rule will fail. */ updateJsonBody?: { [key: string]: any; }; /** * Perform a series of string match & replace operations on the request body. * * This parameter should be an array of pairs, which will be applied to the body * decoded as a string like `body.replace(p1, p2)`, applied in the order provided. * The first parameter can be either a string or RegExp to match, and the second * must be a string to insert. The normal `str.replace` $ placeholders can be * used in the second argument, so that e.g. $1 will insert the 1st matched group. */ matchReplaceBody?: Array<[string | RegExp, string]>; } export interface ResponseTransform { /** * A replacement response status code. */ replaceStatus?: number; /** * A headers object which will be merged with the real response headers to add or * replace values. Headers with undefined values will be removed. */ updateHeaders?: Headers; /** * A headers object which will completely replace the real response headers. */ replaceHeaders?: Headers; /** * A string or buffer that replaces the response body entirely. * * If this is specified, the downstream response will not wait for the original response * body, so this may make responses arrive faster than they would be otherwise given large * response bodies or slow/streaming servers. */ replaceBody?: string | Uint8Array | Buffer; /** * The path to a file, which will be used to replace the response body entirely. The * file will be re-read for each response, so the body will always reflect the latest * file contents. * * If this is specified, the downstream response will not wait for the original response * body, so this may make responses arrive faster than they would be otherwise given large * response bodies or slow/streaming servers. */ replaceBodyFromFile?: string; /** * A JSON object which will be merged with the real response body. Undefined values * will be removed. Any responses which are received with an invalid JSON body that * match this rule will fail. */ updateJsonBody?: { [key: string]: any; }; /** * Perform a series of string match & replace operations on the response body. * * This parameter should be an array of pairs, which will be applied to the body * decoded as a string like `body.replace(p1, p2)`, applied in the order provided. * The first parameter can be either a string or RegExp to match, and the second * must be a string to insert. The normal `str.replace` $ placeholders can be * used in the second argument, so that e.g. $1 will insert the 1st matched group. */ matchReplaceBody?: Array<[string | RegExp, string]>; } /** * @internal */ export interface SerializedPassThroughData { type: 'passthrough'; forwardToLocation?: string; forwarding?: ForwardingOptions; proxyConfig?: SerializedProxyConfig; ignoreHostCertificateErrors?: string[] | boolean; extraCACertificates?: Array<{ cert: string; } | { certPath: string; }>; clientCertificateHostMap?: { [host: string]: { pfx: string; passphrase?: string; }; }; lookupOptions?: PassThroughLookupOptions; simulateConnectionErrors?: boolean; transformRequest?: Replace<RequestTransform, { 'replaceBody'?: string; 'updateHeaders'?: string; 'updateJsonBody'?: string; 'matchReplaceBody'?: Array<[ string | { regexSource: string; flags: string; }, string ]>; }>; transformResponse?: Replace<ResponseTransform, { 'replaceBody'?: string; 'updateHeaders'?: string; 'updateJsonBody'?: string; 'matchReplaceBody'?: Array<[ string | { regexSource: string; flags: string; }, string ]>; }>; hasBeforeRequestCallback?: boolean; hasBeforeResponseCallback?: boolean; } /** * @internal */ export interface BeforePassthroughRequestRequest { args: [Replace<CompletedRequest, { body: string; }>]; } /** * @internal */ export interface BeforePassthroughResponseRequest { args: [Replace<PassThroughResponse, { body: string; }>]; } /** * Used in merging as a marker for values to omit, because lodash ignores undefineds. * @internal */ export declare const SERIALIZED_OMIT = "__mockttp__transform__omit__"; export declare class PassThroughHandlerDefinition extends Serializable implements RequestHandlerDefinition { readonly type = "passthrough"; readonly forwarding?: ForwardingOptions; readonly ignoreHostHttpsErrors: string[] | boolean; readonly clientCertificateHostMap: { [host: string]: { pfx: Buffer; passphrase?: string; }; }; readonly extraCACertificates: Array<{ cert: string | Buffer; } | { certPath: string; }>; readonly transformRequest?: RequestTransform; readonly transformResponse?: ResponseTransform; readonly beforeRequest?: (req: CompletedRequest) => MaybePromise<CallbackRequestResult | void> | void; readonly beforeResponse?: (res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void> | void; readonly proxyConfig?: ProxyConfig; readonly lookupOptions?: PassThroughLookupOptions; readonly simulateConnectionErrors: boolean; protected outgoingSockets: Set<net.Socket>; constructor(options?: PassThroughHandlerOptions); explain(): string; /** * @internal */ serialize(channel: ClientServerChannel): SerializedPassThroughData; } export declare class CloseConnectionHandlerDefinition extends Serializable implements RequestHandlerDefinition { readonly type = "close-connection"; explain(): string; } export declare class ResetConnectionHandlerDefinition extends Serializable implements RequestHandlerDefinition { readonly type = "reset-connection"; explain(): string; } export declare class TimeoutHandlerDefinition extends Serializable implements RequestHandlerDefinition { readonly type = "timeout"; explain(): string; } export declare class JsonRpcResponseHandlerDefinition extends Serializable implements RequestHandlerDefinition { readonly result: { result: any; error?: undefined; } | { error: any; result?: undefined; }; readonly type = "json-rpc-response"; constructor(result: { result: any; error?: undefined; } | { error: any; result?: undefined; }); explain(): string; } export declare const HandlerDefinitionLookup: { simple: typeof SimpleHandlerDefinition; callback: typeof CallbackHandlerDefinition; stream: typeof StreamHandlerDefinition; file: typeof FileHandlerDefinition; passthrough: typeof PassThroughHandlerDefinition; 'close-connection': typeof CloseConnectionHandlerDefinition; 'reset-connection': typeof ResetConnectionHandlerDefinition; timeout: typeof TimeoutHandlerDefinition; 'json-rpc-response': typeof JsonRpcResponseHandlerDefinition; };