Skip to content

Commit f042016

Browse files
committed
Finish passthrough option support for the Send API
This adds upstream proxy & DNS support, allowing for routing that matches our intercepted Docker setup. Should mean we're now feature complete for the core "accurately resend requests" workflow, woo! Each of these has been manually tested with a hacked up UI demo and everything seems to work correctly, so I think we're good to go...
1 parent 44a9cff commit f042016

File tree

10 files changed

+433
-240
lines changed

10 files changed

+433
-240
lines changed

package-lock.json

Lines changed: 52 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"mime-types": "^2.1.27",
7777
"mobx": "^6.3.5",
7878
"mockrtc": "^0.3.1",
79-
"mockttp": "^3.7.6",
79+
"mockttp": "^3.8.0",
8080
"node-abort-controller": "^3.0.1",
8181
"node-fetch": "^2.6.1",
8282
"node-forge": "^1.3.0",

src/api/api-model.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as _ from 'lodash';
2-
import * as stream from 'stream';
32
import * as os from 'os';
43

54
import { generateSPKIFingerprint } from 'mockttp';
@@ -11,7 +10,8 @@ import { reportError, addBreadcrumb } from '../error-tracking';
1110
import { HtkConfig } from "../config";
1211
import { ActivationError, Interceptor } from "../interceptors";
1312
import { getDnsServer } from '../dns-server';
14-
import * as Client from '../client/client';
13+
import * as Client from '../client/client-types';
14+
import { HttpClient } from '../client/http-client';
1515

1616
const INTERCEPTOR_TIMEOUT = 1000;
1717

