Skip to content

Commit 9f8c224

Browse files
coopersimonjuj
authored andcommitted
Added response headers to fetch API (#8486)
* Added response headers to fetch with flag, and headersreceived callback * Added test for headers received * onreadystatechange callback, removed define and responseheaders from fetch struct * added get_response_headers utility functions * added header unpacking functions * Fixes and cleanup * Fixed tests, fixed header helpers, fixed JS interop * fixed indentation in Fetch.js * Altered comment for get_response_headers_length * Changed ready state comment * Changed internal fetch_get_response_headers names, added arg names * Additional header file comments * Correct mapping of function names
1 parent 7eb910a commit 9f8c224

File tree

6 files changed

+177
-6
lines changed

6 files changed

+177
-6
lines changed

src/Fetch.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ function __emscripten_fetch_cache_data(db, fetch, data, onsuccess, onerror) {
276276
}
277277
#endif // ~FETCH_SUPPORT_INDEXEDDB
278278

279-
function __emscripten_fetch_xhr(fetch, onsuccess, onerror, onprogress) {
279+
function __emscripten_fetch_xhr(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
280280
var url = HEAPU32[fetch + {{{ C_STRUCTS.emscripten_fetch_t.url }}} >> 2];
281281
if (!url) {
282282
#if FETCH_DEBUG
@@ -438,6 +438,13 @@ function __emscripten_fetch_xhr(fetch, onsuccess, onerror, onprogress) {
438438
if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
439439
if (onprogress) onprogress(fetch, xhr, e);
440440
}
441+
xhr.onreadystatechange = function(e) {
442+
HEAPU16[fetch + {{{ C_STRUCTS.emscripten_fetch_t.readyState }}} >> 1] = xhr.readyState;
443+
if (xhr.readyState >= 2) {
444+
HEAPU16[fetch + {{{ C_STRUCTS.emscripten_fetch_t.status }}} >> 1] = xhr.status;
445+
}
446+
if (onreadystatechange) onreadystatechange(fetch, xhr, e);
447+
}
441448
#if FETCH_DEBUG
442449
console.log('fetch: xhr.send(data=' + data + ')');
443450
#endif
@@ -451,14 +458,15 @@ function __emscripten_fetch_xhr(fetch, onsuccess, onerror, onprogress) {
451458
}
452459
}
453460

454-
function emscripten_start_fetch(fetch, successcb, errorcb, progresscb) {
461+
function emscripten_start_fetch(fetch, successcb, errorcb, progresscb, readystatechangecb) {
455462
if (typeof Module !== 'undefined') Module['noExitRuntime'] = true; // If we are the main Emscripten runtime, we should not be closing down.
456463

457464
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
458465
var requestMethod = UTF8ToString(fetch_attr);
459466
var onsuccess = HEAPU32[fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.onsuccess }}} >> 2];
460467
var onerror = HEAPU32[fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.onerror }}} >> 2];
461468
var onprogress = HEAPU32[fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.onprogress }}} >> 2];
469+
var onreadystatechange = HEAPU32[fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.onreadystatechange }}} >> 2];
462470
var fetchAttributes = HEAPU32[fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.attributes }}} >> 2];
463471
var fetchAttrLoadToMemory = !!(fetchAttributes & {{{ cDefine('EMSCRIPTEN_FETCH_LOAD_TO_MEMORY') }}});
464472
var fetchAttrStreamData = !!(fetchAttributes & {{{ cDefine('EMSCRIPTEN_FETCH_STREAM_DATA') }}});
@@ -490,11 +498,19 @@ function emscripten_start_fetch(fetch, successcb, errorcb, progresscb) {
490498
else if (errorcb) errorcb(fetch);
491499
};
492500

