Skip to content

Commit 545eaee

Browse files
abcthomaskripken
authored andcommitted
Fetch API headers implementation (emscripten-core#6514)
This pull request combines the work done in pull requests emscripten-core#5619 & emscripten-core#6207 to fix issue emscripten-core#5865. This may also fix emscripten-core#6359.
1 parent 140cc79 commit 545eaee

5 files changed

Lines changed: 168 additions & 10 deletions

File tree

AUTHORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,6 @@ a license to everyone to use it as detailed in LICENSE.)
348348
* Ryhor Spivak <grisha@rusteddreams.net>
349349
* Jan Schär <jscissr@gmail.com>
350350
* Ryhor Spivak <grisha@rusteddreams.net>
351+
* Alexander Bich <quyse0@gmail.com>
352+
* Ashleigh Thomas <ashleighbcthomas@gmail.com>
351353
* Veniamin Petrenko <bjpbjpbjp10@gmail.com>

system/include/emscripten/fetch.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ typedef struct emscripten_fetch_attr_t
9393

9494
// If non-zero, specifies a pointer to the data that is to be passed as the body (payload) of the request
9595
// that is being performed. Leave as zero if no request body needs to be sent.
96-
// The memory pointed to by this field is provided by the user, and needs to be valid only until the call to
97-
// emscripten_fetch() returns.
96+
// The memory pointed to by this field is provided by the user, and needs to be valid throughout the
97+
// duration of the fetch operation. If passing a non-zero pointer into this field, make sure to implement
98+
// *both* the onsuccess and onerror handlers to be notified when the fetch finishes to know when this memory
99+
// block can be freed. Do not pass a pointer to memory on the stack or other temporary area here.
98100
const char *requestData;
99101

100102
// Specifies the length of the buffer pointed by 'requestData'. Leave as 0 if no request body needs to be sent.

system/lib/fetch/emscripten_fetch.cpp

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ struct __emscripten_fetch_queue
1414
int queueSize;
1515
};
1616