@@ -21,6 +21,7 @@ export class ApiModel {
2121
private config: HtkConfig,
2222
private interceptors: _.Dictionary<Interceptor>,
2323
private getRuleParamKeys: () => string[],
24+
private httpClient: HttpClient,
2425
private callbacks: {
2526
onTriggerUpdate: () => void,
2627
onTriggerShutdown: () => void
@@ -209,7 +210,7 @@ export class ApiModel {
209210
requestDefinition: Client.RequestDefinition,
210211
requestOptions: Client.RequestOptions
211212
) {
212-
return Client.sendRequest(requestDefinition, requestOptions);
213+
return this.httpClient.sendRequest(requestDefinition, requestOptions);
213214
}
214215

215216
}

src/api/api-server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { shutdown } from '../shutdown';
1212
import { ApiModel } from './api-model';
1313
import { exposeGraphQLAPI } from './graphql-api';
1414
import { exposeRestAPI } from './rest-api';
15+
import { HttpClient } from '../client/http-client';
1516

1617
/**
1718
* This file contains the core server API, used by the UI to query
@@ -39,7 +40,11 @@ export class HttpToolkitServerApi extends events.EventEmitter {
3940

4041
private server: express.Application;
4142

42-
constructor(config: HtkConfig, getRuleParamKeys: () => string[]) {
43+
constructor(
44+
config: HtkConfig,
45+
httpClient: HttpClient,
46+
getRuleParamKeys: () => string[]
47+
) {
4348
super();
4449

4550
const interceptors = buildInterceptors(config);
@@ -114,6 +119,7 @@ export class HttpToolkitServerApi extends events.EventEmitter {
114119
config,
115120
interceptors,
116121
getRuleParamKeys,
122+
httpClient,
117123
{
118124
onTriggerUpdate: () => this.emit('update-requested'),
119125
onTriggerShutdown: () => shutdown('API call')

src/api/rest-api.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { ParsedQs } from 'qs';
1414
import { ErrorLike, StatusError } from '../util/error';
1515
import { reportError } from '../error-tracking';
1616
import { ApiModel } from './api-model';
17-
import * as Client from '../client/client';
17+
import * as Client from '../client/client-types';
1818

1919
/**
2020
* This file exposes the API model via a REST-ish classic HTTP API.
@@ -96,18 +96,13 @@ export function exposeRestAPI(
9696

9797
// Various buffers are serialized as base64 (for both requests & responses)
9898
request.rawBody = Buffer.from(request.rawBody ?? '', 'base64');
99-
if (options.trustAdditionalCAs) {
100-
options.trustAdditionalCAs = options.trustAdditionalCAs.map(
101-
({ cert }: { cert: string }) => Buffer.from(cert, 'base64')
102-
);
103-
}
10499
if (options.clientCertificate) {
105100
options.clientCertificate.pfx = Buffer.from(options.clientCertificate.pfx, 'base64');
106101
}
107102

108103
// Start actually sending the request:
109104
const abortController = new AbortController();
110-
const resultStream = apiModel.sendRequest(request, {
105+
const resultStream = await apiModel.sendRequest(request, {
111106
...options,
112107
abortSignal: abortController.signal
113108
});

src/client/client-types.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type * as Mockttp from 'mockttp';
2+
3+
// --- Request definition types ---
4+
5+
export type RawHeaders = Mockttp.RawHeaders;
6+
7+
export interface RequestDefinition {
8+
method: string;
9+
url: string;
10+
11+
/**
12+
* The raw headers to send. These will be sent exactly as provided - no headers
13+
* will be added automatically.
14+
*
15+
* Note that this means omitting the 'Host' header may cause problems, as will
16+
* omitting both 'Content-Length' and 'Transfer-Encoding' on requests with
17+
* bodies.
18+
*/
19+
headers: RawHeaders;
20+
21+
rawBody?: Uint8Array;
22+
}
23+
24+
// --- Request option types ---
25+
26+
export const RULE_PARAM_REF_KEY = '__rule_param_reference__';
27+
type ClientProxyRuleParamReference = { [RULE_PARAM_REF_KEY]: string };
28+
29+
export type ClientProxyConfig =
30+
| undefined // No Docker, no user or system proxy
31+
| Mockttp.ProxySetting // User or system proxy
32+
| ClientProxyRuleParamReference // Docker proxy (must be dereferenced)
33+
| Array<Mockttp.ProxySetting | ClientProxyRuleParamReference> // Both, ordered
34+
35+
export interface RequestOptions {
36+
/**
37+
* A list of hostnames for which server certificate and TLS version errors
38+
* should be ignored (none, by default).
39+
*
40+
* If set to 'true', HTTPS errors will be ignored for all hosts. WARNING:
41+
* Use this at your own risk. Setting this to `true` can open your
42+
* application to MITM attacks and should never be used over any network
43+
* that is not completed trusted end-to-end.
44+
*/
45+
ignoreHostHttpsErrors?: string[] | boolean;
46+
47+
/**
48+
* An array of additional certificates, which should be trusted as certificate
49+
* authorities for upstream hosts, in addition to Node.js's built-in certificate
50+
* authorities.
51+
*
52+
* Each certificate should be an object with a `cert` key containing the PEM
53+
* certificate as a string.
54+
*/
55+
trustAdditionalCAs?: Array<{ cert: string }>;
56+
57+
/**
58+
* A client certificate that should be used for the connection, if the server
59+
* requests one during the TLS handshake.
60+
*/
61+
clientCertificate?: { pfx: Buffer, passphrase?: string };
62+
63+
/**
64+
* Proxy configuration, specifying how (if at all) a proxy that should be used
65+
* for upstream connections.
66+
*/
67+
proxyConfig?: ClientProxyConfig;
68+
69+
/**
70+
* Custom DNS options, to allow configuration of the resolver used when
71+
* forwarding requests upstream. Passing any option switches from using node's
72+
* default dns.lookup function to using the cacheable-lookup module, which
73+
* will cache responses.
74+
*/
75+
lookupOptions?: { servers?: string[] };
76+
77+
/**
78+
* An abort signal, which can be used to cancel the in-process request if
79+
* required.
80+
*/
81+
abortSignal?: AbortSignal;
82+
}
83+
84+
// --- Response types ---
85+
86+
export type ResponseStreamEvents =
87+
| RequestStart
88+
| ResponseHead
89+
| ResponseBodyPart
90+
| ResponseEnd;
91+
// Other notable event is errors (via 'error' event)
92+
93+
export interface RequestStart {
94+
type: 'request-start';
95+
startTime: number; // Unix timestamp
96+
timestamp: number; // High precision timer (for relative calculations on later events)
97+
}
98+
99+
export interface ResponseHead {
100+
type: 'response-head';
101+
statusCode: number;
102+
statusMessage?: string;
103+
headers: RawHeaders;
104+
timestamp: number;
105+
}
106+
107+
export interface ResponseBodyPart {
108+
type: 'response-body-part';
109+
rawBody: Buffer;
110+
timestamp: number;
111+
}
112+
113+
export interface ResponseEnd {
114+
type: 'response-end';
115+
timestamp: number;
116+
}

0 commit comments

Comments
 (0)