501+
var reportReadyStateChange = function(fetch, xhr, e) {
502+
#if FETCH_DEBUG
503+
console.log('fetch: ready state change. e: ' + e);
504+
#endif
505+
if (onreadystatechange) {{{ makeDynCall('vi') }}}(onreadystatechange, fetch);
506+
else if (readystatechangecb) readystatechangecb(fetch);
507+
}
508+
493509
var performUncachedXhr = function(fetch, xhr, e) {
494510
#if FETCH_DEBUG
495511
console.error('fetch: starting (uncached) XHR: ' + e);
496512
#endif
497-
__emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress);
513+
__emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress, reportReadyStateChange);
498514
};
499515

500516
#if FETCH_SUPPORT_INDEXEDDB
@@ -523,7 +539,7 @@ function emscripten_start_fetch(fetch, successcb, errorcb, progresscb) {
523539
#if FETCH_DEBUG
524540
console.error('fetch: starting (cached) XHR: ' + e);
525541
#endif
526-
__emscripten_fetch_xhr(fetch, cacheResultAndReportSuccess, reportError, reportProgress);
542+
__emscripten_fetch_xhr(fetch, cacheResultAndReportSuccess, reportError, reportProgress, reportReadyStateChange);
527543
};
528544

529545
// Should we try IndexedDB first?
@@ -545,7 +561,7 @@ function emscripten_start_fetch(fetch, successcb, errorcb, progresscb) {
545561
} else if (!fetchAttrReplace) {
546562
__emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, fetchAttrNoDownload ? reportError : (fetchAttrPersistFile ? performCachedXhr : performUncachedXhr));
547563
} else if (!fetchAttrNoDownload) {
548-
__emscripten_fetch_xhr(fetch, fetchAttrPersistFile ? cacheResultAndReportSuccess : reportSuccess, reportError, reportProgress);
564+
__emscripten_fetch_xhr(fetch, fetchAttrPersistFile ? cacheResultAndReportSuccess : reportSuccess, reportError, reportProgress, reportReadyStateChange);
549565
} else {
550566
#if FETCH_DEBUG
551567
console.error('fetch: Invalid combination of flags passed.');
@@ -554,7 +570,18 @@ function emscripten_start_fetch(fetch, successcb, errorcb, progresscb) {
554570
}
555571
return fetch;
556572
#else // !FETCH_SUPPORT_INDEXEDDB
557-
__emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress);
573+
__emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress, reportReadyStateChange);
558574
return fetch;
559575
#endif // ~FETCH_SUPPORT_INDEXEDDB
560576
}
577+
578+
function _fetch_get_response_headers_length(id) {
579+
return lengthBytesUTF8(Fetch.xhrs[id-1].getAllResponseHeaders()) + 1;
580+
}
581+
582+
function _fetch_get_response_headers(id, dst, dstSizeBytes) {
583+
var responseHeaders = Fetch.xhrs[id-1].getAllResponseHeaders();
584+
var lengthBytes = lengthBytesUTF8(responseHeaders) + 1;
585+
stringToUTF8(responseHeaders, dst, dstSizeBytes);
586+
return Math.min(lengthBytes, dstSizeBytes);
587+
}

