Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft implementation of server.on(handle-error, ...) #79

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/admin/mockttp-admin-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const RESPONSE_COMPLETED_TOPIC = 'response-completed';
const REQUEST_ABORTED_TOPIC = 'request-aborted';
const TLS_CLIENT_ERROR_TOPIC = 'tls-client-error';
const CLIENT_ERROR_TOPIC = 'client-error';
const REQUEST_HANDLER_ERROR_TOPIC = 'request-handler-error';

async function buildMockedEndpointData(endpoint: ServerMockedEndpoint): Promise<MockedEndpointData> {
return {
Expand Down Expand Up @@ -82,6 +83,12 @@ export function buildAdminServerModel(
})
});

mockServer.on('request-handler-error', (error) => {
pubsub.publish(REQUEST_HANDLER_ERROR_TOPIC, {
requestHandlerError: error
})
});

return <any> {
Query: {
mockedEndpoints: async (): Promise<MockedEndpointData[]> => {
Expand Down Expand Up @@ -181,4 +188,4 @@ export function buildAdminServerModel(
}
}
};
}
}
10 changes: 9 additions & 1 deletion src/admin/mockttp-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ export const MockttpSchema = gql`
remotePort: Int!
}

type RequestHandlerError {
code: String
cmd: String
signal: String
statusCode: Int
statusMessage: String
}

type InitiatedRequest {
id: ID!
timingEvents: Json!
Expand Down Expand Up @@ -133,4 +141,4 @@ export const MockttpSchema = gql`
rawHeaders: Json!
body: Buffer!
}
`;
`;
11 changes: 10 additions & 1 deletion src/client/mockttp-admin-request-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,15 @@ export class MockttpAdminRequestBuilder {
body
}
}
}`,
'request-handler-error': gql`subscription OnRequestHandlerError {
requestHandlerError {
code
cmd
signal
statusCode
statusMessage
}
}`
}[event];

Expand Down Expand Up @@ -367,4 +376,4 @@ export class MockttpAdminRequestBuilder {

return mockedEndpoint;
}
}
}
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@ export * as PluggableAdmin from './pluggable-admin-api/pluggable-admin';
* other plugins.
* @category Internal
*/
export * as MockttpPluggableAdmin from './pluggable-admin-api/mockttp-pluggable-admin';
export * as MockttpPluggableAdmin from './pluggable-admin-api/mockttp-pluggable-admin';

export { RequestHandlerError } from "./types";
21 changes: 18 additions & 3 deletions src/mockttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
TlsRequest,
InitiatedRequest,
ClientError,
RulePriority
RulePriority,
RequestHandlerError
} from "./types";
import type { RequestRuleData } from "./rules/requests/request-rule";
import type { WebSocketRuleData } from "./rules/websockets/websocket-rule";
Expand Down Expand Up @@ -415,6 +416,19 @@ export interface Mockttp {
*/
on(event: 'client-error', callback: (error: ClientError) => void): Promise<void>;

/**
* Subscribe to hear about requests that fail during request handling.
*
* This is useful in case of DNS resolution problems or connecting to the remote peer.
*
* The callback will be called asynchronously from request handling. This function
* returns a promise, and the callback is not guaranteed to be registered until
* the promise is resolved.
*
* @category Events
*/
on(event: 'request-handler-error', callback: (error: RequestHandlerError) => void): Promise<void>;

/**
* Adds the given rules to the server.
*
Expand Down Expand Up @@ -576,7 +590,8 @@ export type SubscribableEvent =
| 'response'
| 'abort'
| 'tls-client-error'
| 'client-error';
| 'client-error'
| 'request-handler-error';

/**
* @hidden
Expand Down Expand Up @@ -677,4 +692,4 @@ export abstract class AbstractMockttp {
return new WebSocketRuleBuilder(this.addWebSocketRule);
}

}
}
2 changes: 1 addition & 1 deletion src/rules/requests/request-handler-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -985,4 +985,4 @@ export const HandlerDefinitionLookup = {
'passthrough': PassThroughHandlerDefinition,
'close-connection': CloseConnectionHandlerDefinition,
'timeout': TimeoutHandlerDefinition
}
}
2 changes: 1 addition & 1 deletion src/rules/requests/request-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1025,4 +1025,4 @@ export const HandlerLookup: typeof HandlerDefinitionLookup = {
'passthrough': PassThroughHandler,
'close-connection': CloseConnectionHandler,
'timeout': TimeoutHandler
}
}
57 changes: 32 additions & 25 deletions src/server/mockttp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
TlsRequest,
ClientError,
TimingEvents,
OngoingBody
OngoingBody,
RequestHandlerError
} from "../types";
import { CAOptions } from '../util/tls';
import { DestroyableServer } from "../util/destroyable-server";
Expand Down Expand Up @@ -277,6 +278,7 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
public on(event: 'tls-client-error', callback: (req: TlsRequest) => void): Promise<void>;
public on(event: 'tlsClientError', callback: (req: TlsRequest) => void): Promise<void>;
public on(event: 'client-error', callback: (error: ClientError) => void): Promise<void>;
public on(event: 'request-handler-error', callback: (error: RequestHandlerError) => void): Promise<void>;
public on(event: string, callback: (...args: any[]) => void): Promise<void> {
this.eventEmitter.on(event, callback);
return Promise.resolve();
Expand Down Expand Up @@ -464,32 +466,37 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
}
result = result || 'responded';
} catch (e) {
if (e instanceof AbortError) {
abort();

if (this.debug) {
console.error("Failed to handle request due to abort:", e);
}
} else {
console.error("Failed to handle request:",
this.debug
? e
: (isErrorLike(e) && e.message) || e
);
if (this.eventEmitter.listeners('requerst-handle-error').length > 0) {
this.eventEmitter.emit('requerst-handle-error', e);
}
else {
if (e instanceof AbortError) {
abort();

// Do whatever we can to tell the client we broke
try {
response.writeHead(
(isErrorLike(e) && e.statusCode) || 500,
(isErrorLike(e) && e.statusMessage) || 'Server error'
if (this.debug) {
console.error("Failed to handle request due to abort:", e);
}
} else {
console.error("Failed to handle request:",
this.debug
? e
: (isErrorLike(e) && e.message) || e
);
} catch (e) {}

try {
response.end((isErrorLike(e) && e.toString()) || e);
result = result || 'responded';
} catch (e) {
abort();
// Do whatever we can to tell the client we broke
try {
response.writeHead(
(isErrorLike(e) && e.statusCode) || 500,
(isErrorLike(e) && e.statusMessage) || 'Server error'
);
} catch (e) {}

try {
response.end((isErrorLike(e) && e.toString()) || e);
result = result || 'responded';
} catch (e) {
abort();
}
}
}
}
Expand Down Expand Up @@ -818,4 +825,4 @@ ${await this.suggestRule(request)}`
response: 'aborted' // These h2 errors get no app-level response, just a shutdown.
});
}
}
}
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import stream = require('stream');
import http = require('http');
import { EventEmitter } from 'events';
import { ErrorLike } from './util/error';

export const DEFAULT_ADMIN_SERVER_PORT = 45454;

Expand Down Expand Up @@ -205,6 +206,11 @@ export interface ClientError {
response: CompletedResponse | 'aborted';
}

/**
* RequestHandlerError dummy
*/
export interface RequestHandlerError extends ErrorLike {}

/**
* A mocked endpoint provides methods to see the current state of
* a mock rule.
Expand Down Expand Up @@ -256,4 +262,4 @@ export interface ProxyEnvConfig {
// A slightly weird one: this is necessary because we export types that inherit from EventEmitter,
// so the docs include EventEmitter's methods, which @link to this type, that's otherwise not
// defined in this module. Reexporting the values avoids warnings for that.
export type defaultMaxListeners = typeof EventEmitter.defaultMaxListeners;
export type defaultMaxListeners = typeof EventEmitter.defaultMaxListeners;
2 changes: 1 addition & 1 deletion src/util/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export function isErrorLike(error: any): error is ErrorLike {
error.code ||
error.stack
)
}
}