gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-quickjs-tart] branch master updated: native http lib interface


From: gnunet
Subject: [taler-quickjs-tart] branch master updated: native http lib interface
Date: Mon, 26 Feb 2024 21:07:16 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository quickjs-tart.

The following commit(s) were added to refs/heads/master by this push:
     new 47fc985  native http lib interface
47fc985 is described below

commit 47fc98540143afee8f77c6298fdeb3c0b0d7b6ec
Author: Florian Dold <florian@dold.me>
AuthorDate: Fri Feb 23 15:54:29 2024 +0100

    native http lib interface
---
 meson.build                              |   2 +-
 qtart.c                                  |  13 +-
 quickjs/quickjs-http.c                   | 497 ++++++++++++++++++++
 quickjs/quickjs-http.h                   | 213 +++++++++
 quickjs/quickjs-libc.c                   | 753 +++++++++++++++++--------------
 quickjs/quickjs-libc.h                   |   8 +
 quickjs/quickjs.c                        |  11 +
 quickjs/quickjs.h                        |   1 +
 taler_wallet_core_lib.c                  |  17 +-
 taler_wallet_core_lib.h                  |  13 +
 taler_wallet_core_lib_preprocessed.h     | 381 ++++++++++++++++
 xcode/FTalerWalletcore-Bridging-Header.h |   2 +-
 12 files changed, 1578 insertions(+), 333 deletions(-)

diff --git a/meson.build b/meson.build
index e784384..43bd182 100644
--- a/meson.build
+++ b/meson.build
@@ -59,7 +59,7 @@ libunicode = static_library('unicode', 'quickjs/libunicode.c')
 # general utilities
 cutils = static_library('cutils', 'quickjs/cutils.c')
 # standard library for quickjs (std and os modules)
-quickjs_libc = static_library('quickjs-libc', 'quickjs/quickjs-libc.c', 
dependencies : curl_dep )
+quickjs_libc = static_library('quickjs-libc', ['quickjs/quickjs-libc.c', 
'quickjs/quickjs-http.c'], dependencies : curl_dep )
 # base JS interpreter
 quickjs = static_library('quickjs', 'quickjs/quickjs.c')
 sqlite3 = static_library('sqlite3', 'sqlite3/sqlite3.c')
diff --git a/qtart.c b/qtart.c
index cabde5a..0d2ab65 100644
--- a/qtart.c
+++ b/qtart.c
@@ -40,6 +40,7 @@
 #endif
 
 #include "quickjs/cutils.h"
+#include "quickjs/quickjs-http.h"
 
 #include "tart_module.h"
 
@@ -326,6 +327,7 @@ int main(int argc, char **argv)
 {
     JSRuntime *rt;
     JSContext *ctx;
+    struct JSHttpClientImplementation *http_impl = NULL;
     struct trace_malloc_data trace_data = { NULL };
     int optind;
     char *expr = NULL;
@@ -459,11 +461,17 @@ int main(int argc, char **argv)
     js_std_set_worker_new_context_func(JS_NewCustomContext);
     js_std_init_handlers(rt);
     ctx = JS_NewCustomContext(rt);
-    js_os_set_host_message_handler(ctx, handle_host_message, NULL);
     if (!ctx) {
         fprintf(stderr, "qjs: cannot allocate JS context\n");
         exit(2);
     }
+    js_os_set_host_message_handler(ctx, handle_host_message, NULL);
+    http_impl = js_curl_http_client_create();
+    if (!http_impl) {
+        fprintf(stderr, "qjs: cannot create HTTP client implementation\n");
+        exit(2);
+    }
+    js_os_set_http_impl(rt, http_impl);
 
     /* loader for ES6 modules */
     JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
@@ -518,6 +526,8 @@ int main(int argc, char **argv)
         }
         js_std_loop(ctx);
     }
+
+    fprintf(stderr, "done with main loop\n");
     
     if (dump_memory) {
         JSMemoryUsage stats;
@@ -525,6 +535,7 @@ int main(int argc, char **argv)
         JS_DumpMemoryUsage(stdout, &stats, rt);
     }
     js_std_free_handlers(rt);
+    js_curl_http_client_destroy(http_impl);
     JS_FreeContext(ctx);
     JS_FreeRuntime(rt);
 