src/library_fetch.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ var LibraryFetch = {
1818
_emscripten_get_fetch_work_queue: function() {
1919
return _fetch_work_queue;
2020
},
21+
_emscripten_fetch_get_response_headers_length: _fetch_get_response_headers_length,
22+
_emscripten_fetch_get_response_headers: _fetch_get_response_headers,
2123

2224
#if FETCH_SUPPORT_INDEXEDDB
2325
$__emscripten_fetch_delete_cached_data: __emscripten_fetch_delete_cached_data,

src/struct_info.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,7 @@
15661566
"onsuccess",
15671567
"onerror",
15681568
"onprogress",
1569+
"onreadystatechange",
15691570
"attributes",
15701571
"timeoutMSecs",
15711572
"withCredentials",

system/include/emscripten/fetch.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ typedef struct emscripten_fetch_attr_t
6868
void (*onsuccess)(struct emscripten_fetch_t *fetch);
6969
void (*onerror)(struct emscripten_fetch_t *fetch);
7070
void (*onprogress)(struct emscripten_fetch_t *fetch);
71+
void (*onreadystatechange)(struct emscripten_fetch_t *fetch);
7172

7273
// EMSCRIPTEN_FETCH_* attributes
7374
uint32_t attributes;
@@ -185,6 +186,24 @@ EMSCRIPTEN_RESULT emscripten_fetch_wait(emscripten_fetch_t *fetch, double timeou
185186
// onerror() handler will be called in the calling thread before this function returns.
186187
EMSCRIPTEN_RESULT emscripten_fetch_close(emscripten_fetch_t *fetch);
187188

189+
// Gets the size (in bytes) of the response headers as plain text.
190+
// This must be called on the same thread as the fetch originated on.
191+
// Note that this will return 0 if readyState < HEADERS_RECEIVED.
192+
size_t emscripten_fetch_get_response_headers_length(emscripten_fetch_t *fetch);
193+
194+
// Gets the response headers as plain text. dstSizeBytes should be headers_length + 1 (for the null terminator).
195+
// This must be called on the same thread as the fetch originated on.
196+
size_t emscripten_fetch_get_response_headers(emscripten_fetch_t *fetch, char *dst, size_t dstSizeBytes);
197+
198+
// Converts the plain text headers into an array of strings. This array takes the form
199+
// {"key1", "value1", "key2", "value2", "key3", "value3", ..., 0 }; Note especially that the array
200+
// is terminated with a null pointer.
201+
char **emscripten_fetch_unpack_response_headers(const char *headersString);
202+
203+
// This frees the memory used by the array of headers. Call this when finished with the data returned
204+
// by emscripten_fetch_unpack_response_headers.
205+
void emscripten_fetch_free_unpacked_response_headers(char **unpackedHeaders);
206+
188207
#define emscripten_asmfs_open_t int
189208

190209
// The following flags specify how opening files for reading works (from strictest behavior to most flexible)

system/lib/fetch/emscripten_fetch.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ __emscripten_fetch_queue* _emscripten_get_fetch_queue() {
4040
}
4141
return queue;
4242
}
43+
44+
int32_t _emscripten_fetch_get_response_headers_length(int32_t fetchID);
45+
int32_t _emscripten_fetch_get_response_headers(int32_t fetchID, int32_t dst, int32_t dstSizeBytes);
4346
}
4447

