Skip to content

Commit 03f1829

Browse files
committed
Tighten up more of the client API details
We now preserve headers _exactly_, so far as possible, to precisely control traffic - including raw header order & casing. This also adds a type field to the REST response message, which should make it easier to support multiple types later, and so to potentially stream ND-JSON for incremental responses.
1 parent 3e3fd00 commit 03f1829

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

src/api/rest-api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ export function exposeRestAPI(
9797
});
9898

9999
res.send({
100-
result: {
100+
type: 'response', // Later we may send ND-JSON with chunked data & other types
101+
response: {
101102
...result,
102103
rawBody: result.rawBody?.toString('base64') ?? ''
103104
}

src/client/client.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ export type RawHeaders = Array<[key: string, value: string]>;
88
export interface RequestDefinition {
99
method: string;
1010
url: string;
11+
12+
/**
13+
* The raw headers to send. These will be sent exactly as provided - no headers
14+
* will be added automatically.
15+
*
16+
* Note that this means omitting the 'Host' header may cause problems, as will
17+
* omitting both 'Content-Length' and 'Transfer-Encoding' on requests with
18+
* bodies.
19+
*/
1120
headers: RawHeaders;
1221
rawBody?: Uint8Array;
1322
}
@@ -30,12 +39,21 @@ export async function sendRequest(
3039

3140
const request = (url.protocol === 'https' ? https : http).request(requestDefn.url, {
3241
method: requestDefn.method,
33-
34-
// Node supports sending raw headers via [key, value, key, value] array, but we need an
35-
// 'any' as the types don't believe it:
36-
headers: flattenPairedRawHeaders(requestDefn.headers) as any
3742
});
3843

44+
// Node supports sending raw headers via [key, value, key, value] array, but if we do
45+
// so with 'headers' above then we can't removeHeader first (to disable the defaults).
46+
// Instead we remove headers and then manunally trigger the 'raw' write behaviour.
47+
48+
request.removeHeader('connection');
49+
request.removeHeader('transfer-encoding');
50+
request.removeHeader('content-length');
51+
52+
(request as any)._storeHeader(
53+
request.method + ' ' + request.path + ' HTTP/1.1\r\n',
54+
flattenPairedRawHeaders(requestDefn.headers)
55+
);
56+
3957
if (requestDefn.rawBody?.byteLength) {
4058
request.end(requestDefn.rawBody);
4159
} else {

test/client/send-request.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { sendRequest } from '../../src/client/client';
55

66
describe("The HTTP client API", () => {
77

8-
const mockServer = mockttp.getLocal({ debug: true });
8+
const mockServer = mockttp.getLocal();
99

1010
beforeEach(() => mockServer.start());
1111
afterEach(() => mockServer.stop());
@@ -14,9 +14,11 @@ describe("The HTTP client API", () => {
1414
await mockServer.forAnyRequest().thenCallback(async (request) => {
1515
expect(request.url).to.equal(`http://localhost:${mockServer.port}/path?qwe=asd`);
1616
expect(request.method).to.equal('POST');
17-
expect(
18-
request.rawHeaders.find(([key]) => key === 'CUSTOM-header')
19-
).to.deep.equal(['CUSTOM-header', 'CUSTOM-value']);
17+
expect(request.rawHeaders).to.deep.equal([
18+
['host', `localhost:${mockServer.port}`],
19+
['content-length', '12'],
20+
['CUSTOM-header', 'CUSTOM-value']
21+
]);
2022
expect(await request.body.getText()).to.equal('Request body')
2123

2224
return {

0 commit comments

Comments
 (0)