Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/site/source/docs/api_reference/fetch.rst
4154 views
.. _fetch-api:

=========
Fetch API
=========

The Emscripten Fetch API allows native code to transfer files via XHR (HTTP GET,
PUT, POST) from remote servers, and to persist the downloaded files locally in
browser's IndexedDB storage, so that they can be reaccessed locally on
subsequent page visits. The Fetch API is callable from multiple threads, and the
network requests can be run either synchronously or asynchronously as desired.

.. note::

  In order to use the Fetch API, you would need to compile your code with
  ``-sFETCH``.

Introduction
============

The use of the Fetch API is quick to illustrate via an example. The following
application downloads a file from a web server asynchronously to memory inside
the application heap.

.. code-block:: cpp

  #include <stdio.h>
  #include <string.h>
  #include <emscripten/fetch.h>

  void downloadSucceeded(emscripten_fetch_t *fetch) {
    printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
    // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
    emscripten_fetch_close(fetch); // Free data associated with the fetch.
  }

  void downloadFailed(emscripten_fetch_t *fetch) {
    printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
    emscripten_fetch_close(fetch); // Also free data on failure.
  }

  int main() {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "GET");
    attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
    attr.onsuccess = downloadSucceeded;
    attr.onerror = downloadFailed;
    emscripten_fetch(&attr, "myfile.dat");
  }

If a relative pathname is specified to a call to emscripten_fetch, like in the
above example, the XHR is performed relative to the href (URL) of the current
page. Passing a fully qualified absolute URL allows downloading files across
domains, however these are subject to `HTTP access control (CORS) rules
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS>`_.

By default the Fetch API runs asynchronously, which means that the
emscripten_fetch() function call returns immediately and the operation will
continue to occur on the background. When the operation finishes, either the
success or the failure callback will be invoked.

Persisting data
===============

The XHR requests issued by the Fetch API are subject to the usual browser
caching behavior. These caches are transient (temporary) so there is no
guarantee that the data will persist in the cache for a given period of time.
Additionally, if the files are somewhat large (multiple megabytes), browsers
typically don't cache the downloads at all.

To enable a more explicit control for persisting the downloaded files, the Fetch
API interacts with the browser's IndexedDB API, which can load and store large
data files that are available on subsequent visits to the page. To enable
IndexedDB storage, pass the EMSCRIPTEN_FETCH_PERSIST_FILE flag in the fetch
attributes:

.. code-block:: cpp

  int main() {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    ...
    attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_PERSIST_FILE;
    ...
    emscripten_fetch(&attr, "myfile.dat");
  }

For a full example, see the file
``test/fetch/test_fetch_persist.c``.

Persisting data bytes from memory
---------------------------------

Sometimes it is useful to persist a range of bytes from application memory to
IndexedDB (without having to perform any XHRs). This is possible with the
Emscripten Fetch API by passing the special HTTP action verb "EM_IDB_STORE" to
the Emscripten Fetch operation.

.. code-block:: cpp

  void success(emscripten_fetch_t *fetch) {
    printf("IDB store succeeded.\n");
    emscripten_fetch_close(fetch);
  }

  void failure(emscripten_fetch_t *fetch) {
    printf("IDB store failed.\n");
    emscripten_fetch_close(fetch);
  }

  void persistFileToIndexedDB(const char *outputFilename, uint8_t *data, size_t numBytes) {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "EM_IDB_STORE");
    attr.attributes = EMSCRIPTEN_FETCH_REPLACE | EMSCRIPTEN_FETCH_PERSIST_FILE;
    attr.requestData = (char *)data;
    attr.requestDataSize = numBytes;
    attr.onsuccess = success;
    attr.onerror = failure;
    emscripten_fetch(&attr, outputFilename);
  }

  int main() {
    // Create data
    uint8_t *data = (uint8_t*)malloc(10240);
    srand(time(NULL));
    for(int i = 0; i < 10240; ++i) data[i] = (uint8_t)rand();

    persistFileToIndexedDB("outputfile.dat", data, 10240);
  }

Deleting a file from IndexedDB
------------------------------

Files can be cleaned up from IndexedDB by using the HTTP action verb "EM_IDB_DELETE":