diff --git a/quickjs/quickjs-http.c b/quickjs/quickjs-http.c
new file mode 100644
index 0000000..9fb1fa2
--- /dev/null
+++ b/quickjs/quickjs-http.c
@@ -0,0 +1,497 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2024 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <curl/curl.h>
+#include <arpa/inet.h>
+#include <strings.h>
+#include <string.h>
+#include <assert.h>
+
+#include "cutils.h"
+#include "quickjs-http.h"
+#include "list.h"
+
+struct CurlClientState {
+    pthread_t thread;
+    pthread_mutex_t mutex;
+    BOOL started;
+    BOOL stopped;
+    CURLSH *curlsh;
+    CURLM *curlm;
+    int last_request_id;
+    struct list_head request_list; /* list of CurlRequestState.link */
+};
+
+struct CurlRequestState {
+    struct CurlClientState *ccs;
+    struct list_head link;
+    DynBuf response_data;
+    BOOL cancelled;
+    CURL *curl;
+    int request_id;
+    enum JSHttpRedirectFlag redirect;
+    JSHttpResponseCb response_cb;
+    void *response_cb_cls;
+    // Request headers
+    struct curl_slist *req_headers;
+    struct curl_slist *resp_headers;
+    char *errbuf;
+};
+
+// Must only be called with locked client mutex
+static void destroy_curl_request_state(struct CurlRequestState *crs)
+{
+    struct CurlClientState *ccs;
+
+    if (!crs) {
+        return;
+    }
+    ccs = crs->ccs;
+    crs->ccs = NULL;
+
+    list_del(&crs->link);
+    curl_slist_free_all(crs->req_headers);
+    curl_slist_free_all(crs->resp_headers);
+    dbuf_free(&crs->response_data);
+    if (crs->curl) {
+        curl_easy_cleanup(crs->curl);
+        crs->curl = NULL;
+    }
+    free(crs->errbuf);
+    free(crs);
+}
+
+static void *
+handle_done(CURL *curl, CURLcode res)
+{
+    struct CurlRequestState *crs = NULL;
+    struct CurlClientState *ccs = NULL;
+    struct JSHttpResponseInfo hri = { 0 };
+    long resp_code;
+    char **headers = NULL;
+    BOOL cancelled;
+
+    curl_easy_getinfo(curl, CURLINFO_PRIVATE, &crs);
+    ccs = crs->ccs;
+
+    hri.request_id = crs->request_id;
+
+    if (CURLE_OK == res) {
+        int num_headers = 0;
+        int i;
+        char **headers;
+        struct curl_slist *sl = crs->resp_headers;
+        char *url = NULL;
+
+        curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &url);
+
+        if (crs->redirect == JS_HTTP_REDIRECT_ERROR && NULL != url) {
+            hri.status = 0;
+            hri.errmsg = crs->errbuf;
+            strncpy(crs->errbuf, "Got redirect status, but redirects are not 
allowed for this request", CURL_ERROR_SIZE);
+            goto done;
+        }
+
+        while (sl != NULL) {
+            if (NULL != strchr(sl->data, ':')) {
+                num_headers++;
+            }
+            sl = sl->next;
+        }
+
+        headers = malloc((num_headers + 1) * sizeof(char *));
+        if (!headers) {
+            hri.status = 0;
+            goto done;
+        }
+        memset(headers, 0, (num_headers + 1) * sizeof (char *));
+        sl = crs->resp_headers;
+        i = 0;
+        while (sl != NULL) {
+            if (NULL != strchr(sl->data, ':')) {
+                headers[i] = sl->data;
+                i++;
+            }
+          sl = sl->next;
+        }
+        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp_code);
+        hri.status = resp_code;
+        hri.body = crs->response_data.buf;
+        hri.body_len = crs->response_data.size;
+        hri.response_headers = headers;
+    } else {
+        hri.status = 0;
+        hri.errmsg = crs->errbuf;
+    }
+
+done:
+
+    pthread_mutex_lock(&ccs->mutex);
+    cancelled = crs->cancelled;
+    pthread_mutex_unlock(&ccs->mutex);
+
+    if (cancelled == FALSE) {
+      // FIXME: What if this CB somehow destroys the client?
+      crs->response_cb(crs->response_cb_cls, &hri);
+    }
+
+    if (NULL != headers) {
+        for (char **h=headers; *h; h++) {
+            free(*h);
+        }
+        free(headers);
+    }
+    pthread_mutex_lock(&ccs->mutex);
+    destroy_curl_request_state(crs);
+    pthread_mutex_unlock(&ccs->mutex);
+    return NULL;
+}
+
+static size_t curl_header_callback(char *buffer, size_t size,
+                              size_t nitems, void *userdata)
+{
+    struct CurlRequestState *crs = userdata;
+    size_t sz = size * nitems;
+    char *hval;
+
+    hval = strndup(buffer, sz);
+    if (!hval) {
+        return 0;
+    }
+    crs->resp_headers = curl_slist_append(crs->resp_headers, hval);
+    free(hval);
+    return sz;
+}
+
+
+static size_t curl_write_cb(void *data, size_t size, size_t nmemb, void *userp)
+{
+    size_t realsize = size * nmemb;
+    struct CurlRequestState *rctx = userp;
+
+    if (0 != dbuf_put(&rctx->response_data, data, realsize)) {
+        return 0;
+    }
+
+    return realsize;
+}
+
+
+static int
+create_impl(void *cls, struct JSHttpRequestInfo *req_info)
+{
+    struct CurlClientState *ccs = cls;
+    struct CurlRequestState *crs;
+    pthread_t thread;
+    int res;
+    CURL *curl;
+    BOOL debug = req_info->debug > 0;
+    const char *method = req_info->method;
+
+    crs = malloc(sizeof *crs);
+    if (!crs) {
+      return -1;
+    }
+    memset(crs, 0, sizeof *crs);
+    crs->request_id = ++ccs->last_request_id;
+    crs->ccs = ccs;
+    crs->response_cb = req_info->response_cb;
+    crs->response_cb_cls = req_info->response_cb_cls;
+    crs->errbuf = malloc(CURL_ERROR_SIZE);
+    if (!crs->errbuf) {
+        goto error;
+    }
+    memset(crs->errbuf, 0, CURL_ERROR_SIZE);
+    dbuf_init(&crs->response_data);
+
+    curl = curl_easy_init();
+    crs->curl = curl;
+    curl_easy_setopt(curl, CURLOPT_PRIVATE, crs);
+    curl_easy_setopt(curl, CURLOPT_SHARE, ccs->curlsh);
+    curl_easy_setopt(curl, CURLOPT_URL, req_info->url);
+    curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, "9.9.9.9");
+    curl_easy_setopt(curl, CURLOPT_USERAGENT, "qtart");
+    curl_easy_setopt(curl, CURLOPT_CAINFO, 
"/etc/ssl/certs/ca-certificates.crt");
+    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
+    curl_easy_setopt(curl, CURLOPT_HEADERDATA, crs);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, crs);
+
+    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, crs->errbuf);
+
+    // FIXME: This is only a temporary hack until we have proper TLS CA support
+    // on all platforms
+    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+
+    if (req_info->timeout_ms < 0) {
+        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 0L);
+    } else if (0 == req_info->timeout_ms) {
+        // Default timeout of 5 minutes.
+        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5L * 60000L);
+    } else {
+        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long) 
req_info->timeout_ms);
+    }
+
+    if (debug == TRUE) {
+        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+    }
+
+    crs->redirect = req_info->redirect;
+
+    switch (req_info->redirect) {
+      case JS_HTTP_REDIRECT_TRANSPARENT:
+        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+        break;
+      case JS_HTTP_REDIRECT_MANUAL:
+        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
+        break;
+      case JS_HTTP_REDIRECT_ERROR:
+        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
+        break;
+      default:
+        assert(0);
+    }
+
+    if (0 == strcasecmp(req_info->method, "get")) {
+        curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+    } else if (0 == strcasecmp(method, "delete")) {
+        curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+    } else if (0 == strcasecmp(method, "head")) {
+        curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
+    } else if ((0 == strcasecmp(method, "post")) ||
+               (0 == strcasecmp(method, "put"))) {
+        curl_easy_setopt(curl, CURLOPT_POST, 1L);
+        if (0 == strcasecmp(method, "put")) {
+            curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
+        }
+        if (req_info->req_body_len > 0) {
+            curl_off_t len = req_info->req_body_len;
+            curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, len);
+            curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req_info->req_body);
+        }
+    } else {
+        goto error;
+    }
+
+    if (req_info->request_headers != NULL) {
+      char **h = req_info->request_headers;
+      while (*h) {
+        crs->req_headers = curl_slist_append(crs->req_headers, *h);
+        h++;
+      }
+    }
+    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, crs->req_headers);
+
+    pthread_mutex_lock(&ccs->mutex);
+    list_add_tail(&crs->link, &ccs->request_list);
+    pthread_mutex_unlock(&ccs->mutex);
+
+    curl_multi_add_handle(ccs->curlm, curl);
+    curl_multi_wakeup(ccs->curlm);
+
+    return crs->request_id;
+error:
+    if (crs) {
+      dbuf_free(&crs->response_data);
+      if (crs->errbuf) {
+        free(crs->errbuf);
+      }
+      if (crs->curl) {
+        curl_easy_cleanup(crs->curl);
+      }
+      free(crs);
+    }
+    return -1;
+}
+
+static int
+destroy_impl(void *cls, int request_id)
+{
+    struct list_head *el;
+    struct CurlClientState *ccs = cls;  
+
+    pthread_mutex_lock(&ccs->mutex);
+
+    list_for_each(el, &ccs->request_list) {
+        struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, 
link);
+        if (crs->request_id == request_id) {
+          crs->cancelled = TRUE;
+        }
+    }
+
+    pthread_mutex_unlock(&ccs->mutex);
+
+    return 0;
+}
+
+/**
+ * Entry point for the thread that processes HTTP requests with libcurl.
+ */
+static void *
+curl_multi_thread_run(void *cls)
+{
+    struct CurlClientState *ccs = cls;
+    int still_running;
+    struct CURLMsg *m;
+    BOOL stopped;
+
+    while (1) {
+        CURLMcode mc;
+
+        mc = curl_multi_perform(ccs->curlm, &still_running);
+
+        if (CURLM_OK != mc) {
+            fprintf(stderr, "curl_multi_perform failed\n");
+            break;
+        }
+
+        mc = curl_multi_poll(ccs->curlm, NULL, 0, 1000, NULL);
+        if (CURLM_OK != mc) {
+            fprintf(stderr, "curl_multi_poll failed\n");
+            break;
+        }
+
+        pthread_mutex_lock(&ccs->mutex);
+        stopped = ccs->stopped;
+        pthread_mutex_unlock(&ccs->mutex);
+
+        if (stopped) {
+            break;
+        }
+
+        do {
+            int msgq = 0;
+            m = curl_multi_info_read(ccs->curlm, &msgq);
+            if (m && (m->msg == CURLMSG_DONE)) {
+                CURL *e = m->easy_handle;
+                curl_multi_remove_handle(ccs->curlm, e);
+                handle_done(e, m->data.result);
+            }
+        } while(m);
+    }
+    if (CURLM_OK != curl_multi_cleanup(ccs->curlm)) {
+        fprintf(stderr, "warning: curl_multi_cleanup failed\n");
+    }
+    if (CURLSHE_OK != curl_share_cleanup(ccs->curlsh)) {
+        fprintf(stderr, "warning: curl_share_cleanup failed\n");
+    }
+}
+
+struct JSHttpClientImplementation *
+js_curl_http_client_create()
+{
+    struct JSHttpClientImplementation *impl = NULL;
+    struct CurlClientState *ccs = NULL;
+    int res;
+
+    ccs = malloc(sizeof *ccs);
+    if (!ccs) {
+        goto error;
+    }
+
+    pthread_mutex_init(&ccs->mutex, NULL);
+    ccs->started = FALSE;
+    ccs->stopped = FALSE;
+    ccs->last_request_id = 0;
+    ccs->curlsh = curl_share_init();
+    if (!ccs->curlsh) {
+      goto error;
+    }
+    ccs->curlm = curl_multi_init();
+    if (!ccs->curlm) {
+      goto error;
+    }
+    init_list_head(&ccs->request_list);
+
+    curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
+    curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, 
CURL_LOCK_DATA_SSL_SESSION);
+    curl_share_setopt(ccs->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
+
+    impl = malloc(sizeof *impl);
+    if (!impl) {
+        goto error;
+    }
+    impl->req_create = &create_impl;
+    impl->req_cancel = &destroy_impl;
+    impl->cls = ccs;
+
+    res = pthread_create(&ccs->thread, NULL, &curl_multi_thread_run, ccs);
+    ccs->started = TRUE;
+
+    if (0 != res) {
+        goto error;
+    }
+
+    return impl;
+error:
+    if (ccs) {
+      curl_share_cleanup(ccs->curlsh);
+      curl_multi_cleanup(ccs->curlm);
+      free(ccs);
+    }
+    if (impl) {
+      free(impl);
+    }
+    return NULL;
+}
+
+static void
+destroy_client_state(struct CurlClientState *ccs)
+{
+    struct list_head *el, *el1;
+    if (!ccs) {
+        return;
+    }
+    if (ccs->started == TRUE) {
+        void *retval;
+        int res;
+
+        pthread_mutex_lock(&ccs->mutex);
+        ccs->stopped = TRUE;
+        pthread_mutex_unlock(&ccs->mutex);
+        curl_multi_wakeup(ccs->curlm);
+        res = pthread_join(ccs->thread, &retval);
+        if (0 != res) {
+            fprintf(stderr, "warning: could not join with curl thread\n");
+        }
+        ccs->started = FALSE;
+    }
+    pthread_mutex_lock(&ccs->mutex);
+    list_for_each_safe(el, el1, &ccs->request_list) {
+        struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, 
link);
+        destroy_curl_request_state(crs);
+    }
+    pthread_mutex_unlock(&ccs->mutex);
+    pthread_mutex_destroy(&ccs->mutex);
+    free(ccs);
+}
+
+void
+js_curl_http_client_destroy(struct JSHttpClientImplementation *impl)
+{
+    if (!impl) {
+        return;
+    }
+    destroy_client_state(impl->cls);
+    impl->cls = NULL;
+    free(impl);
+}
+
diff --git a/quickjs/quickjs-http.h b/quickjs/quickjs-http.h
new file mode 100644
index 0000000..437731c
--- /dev/null
+++ b/quickjs/quickjs-http.h
@@ -0,0 +1,213 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2024 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+// ## Native HTTP client library support.
+
+// Considerations:
+// - the API is designed for the HTTP client implementation
+//   to run in its own thread and *not* be integrated with the
+//   application's main event loop.
+// - focus on small API
+// - not a generic HTTP client, only supposed to serve the needs
+//   of a JS runtime
+// - only very tiny subset of HTTP supported
+// - no request/response streaming
+// - should be appropriate to implement a JS HTTP fetch function
+//   in the style of WHATWG fetch
+// - no focus on ABI compatibility whatsoever
+
+
+#ifndef _QUICKJS_HTTP_H
+#define _QUICKJS_HTTP_H
+
+#include <stdint.h>
+#include <limits.h>
+#include <stddef.h>
+
+// Forward declaration;
+struct JSHttpResponseInfo;
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+enum JSHttpRedirectFlag {
+  /**
+   * Handle redirects transparently.
+   */
+  JS_HTTP_REDIRECT_TRANSPARENT = 0,
+  /**
+   * Redirect status codes are returned to the client.
+   * The client can choose to follow them manually (or not).
+   */
+  JS_HTTP_REDIRECT_MANUAL = 1,
+  /**
+   * All redirect status codes result in an error.
+   */
+  JS_HTTP_REDIRECT_ERROR = 2,
+};
+
+/**
+ * Info needed to start a new HTTP request.
+ */
+struct JSHttpRequestInfo {
+  /**
+   * Callback called with the response for the request.
+   */
+  JSHttpResponseCb response_cb;
+
+  /**
+   * Closure for response_cb.
+   */
+  void *response_cb_cls;
+
+  /**
+   * Request URL.
+   */
+  const char *url;
+
+  /**
+   * Request method.
+   */
+  const char *method;
+
+  int num_request_headers;
+
+  /**
+   * Array of `num_request_headers` request headers.
+   */
+  char **request_headers;
+
+  /**
+   * 0: Handle redirects transparently.
+   * 1: Handle redirects manually.
+   * 2: Redirects result in an error.
+   */
+  enum JSHttpRedirectFlag redirect;
+
+  /**
+   * Request timeout in milliseconds.
+   *
+   * When 0 is specified, the timeout is the default request
+   * timeout for the platform.
+   *
+   * When -1 is specified, there is no timeout.  This might not be
+   * supported on all platforms.
+   */
+  int timeout_ms;
+
+  /**
+   * Enable debug output for this request.
+   */
+  int debug;
+
+  /**
+   * Request body or NULL.
+   */
+  void *req_body;
+
+  /**
+   * Length or request body or 0.
+   */
+  size_t req_body_len;
+};
+
+/**
+ * Contents of an HTTP response.
+ */
+struct JSHttpResponseInfo {
+
+  /**
+   * Request that this is a response to.
+   *
+   * (Think of the request ID like a file descriptor number).
+   */
+  int request_id;
+
+  /**
+   * HTTP response status code or 0 on error.
+   */
+  int status;
+
+  /**
+   * When status is 0, error message.
+   */
+  char *errmsg;
+
+  /**
+   * Array of `num_response_headers` response headers.
+   */
+  char **response_headers;
+
+  /**
+   * Number of response headers.
+   */
+  int num_response_headers;
+
+  /**
+   * Response body or NULL.
+   */
+  void *body;
+
+  /**
+   * Length of the response body or 0.
+   */
+  size_t body_len;
+};
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+/**
+ * Function to create a new HTTP fetch request.
+ * The request can still be configured until it is started.
+ * An identifier for the request will be written to @a handle.
+ *
+ * @return negative number on error, positive request_id on success
+ */
+typedef int (*JSHttpReqCreateFn)(void *cls, struct JSHttpRequestInfo 
*req_info);
+
+/**
+ * Cancel a request. The request_id will become invalid
+ * and the callback won't be called with request_id.
+ */
+typedef int (*JSHttpReqCancelFn)(void *cls, int request_id);
+
+struct JSHttpClientImplementation {
+  /**
+   * Opaque closure passed to client functions.
+   */
+  void *cls;
+  JSHttpReqCreateFn req_create;
+  JSHttpReqCancelFn req_cancel;
+};
+
+
+struct JSHttpClientImplementation *
+js_curl_http_client_create(void);
+
+void
+js_curl_http_client_destroy(struct JSHttpClientImplementation *impl);
+
+#endif /* _QUICKJS_HTTP_H */
diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c
index 75114a6..acecd8b 100644
--- a/quickjs/quickjs-libc.c
+++ b/quickjs/quickjs-libc.c
@@ -79,10 +79,11 @@ typedef sig_t sighandler_t;
 */
 
 #ifndef NO_HTTP
