If you need more control over the POST requests, you might wish to implement your own POST handler within your Node JS server application.
Then you'll come across three
ENCTYPEs to deal with:
- application/x-www-form-urlencoded:
read the stream (message body), parse and URL decode the fields (key-value pairs);
- text/plain:
read the stream and... be happy. No need to decode or parse, as the it arrives as it was sent. Quite handy when it comes to JSON structures, using only JSON.stringify() and JSON.parse() at the both ends of the communication;
- multipart/form-data:
here everything gets more complicated, since the stream is organised in packages, sent one by one. Each chunk might have multiple form fields, files or parts of the file. Handling the files is a bit tricky, because in one chunk the file could be started, then several chunk could convey parts of this large file, then it could be consumed in another chunk.
For files, there are two main strategies:
- read all the chunks into one big buffer, and then parse it as one huge string, by splitting it into sections, parsing each header of the section and retrieving its value. The obvious downside of such an approach is that you consume a lot of resources, by which reason many POST implementations restrict the transmission to 2Mb, 5Mb, 10Mb, allocated for the entire transmission;
- another strategy is to deal with each chunk apart and as it is available for reading. It allows you to use the minimum of resources along with no restrictions on the transmission. The core of this approach is marking the points when the file starts and when it ends, which might occur in different chunks/packages.
The example below implements the second approach: chunk by chunk, no limits, low resources. The application is minimalistic too and provides only:
- one GET route to deliver an HTML with three ENCTYPEs (x-www-form, plain text and multipart) to experiment with:
X-WWW-Form: |
Text/Plain: |
Multipart: |
- and one POST channel to consume the three POST enctype submissions. For that, we wrote the class doPost with three main methods, respectively.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | const http = require('http'),
fs = require('fs'),
doPost = require('./doPost');
const server = http.createServer( (req, res) => {
if(req.method == 'POST') new doPost(req, res);
else doGet(req, res);
});
server.listen(8080);
console.log("> Server listens on 8080");
function doGet(req, res) {
let RES = "<html><head><style>input { margin-bottom: 10px; }</style></head><body><table border='1' cellpadding='12' bgcolor='#eee'><tr>";
RES += "<td valign='top'><h3>X-Form:</h3><form action='/' method='post' enctype='application/x-www-form-urlencoded'>";
RES += "First name:<br><input type='text' id='fname' name='fname' value='First Name'><br>";
RES += "Last name:<br><input type='text' id='lname' name='lname' value='Last Name'><br>";
RES += "<input type='submit' value='Submit'>";
RES += "</form></td>";
RES += "<td valign='top'><h3>Text/Plain:</h3><form action='/' method='post' enctype='text/plain'>";
RES += "First name:<br><input type='text' id='fname' name='fname' value='First Name'><br>";
RES += "Last name:<br><input type='text' id='lname' name='lname' value='Last Name'><br>";
RES += "<input type='submit' value='Submit'>";
RES += "</form></td>";
RES += "<td valign='top'><h3>Multipart:</h3><form action='/' method='post' enctype='multipart/form-data'>";
RES += "First name:<br><input type='text' id='fname' name='fname' value='First Name'><br>";
RES += "Last name:<br><input type='text' id='lname' name='lname' value='Last Name'><br>";
RES += "<input type='file' id='myfile' name='myfile'><br>";
RES += "<input type='submit' value='Submit'>";
RES += "</form></td>";
RES += "</tr></table></body></html>";
res.end(RES);
} |
And here is the
class doPost (module):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 | module.exports = class doPost {
constructor(req, res) {
const encType = req.headers['content-type'].split(';')[0];
if(encType == "application/x-www-form-urlencoded") this.doXForm(req, res);
else if(encType == "text/plain") this.doTextPlain(req, res);
else if(encType == "multipart/form-data") this.doMultiPart(req, res);
else {
var r = "Unknown ENCTYPE: " + req.headers['content-type'];
console.log(r); res.end(r);
}
return;
}
doXForm(req, res) { /* --- handle application/x-www-form-urlencoded --- */
var body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
var r = 'FIELDS:\n', arr = body.split('&');
for(var s of arr) {
var itms = s.split('=');
r += itms[0] + '=' + decodeURI(itms[1].replace(/\+/g, ' ')) + '\n';
}
res.end(r);
});
}
doTextPlain(req, res) { /* --- handle text/plain ---*/
var body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => res.end(body));
}
doMultiPart(req, res) { /* --- handle multipart/form-data --- */
var Js = this.parseHeader(req.headers['content-type']);
var bndry = "--" + Js.boundary, bLen = bndry.length;
var FL = '';
var fFlag = false;
var FLDS = 'FIELDS:\n';
req.on('data', chunk => {
var ix = 0, first = true;
while(true) {
ix = chunk.indexOf(bndry, ix);
if(ix < 0){
if(fFlag) { console.log('Continue writting file.'); FL+=chunk; return; }
console.log('Don\'t know what is happening.'); return;
}
if(first) {
first = false;
if(fFlag && ix > 0) {
console.log('Finishing writting file.');
FL += chunk.slice(0, chunk.indexOf(bndry)-2);
fFlag = false;
continue;
}
}
if(chunk.slice(ix+bLen, ix+bLen+2) == '--') { console.log("FINISH"); return; }
/* --- section --- */
fFlag = false;
var fi = chunk.indexOf("\r\n\r\n", ix+bLen+2);
var J = this.parseHeader(chunk.slice(ix+bLen+2, fi).toString());
this.listObj(J);
if("filename" in J) {
console.log('filename=' + J.filename + ', Type=' + J['Content-Type']);
if(J.filename === '') {
console.log("No file transmitted.");
ix = fi+4;
continue;
}
ix = chunk.indexOf(bndry, fi+4);
if(ix < 0 ) ix = chunk.length;
//console.log('FILE>\n['+chunk.slice(fi+4, ix-2).toString()+']');
FL += J.filename + '[' + chunk.slice(fi+4, ix-2).toString();
FLDS += J.name + '=' + J.filename + '\n';
fFlag = true;
}
else {
ix = chunk.indexOf(bndry, fi+4);
if(ix < 0 ) ix = chunk.length;
FLDS += J.name + '=' + chunk.slice(fi+4, ix-2).toString() + '\n';
console.log('VALUE = ['+chunk.slice(fi+4, ix-2).toString()+']');
}
ix = fi+4;
}
});
req.on('end', () => res.end(FLDS + (FL !== ''?('\n' + FL + ']'):'')));
}
parseHeader(hd) {
var js = {};
var els = hd.split(/; |\r\n/);
for(var el of els) {
if(el === '') continue;
var pair = el.split(/: |=/);
if(pair.length < 2) { pair[1] = pair[0]; pair[0] = 'value'; }
js[pair[0]] = pair[1].indexOf('"') < 0 ? pair[1] : pair[1].replace(/\"/g,'');
}
return js;
}
listObj(Js) {
console.log(".");
for(var k in Js) console.log(">[" + k + "]=[" + Js[k] + ']');
}
} |
No comments:
Post a Comment