Skip to content

Commit b045bb7

Browse files
committed
feat: add doOriginalCall method for nodejs request modules
1 parent 3a72370 commit b045bb7

File tree

7 files changed

+172
-18
lines changed

7 files changed

+172
-18
lines changed

experiment/try-get.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
const https = require('https');
3+
4+
const request = (url) => new Promise(resolve => {
5+
let buffer = '';
6+
https.get(url, (res) => {
7+
res.on('data', chunk => (buffer += chunk));
8+
res.on('end', () => resolve(buffer));
9+
}).on('error', (e) => {
10+
console.log('https.get error:', '[', e, ']');
11+
resolve(e.message);
12+
});
13+
});
14+
15+
const main = async () => {
16+
const res = await request('https://jsonplaceholder.typicode.com/todos/1');
17+
console.log('res:', res);
18+
};
19+
20+
main();

experiment/try-request.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
const https = require('https');
3+
4+
const request = (url, callback) => new Promise(resolve => {
5+
let buffer = '';
6+
const options = {};
7+
const req = https
8+
.request(url, options, async res => {
9+
10+
// log the data
11+
res.on('data', d => {
12+
buffer += d;
13+
});
14+
res.on('end', () => {
15+
resolve(buffer);
16+
console.log('end: ', buffer);
17+
});
18+
await callback(res);
19+
})
20+
.on('error', err => {
21+
console.log('Error: ' + err.message);
22+
});
23+
req.end();
24+
});
25+
26+
const main = async () => {
27+
let buffer = '';
28+
const data = await request('https://jsonplaceholder.typicode.com/todos/1', async res => {
29+
res.on('data', d => {
30+
buffer += d;
31+
});
32+
res.on('end', () => {
33+
console.log('end2: ', buffer);
34+
});
35+
});
36+
console.log('res:', data);
37+
};
38+
39+
main();

src/common/request.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import { http, https } from 'follow-redirects';
12
import { IncomingMessage } from 'http';
3+
import Stream from 'stream';
24
import { URL } from 'url';
35
import * as zlib from 'zlib';
4-
import { AnyObject } from './../types';
5-
import { tryToParseJson } from './utils';
6-
import Stream from 'stream';
7-
import { http, https } from 'follow-redirects';
6+
import { AnyObject, OriginalResponse } from './../types';
7+
import { str2arrayBuffer, tryToParseJson } from './utils';
88

99
/**
1010
* In nodejs environment, by default for XMLHttpRequest, fetch and wx.request, http-request-mock
@@ -74,7 +74,20 @@ function isHttpsUrl(url: string | URL) {
7474
return false;
7575
}
7676

77-
function getResponseBody(response: IncomingMessage): Promise<{ body: string, json: AnyObject }> {
77+
async function getResponseBody(response: IncomingMessage): Promise<{ body: string, json: AnyObject }> {
78+
try {
79+
const data = await parseResponseBody(response);
80+
if (data.error) {
81+
throw data.error;
82+
}
83+
84+
return { body: data.responseText as string, json: data.responseJson as AnyObject };
85+
} catch(err) {
86+
throw new Error(`getResponseBody error: ${(err as Error).message}`);
87+
}
88+
}
89+
90+
export function parseResponseBody(response: IncomingMessage): Promise<OriginalResponse> {
7891
let stream: Stream;
7992
if (['gzip', 'compress', 'deflate'].includes(response.headers['content-encoding'] || '')) {
8093
stream = response.pipe(zlib.createGunzip());
@@ -84,18 +97,38 @@ function getResponseBody(response: IncomingMessage): Promise<{ body: string, jso
8497
stream = response;
8598
}
8699

87-
return new Promise((resolve, reject) => {
100+
return new Promise((resolve) => {
88101
stream.once('error', (error) => {
89102
stream.removeAllListeners();
90-
reject(error);
103+
resolve({
104+
status: null,
105+
headers: {},
106+
responseText: null,
107+
responseJson: null,
108+
responseBuffer: null,
109+
responseBlob: null,
110+
error,
111+
});
91112
});
92113

93114
const buf: Buffer[] = [];
94115
stream.on('data', chunk => buf.push(chunk));
95116

96117
stream.once('end', () => {
97-
const body = Buffer.concat(buf).toString();
98-
resolve({ body, json: tryToParseJson(body, null) });
118+
const type = (response.headers['content-type'] || '').replace(/^application\//, '').replace(/;.*/, '');
119+
const responseText = Buffer.concat(buf).toString();
120+
const responseJson = tryToParseJson(responseText, null);
121+
const responseBuffer = str2arrayBuffer(responseText);
122+
const responseBlob = typeof Blob === 'function' ? new Blob([responseText], { type }) : null;
123+
resolve({
124+
status: response.statusCode || null,
125+
headers: response.headers,
126+
responseText,
127+
responseJson,
128+
responseBuffer,
129+
responseBlob,
130+
error: null,
131+
} as OriginalResponse);
99132
stream.removeAllListeners();
100133
});
101134
});

src/common/utils.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,16 @@ export function str2arrayBuffer(str: string) {
116116
return new TextEncoder().encode(str);
117117
}
118118

119-
const buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
120-
const bufView = new Uint16Array(buf);
121-
for (let i=0, strLen=str.length; i<strLen; i++) {
122-
bufView[i] = str.charCodeAt(i);
119+
if (typeof ArrayBuffer === 'function') {
120+
const buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
121+
const bufView = new Uint16Array(buf);
122+
for (let i=0, strLen=str.length; i<strLen; i++) {
123+
bufView[i] = str.charCodeAt(i);
124+
}
125+
return buf;
123126
}
124-
return buf;
127+
128+
return null;
125129
}
126130