-#include <curl/curl.h>
 #include <arpa/inet.h>
+#include "quickjs-http.h"
 #endif
 
+
 typedef struct {
     struct list_head link;
     int fd;
@@ -133,6 +134,23 @@ typedef struct {
     int write_fd;
 } JSHostMessagePipe;
 
+typedef struct {
+    struct list_head link;
+    int request_id;
+    int status;
+    char *errmsg;
+    char **response_headers;
+    void *body;
+    size_t body_len;
+} JSHttpMessage;
+
+typedef struct {
+    pthread_mutex_t mutex;
+    struct list_head msg_queue; /* list of JSHttpMessage.link */
+    int read_fd;
+    int write_fd;
+} JSHttpMessagePipe;
+
 typedef struct {
     struct list_head link;
     JSWorkerMessagePipe *recv_pipe;
@@ -151,6 +169,9 @@ typedef struct JSThreadState {
     // send/receive message to/from the host in the main thread
     JSHostMessagePipe *host_pipe;
 
+    // receive messages from the HTTP client thread
+    JSHttpMessagePipe *http_pipe;
+
     JSValue on_host_message_func;
 
     JSHostMessageHandlerFn host_message_handler_f;
@@ -158,11 +179,12 @@ typedef struct JSThreadState {
 
     int is_worker_thread;
 
+    struct list_head http_requests;
+
 #ifndef NO_HTTP
-    CURLM *curlm;
-    CURLSH *curlsh;
-    struct list_head curl_requests;
+    struct JSHttpClientImplementation *http_client_impl;
 #endif
+
 } JSThreadState;
 
 static uint64_t os_pending_signals;
@@ -2106,233 +2128,246 @@ static void js_os_timer_mark(JSRuntime *rt, 
JSValueConst val,
 typedef struct {
     // linked list of all requests
     struct list_head link;
-    DynBuf response_data;
-    // curl request headers, must be kept around during the request
-    struct curl_slist *headers;
-    // Response headers
-    JSValue headers_list;
-    JSValue resolve_func;
-    JSValue reject_func;
-    JSContext* ctx;
-    uint8_t *readbuf;
-    size_t readpos;
-    size_t readlen;
-    CURL *curl;
-    BOOL client_has_accept_header;
-    BOOL client_has_content_type_header;
-} CurlRequestContext;
-
-static size_t curl_header_callback(char *buffer, size_t size,
-                              size_t nitems, void *userdata)
-{
-  CurlRequestContext *rctx = userdata;
-  size_t sz = size * nitems;
-
-  qjs_array_append_new(rctx->ctx, rctx->headers_list, 
JS_NewStringLen(rctx->ctx, buffer, sz));
 
-  return sz;
-}
+    int request_id;
 
-static size_t curl_write_cb(void *data, size_t size, size_t nmemb, void *userp)
-{
-  size_t realsize = size * nmemb;
-  CurlRequestContext *rctx = userp;
+    JSValue resolve_func;
+    JSValue reject_func;
 
-  if (0 != dbuf_put(&rctx->response_data, data, realsize)) {
-    return 0;
-  }
+    JSContext* ctx;
+} HttpRequestContext;
 
-  return realsize;
-}
 
-size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+void js_os_set_http_impl(JSRuntime *rt, struct JSHttpClientImplementation 
*impl)
 {
-  CurlRequestContext *rctx = userdata;
-  ssize_t src_available = rctx->readlen - rctx->readpos;
-  size_t dst_available = size * nmemb;
-  size_t n;
+    JSThreadState *ts = JS_GetRuntimeOpaque(rt);
 
-  if (!rctx->readbuf) {
-    return 0;
-  }
-  if (src_available <= 0) {
-    return 0;
-  }
-  n = src_available;
-  if (dst_available < src_available) {
-    n = dst_available;
-  }
-  memcpy(ptr, rctx->readbuf + rctx->readpos, n);
-  rctx->readpos += n;
-  return n;
+    ts->http_client_impl = impl;
 }
 
 int expect_property_str_bool(JSContext *ctx, JSValueConst this_val, const char 
*prop_name)
 {
-  JSValue prop_val;
-  BOOL bool_val;
-
-  prop_val = JS_GetPropertyStr(ctx, this_val, prop_name);
-  if (JS_IsException(prop_val)) {
-    return -1;
-  }
-  bool_val = JS_ToBool(ctx, prop_val);
-  JS_FreeValue(ctx, prop_val);
-  return bool_val;
-}
+    JSValue prop_val;
+    BOOL bool_val;
 
-BOOL starts_with_ignorecase(const char *str, const char *prefix)
-{
-  for (int i = 0; i < strlen(prefix); i++) {
-    if (!str[i]) {
-      return FALSE;
-    }
-    if (tolower(str[i]) != tolower(prefix[i])) {
-      return FALSE;
+    prop_val = JS_GetPropertyStr(ctx, this_val, prop_name);
+    if (JS_IsException(prop_val)) {
+        return -1;
     }
-  }
-  return TRUE;
+    bool_val = JS_ToBool(ctx, prop_val);
+    JS_FreeValue(ctx, prop_val);
+    return bool_val;
 }
 
-static int gather_headers(JSContext *ctx, JSValueConst js_headers, 
CurlRequestContext *req_context)
-{
-  JSValue length_prop;
-  uint32_t length;
-
-  length_prop = JS_GetPropertyStr(ctx, js_headers, "length");
-  if (JS_IsException(length_prop)) {
-    return -1;
-  }
-  if (0 != JS_ToUint32(ctx, &length, length_prop)) {
-    return -1;
-  }
-  JS_FreeValue(ctx, length_prop);
-
-  for (uint32_t i = 0; i < length; i++) {
-    JSValue item = JS_GetPropertyUint32(ctx, js_headers, i);
-    if (JS_IsException(item)) {
-      goto exception;
-    }
-    const char *cstr = JS_ToCString(ctx, item);
-    if (starts_with_ignorecase(cstr, "accept:")) {
-      req_context->client_has_accept_header = TRUE;
-    } else if (starts_with_ignorecase(cstr, "content-type:")) {
-      req_context->client_has_content_type_header = TRUE;
-    }
-    if (!cstr) {
-      JS_FreeValue(ctx, item);
-      goto exception;
-    }
-    req_context->headers = curl_slist_append (req_context->headers, cstr);
-    JS_FreeCString(ctx, cstr);
-    JS_FreeValue(ctx, item);
-  }
-  return 0;
-exception:
-  return -1;
-}
-
-static void free_fetch_request_context(CurlRequestContext *req_context)
+static void free_http_request_context(HttpRequestContext *req_context)
 {
     JSContext *ctx;
+    JSThreadState *ts;
 
     if (!req_context) {
         return;
     }
     ctx = req_context->ctx;
+    ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
+    ts->http_client_impl->req_cancel(ts->http_client_impl->cls, 
req_context->request_id);
     req_context->ctx = NULL;
-    if (req_context->curl) {
-        curl_easy_cleanup(req_context->curl);
-        req_context->curl = NULL;
-    }
-    if (NULL != req_context->readbuf) {
-        free(req_context->readbuf);
-    }
-    dbuf_free(&req_context->response_data);
-    JS_FreeValue(ctx, req_context->headers_list);
     JS_FreeValue(ctx, req_context->resolve_func);
     JS_FreeValue(ctx, req_context->reject_func);
-    curl_slist_free_all(req_context->headers);
     if (NULL != req_context->link.prev) {
-      list_del(&req_context->link);
+        list_del(&req_context->link);
     }
     js_free(ctx, req_context);
 }
 
-static void
-finish_fetch_http(CurlRequestContext *req_context, CURLcode result)
+static void js_free_http_message(JSHttpMessage *msg)
 {
+    if (msg->body) {
+        free(msg->body);
+        msg->body = NULL;
+    }
+    if (msg->errmsg) {
+        free(msg->errmsg);
+        msg->errmsg = NULL;
+    }
+    if (msg->response_headers) {
+        char **h;
+        for (h = msg->response_headers; *h; h++) {
+            free(*h);
+        }
+        free(msg->response_headers);
+        msg->response_headers = NULL;
+    }
+    free(msg);
+}
+
+static void handle_http_resp(void *cls, struct JSHttpResponseInfo *resp_info)
+{
+    // Called from a different thread.
+    // We must enqueue something that the message loop will process
+    //
+    HttpRequestContext *req_context = cls;
     JSContext *ctx = req_context->ctx;
-    long resp_code;
-    JSValue ret_val;
-    JSValue cb_ret;
-
-    if (CURLE_OK != result) {
-        const char *errmsg;
-        JSAtom atom_message;
-
-        atom_message = JS_NewAtom(ctx, "message");
-        errmsg = curl_easy_strerror(result);
-        ret_val = JS_NewError(ctx);
-        JS_DefinePropertyValue(ctx, ret_val, atom_message,
-                               JS_NewString(ctx, errmsg),
-                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
-        cb_ret = JS_Call(ctx, req_context->reject_func, JS_UNDEFINED, 1, 
&ret_val);
-        JS_FreeAtom(ctx, atom_message);
-        JS_FreeValue(ctx, cb_ret);
-        JS_FreeValue(ctx, ret_val);
-        free_fetch_request_context(req_context);
-        return;
+    JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
+    JSHttpMessage *msg;
+    JSHttpMessagePipe *hp;
+
+    msg = malloc(sizeof (*msg));
+    if (!msg) {
+        goto fail;
+    }
+    memset(msg, 0, sizeof (*msg));
+
+    msg->status = resp_info->status;
+    msg->request_id = resp_info->request_id;
+
+    if (resp_info->response_headers) {
+      int num_headers = 0;
+      char **h;
+
+      h = resp_info->response_headers;
+      while (*h) {
+          num_headers++;
+          h++;
+      }
+
+      msg->response_headers = malloc((num_headers + 1) * sizeof (char *));
+      if (!msg->response_headers) {
+          goto fail;
+      }
+      memset(msg->response_headers, 0, (num_headers + 1) * sizeof (char *));
+      for (int i = 0; i < num_headers; i++) {
+          msg->response_headers[i] = strdup(resp_info->response_headers[i]);
+          if (!msg->response_headers[i]) {
+              goto fail;
+          }
+      }
+    } else {
+        msg->response_headers = NULL;
     }
 
-    ret_val = JS_NewObject(ctx);
-    if (JS_IsException(ret_val)) {
-        // Huh, we're out of memory in the request handler?
-        fprintf(stderr, "fatal: can't allocate object in finish_fetch_http\n");
+    if (resp_info->errmsg != NULL) {
+        msg->errmsg = strdup(resp_info->errmsg);
+        if (!msg->errmsg) {
+            goto fail;
+        }
+    }
+
+    if (resp_info->body_len > 0) {
+        msg->body = malloc(resp_info->body_len);
+        if (!msg->body) {
+            goto fail;
+        }
+        msg->body_len = resp_info->body_len;
+        memcpy(msg->body, resp_info->body, resp_info->body_len);
+    }
+
+    hp = ts->http_pipe;
+    pthread_mutex_lock(&hp->mutex);
+    /* indicate that data is present */
+    if (list_empty(&hp->msg_queue)) {
+        uint8_t ch = '\0';
+        int ret;
+        for(;;) {
+            ret = write(hp->write_fd, &ch, 1);
+            if (ret == 1)
+                break;
+            if (ret < 0 && (errno != EAGAIN || errno != EINTR))
+                break;
+        }
+    }
+    list_add_tail(&msg->link, &hp->msg_queue);
+    pthread_mutex_unlock(&hp->mutex);
+    return;
+ fail:
+    js_free_http_message(msg);
+    return;
+}
+
+static void
+free_http_headers(JSContext *ctx, char **headers)
+{
+    if (!headers) {
         return;
     }
+    for (char **h = headers; *h != NULL; h++) {
+        js_free(ctx, *h);
+    }
+    js_free(ctx, headers);
+}
+
+static char **gather_http_headers(JSContext *ctx, JSValueConst js_headers)
+{
+    JSValue length_prop;
+    uint32_t length;
+    char **headers = NULL;
+
+    length_prop = JS_GetPropertyStr(ctx, js_headers, "length");
+    if (JS_IsException(length_prop)) {
+        return NULL;
+    }
+    if (0 != JS_ToUint32(ctx, &length, length_prop)) {
+        return NULL;
+    }
+    JS_FreeValue(ctx, length_prop);
 
-    curl_easy_getinfo(req_context->curl, CURLINFO_RESPONSE_CODE, &resp_code);
-    JS_SetPropertyStr(ctx, ret_val, "status", JS_NewInt32(ctx, resp_code));
-    JS_SetPropertyStr(ctx, ret_val, "headers", req_context->headers_list);
-    req_context->headers_list = JS_UNINITIALIZED;
-    // FIXME: Don't copy, own buffer
-    JS_SetPropertyStr(ctx,
-                      ret_val,
-                      "data",
-                      JS_NewArrayBufferCopy(ctx,
-                                            req_context->response_data.buf,
-                                            req_context->response_data.size));
-    cb_ret = JS_Call(ctx, req_context->resolve_func, JS_UNDEFINED, 1, 
&ret_val);
-    JS_FreeValue(ctx, cb_ret);
-    JS_FreeValue(ctx, ret_val);
-    free_fetch_request_context(req_context);
+    headers = js_mallocz(ctx, (length + 1) * sizeof (char *));
+    if (!headers) {
+        goto exception;
+    }
+
+    for (uint32_t i = 0; i < length; i++) {
+        char *hval;
+        JSValue item = JS_GetPropertyUint32(ctx, js_headers, i);
+        if (JS_IsException(item)) {
+            goto exception;
+        }
+        const char *cstr = JS_ToCString(ctx, item);
+        if (!cstr) {
+            JS_FreeValue(ctx, item);
+            goto exception;
+        }
+        hval = js_strdup(ctx, cstr);
+        if (!hval) {
+            goto exception;
+        }
+        JS_FreeCString(ctx, cstr);
+        JS_FreeValue(ctx, item);
+        headers[i] = hval;
+    }
+    return headers;
+exception:
+    free_http_headers(ctx, headers);
+    return NULL;
 }
 
+
 /**
  * fetchHttp(url, { method, headers, body }): Promise<Response>
  */
 static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val,
-                                int argc, JSValueConst *argv)
+                                 int argc, JSValueConst *argv)
 {
+    JSValue ret_val = JS_UNINITIALIZED;
     JSRuntime *rt = JS_GetRuntime(ctx);
     JSThreadState *ts = JS_GetRuntimeOpaque(rt);
-    const char *req_url = NULL;
-    JSValue ret_val = JS_UNDEFINED;
     JSValue resolving_funs[2];
     JSValue options = JS_UNINITIALIZED;
     JSValue method = JS_UNINITIALIZED;
     const char *method_str = NULL;
-    CURLMcode mres;
-    CurlRequestContext *req_context = {0};
+    const char *req_url = NULL;
+    struct JSHttpRequestInfo req = { 0 };
+    HttpRequestContext *req_context = NULL;
     BOOL debug = FALSE;
-    BOOL req_started = FALSE;
+    int redirect = 0;
+    int ret;
 
-    req_context = js_mallocz(ctx, sizeof *req_context);
+    if (NULL == ts->http_client_impl) {
+        JS_ThrowInternalError(ctx, "no HTTP client implementation available");
+        goto exception;
+    }
 
+    req_context = js_mallocz(ctx, sizeof *req_context);
     req_context->ctx = ctx;
-    req_context->headers_list = JS_NewArray(ctx);
-    dbuf_init(&req_context->response_data);
 
     req_url = JS_ToCString(ctx, argv[0]);
     if (!req_url) {
@@ -2342,116 +2377,106 @@ static JSValue js_os_fetchHttp(JSContext *ctx, 
JSValueConst this_val,
     options = argv[1];
     if (JS_VALUE_GET_TAG(options) == JS_TAG_UNDEFINED) {
         method = JS_NewString(ctx, "get");
-    }
-    else if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
+    } else if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
+        int has_prop_redirect;
+
         method = JS_GetPropertyStr(ctx, options, "method");
         debug = expect_property_str_bool(ctx, options, "debug");
+
+        has_prop_redirect = JS_HasPropertyStr(ctx, options, "redirect");
+        if (has_prop_redirect < 0) {
+            goto exception;
+        }
+        if (has_prop_redirect) {
+            int32_t redir_num;
+            JSValue redir_val = JS_GetPropertyStr(ctx, options, "redirect");
+            if (JS_IsException(redir_val)) {
+                goto exception;
+            }
+            if (JS_ToInt32(ctx, &redir_num, redir_val)) {
+                goto exception;
+            }
+            if (redir_num < 0 || redir_num > JS_HTTP_REDIRECT_ERROR) {
+                JS_ThrowTypeError(ctx, "redirect option out of range");
+                goto exception;
+            }
+            redirect = redir_num;
+        }
     } else {
         JS_ThrowTypeError(ctx, "invalid options");
         goto exception;
     }
 
-    req_context->curl = curl_easy_init();
-    if (!req_context->curl) {
-        JS_ThrowInternalError(ctx, "unable to init libcurl");
-        goto exception;
-    }
-    curl_easy_setopt(req_context->curl, CURLOPT_PRIVATE, req_context);
-    curl_easy_setopt(req_context->curl, CURLOPT_SHARE, ts->curlsh);
-    curl_easy_setopt(req_context->curl, CURLOPT_URL, req_url);
-//    curl_easy_setopt(req_context->curl, CURLOPT_DNS_SERVERS, "8.8.8.8");
-    curl_easy_setopt(req_context->curl, CURLOPT_DNS_SERVERS, "9.9.9.9");
-    curl_easy_setopt(req_context->curl, CURLOPT_USERAGENT, "qtart");
-    curl_easy_setopt(req_context->curl, CURLOPT_CAINFO, 
"/etc/ssl/certs/ca-certificates.crt");
-    curl_easy_setopt(req_context->curl, CURLOPT_HEADERFUNCTION, 
curl_header_callback);
-    curl_easy_setopt(req_context->curl, CURLOPT_HEADERDATA, req_context);
-    curl_easy_setopt(req_context->curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
-    curl_easy_setopt(req_context->curl, CURLOPT_WRITEDATA, req_context);
-    if (debug == TRUE) {
-        curl_easy_setopt(req_context->curl, CURLOPT_VERBOSE, 1L);
-    }
-
-    // FIXME: This is only a temporary hack until we have proper TLS CA support
-    // on all platforms
-    curl_easy_setopt(req_context->curl, CURLOPT_SSL_VERIFYPEER, 0);
-    curl_easy_setopt(req_context->curl, CURLOPT_SSL_VERIFYHOST, 0);
-
     if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
         JSValue header_item = JS_GetPropertyStr(ctx, options, "headers");
         if (JS_IsException(header_item)) {
             goto exception;
         }
         if (JS_VALUE_GET_TAG(header_item) == JS_TAG_OBJECT) {
-            if (0 != gather_headers(ctx, header_item, req_context)) {
-                JS_FreeValue(ctx, header_item);
-                goto exception;
+            char **headers = gather_http_headers(ctx, header_item);
+            if (NULL == headers) {
+                 JS_FreeValue(ctx, header_item);
+                 goto exception;
             }
+            req.request_headers = headers;
         }
         JS_FreeValue(ctx, header_item);
     }
 
-    method_str = JS_ToCString(ctx, method);
-
-    if (0 == strcasecmp(method_str, "get")) {
-        curl_easy_setopt(req_context->curl, CURLOPT_HTTPGET, 1L);
-        if (!req_context->client_has_accept_header) {
-          req_context->headers = curl_slist_append (req_context->headers, 
"Accept: application/json");
-        }
-    } else if (0 == strcasecmp(method_str, "delete")) {
-        curl_easy_setopt(req_context->curl, CURLOPT_HTTPGET, 1L);
-        curl_easy_setopt(req_context->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
-    } else if ((0 == strcasecmp(method_str, "post")) ||
-               (0 == strcasecmp(method_str, "put"))) {
+    if (JS_VALUE_GET_TAG(options) == JS_TAG_OBJECT) {
         JSValue data;
         uint8_t *data_ptr;
         size_t data_len;
+        int has_prop;
 
-        data = JS_GetPropertyStr(ctx, options, "data");
-        if (JS_IsException(data)) {
-            goto exception;
-        }
-        data_ptr = JS_GetArrayBuffer(ctx, &data_len, data);
-        if (!data_ptr) {
+        has_prop = JS_HasPropertyStr(ctx, options, "data");
+
+        if (-1 == has_prop) {
             goto exception;
         }
-        req_context->readbuf = malloc(data_len);
-        memcpy(req_context->readbuf, data_ptr, data_len);
-        req_context->readlen = data_len;
-        JS_FreeValue(ctx, data);
-        curl_easy_setopt(req_context->curl, CURLOPT_POST, 1L);
-        if (0 == strcasecmp(method_str, "put")) {
-            curl_easy_setopt(req_context->curl, CURLOPT_CUSTOMREQUEST, "PUT");
-        }
-        curl_easy_setopt(req_context->curl, CURLOPT_READFUNCTION, 
read_callback);
-        curl_easy_setopt(req_context->curl, CURLOPT_READDATA, req_context);
-        if (!req_context->client_has_content_type_header) {
-            req_context->headers = curl_slist_append(req_context->headers, 
"Content-Type: application/json");
+
+        if (has_prop) {
+            data = JS_GetPropertyStr(ctx, options, "data");
+            if (JS_IsException(data)) {
+                goto exception;
+            }
+            if (!JS_IsNull(data) && !JS_IsUndefined(data)) {
+              data_ptr = JS_GetArrayBuffer(ctx, &data_len, data);
+              if (!data_ptr) {
+                  goto exception;
+              }
+            }
+            req.req_body = data_ptr;
+            req.req_body_len = data_len;
         }
-    } else {
-        JS_ThrowTypeError(ctx, "invalid request method");
-        goto exception;
     }
 
-    curl_easy_setopt(req_context->curl, CURLOPT_HTTPHEADER, 
req_context->headers);
+    method_str = JS_ToCString(ctx, method);
+
+    req.method = method_str;
+    req.url = req_url;
+    req.debug = debug;
+    req.redirect = redirect;
+    req.response_cb = &handle_http_resp;
+    req.response_cb_cls = req_context;
+    ret = ts->http_client_impl->req_create(ts->http_client_impl->cls, &req);
 
-    mres = curl_multi_add_handle(ts->curlm, req_context->curl);
-    if (CURLM_OK != mres) {
-        JS_ThrowInternalError(ctx, "fetch failed: %s", 
curl_multi_strerror(mres));
+    if (ret < 0) {
+        JS_ThrowInternalError(ctx, "failed to create request");
         goto exception;
     }
-    list_add_tail(&req_context->link, &ts->curl_requests);
-    req_started = TRUE;
+
+    list_add_tail(&req_context->link, &ts->http_requests);
 
     ret_val = JS_NewPromiseCapability(ctx, resolving_funs);
     if (JS_IsException(ret_val)) {
         goto done;
     }
+    req_context->request_id = ret;
     req_context->resolve_func = resolving_funs[0];
     req_context->reject_func = resolving_funs[1];
 done:
-    if (FALSE == req_started) {
-        free_fetch_request_context(req_context);
-    }
+    free_http_headers(ctx, req.request_headers);
     JS_FreeValue(ctx, method);
     JS_FreeCString(ctx, req_url);
     JS_FreeCString(ctx, method_str);
@@ -2459,6 +2484,7 @@ done:
 exception:
     ret_val = JS_EXCEPTION;
     goto done;
+
 }
 
 #endif
@@ -2735,38 +2761,97 @@ static int handle_host_message(JSRuntime *rt, JSContext 
*ctx)
     return ret;
 }
 
-// Perform curl network requests and 
-static void do_curl(JSContext *ctx)
+/* return 1 if a message was handled, 0 if no message */
+static int handle_http_message(JSRuntime *rt, JSContext *ctx)
 {
 #ifndef NO_HTTP
-    JSRuntime *rt = JS_GetRuntime(ctx);
-    JSThreadState *ts = JS_GetRuntimeOpaque(rt);
-    CURLMcode curl_ret;
-    struct CURLMsg *m;
-    int running_handles;
+    JSThreadState *ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
+    JSHttpMessagePipe *hp = ts->http_pipe;
+    int ret;
+    struct list_head *el;
+    struct list_head *req_el;
+    JSHttpMessage *msg;
+    JSValue obj, func, retval;
+    HttpRequestContext *request_ctx;
+    
+    pthread_mutex_lock(&hp->mutex);
+    if (!list_empty(&hp->msg_queue)) {
+        el = hp->msg_queue.next;
+        msg = list_entry(el, JSHttpMessage, link);
 
-    curl_ret = curl_multi_perform(ts->curlm, &running_handles);
-    if (0 != curl_ret) {
-        fprintf(stderr, "curlm error: %s\n", curl_multi_strerror(curl_ret));
-        return;
-    }
+        /* remove the message from the queue */
+        list_del(&msg->link);
 
-    do {
-        int msgq = 0;
-        m = curl_multi_info_read(ts->curlm, &msgq);
-        if (m && (m->msg == CURLMSG_DONE)) {
-            CurlRequestContext *req_ctx;
-            CURL *e = m->easy_handle;
-            CURLcode result = m->data.result;
-            if (CURLE_OK != curl_easy_getinfo(e, CURLINFO_PRIVATE, &req_ctx)) {
-                fprintf(stderr, "fatal: curl handle has no private data");
-                continue;
+        if (list_empty(&hp->msg_queue)) {
+            uint8_t buf[16];
+            int ret;
+            for(;;) {
+                ret = read(hp->read_fd, buf, sizeof(buf));
+                if (ret >= 0)
+                    break;
+                if (errno != EAGAIN && errno != EINTR)
+                    break;
             }
-            curl_multi_remove_handle(ts->curlm, e);
-            finish_fetch_http(req_ctx, result);
         }
-    } while (m);
-#endif
+
+        pthread_mutex_unlock(&hp->mutex);
+
+        list_for_each(req_el, &ts->http_requests) {
+          request_ctx = list_entry(req_el, HttpRequestContext, link);
+          if (request_ctx->request_id == msg->request_id) {
+            if (msg->status != 0) {
+              JSValue headers_list = JS_NewArray(ctx);
+
+              obj = JS_NewObject(ctx);
+
+              if (msg->response_headers) {
+                char **h = msg->response_headers;
+                while (*h) {
+                  qjs_array_append_new(ctx, headers_list, JS_NewString(ctx, 
*h));
+                  h++;
+                }
+              }
+              JS_SetPropertyStr(ctx, obj, "headers", headers_list);
+
+              //JS_SetPropertyStr(ctx, obj, "data", JS_NewTypedArray(ctx, 
JS_NewArrayBufferCopy(ctx, msg->body, msg->body_len), 1));
+              JS_SetPropertyStr(ctx, obj, "data", JS_NewArrayBufferCopy(ctx, 
msg->body, msg->body_len));
+
+              JS_SetPropertyStr(ctx, obj, "status", JS_NewInt32(ctx, 
msg->status));
+              func = JS_DupValue(ctx, request_ctx->resolve_func);
+              retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst 
*)&obj);
+              JS_FreeValue(ctx, obj);
+              JS_FreeValue(ctx, func);
+              if (JS_IsException(retval)) {
+                  js_std_dump_error(ctx);
+              } else {
+                  JS_FreeValue(ctx, retval);
+              }
+            } else {
+              JSAtom atom_message;
+
+              atom_message = JS_NewAtom(ctx, "message");
+              obj = JS_NewError(ctx);
+              JS_DefinePropertyValue(ctx, obj, atom_message,
+                                     JS_NewString(ctx, msg->errmsg),
+                                     JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
+              retval = JS_Call(ctx, request_ctx->reject_func, JS_UNDEFINED, 1, 
&obj);
+              JS_FreeAtom(ctx, atom_message);
+              JS_FreeValue(ctx, retval);
+            }
+            break;
+          }
+        }
+
+        js_free_http_message(msg);
+        ret = 1;
+    } else {
+        pthread_mutex_unlock(&hp->mutex);
+        ret = 0;
+    }
+    return ret;
+#else
+    return 0;
+#endif /* NO_HTTP */
 }
 
 static int js_os_poll(JSContext *ctx)
@@ -2799,7 +2884,7 @@ static int js_os_poll(JSContext *ctx)
     }
 
 #ifndef NO_HTTP
-    have_http_requests = !list_empty(&ts->curl_requests);
+    have_http_requests = !list_empty(&ts->http_requests);
 #endif
 
     if ((!have_http_requests) && list_empty(&ts->os_rw_handlers) && 
list_empty(&ts->os_timers) &&
@@ -2860,36 +2945,8 @@ static int js_os_poll(JSContext *ctx)
     fd_max = max_int(fd_max, ts->host_pipe->read_fd);
     FD_SET(ts->host_pipe->read_fd, &rfds);
 
-#ifndef NO_HTTP
-    {
-        int curl_fd_max = -1;
-        CURLMcode mret;
-        long timeo = -1;
-
-        mret = curl_multi_fdset(ts->curlm, &rfds, &wfds, &ecxfds, 
&curl_fd_max);
-        if (CURLM_OK != mret) {
-            fprintf(stderr, "curlm error: %s\n", curl_multi_strerror(mret));
-        } else if (curl_fd_max != -1) {
-            fd_max = max_int(fd_max, curl_fd_max);
-        }
-
-        curl_multi_timeout(ts->curlm, &timeo);
-        if (timeo > 0) {
-            long timeo_sec = timeo / 1000;
-            long timeo_usec = (timeo % 1000) * 1000;
-            tvp = &tv;
-            if (tv.tv_sec < timeo_sec) {
-                tv.tv_sec = timeo_sec;
-                tv.tv_usec = timeo_usec;
-            } else if ((tv.tv_sec == timeo_sec) && tv.tv_usec < timeo_usec) {
-                tv.tv_usec = timeo_usec;
-            }
-        } else if (timeo == 0) {
-            do_curl(ctx);
-            goto done;
-        }
-    }
-#endif
+    fd_max = max_int(fd_max, ts->http_pipe->read_fd);
+    FD_SET(ts->http_pipe->read_fd, &rfds);
 
     ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp);
     if (ret > 0) {
@@ -2926,7 +2983,11 @@ static int js_os_poll(JSContext *ctx)
             }
         }
 
-        do_curl(ctx);
+        if (FD_ISSET(ts->http_pipe->read_fd, &rfds)) {
+            if (handle_http_message(rt, ctx)) {
+                goto done;
+            }
+        }
     }
     done:
     return 0;
@@ -3811,6 +3872,27 @@ static JSHostMessagePipe *js_new_host_message_pipe(void)
     return ps;
 }
 
+static JSHttpMessagePipe *js_new_http_message_pipe(void)
+{
+    JSHttpMessagePipe *ps;
+    int pipe_fds[2];
+    
+    if (pipe(pipe_fds) < 0)
+        return NULL;
+
+    ps = malloc(sizeof(*ps));
+    if (!ps) {
+        close(pipe_fds[0]);
+        close(pipe_fds[1]);
+        return NULL;
+    }
+    init_list_head(&ps->msg_queue);
+    pthread_mutex_init(&ps->mutex, NULL);
+    ps->read_fd = pipe_fds[0];
+    ps->write_fd = pipe_fds[1];
+    return ps;
+}
+
 static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps)
 {
     atomic_add_int(&ps->ref_count, 1);
@@ -3880,6 +3962,32 @@ static void js_free_host_message_pipe(JSHostMessagePipe 
*ps)
     }
 }
 
+#ifndef NO_HTTP
+
+static void js_free_http_message_pipe(JSHttpMessagePipe *ps)
+{
+    struct list_head *el, *el1;
+    JSHttpMessage *msg;
+    int ref_count;
+    
+    if (!ps)
+        return;
+    
+    assert(ref_count >= 0);
+    if (ref_count == 0) {
+        list_for_each_safe(el, el1, &ps->msg_queue) {
+            msg = list_entry(el, JSHttpMessage, link);
+            js_free_http_message(msg);
+        }
+        pthread_mutex_destroy(&ps->mutex);
+        close(ps->read_fd);
+        close(ps->write_fd);
+        free(ps);
+    }
+}
+
+#endif
+
 static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port)
 {
     if (port) {
@@ -4416,13 +4524,6 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m)
     JS_NewClassID(&js_os_timer_class_id);
     JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class);
 
-#ifndef NO_HTTP
-    if (CURLE_OK != curl_global_init (CURL_GLOBAL_DEFAULT)) {
-      JS_ThrowInternalError(ctx, "unable to init libcurl (global)");
-      return -1;
-    }
-#endif
-
 #ifdef USE_WORKER
     {
         JSRuntime *rt = JS_GetRuntime(ctx);
@@ -4537,6 +4638,7 @@ oom_fail:
     init_list_head(&ts->port_list);
     ts->on_host_message_func = JS_NULL;
     ts->host_pipe = js_new_host_message_pipe();
+    ts->http_pipe = js_new_http_message_pipe();
     if (!ts->host_pipe) {
         goto oom_fail;
     }
@@ -4544,12 +4646,7 @@ oom_fail:
     JS_SetRuntimeOpaque(rt, ts);
 
 #ifndef NO_HTTP
-    ts->curlm = curl_multi_init();
-    ts->curlsh = curl_share_init();
-    curl_share_setopt(ts->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
-    curl_share_setopt(ts->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
-    curl_share_setopt(ts->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
-    init_list_head(&ts->curl_requests);
+    init_list_head(&ts->http_requests);
 #endif
 
 #ifdef USE_WORKER
@@ -4588,16 +4685,10 @@ void js_std_free_handlers(JSRuntime *rt)
     }
 
 #ifndef NO_HTTP
-
-    list_for_each_safe(el, el1, &ts->curl_requests) {
-        CurlRequestContext *request_ctx = list_entry(el, CurlRequestContext, 
link);
-        free_fetch_request_context(request_ctx);
+    list_for_each_safe(el, el1, &ts->http_requests) {
+        HttpRequestContext *request_ctx = list_entry(el, HttpRequestContext, 
link);
+        free_http_request_context(request_ctx);
     }
-
-    curl_multi_cleanup(ts->curlm);
-    ts->curlm = NULL;
-    curl_share_cleanup(ts->curlsh);
-    ts->curlsh = NULL;
 #endif
 
     JS_FreeValueRT(rt, ts->on_host_message_func);
@@ -4610,6 +4701,10 @@ void js_std_free_handlers(JSRuntime *rt)
 
     js_free_host_message_pipe(ts->host_pipe);
 
+#ifndef NO_HTTP
+    js_free_http_message_pipe(ts->http_pipe);
+#endif
+
     free(ts);
     JS_SetRuntimeOpaque(rt, NULL); /* fail safe */
 }
diff --git a/quickjs/quickjs-libc.h b/quickjs/quickjs-libc.h
index 4a7697e..650ff36 100644
--- a/quickjs/quickjs-libc.h
+++ b/quickjs/quickjs-libc.h
@@ -29,6 +29,10 @@
 
 #include "quickjs.h"
 
+#ifndef NO_HTTP
+#include "quickjs-http.h"
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -55,6 +59,10 @@ void js_std_promise_rejection_tracker(JSContext *ctx, 
JSValueConst promise,
 void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt));
 void js_os_set_host_message_handler(JSContext *ctx, JSHostMessageHandlerFn f, 
void *cls);
 int js_os_post_message_from_host(JSContext *ctx, const char *msg_str);
+
+#ifndef NO_HTTP
+void js_os_set_http_impl(JSRuntime *rt, struct JSHttpClientImplementation 
*impl);
+#endif
                                         
 #ifdef __cplusplus
 } /* extern "C" { */
diff --git a/quickjs/quickjs.c b/quickjs/quickjs.c
index bae5541..561374b 100644
--- a/quickjs/quickjs.c
+++ b/quickjs/quickjs.c
@@ -7775,6 +7775,17 @@ int JS_PreventExtensions(JSContext *ctx, JSValueConst 
obj)
     return TRUE;
 }
 
+/* return -1 if exception otherwise TRUE or FALSE */
+int JS_HasPropertyStr(JSContext *ctx, JSValueConst obj, const char *propname)
+{
+    JSAtom atom;
+    int ret;
+    atom = JS_NewAtom(ctx, propname);
+    ret = JS_HasProperty(ctx, obj, atom);
+    JS_FreeAtom(ctx, atom);
+    return ret;
+}
+
 /* return -1 if exception otherwise TRUE or FALSE */
 int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop)
 {
diff --git a/quickjs/quickjs.h b/quickjs/quickjs.h
index c1cf768..d616c16 100644
--- a/quickjs/quickjs.h
+++ b/quickjs/quickjs.h
@@ -751,6 +751,7 @@ int JS_SetPropertyInt64(JSContext *ctx, JSValueConst 
this_obj,
 int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj,
                       const char *prop, JSValue val);
 int JS_HasProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop);
+int JS_HasPropertyStr(JSContext *ctx, JSValueConst this_obj, const char 
*propname);
 int JS_IsExtensible(JSContext *ctx, JSValueConst obj);
 int JS_PreventExtensions(JSContext *ctx, JSValueConst obj);
 int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int 
flags);
diff --git a/taler_wallet_core_lib.c b/taler_wallet_core_lib.c
index ae82358..a7c2898 100644
--- a/taler_wallet_core_lib.c
+++ b/taler_wallet_core_lib.c
@@ -52,6 +52,8 @@ struct TALER_WALLET_Instance
 
     TALER_WALLET_LogHandlerFn log_handler_f;
     void *log_handler_cls;