.. code-block:: cpp

  void success(emscripten_fetch_t *fetch) {
    printf("Deleting file from IDB succeeded.\n");
    emscripten_fetch_close(fetch);
  }

  void failure(emscripten_fetch_t *fetch) {
    printf("Deleting file from IDB failed.\n");
    emscripten_fetch_close(fetch);
  }

  int main() {
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "EM_IDB_DELETE");
    emscripten_fetch(&attr, "filename_to_delete.dat");
  }

Synchronous Fetches
===================

In some scenarios, it would be nice to be able to perform an XHR request or an
IndexedDB file operation synchronously in the calling thread. This can make
porting applications easier and simplify code flow by avoiding the need to pass
a callback.

All types of Emscripten Fetch API operations (XHRs, IndexedDB accesses) can be
performed synchronously by passing the EMSCRIPTEN_FETCH_SYNCHRONOUS flag. When
this flag is passed, the calling thread will block to sleep until the fetch
operation finishes. See the following example.

.. code-block:: cpp

  int main() {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "GET");
    attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS;
    emscripten_fetch_t *fetch = emscripten_fetch(&attr, "file.dat"); // Blocks here until the operation is complete.
    if (fetch->status == 200) {
      printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
      // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
    } else {
      printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
    }
    emscripten_fetch_close(fetch);
  }

In the above code sample, the success and failure callback functions are not
used. However, if specified, they will be synchronously called before
emscripten_fetch() returns.

.. note::

  Synchronous Emscripten Fetch operations are subject to a number of
  restrictions, depending on which Emscripten build mode (linker flags) is used:

  - **No flags**: Only asynchronous Fetch operations are available.
  - ``--proxy-to-worker``: Synchronous Fetch operations are allowed for fetches
    that only do an XHR but do not interact with IndexedDB.
  - ``-pthread``: Synchronous Fetch operations are available on
    pthreads, but not on the main thread.
  - ``--proxy-to-worker`` + ``-pthread``: Synchronous Synchronous Fetch operations
    are available both on the main thread and pthreads.

Tracking Progress
=================

For robust fetch management, there are several fields available to track the
status of an XHR.

The onprogress callback is called whenever new data has been received. This
allows one to measure the download speed and compute an ETA for completion.
Additionally, the emscripten_fetch_t structure passes the XHR object fields
readyState, status and statusText, which give information about the HTTP loading
state of the request.

The emscripten_fetch_attr_t object has a timeoutMSecs field which allows
specifying a timeout duration for the transfer. Additionally,
emscripten_fetch_close() can be called at any time for asynchronous and waitable
fetches to abort the download.
The following example illustrates these fields
and the onprogress handler.

.. code-block:: cpp

  void downloadProgress(emscripten_fetch_t *fetch) {
    if (fetch->totalBytes) {
      printf("Downloading %s.. %.2f%% complete.\n", fetch->url, fetch->dataOffset * 100.0 / fetch->totalBytes);
    } else {
      printf("Downloading %s.. %lld bytes complete.\n", fetch->url, fetch->dataOffset + fetch->numBytes);
    }
  }

  int main() {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "GET");
    attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
    attr.onsuccess = downloadSucceeded;
    attr.onprogress = downloadProgress;
    attr.onerror = downloadFailed;
    emscripten_fetch(&attr, "myfile.dat");
  }

Managing Large Files
====================

Particular attention should be paid to the memory usage strategy of a fetch.
Previous examples have all passed the EMSCRIPTEN_FETCH_LOAD_TO_MEMORY flag,
which causes emscripten_fetch() to populate the downloaded file in full in
memory in the onsuccess() callback. This is convenient when the whole file is to
be immediately accessed afterwards, but for large files, this can be a wasteful
strategy in terms of memory usage. If the file is very large, it might not even
fit inside the application's heap area.

The following subsections provide ways to manage large fetches in a memory
efficient manner.

Downloading directly to IndexedDB
---------------------------------

If an application wants to download a file for local access, but does not
immediately need to use the file, e.g. when preloading data up front for later
access, it is a good idea to avoid the EMSCRIPTEN_FETCH_LOAD_TO_MEMORY flag
altogether, and only pass the EMSCRIPTEN_FETCH_PERSIST_FILE flag instead. This
causes the fetch to download the file directly to IndexedDB, which avoids
temporarily populating the file in memory after the download finishes. In this
scenario, the onsuccess() handler will only report the total downloaded file
size, but will not contain the data bytes to the file.

Streaming Downloads
-------------------

Note: This currently only works in Firefox as it uses 'moz-chunked-arraybuffer'.

