|
1 |
| -import async_hooks, { AsyncHook } from 'async_hooks'; |
| 1 | +import { |
| 2 | + PREFIX_RESPONSE_BODY_DATA, |
| 3 | + PREFIX_SERVER_RESPONSE |
| 4 | +} from './constants'; |
2 | 5 | import type {
|
3 |
| - ObjectForResBodyArg, |
4 | 6 | ObjectForResBodyBufferItem,
|
5 | 7 | ServerResponseArg
|
6 | 8 | } from './types/asyncEventTypes';
|
7 |
| -import APICollector from './APICollector'; |
| 9 | +import type { APICollectorInterface } from './APICollector.interface'; |
8 | 10 |
|
9 | 11 | /**
|
10 | 12 | * The response body data comes before ServerResponse event
|
11 | 13 | * The ServerResponse event will tell which asyncId it triggered
|
12 |
| - * By this way we can get the full API data including req and res |
| 14 | + * By this way we can get the full response |
13 | 15 | */
|
14 | 16 | type EventMap = Record<number, ObjectForResBodyBufferItem | undefined>
|
15 | 17 |
|
| 18 | +type ResponseBodyDataFromChildProcess = { |
| 19 | + asyncId: number, |
| 20 | + data: ObjectForResBodyBufferItem |
| 21 | +} |
| 22 | + |
| 23 | +type ServerResponseFromChildProcess = { |
| 24 | + triggerAsyncId: number, |
| 25 | + data: ServerResponseArg |
| 26 | +} |
| 27 | + |
16 | 28 | export default class RequestHook {
|
17 |
| - private asyncHook: AsyncHook | null; |
18 | 29 | private eventMap: EventMap;
|
19 |
| - private apiCollector: APICollector; |
20 | 30 |
|
21 |
| - constructor () { |
22 |
| - this.asyncHook = null; |
23 |
| - this.eventMap = {}; |
24 |
| - this.apiCollector = new APICollector(); |
25 |
| - } |
| 31 | + public static getInjectedCodes (): string { |
| 32 | + return ` |
| 33 | + const async_hooks = require('async_hooks') |
| 34 | + const asyncHook = async_hooks.createHook({ init }); |
| 35 | + asyncHook.enable(); |
26 | 36 |
|
27 |
| - public enable (): void { |
28 |
| - this.asyncHook = async_hooks.createHook({ init: this.init }); |
29 |
| - this.asyncHook.enable(); |
30 |
| - } |
| 37 | + function init(asyncId, type, triggerAsyncId, resource) { |
| 38 | + if (type === "TickObject" && resource.args) { |
| 39 | + const className = resource.args?.[0]?.constructor.name; |
31 | 40 |
|
32 |
| - public disable (): void { |
33 |
| - if (this.asyncHook) { |
34 |
| - this.asyncHook.disable(); |
35 |
| - this.asyncHook = null; |
36 |
| - } |
37 |
| - } |
| 41 | + // Response body data |
| 42 | + if (className === "Object") { |
| 43 | + const arg = resource.args[0] |
| 44 | + if (arg?.stream?.server && arg?.state?.buffered) { |
| 45 | + const dataItem = arg.state.buffered.find(item => { |
| 46 | + if (!item) return false |
| 47 | + return ['buffer', 'utf-8'].includes(item.encoding) |
| 48 | + }) |
| 49 | + if (dataItem) { |
| 50 | + const chunk = dataItem.encoding === 'buffer' |
| 51 | + ? dataItem.chunk.toString() |
| 52 | + : dataItem.chunk |
| 53 | + const res = { |
| 54 | + asyncId, |
| 55 | + data: { |
| 56 | + encoding: dataItem.encoding, |
| 57 | + chunk |
| 58 | + } |
| 59 | + } |
| 60 | + console.log("${PREFIX_RESPONSE_BODY_DATA}" + JSON.stringify(res)) |
| 61 | + } |
| 62 | + } |
| 63 | + } |
38 | 64 |
|
39 |
| - private extractResBody (arg: ObjectForResBodyArg): ObjectForResBodyBufferItem | undefined { |
40 |
| - return arg.state.buffered.find(item => ['buffer', 'utf-8'].includes(item.encoding)); |
| 65 | + // Server response |
| 66 | + if (className === "ServerResponse") { |
| 67 | + const arg = resource.args[0]; |
| 68 | + const res = { |
| 69 | + triggerAsyncId, |
| 70 | + data: { |
| 71 | + _header: arg._header, |
| 72 | + statusCode: arg.statusCode, |
| 73 | + statusMessage: arg.statusMessage, |
| 74 | + req: { |
| 75 | + rawHeaders: arg.req.rawHeaders, |
| 76 | + url: arg.req.url, |
| 77 | + method: arg.req.method, |
| 78 | + params: arg.req.params, |
| 79 | + query: arg.req.query, |
| 80 | + baseUrl: arg.req.baseUrl, |
| 81 | + originalUrl: arg.req.originalUrl, |
| 82 | + body: arg.req.body |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | + if (arg.req._readableState?.buffer?.head?.data) { |
| 87 | + res.data.req._readableState = { |
| 88 | + buffer: { |
| 89 | + head: { |
| 90 | + data: arg.req._readableState.buffer.head.data.toString() |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + console.log("${PREFIX_SERVER_RESPONSE}" + JSON.stringify(res)) |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + `; |
41 | 100 | }
|
42 | 101 |
|
43 |
| - private init ( |
44 |
| - asyncId: number, |
45 |
| - type: string, |
46 |
| - triggerAsyncId: number, |
47 |
| - resource: { |
48 |
| - args: Array<ObjectForResBodyArg | ServerResponseArg> |
49 |
| - } |
50 |
| - ): void { |
51 |
| - if (type === "TickObject" && resource.args) { |
52 |
| - const className = resource.args?.[0]?.constructor.name; |
| 102 | + constructor (private apiCollector: APICollectorInterface) { |
| 103 | + this.eventMap = {}; |
| 104 | + } |
53 | 105 |
|
54 |
| - // Check if it is response data, save it into eventMap |
55 |
| - if (className === "Object") { |
56 |
| - const myArg = resource.args[0] as ObjectForResBodyArg; |
57 |
| - if (myArg?.stream?.server) { |
58 |
| - this.eventMap[asyncId] = this.extractResBody(myArg); |
59 |
| - } |
60 |
| - } |
| 106 | + public handleResponseBodyData (res: ResponseBodyDataFromChildProcess): void { |
| 107 | + this.eventMap[res.asyncId] = res.data; |
| 108 | + } |
61 | 109 |
|
62 |
| - // Check if it is server response, pass it to APICollector |
63 |
| - if (className === "ServerResponse") { |
64 |
| - const myArg = resource.args[0] as ServerResponseArg; |
65 |
| - const responseBodyData = this.eventMap[triggerAsyncId]; |
66 |
| - this.apiCollector.addAPIItem(myArg, responseBodyData); |
67 |
| - delete this.eventMap[triggerAsyncId]; |
68 |
| - } |
69 |
| - } |
| 110 | + public handleServerResponse (res: ServerResponseFromChildProcess): void { |
| 111 | + const triggerAsyncId = res.triggerAsyncId; |
| 112 | + const responseBodyData = this.eventMap[triggerAsyncId]; |
| 113 | + this.apiCollector.addAPIItem(res.data, responseBodyData); |
| 114 | + delete this.eventMap[triggerAsyncId]; |
70 | 115 | }
|
71 | 116 | }
|
0 commit comments