17+
static void fetch_free( emscripten_fetch_t *fetch );
18+
1719
extern "C" {
1820
void emscripten_start_fetch(emscripten_fetch_t *fetch);
1921
__emscripten_fetch_queue *_emscripten_get_fetch_work_queue();
@@ -66,16 +68,67 @@ emscripten_fetch_t *emscripten_fetch(emscripten_fetch_attr_t *fetch_attr, const
6668
}
6769

6870
emscripten_fetch_t *fetch = (emscripten_fetch_t *)malloc(sizeof(emscripten_fetch_t));
71+
if (!fetch) return 0;
6972
memset(fetch, 0, sizeof(emscripten_fetch_t));
7073
fetch->id = globalFetchIdCounter++; // TODO: make this thread-safe!
7174
fetch->userData = fetch_attr->userData;
72-
fetch->url = strdup(url); // TODO: free
73-
fetch->__attributes = *fetch_attr;
74-
fetch->__attributes.destinationPath = fetch->__attributes.destinationPath ? strdup(fetch->__attributes.destinationPath) : 0; // TODO: free
75-
fetch->__attributes.userName = fetch->__attributes.userName ? strdup(fetch->__attributes.userName) : 0; // TODO: free
76-
fetch->__attributes.password = fetch->__attributes.password ? strdup(fetch->__attributes.password) : 0; // TODO: free
77-
fetch->__attributes.requestHeaders = 0;// TODO:strdup(fetch->__attributes.requestHeaders);
78-
fetch->__attributes.overriddenMimeType = fetch->__attributes.overriddenMimeType ? strdup(fetch->__attributes.overriddenMimeType) : 0; // TODO: free
75+
fetch->__attributes.timeoutMSecs = fetch_attr->timeoutMSecs;
76+
fetch->__attributes.attributes = fetch_attr->attributes;
77+
fetch->__attributes.withCredentials = fetch_attr->withCredentials;
78+
fetch->__attributes.requestData = fetch_attr->requestData;
79+
fetch->__attributes.requestDataSize = fetch_attr->requestDataSize;
80+
strcpy(fetch->__attributes.requestMethod, fetch_attr->requestMethod);
81+
fetch->__attributes.onerror = fetch_attr->onerror;
82+
fetch->__attributes.onsuccess = fetch_attr->onsuccess;
83+
fetch->__attributes.onprogress = fetch_attr->onprogress;
84+
#define STRDUP_OR_ABORT(s, str_to_dup) \
85+
if (str_to_dup) \
86+
{ \
87+
s = strdup(str_to_dup); \
88+
if (!s) \
89+
{ \
90+
fetch_free(fetch); \
91+
return 0; \
92+
} \
93+
}
94+
STRDUP_OR_ABORT(fetch->url, url);
95+
STRDUP_OR_ABORT(fetch->__attributes.destinationPath, fetch_attr->destinationPath);
96+
STRDUP_OR_ABORT(fetch->__attributes.userName, fetch_attr->userName);
97+
STRDUP_OR_ABORT(fetch->__attributes.password,fetch_attr->password);
98+
STRDUP_OR_ABORT(fetch->__attributes.overriddenMimeType, fetch_attr->overriddenMimeType);
99+
if (fetch_attr->requestHeaders)
100+
{
101+
size_t headersCount = 0;
102+
while (fetch_attr->requestHeaders[headersCount]) ++headersCount;
103+
const char** headers = (const char**)malloc((headersCount + 1) * sizeof(const char*));
104+
if (!headers)
105+
{
106+
fetch_free(fetch);
107+
return 0;
108+
}
109+
memset((void*)headers, 0, (headersCount + 1) * sizeof(const char*));
110+
111+
for (size_t i = 0; i < headersCount; ++i)
112+
{
113+
headers[i] = strdup(fetch_attr->requestHeaders[i]);
114+
if (!headers[i])
115+
116+
{
117+
for (size_t j = 0; j < i; ++j)
118+
{
119+
free((void*)headers[j]);
120+
}
121+
free((void*)headers);
122+
fetch_free(fetch);
123+
return 0;
124+
}
125+
}
126+
headers[headersCount] = 0;
127+
fetch->__attributes.requestHeaders = headers;
128+
}
129+
130+
#undef STRDUP_OR_ABORT
131+
79132

80133
#if __EMSCRIPTEN_PTHREADS__
81134
const bool waitable = (fetch_attr->attributes & EMSCRIPTEN_FETCH_WAITABLE) != 0;
@@ -144,8 +197,25 @@ EMSCRIPTEN_RESULT emscripten_fetch_close(emscripten_fetch_t *fetch)
144197
strcpy(fetch->statusText, "aborted with emscripten_fetch_close()");
145198
fetch->__attributes.onerror(fetch);
146199
}
200+
201+
fetch_free(fetch);
202+
return EMSCRIPTEN_RESULT_SUCCESS;
203+
}
204+
205+
static void fetch_free(emscripten_fetch_t *fetch)
206+
{
147207
fetch->id = 0;
148208
free((void*)fetch->data);
209+
free((void*)fetch->url);
210+
free((void*)fetch->__attributes.destinationPath);
211+
free((void*)fetch->__attributes.userName);
212+
free((void*)fetch->__attributes.password);
213+
if(fetch->__attributes.requestHeaders)
214+
{
215+
for(size_t i = 0; fetch->__attributes.requestHeaders[i]; ++i)
216+
free((void*)fetch->__attributes.requestHeaders[i]);
217+
free((void*)fetch->__attributes.requestHeaders);
218+
}
219+
free((void*)fetch->__attributes.overriddenMimeType);
149220
free(fetch);
150-
return EMSCRIPTEN_RESULT_SUCCESS;
151221
}

