11const { URL } = require ( "url" ) ;
22const http = require ( "http" ) ;
33const https = require ( "https" ) ;
4+ const querystring = require ( "querystring" ) ;
45const generateId = require ( "nanoid" ) ;
56const cookie = require ( "cookie" ) ;
67const setCookie = require ( "set-cookie-parser" ) ;
@@ -32,6 +33,7 @@ function handleRequest(harEntryMap, request, options) {
3233 const url = new URL ( options . url || options . href ) ; // Depends on Node version?
3334
3435 const entry = {
36+ _parent : parentEntry ,
3537 _timestamps : {
3638 start : now ,
3739 sent : now
@@ -54,14 +56,14 @@ function handleRequest(harEntryMap, request, options) {
5456 ssl : - 1
5557 } ,
5658 request : {
57- url : url . href ,
5859 method : request . method ,
60+ url : url . href ,
61+ cookies : buildRequestCookies ( headers ) ,
62+ headers : buildHeaders ( headers ) ,
5963 queryString : [ ...url . searchParams ] . map ( ( [ name , value ] ) => ( {
6064 name,
6165 value
6266 } ) ) ,
63- cookies : buildRequestCookies ( headers ) ,
64- headers : buildHeaders ( headers ) ,
6567 headersSize : - 1 ,
6668 bodySize : - 1
6769 } ,
@@ -71,28 +73,87 @@ function handleRequest(harEntryMap, request, options) {
7173 }
7274 } ;
7375
74- if ( parentEntry ) {
75- entry . _parent = parentEntry ;
76- }
76+ // Some versions of `node-fetch` will put `body` in the `options` received by
77+ // this function and others exclude it. Instead we have to capture writes to
78+ // the `ClientRequest` stream. There might be some official way to do this
79+ // with streams, but the events and piping I tried didn't work. FIXME?
80+ const _write = request . write ;
81+ const _end = request . end ;
82+ let requestBody ;
83+
84+ const concatBody = chunk => {
85+ // Assume the writer will be consistent such that we wouldn't get Buffers in
86+ // some writes and strings in others.
87+ if ( typeof chunk === "string" ) {
88+ if ( requestBody == null ) {
89+ requestBody = chunk ;
90+ } else {
91+ requestBody += chunk ;
92+ }
93+ } else if ( Buffer . isBuffer ( chunk ) ) {
94+ if ( requestBody == null ) {
95+ requestBody = chunk ;
96+ } else {
97+ requestBody = Buffer . concat ( [ requestBody , chunk ] ) ;
98+ }
99+ }
100+ } ;
101+
102+ request . write = function ( ...args ) {
103+ concatBody ( ...args ) ;
104+ return _write . call ( this , ...args ) ;
105+ } ;
106+
107+ request . end = function ( ...args ) {
108+ concatBody ( ...args ) ;
77109
78- entry . request . url = url . href ;
110+ if ( requestBody != null ) {
111+ // Works for both buffers and strings.
112+ entry . request . bodySize = requestBody . length ;
113+
114+ let mimeType ;
115+ for ( const name in headers ) {
116+ if ( name . toLowerCase ( ) === "content-type" ) {
117+ mimeType = headers [ name ] [ 0 ] ;
118+ break ;
119+ }
120+ }
121+
122+ if ( mimeType ) {
123+ const bodyString = requestBody . toString ( ) ; // FIXME: Assumes encoding?
124+ if ( mimeType === "application/x-www-form-urlencoded" ) {
125+ entry . request . postData = {
126+ mimeType,
127+ params : buildParams ( bodyString )
128+ } ;
129+ } else {
130+ entry . request . postData = { mimeType, text : bodyString } ;
131+ }
132+ }
133+ }
134+
135+ return _end . call ( this , ...args ) ;
136+ } ;
79137
80138 request . on ( "response" , response => {
81139 entry . _timestamps . firstByte = Date . now ( ) ;
82140 harEntryMap . set ( requestId , entry ) ;
83141 const httpVersion = `HTTP/${ response . httpVersion } ` ;
142+
143+ // Populate request info that isn't available until now.
84144 entry . request . httpVersion = httpVersion ;
145+
85146 entry . response = {
86- httpVersion,
87147 status : response . statusCode ,
88148 statusText : response . statusMessage ,
89- redirectURL : response . headers . location || "" ,
90- headers : buildHeaders ( response . rawHeaders ) ,
149+ httpVersion,
91150 cookies : buildResponseCookies ( response . headers ) ,
151+ headers : buildHeaders ( response . rawHeaders ) ,
92152 content : {
93153 size : - 1 ,
94154 mimeType : response . headers [ "content-type" ]
95155 } ,
156+ redirectURL : response . headers . location || "" ,
96157 headersSize : - 1 ,
97158 bodySize : - 1
98159 } ;
@@ -145,6 +206,22 @@ function buildRequestCookies(headers) {
145206 return cookies ;
146207}
147208
209+ function buildParams ( paramString ) {
210+ const params = [ ] ;
211+ const parsed = querystring . parse ( paramString ) ;
212+ for ( const name in parsed ) {
213+ const value = parsed [ name ] ;
214+ if ( Array . isArray ( value ) ) {
215+ value . forEach ( item => {
216+ params . push ( { name, value : item } ) ;
217+ } ) ;
218+ } else {
219+ params . push ( { name, value } ) ;
220+ }
221+ }
222+ return params ;
223+ }
224+
148225function buildResponseCookies ( headers ) {
149226 const cookies = [ ] ;
150227 const setCookies = headers [ "set-cookie" ] ;
0 commit comments