If the application does not need random seek access to the file, but is able to
process the file in a streaming manner, it can use the
EMSCRIPTEN_FETCH_STREAM_DATA flag to stream through the bytes in the file as
they are downloaded. If this flag is passed, the downloaded data chunks are
passed into the onprogress() callback in coherent file sequential order. See the
following snippet for an example.

.. code-block:: cpp

  void downloadProgress(emscripten_fetch_t *fetch) {
    printf("Downloading %s.. %.2f%%s complete. HTTP readyState: %d. HTTP status: %d.\n"
      "HTTP statusText: %s. Received chunk [%llu, %llu[\n",
      fetch->url, fetch->totalBytes > 0 ? (fetch->dataOffset + fetch->numBytes) * 100.0 / fetch->totalBytes : (fetch->dataOffset + fetch->numBytes),
      fetch->totalBytes > 0 ? "%" : " bytes",
      fetch->readyState, fetch->status, fetch->statusText,
      fetch->dataOffset, fetch->dataOffset + fetch->numBytes);

    // Process the partial data stream fetch->data[0] thru fetch->data[fetch->numBytes-1]
    // This buffer represents the file at offset fetch->dataOffset.
    for(size_t i = 0; i < fetch->numBytes; ++i)
      ; // Process fetch->data[i];
  }

  int main() {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "GET");
    attr.attributes = EMSCRIPTEN_FETCH_STREAM_DATA;
    attr.onsuccess = downloadSucceeded;
    attr.onprogress = downloadProgress;
    attr.onerror = downloadFailed;
    attr.timeoutMSecs = 2*60;
    emscripten_fetch(&attr, "myfile.dat");
  }

In this case, the onsuccess() handler will not receive the final file buffer at
all so memory usage will remain at a minimum.

Byte Range Downloads
--------------------

Large files can also be managed in smaller chunks by performing Byte Range
downloads on them. This initiates an XHR or IndexedDB transfer that only fetches
the desired subrange of the whole file. This is useful for example when a large
package file contains multiple smaller ones at certain seek offsets, which can
be dealt with separately.

.. code-block:: cpp

  #include <stdio.h>
  #include <string.h>
  #include <emscripten/fetch.h>

  void downloadSucceeded(emscripten_fetch_t *fetch) {
    printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
    // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
    emscripten_fetch_close(fetch); // Free data associated with the fetch.
  }

  void downloadFailed(emscripten_fetch_t *fetch) {
    printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
    emscripten_fetch_close(fetch); // Also free data on failure.
  }

  int main() {
    emscripten_fetch_attr_t attr;
    emscripten_fetch_attr_init(&attr);
    strcpy(attr.requestMethod, "GET");
    attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
    // Make a Range request to only fetch bytes 10 to 20
    const char* headers[] = {"Range", "bytes=10-20", NULL};
    attr.requestHeaders = headers;
    attr.onsuccess = downloadSucceeded;
    attr.onerror = downloadFailed;
    emscripten_fetch(&attr, "myfile.dat");
  }

Redirections
============

The XHR requests issued by the Fetch API are subject to the usual browser
redirection behavior (transparently followed except when infinitely looping).
When this happens, the `emscripten_fetch_t` structure will contain the final URL
after the redirection in the `responseUrl` field. Many HTTP status codes in the 3xx
range are used for redirection, and the Fetch API will follow these, in particular
the 301 (Moved Permanently), 302 (Found), 303 (See Other), 307 (Temporary Redirect),
and 308 (Permanent Redirect) status codes.

TODO To Document
================

Emscripten_fetch() supports the following operations as well, that need
documenting:

 - Emscripten_fetch can be used to upload files to remote servers via HTTP PUT
 - Emscripten_fetch_attr_t allows setting custom HTTP request headers (e.g. for
   cache control)
 - Document HTTP simple auth fields in Emscripten_fetch_attr_t.
 - Document overriddenMimeType attribute in Emscripten_fetch_attr_t.
 - Reference documentation of the individual fields in Emscripten_fetch_attr_t,
   Emscripten_fetch_t and #defines.
 - Example about loading only from IndexedDB without XHRing.
 - Example about overriding an existing file in IndexedDB with a new XHR.
 - Example how to preload a whole filesystem to IndexedDB for easy replacement
   of --preload-file.
 - Example how to persist content as gzipped to IndexedDB and decompress on
   load.
 - Example how to abort and resume partial transfers to IndexedDB.