127131
/**

src/interceptor/node/client-request.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
import { IncomingMessage } from 'http';
12
/* eslint-disable @typescript-eslint/ban-types */
23
import http from 'http';
34
import { Socket } from 'net';
45
import { inherits } from 'util';
56
import Bypass from '../../common/bypass';
6-
import simpleRequest from '../../common/request';
7+
import simpleRequest, { parseResponseBody } from '../../common/request';
78
import { getQuery } from '../../common/utils';
89
import { HTTPStatusCodes } from '../../config';
910
import MockItem from '../../mocker/mock-item';
1011
import Mocker from '../../mocker/mocker';
11-
import { ClientRequestOptions, ClientRequestType, RequestInfo } from '../../types';
12+
import { ClientRequestOptions, ClientRequestType, OriginalResponse, RequestInfo } from '../../types';
1213
import { RemoteResponse } from './../../types';
1314

1415
/**
@@ -93,7 +94,12 @@ function ClientRequest(
9394
return this;
9495
};
9596

96-
this.setOriginalRequestInfo = (nativeReqestMethod: Function, nativeRequestArgs: unknown[]) => {
97+
this.setOriginalRequestInfo = (
98+
getOrRequest: 'get' | 'request',
99+
nativeReqestMethod: Function,
100+
nativeRequestArgs: unknown[]
101+
) => {
102+
this.nativeReqestName = getOrRequest; // get or request
97103
this.nativeReqestMethod = nativeReqestMethod;
98104
this.nativeRequestArgs = nativeRequestArgs;
99105
};
@@ -205,6 +211,12 @@ function ClientRequest(
205211
body: method === 'GET' ? undefined : this.bufferToString(this.requestBody)
206212
};
207213

214+
requestInfo.doOriginalCall = async (): Promise<OriginalResponse> => {
215+
const res = await this.getOriginalResponse();
216+
requestInfo.doOriginalCall = undefined;
217+
return res;
218+
};
219+
208220
let remoteResponse: RemoteResponse | null = null;
209221
const remoteInfo = mockItem?.getRemoteInfo(url);
210222
if (remoteInfo) {
@@ -310,6 +322,46 @@ function ClientRequest(
310322
return this.nativeInstance;
311323
};
312324

325+
this.getOriginalResponse = (): Promise<OriginalResponse> => {
326+
const callback = this.nativeRequestArgs[this.nativeRequestArgs.length - 1];
327+
328+
const defaultResponse = {
329+
status: null,
330+
headers: {},
331+
responseText: null,
332+
responseJson: null,
333+
responseBuffer: null,
334+
responseBlob: null,
335+
error: null,
336+
};
337+
return new Promise((resolve) => {
338+
const newCallback = (res: IncomingMessage) => {
339+
parseResponseBody(res).then(data => {
340+
resolve(data);
341+
}).catch(err => {
342+
resolve({ ...defaultResponse, error: err });
343+
});
344+
if (typeof callback === 'function') {
345+
callback(res);
346+
}
347+
};
348+
const callbackIndex = typeof callback === 'function'
349+
? this.nativeRequestArgs.length - 1
350+
: this.nativeRequestArgs.length;
351+
352+
this.nativeRequestArgs[callbackIndex] = newCallback;
353+
354+
// do original call
355+
const req = this.nativeReqestMethod(...this.nativeRequestArgs);
356+
req.on('error', (err: Error) => {
357+
resolve({ ...defaultResponse, error: err });
358+
});
359+
if (this.nativeReqestName = 'get') {
360+
req.end();
361+
}
362+
});
363+
};
364+
313365
/**
314366
* https://nodejs.org/api/http.html#http_request_end_data_encodingcallback
315367
*

src/interceptor/node/http-and-https.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export default class NodeHttpAndHttpsRequestInterceptor extends Base{
107107
}
108108

109109
const client = result as ClientRequestType;
110-
client.setOriginalRequestInfo(getOrRequestFunc, args);
110+
client.setOriginalRequestInfo(getOrRequest, getOrRequestFunc, args);
111111
getOrRequest === 'get' && client.end();
112112
return result;
113113
}
@@ -130,6 +130,10 @@ export default class NodeHttpAndHttpsRequestInterceptor extends Base{
130130
const mockItem:MockItem | null = this.matchMockRequest(url, method);
131131
if (!mockItem) return false;
132132

133+
//
134+
//
135+
//s
136+
133137
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
134138
// @ts-ignore
135139
const clientRequest: ClientRequestType = new ClientRequest(url, options, callback);

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export interface WxResponse {
139139

140140
export interface ClientRequestType extends http.ClientRequest{
141141
nativeInstance: null | http.ClientRequest;
142+
nativeReqestName: 'get' | 'request';
142143
nativeReqestMethod: Function;
143144
nativeRequestArgs: unknown[];
144145

@@ -163,6 +164,7 @@ export interface ClientRequestType extends http.ClientRequest{
163164
getRequestHeaders: Function;
164165
bufferToString: Function;
165166
fallbackToNativeRequest: Function;
167+
getOriginalResponse: Function;
166168
}
167169

168170
export interface NodeRequestOpts {

0 commit comments

Comments
 (0)