+
+    struct JSHttpClientImplementation *http_impl;
 };
 
 
@@ -176,8 +178,14 @@ run(void *cls)
     wh->rt = JS_NewRuntime();
 
     js_std_init_handlers(wh->rt);
-    wh->ctx = JS_NewCustomContext(wh->rt);
 
+    if (wh->http_impl) {
+        js_os_set_http_impl(wh->rt, wh->http_impl);
+    } else {
+        fprintf(stderr, "warning: no HTTP client implementation provided for 
wallet-core\n");
+    }
+
+    wh->ctx = JS_NewCustomContext(wh->rt);
 
     if (!wh->ctx) {
         fprintf(stderr, "qjs: cannot allocate JS context\n");
@@ -322,3 +330,10 @@ TALER_start_redirect_std(TALER_LogFn logfn, void *cls)
     pthread_detach(log_thr);
     return 0;
 }
+
+void
+TALER_set_http_client_implementation(struct TALER_WALLET_Instance *instance,
+                                     struct JSHttpClientImplementation *impl)
+{
+  instance->http_impl = impl;
+}
diff --git a/taler_wallet_core_lib.h b/taler_wallet_core_lib.h
index 166612d..660690d 100644
--- a/taler_wallet_core_lib.h
+++ b/taler_wallet_core_lib.h
@@ -25,6 +25,8 @@
 #ifndef _TALER_WALLET_LIB_H
 #define _TALER_WALLET_LIB_H
 
+#include "quickjs/quickjs-http.h"
+
 /**
  * Opaque handle to a Taler wallet-core instance.
  */
@@ -153,4 +155,15 @@ typedef void (*TALER_LogFn)(void *cls, int stream, const 
char *msg);
 int
 TALER_start_redirect_std(TALER_LogFn logfn, void *cls);
 
+
+/**
+ * Set the HTTP client implementation to be used by the wallet.
+ *
+ * @param instance wallet-core instance
+ * @param HTTP client implementation
+ */
+void
+TALER_set_http_client_implementation(struct TALER_WALLET_Instance *instance,
+                                     struct JSHttpClientImplementation *impl);
+
 #endif /*_TALER_WALLET_LIB_H */
diff --git a/taler_wallet_core_lib_preprocessed.h 
b/taler_wallet_core_lib_preprocessed.h
new file mode 100644
index 0000000..a050eb1
--- /dev/null
+++ b/taler_wallet_core_lib_preprocessed.h
@@ -0,0 +1,381 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2014-2022 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * C interface to the functionality of wallet-core.
+ *
+ * Currently, the underlying implementation uses the JS implementation of
+ * wallet-core, but this may (or may not) change in the future.
+ *
+ * @author Florian Dold
+ */
+#ifndef _TALER_WALLET_LIB_H
+#define _TALER_WALLET_LIB_H
+
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2024 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+// ## Native HTTP client library support.
+
+// Considerations:
+// - the API is designed for the HTTP client implementation
+//   to run in its own thread and *not* be integrated with the
+//   application's main event loop.
+// - focus on small API
+// - not a generic HTTP client, only supposed to serve the needs
+//   of a JS runtime
+// - only very tiny subset of HTTP supported
+// - no request/response streaming
+// - should be appropriate to implement a JS HTTP fetch function
+//   in the style of WHATWG fetch
+// - no focus on ABI compatibility whatsoever
+
+
+#ifndef _QUICKJS_HTTP_H
+#define _QUICKJS_HTTP_H
+
+#include <stdint.h>
+#include <limits.h>
+#include <stddef.h>
+
+// Forward declaration;
+struct JSHttpResponseInfo;
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+enum JSHttpRedirectFlag {
+  /**
+   * Handle redirects transparently.
+   */
+  JS_HTTP_REDIRECT_TRANSPARENT = 0,
+  /**
+   * Redirect status codes are returned to the client.
+   * The client can choose to follow them manually (or not).
+   */
+  JS_HTTP_REDIRECT_MANUAL = 1,
+  /**
+   * All redirect status codes result in an error.
+   */
+  JS_HTTP_REDIRECT_ERROR = 2,
+};
+
+/**
+ * Info needed to start a new HTTP request.
+ */
+struct JSHttpRequestInfo {
+  /**
+   * Callback called with the response for the request.
+   */
+  JSHttpResponseCb response_cb;
+
+  /**
+   * Closure for response_cb.
+   */
+  void *response_cb_cls;
+
+  /**
+   * Request URL.
+   */
+  const char *url;
+
+  /**
+   * Request method.
+   */
+  const char *method;
+
+  int num_request_headers;
+
+  /**
+   * Array of `num_request_headers` request headers.
+   */
+  char **request_headers;
+
+  /**
+   * 0: Handle redirects transparently.
+   * 1: Handle redirects manually.
+   * 2: Redirects result in an error.
+   */
+  enum JSHttpRedirectFlag redirect;
+
+  /**
+   * Request timeout in milliseconds.
+   *
+   * When 0 is specified, the timeout is the default request
+   * timeout for the platform.
+   *
+   * When -1 is specified, there is no timeout.  This might not be
+   * supported on all platforms.
+   */
+  int timeout_ms;
+
+  /**
+   * Enable debug output for this request.
+   */
+  int debug;
+
+  /**
+   * Request body or NULL.
+   */
+  void *req_body;
+
+  /**
+   * Length or request body or 0.
+   */
+  size_t req_body_len;
+};
+
+/**
+ * Contents of an HTTP response.
+ */
+struct JSHttpResponseInfo {
+
+  /**
+   * Request that this is a response to.
+   *
+   * (Think of the request ID like a file descriptor number).
+   */
+  int request_id;
+
+  /**
+   * HTTP response status code or 0 on error.
+   */
+  int status;
+
+  /**
+   * When status is 0, error message.
+   */
+  char *errmsg;
+
+  /**
+   * Array of `num_response_headers` response headers.
+   */
+  char **response_headers;
+
+  /**
+   * Number of response headers.
+   */
+  int num_response_headers;
+
+  /**
+   * Response body or NULL.
+   */
+  void *body;
+
+  /**
+   * Length of the response body or 0.
+   */
+  size_t body_len;
+};
+
+/**
+ * Callback called when an HTTP response has arrived.
+ *
+ * IMPORTANT: May be called from an arbitrary thread.
+ */
+typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp);
+
+/**
+ * Function to create a new HTTP fetch request.
+ * The request can still be configured until it is started.
+ * An identifier for the request will be written to @a handle.
+ *
+ * @return negative number on error, positive request_id on success
+ */
+typedef int (*JSHttpReqCreateFn)(void *cls, struct JSHttpRequestInfo 
*req_info);
+
+/**
+ * Cancel a request. The request_id will become invalid
+ * and the callback won't be called with request_id.
+ */
+typedef int (*JSHttpReqCancelFn)(void *cls, int request_id);
+
+struct JSHttpClientImplementation {
+  /**
+   * Opaque closure passed to client functions.
+   */
+  void *cls;
+  JSHttpReqCreateFn req_create;
+  JSHttpReqCancelFn req_cancel;
+};
+
+
+struct JSHttpClientImplementation *
+js_curl_http_client_create(void);
+
+void
+js_curl_http_client_destroy(struct JSHttpClientImplementation *impl);
+
+#endif /* _QUICKJS_HTTP_H */
+
+/**
+ * Opaque handle to a Taler wallet-core instance.
+ */
+struct TALER_WALLET_Instance;
+
+/**
+ * Handler for messages from the wallet.
+ *
+ * @param handler_p opaque closure for the message handler
+ * @param message message from wallet-core as a JSON string
+ */
+typedef void (*TALER_WALLET_MessageHandlerFn)(void *handler_p, const char 
*message);
+
+enum TALER_WALLET_LogLevel {
+    TALER_WALLET_LOG_TRACE = 1,
+    TALER_WALLET_LOG_INFO = 2,
+    TALER_WALLET_LOG_MESSAGE = 3,
+    TALER_WALLET_LOG_WARN = 4,
+    TALER_WALLET_LOG_ERROR = 5
+};
+
+/**
+ * Handler for log message from wallet-core.
+ *
+ * @param log_p opaque closure for the log handler
+ * @param level log level of the log message
+ * @param tag log tag (usually the file from which the message gets logged)
+ * @param msg the log message
+ */
+typedef void (*TALER_WALLET_LogHandlerFn)(void *log_p,
+                                          enum TALER_WALLET_LogLevel level,
+                                          const char *tag,
+                                          const char *msg);
+
+/**
+ * Create a new wallet-core instance..
+ */
+struct TALER_WALLET_Instance *
+TALER_WALLET_create(void);
+
+/**
+ * Set a handler for notification and response messages.
+ * Must be called before the wallet runs.
+ *
+ * Caution: The handler will be called from a different thread.
+ */
+void
+TALER_WALLET_set_message_handler(struct TALER_WALLET_Instance *twi,
+                                 TALER_WALLET_MessageHandlerFn handler_f,
+                                 void *handler_p);
+
+
+/**
+ * Set a handler for log messages from wallet-core.
+ * Must be called before the wallet runs.
+ *
+ * Caution: The log message handler will be called from a different thread.
+ */
+void
+TALER_WALLET_set_log_handler(struct TALER_WALLET_Instance *twi,
+                             TALER_WALLET_LogHandlerFn handler_f,
+                             void *handler_p);
+
+
+/**
+ * Set/override the JS file with the wallet-core implementation.
+ * Must be called before the wallet runs.
+ */
+// FIXME: Not implemented!
+//void
+//TALER_WALLET_set_jsfile(struct TALER_WALLET_Instance *twi,
+//                        const char *filename);
+
+/**
+ * Send a message to wallet-core.
+ *
+ * Responses will be sent asynchronously to the message handler
+ * set with #TALER_WALLET_set_message_handler.
+ */
+int
+TALER_WALLET_send_request(struct TALER_WALLET_Instance *twi,
+                          const char *request);
+
+/**
+ * Run wallet-core in a thread.
+ *
+ * This function creates a new thread and returns immediately.
+ *
+ * Returns 0 on success or a non-zero error code otherwise.
+ */
+int
+TALER_WALLET_run(struct TALER_WALLET_Instance *twi);
+
+/**
+ * Block until the wallet returns.
+ */
+void
+TALER_WALLET_join(struct TALER_WALLET_Instance *twi);
+
+/**
+ * Destroy the wallet handle and free resources associated with it.
+ *
+ * Note that for a graceful shutdown of the wallet,
+ * an appropriate shutdown message should be sent first,
+ * and destroy() should only be called after the wallet has
+ * sent a response to the shutdown message.
+ */
+//void
+//TALER_WALLET_destroy(struct TALER_WALLET_Instance *twi);
+
+/**
+ * Handler for messages that should be logged.
+ *
+ * @param stream NOT YET IMPLEMENTED: indicator for the stream that
+ *               the message is coming from,
+ */
+typedef void (*TALER_LogFn)(void *cls, int stream, const char *msg);
+
+/**
+ * Redirect stderr and stdout to a function.
+ *
+ * Workaround for platforms where stderr is not visible in logs.
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+TALER_start_redirect_std(TALER_LogFn logfn, void *cls);
+
+
+/**
+ * Set the HTTP client implementation to be used by the wallet.
+ *
+ * @param instance wallet-core instance
+ * @param HTTP client implementation
+ */
+void
+TALER_set_http_client_implementation(struct TALER_WALLET_Instance *instance,
+                                     struct JSHttpClientImplementation *impl);
+
+#endif /*_TALER_WALLET_LIB_H */
diff --git a/xcode/FTalerWalletcore-Bridging-Header.h 
b/xcode/FTalerWalletcore-Bridging-Header.h
index 83a9bf6..a93d412 100644
--- a/xcode/FTalerWalletcore-Bridging-Header.h
+++ b/xcode/FTalerWalletcore-Bridging-Header.h
@@ -1 +1 @@
-#import "taler_wallet_core_lib.h"
+#import "taler_wallet_core_lib_preprocessed.h"

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]