From 363380bdd017b2cf3233240ea4cd04dc9785ab5f Mon Sep 17 00:00:00 2001 From: Federico Pereiro Date: Tue, 27 Jun 2017 15:25:41 +0200 Subject: [PATCH] Fix multipart file upload. --- hitit.js | 54 ++++++++++++++++++++++++++++++++++------------------ package.json | 2 +- readme.md | 6 +++--- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/hitit.js b/hitit.js index 3543359..3661d01 100644 --- a/hitit.js +++ b/hitit.js @@ -1,5 +1,5 @@ /* -hitit - v1.0.0 +hitit - v1.1.0 Written by Federico Pereiro (fpereiro@gmail.com) and released into the public domain. */ @@ -69,24 +69,10 @@ Written by Federico Pereiro (fpereiro@gmail.com) and released into the public do o.headers = dale.obj (o.headers || {}, teishi.c (state.headers) || {}, function (v, k) {return [k, v]}); - if (type (o.body) === 'object' && o.body.multipart) { + var multipart = type (o.body) === 'object' && o.body.multipart; + if (multipart) { var boundary = Math.floor (Math.random () * Math.pow (10, 16)); - var content = type (o.body.multipart) === 'array' ? o.body.multipart : [o.body.multipart]; - o.body = ''; o.headers ['content-type'] = 'multipart/form-data; boundary=' + boundary; - dale.do (content, function (v) { - if (type (v) !== 'object') return log ('Invalid multipart file or field!'); - if (v.path && ! v.filename) v.filename = Path.basename (v.path); - - o.body += '--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="' + v.name + '";'; - if (v.filename) o.body += ' filename="' + encodeURIComponent (v.filename) + '"'; - var contentType = v.contentType || (v.path ? mime.lookup (v.path) : (teishi.complex (v.value) ? 'application/json' : 'text/plain')); - o.body += '\r\nContent-Type: ' + contentType + '; charset=utf-8'; - o.body += '\r\n\r\n'; - o.body += v.path ? fs.readFileSync (v.path, 'utf8') : (teishi.complex (v.value) ? teishi.s (v.value) : v.value); - o.body += '\r\n'; - }); - o.body += '--' + boundary + '--\r\n'; } else if (teishi.complex (o.body)) { @@ -158,7 +144,39 @@ Written by Federico Pereiro (fpereiro@gmail.com) and released into the public do if (! timeout) cb ({code: -1, error: error.toString (), request: o}); }); - request.end (o.body); + if (! multipart) request.end (o.body); + else { + var content = type (o.body.multipart) === 'array' ? o.body.multipart : [o.body.multipart]; + + var queue = [], counter = 1, rwrite = function (what, enc, p) { + if (p === undefined) { + p = counter++; + queue.push (p); + } + if (Math.min.apply (Math, queue) === p) { + request.write (what, enc, function () { + queue.splice (queue.indexOf (p), 1); + if (queue.length === 0) request.end (); + }); + } + else setTimeout (function () { + rwrite (what, enc, p); + }, 1); + } + + dale.do (content, function (v) { + if (type (v) !== 'object') return log ('Invalid multipart file or field!', v); + if (v.path && ! v.filename) v.filename = Path.basename (v.path); + rwrite ('--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="' + v.name + '";'); + if (v.filename) rwrite (' filename="' + encodeURIComponent (v.filename) + '"'); + var contentType = v.contentType || (v.path ? mime.lookup (v.path) : (teishi.complex (v.value) ? 'application/json' : 'text/plain')); + if (contentType !== 'application/octet-stream') rwrite ('\r\nContent-Type: ' + contentType + '; charset=utf-8'); + rwrite ('\r\n\r\n'); + rwrite (v.path ? fs.readFileSync (v.path, 'binary') : (teishi.complex (v.value) ? teishi.s (v.value) : v.value + ''), v.path ? 'binary' : 'utf8'); + rwrite ('\r\n'); + }); + rwrite ('--' + boundary + '--\r\n'); + } } h.seq = function (state, seq, cb, map) { diff --git a/package.json b/package.json index 7ca7087..7ff5831 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hitit", - "version": "1.0.0", + "version": "1.1.0", "description": "Minimalistic tool for API testing.", "dependencies": { "dale": "4.3.0", diff --git a/readme.md b/readme.md index 3deef62..f027660 100644 --- a/readme.md +++ b/readme.md @@ -4,9 +4,9 @@ hitit is a minimalistic tool for testing an HTTP(S) API. It is a stopgap until I ## Current status of the project -The current version of hitit, v1.0.0, is considered to be *somewhat stable* and *somewhat complete*. [Suggestions](https://github.com/fpereiro/hitit/issues) and [patches](https://github.com/fpereiro/hitit/pulls) are welcome. Future changes planned are: +The current version of hitit, v1.1.0, is considered to be *somewhat stable* and *somewhat complete*. [Suggestions](https://github.com/fpereiro/hitit/issues) and [patches](https://github.com/fpereiro/hitit/pulls) are welcome. Future changes planned are: -- Improve multipart/form-data (there's at least one bug related to uploading binary files). +- Allow empty tests. - Support for concurrent testing (a.k.a stress testing). ## Installation @@ -91,7 +91,7 @@ In this case, notice that `host` and `port` are not defined and must hence be pa ## Source code -The complete source code is contained in `hitit.js`. It is about 210 lines long. +The complete source code is contained in `hitit.js`. It is about 230 lines long. ## License