forked from rase-/node-XMLHttpRequest
-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathtest-response-type.js
400 lines (367 loc) · 13.5 KB
/
test-response-type.js
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/******************************************************************************************
* This test validates xhr.responseType as described by:
* section 3.6, subsections 8,9,10,11 of https://xhr.spec.whatwg.org/#the-response-attribute
* except xhr.responseType='document' is not yet supported.
*
* 1) Create a simple min-webserver using the node http module.
* 2) Upload 2 different float32 arrays .
* 3) Upload the utf8 encoding of the underlying in-memory representations of 1).
* 4) Upload a stringified JSON object.
* 5) Then these 5 different uploads are downloaded as xhr.reponseType varies over
* [ "text", "", "arraybuffer", "blob", "json" ]
* and then various checks verify that the downloaded content is the same as that uploaded.
*/
'use strict';
const http = require("http");
const XMLHttpRequest = require("../lib/XMLHttpRequest").XMLHttpRequest;
const supressConsoleOutput = true;
function log (_) {
if ( !supressConsoleOutput)
console.log(arguments);
}
var serverProcess;
/******************************************************************************************
* This section has various utility functions:
* 1) Convert typed array to binary string identical to underlying in-memory representation.
* 2) Convert string to typed array when the string is the in-memory representation of a Float32Array.
* 3) Display the underlying in-memory representation of the input string data.
* 4) Pause/sleep for t milliseconds.
* 5) Create a random Float32Array of length N.
* 6) Check to see if 2 array-like objects have the same elements.
* 7) Efficiently concatenate the input Array of Buffers.
*/
/**
* Create a string corresponding to the in-memory representation of typed array ta.
* @param {{ buffer: ArrayBuffer, length: number }} ta
* @returns {string}
*/
function typedArrayToString (ta) {
const u8 = new Uint8Array(ta.buffer);
return u8.reduce((acc, cur) => acc + String.fromCharCode(cur), "");
}
/**
* Assumes str is the in-memory representation of a Float32Array.
* Relies on the fact that the char codes in str are all <= 0xFF.
* Returns Float32Array corresponding to str.
*
* @param {string} str
* @returns {Float32Array}
*/
function stringToFloat32Array (str) {
const u8 = new Uint8Array(str.length);
for (let k = 0; k < str.length; k++)
u8[k] = Number(str.charCodeAt(k));
return new Float32Array(u8.buffer);
}
/**
* Create a random Float32Array of length N.
* @param {number} N
* @returns {Float32Array}
*/
function createFloat32Array (N) {
let ta = new Float32Array(N);
for (let k = 0; k < ta.length; k++)
ta[k] = Math.random();
return ta;
}
/**
* Check to see if 2 array-like objects have the same elements.
* @param {{ length: number }} ar1
* @param {{ length: number }} ar2
* @returns {boolean}
*/
function isEqual (ar1, ar2) {
if (ar1.length !== ar2.length)
return false;
for (let k = 0; k < ar1.length; k++)
if (ar1[k] !== ar2[k])
return false;
return true;
}
/**
* Efficiently concatenate the input Array of Buffers.
* Why not use Buffer.concat(...) ?
* Because bufTotal = Buffer.concat(...) often has byteOffset > 0, so bufTotal.buffer
* is larger than the useable region in bufTotal.
* @param {Array<Buffer>} bufferArray
* @returns
*/
function concat (bufferArray) {
var length = 0, offset = 0, k;
for (k = 0; k < bufferArray.length; k++)
length += bufferArray[k].length;
const result = Buffer.alloc(length);
for (k = 0; k < bufferArray.length; k++)
{
bufferArray[k].copy(result, offset, 0, bufferArray[k].length)
offset += bufferArray[k].length;
}
return result;
};
/******************************************************************************************
* This section produces a web server that serves up anything uploaded.
* The uploaded data is stored as values in a storage object, where the keys are the upload url suffixes.
* E.g. storage['/F32'] === Buffer containing the corresponding upload.
*/
const storage = { ralph: [1,2] };
function storageLength () {
const result = {};
for (const key in storage)
if (key !== '/Json') // json not stored when uploading, but is stored when retrieving, new key makes check fail
result[key] = storage[key].length;
return result;
}
function checkStorage () {
log('-----------------------------------------------------------------------------------');
log('storage:', JSON.stringify(storageLength()));
log('-----------------------------------------------------------------------------------');
}
// Xml doc for testing responseType "document"
const xmlDoc =
'<xml xmlns="a">'
+' <child>test</child>'
+' <child/>'
+'</xml>';
/**
* Serves up anything uploaded.
* Tested with:
* const urlF32 = "http://localhost:8888/F32";
* const urlF32_2 = "http://localhost:8888/F32_2";
* const urlUtf8 = "http://localhost:8888/Utf8";
* const urlUtf8_2 = "http://localhost:8888/Utf8_2";
* const urlJson = "http://localhost:8888/Json";
* const urlXml = "http://localhost:8888/Xml";
*/
function createServer() {
serverProcess = http.createServer(function (req, res) {
req.on('error', err => { console.error('request:', err) });
res.on('error', err => { console.error('response:', err) });
if (req.method === 'POST') {
const chunks = [];
//req.on('data', chunk => chunks.push(chunk));
req.on('data', chunk => {
// console.log('foo', chunk.toString('utf8'));
// console.log('bar', JSON.parse(chunk.toString('utf8')));
// console.log('bar', unescape(chunk.toString('utf8')));
chunks.push(chunk);
});
req.on('end', () => {
const u8 = concat(chunks);
storage[req.url] = u8;
// console.log('server end-handler', req.url, u8.length, req.headers);
// console.log(u8.toString('utf8'));
// console.log('-------------------');
// console.log(xmlDoc);
res.writeHead(200, {"Content-Type": "application/octet-stream"})
res.end(`success:len ${u8.length}`);
});
} else {
if (!storage[req.url])
{
res.writeHead(404, {"Content-Type": "text/plain; charset=utf8"})
res.end("Not in storage");
return;
}
if (req.url === "/Utf8" || req.url === "/Utf8_2" || req.url === "/Json" || req.url === "/Xml")
{
res.writeHead(200, {"Content-Type": "text/plain; charset=utf8"})
res.end(storage[req.url].toString());
return;
}
res.writeHead(200, {"Content-Type": "application/octet-stream"})
res.end(storage[req.url]);
}
}).listen(8888);
process.on("SIGINT", function () {
if (serverProcess)
serverProcess.close();
serverProcess = null;
});
}
createServer();
/******************************************************************************************
* This section creates:
* 1) An upload function that POSTs using xmlhttprequest-ssl.
* 2) A download function that GETs using xmlhttprequest-ssl and allows sepcifying xhr.responseType.
*/
function upload(xhr, url, data) {
return new Promise((resolve, reject) => {
xhr.open("POST", url, true);
xhr.onloadend = () => {
if (xhr.status >= 200 && xhr.status < 300)
resolve(xhr.responseText);
else
{
const errorTxt = `${xhr.status}: ${xhr.statusText}`;
reject(errorTxt);
}
};
xhr.setRequestHeader('Content-Type', 'multipart/form-data'); // Unnecessary.
xhr.send(data);
});
}
function download (xhr, url, responseType)
{
responseType = responseType || 'arraybuffer';
return new Promise((resolve, reject) => {
xhr.open("GET", url, true);
xhr.responseType = responseType;
xhr.onloadend = () => {
if (xhr.status >= 200 && xhr.status < 300)
{
switch (responseType)
{
case "":
case "text":
resolve(xhr.responseText);
break;
case "document":
resolve(xhr.responseXML);
break;
default:
resolve(xhr.response);
break;
}
}
else
{
const errorTxt = `${xhr.status}: ${xhr.statusText}`;
reject(errorTxt);
}
};
xhr.send();
});
}
/******************************************************************************************
* This section:
* 1) Uploads 2 different float32 arrays .
* 2) Uploads the utf8 encoding of the underlying in-memory representations of 1).
* 3) Uploads a stringified JSON object.
* 4) Then these 5 different uploads are downloaded as xhr.reponseType varies over
* [ "text", "", "arraybuffer", "blob", "json" ]
* and then various checks verify that the downloaded content is the same as that uploaded.
*/
const N = 1 * 1000 * 1000;
const _f32 = createFloat32Array(N);
const _f32_2 = new Float32Array([ 1, 5, 6, 7, 2, 8 ]);
const F32 = Buffer.from(_f32.buffer);
const F32_2 = Buffer.from(_f32_2.buffer);
const F32Utf8 = Buffer.from(typedArrayToString(_f32), 'utf8');
const F32Utf8_2 = Buffer.from(typedArrayToString(_f32_2), 'utf8');
const urlF32 = "http://localhost:8888/F32";
const urlF32_2 = "http://localhost:8888/F32_2";
const urlUtf8 = "http://localhost:8888/Utf8";
const urlUtf8_2 = "http://localhost:8888/Utf8_2";
const urlJson = "http://localhost:8888/Json";
const xhr = new XMLHttpRequest();
const type = (o) => { return `type=${o && o.constructor && o.constructor.name}`; };
/**
* 1) Upload Float32Array of length N=1,000,000.
* Then download using xhr.responseType="arraybuffer" and check the the array lengths are the same.
* 2) Convert the Float32Array of 1) into a string, utf8 encode it and upload it.
* Then download using xhr.responseType="text" and check the the string length is the same as the
* byteLength of the array in 1). Downloading as "text" decodes the utf8 into the original.
* 3) Upload Float32Array([1, 5, 6, 7, 2, 8]).
* Then download using xhr.responseType="blob", extract the contained arrayBuffer, view it as
* a Float32Aray and check that the contents are identical.
* 4) Convert the Float32Array of 3) into a string, utf8 encode it and upload it.
* Then download using xhr.responseType="" and check the the string length is the same as the
* byteLength of the array in 3). Downloading as "" decodes the utf8 into the original.
* 5) Let testJson be the current mini-webserver storage object:
* e.g. testJson = {ralph:2,'/F32':4000000,'/Utf8':5333575,'/F32_2':24,'/Utf8_2':28,'/Xml':56,'/Json':77}
* Upload JSON.stringify(testJson) and download it using xhr.responseType="json"
* Check that the objects are the same by comparing the strings after calling JSON.stringify.
* 6) Did a test of xhr.responseType="document" using a simple xml example.
*/
function runTest() {
const uploadPromises = [];
var r;
return upload(xhr, urlF32, F32) // upload float32
.then((r) => {
log('upload urlF32, F32 ', r);
})
.then(() => { // download float32
return download(xhr, urlF32, 'arraybuffer');
})
.then((ab) => { // make sure download is correct
const f32 = new Float32Array(ab);
log('download urlF32 arraybuf', f32.byteLength, type(ab));
if (f32.byteLength !== F32.length)
throw new Error(`Download from urlF32 has incorrect length: ${f32.byteLength} !== ${F32.length}`);
})
.then(() => {
return upload(xhr, urlUtf8, F32Utf8);
})
.then((r) => {
log('upload urlUtf8, F32Utf8 ', r);
})
.then(() => {
return download(xhr, urlF32, 'arraybuffer');
})
.then((ab) => {
const f32 = new Float32Array(ab);
log('download urlF32 arraybuf', f32.byteLength, type(ab));
if (f32.byteLength !== F32.length)
throw new Error(`Download from urlF32 has incorrect length: ${f32.byteLength} !== ${F32.length}`);
})
.then(() => {
return upload(xhr, urlF32_2, F32_2);
})
.then((r) => {
log('upload urlF32_2, F32_2 ', r);
})
.then(() => {
return download(xhr, urlF32, 'arraybuffer');
})
.then((ab) => {
const f32 = new Float32Array(ab)
log('download urlF32 arraybuf', f32.byteLength, type(ab));
if (f32.byteLength !== F32.length)
throw new Error(`Download from urlF32 has incorrect length: ${f32.byteLength} !== ${F32.length}`);
})
.then(() => {
log('XXXXXXXXXXXXXXXXX', urlUtf8_2, F32Utf8_2)
return upload(xhr, urlUtf8_2, F32Utf8_2);
})
.then((r) => {
log('upload urlUtf8_2, F32Utf8_2', r);
})
.then(() => {
return download(xhr, urlUtf8_2, 'text');
})
.then((text2) => {
const text2_f32 = stringToFloat32Array(text2);
log('download urlUtf8_2 default', text2.length, type(text2), text2_f32);
if (!isEqual(text2_f32, _f32_2))
throw new Error(`Download from urlUtf8_2 has incorrect content: ${text2_f32} !== ${_f32_2}`);
})
.then(() => {
return upload(xhr, urlJson, JSON.stringify(storageLength()));
})
.then((r) => {
log('upload:urlJson, storage ', r);
})
.then(() => {
return download(xhr, urlJson, 'json');
})
.then((json) => {
log(`download urlJson json ${JSON.stringify(json).length}`, type(json), json);
const testJson = storageLength();
if (JSON.stringify(json) !== JSON.stringify(testJson))
throw new Error(`Download from urlJson has incorrect content:\n ${JSON.stringify(json)} !== ${JSON.stringify(testJson)}`);
});
}
/**
* Run the test.
* If runTest() fails, an exception will be thrown.
*/
setTimeout(function () {
runTest()
.then(() => { console.log("PASSED"); shutdown(); })
.catch((e) => { console.log("FAILED", e); shutdown(); throw e; });
}, 100);
function shutdown() {
if (serverProcess)
serverProcess.close();
serverProcess = null;
}