tests/fetch/response_headers.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#include <string.h>
2+
#include <stdio.h>
3+
#include <math.h>
4+
#include <assert.h>
5+
#include <emscripten/fetch.h>
6+
7+
int result = 0;
8+
9+
int main()
10+
{
11+
static const char* const headers[] = {
12+
"X-Emscripten-Test",
13+
"1",
14+
0,
15+
};
16+
const size_t n_values = sizeof( headers ) / sizeof( headers[ 0 ] ) - 1;
17+
emscripten_fetch_attr_t attr;
18+
emscripten_fetch_attr_init( &attr );
19+
strcpy( attr.requestMethod, "GET" );
20+
attr.attributes = EMSCRIPTEN_FETCH_REPLACE | EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS;
21+
attr.requestHeaders = headers;
22+
23+
attr.onsuccess = [] ( emscripten_fetch_t *fetch )
24+
{
25+
assert( fetch->__attributes.requestHeaders != 0 );
26+
assert( fetch->__attributes.requestHeaders != headers );
27+
for ( size_t i = 0; i < n_values; ++i )
28+
{
29+
const char* origHeader = headers[ i ];
30+
const char* header = fetch->__attributes.requestHeaders[ i ];
31+
assert( origHeader != header );
32+
assert( strcmp( origHeader, header ) == 0 );
33+
}
34+
assert( fetch->__attributes.requestHeaders[ n_values ] == 0 );
35+
36+
printf( "Finished downloading %llu bytes\n", fetch->numBytes );
37+
// Compute rudimentary checksum of data
38+
uint8_t checksum = 0;
39+
for ( int i = 0; i < fetch->numBytes; ++i )
40+
checksum ^= fetch->data[ i ];
41+
printf( "Data checksum: %02X\n", checksum );
42+
assert( checksum == 0x08 );
43+
emscripten_fetch_close( fetch );
44+
45+
if ( result == 0 ) result = 1;
46+
#ifdef REPORT_RESULT
47+
REPORT_RESULT( result );
48+
#endif
49+
};
50+
51+
attr.onprogress = [] ( emscripten_fetch_t *fetch )
52+
{
53+
if ( fetch->totalBytes > 0 )
54+
{
55+
printf( "Downloading.. %.2f%% complete.\n", ( fetch->dataOffset + fetch->numBytes ) * 100.0 / fetch->totalBytes );
56+
}
57+
else
58+
{
59+
printf( "Downloading.. %lld bytes complete.\n", fetch->dataOffset + fetch->numBytes );
60+
}
61+
};
62+
63+
attr.onerror = [] ( emscripten_fetch_t *fetch )
64+
{
65+
printf( "Download failed!\n" );
66+
emscripten_fetch_close(fetch);
67+
assert( false && "Shouldn't fail!" );
68+
};
69+
70+
emscripten_fetch_t *fetch = emscripten_fetch( &attr, "gears.png" );
71+
if ( result == 0 )
72+
{
73+
result = 2;
74+
printf( "emscripten_fetch() failed to run synchronously!\n" );
75+
}
76+
#ifdef REPORT_RESULT
77+
REPORT_RESULT( result );
78+
#endif
79+
}

tests/test_browser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3735,6 +3735,11 @@ def test_fetch_to_memory(self):
37353735
def test_fetch_cached_xhr(self):
37363736
shutil.copyfile(path_from_root('tests', 'gears.png'), os.path.join(self.get_dir(), 'gears.png'))
37373737
self.btest('fetch/cached_xhr.cpp', expected='1', args=['--std=c++11', '-s', 'FETCH_DEBUG=1', '-s', 'FETCH=1', '-s', 'WASM=0'])
3738+
3739+
# Tests that response headers get set on emscripten_fetch_t values.
3740+
def test_fetch_response_headers(self):
3741+
shutil.copyfile(path_from_root('tests', 'gears.png'), os.path.join(self.get_dir(), 'gears.png'))
3742+
self.btest('fetch/response_headers.cpp', expected='1', args=['--std=c++11', '-s', 'FETCH_DEBUG=1', '-s', 'FETCH=1', '-s', 'USE_PTHREADS=1', '-s', 'PROXY_TO_PTHREAD=1', '-s', 'WASM=0'])
37383743

37393744
# Test emscripten_fetch() usage to stream a XHR in to memory without storing the full file in memory
37403745
def test_fetch_stream_file(self):

0 commit comments

Comments
 (0)