4548
void emscripten_proxy_fetch(emscripten_fetch_t* fetch) {
@@ -100,6 +103,7 @@ emscripten_fetch_t* emscripten_fetch(emscripten_fetch_attr_t* fetch_attr, const
100103
fetch->__attributes.onerror = fetch_attr->onerror;
101104
fetch->__attributes.onsuccess = fetch_attr->onsuccess;
102105
fetch->__attributes.onprogress = fetch_attr->onprogress;
106+
fetch->__attributes.onreadystatechange = fetch_attr->onreadystatechange;
103107
#define STRDUP_OR_ABORT(s, str_to_dup) \
104108
if (str_to_dup) { \
105109
s = strdup(str_to_dup); \
@@ -239,6 +243,63 @@ EMSCRIPTEN_RESULT emscripten_fetch_close(emscripten_fetch_t* fetch) {
239243
return EMSCRIPTEN_RESULT_SUCCESS;
240244
}
241245

246+
size_t emscripten_fetch_get_response_headers_length(emscripten_fetch_t *fetch) {
247+
if (!fetch || fetch->readyState < 2) return 0;
248+
249+
return (size_t)_emscripten_fetch_get_response_headers_length((int32_t)fetch->id);
250+
}
251+
252+
size_t emscripten_fetch_get_response_headers(emscripten_fetch_t *fetch, char *dst, size_t dstSizeBytes) {
253+
if (!fetch || fetch->readyState < 2) return 0;
254+
255+
return (size_t)_emscripten_fetch_get_response_headers((int32_t)fetch->id, (int32_t)dst, (int32_t)dstSizeBytes);
256+
}
257+
258+
char **emscripten_fetch_unpack_response_headers(const char *headersString) {
259+
// Get size of output array and allocate.
260+
size_t numHeaders = 0;
261+
for(const char *pos = strchr(headersString, '\n'); pos; pos = strchr(pos + 1, '\n'))
262+
{
263+
numHeaders++;
264+
}
265+
char **unpackedHeaders = (char**)malloc(sizeof(char*) * ((numHeaders * 2) + 1));
266+
unpackedHeaders[numHeaders * 2] = NULL;
267+
268+
// Allocate each header.
269+
const char *rowStart = headersString;
270+
const char *rowEnd = strchr(rowStart, '\n');
271+
for(size_t headerNum = 0; rowEnd; headerNum += 2)
272+
{
273+
const char *split = strchr(rowStart, ':');
274+
size_t headerSize = (size_t)split - (size_t)rowStart;
275+
char* header = (char*)malloc(headerSize + 1);
276+
strncpy(header, rowStart, headerSize);
277+
header[headerSize] = '\0';
278+
279+
size_t valueSize = (size_t)rowEnd - (size_t)split;
280+
char* value = (char*)malloc(valueSize + 1);
281+
strncpy(value, split + 1, valueSize);
282+
value[valueSize] = '\0';
283+
284+
unpackedHeaders[headerNum] = header;
285+
unpackedHeaders[headerNum+1] = value;
286+
287+
rowStart = rowEnd + 1;
288+
rowEnd = strchr(rowStart, '\n');
289+
}
290+
291+
return unpackedHeaders;
292+
}
293+
294+
void emscripten_fetch_free_unpacked_response_headers(char **unpackedHeaders) {
295+
if(unpackedHeaders)
296+
{
297+
for(size_t i = 0; unpackedHeaders[i]; ++i)
298+
free((void*)unpackedHeaders[i]);
299+
free((void*)unpackedHeaders);
300+
}
301+
}
302+
242303
static void fetch_free(emscripten_fetch_t* fetch) {
243304
fetch->id = 0;
244305
free((void*)fetch->data);

tests/fetch/headers_received.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2016 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
#include <stdio.h>
7+
#include <string.h>
8+
#include <assert.h>
9+
#include <emscripten/fetch.h>
10+
11+
void readyStateChange(emscripten_fetch_t *fetch)
12+
{
13+
if(fetch->readyState != 2) return;
14+
15+
size_t headersLengthBytes = emscripten_fetch_get_response_headers_length(fetch) + 1;
16+
char *headerString = new char[headersLengthBytes];
17+
18+
assert(headerString);
19+
emscripten_fetch_get_response_headers(fetch, headerString, headersLengthBytes);
20+
printf("Got headers: %s\n", headerString);
21+
22+
char **responseHeaders = emscripten_fetch_unpack_response_headers(headerString);
23+
assert(responseHeaders);
24+
25+
delete[] headerString;
26+
27+
int numHeaders = 0;
28+
for(; responseHeaders[numHeaders * 2]; ++numHeaders)
29+
{
30+
// Check both the header and its value are present.
31+
assert(responseHeaders[(numHeaders * 2) + 1]);
32+
printf("Got response header: %s:%s\n", responseHeaders[numHeaders * 2], responseHeaders[(numHeaders * 2) + 1]);
33+
}
34+
35+
printf("Finished receiving %d headers from URL %s.\n", numHeaders, fetch->url);
36+
37+
emscripten_fetch_free_unpacked_response_headers(responseHeaders);
38+
39+
#ifdef REPORT_RESULT
40+
REPORT_RESULT(0);
41+
#endif
42+
}
43+
44+
void success(emscripten_fetch_t *fetch)
45+
{
46+
printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
47+
// The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
48+
emscripten_fetch_close(fetch); // Free data associated with the fetch.
49+
}
50+
51+
int main()
52+
{
53+
emscripten_fetch_attr_t attr;
54+
emscripten_fetch_attr_init(&attr);
55+
strcpy(attr.requestMethod, "GET");
56+
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
57+
attr.onsuccess = success;
58+
attr.onreadystatechange = readyStateChange;
59+
attr.timeoutMSecs = 2*60;
60+
emscripten_fetch(&attr, "myfile.dat");
61+
}

0 commit comments

Comments
 (0)