diff --git a/CHANGELOG.md b/CHANGELOG.md index 766c69b..069d43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ # Libcurl.js Changelog: -## v0.6.0 (3/14/24): +## v0.6.0 (3/19/24): - Refactor JS and C code - Allow for multiple sessions with separate connection pools +- Switch to wolfSSL instead of OpenSSL for significantly smaller binaries +- Add support for sending cookies automatically -## v0.5.3 (3/9/24): +## v0.5.3 (3/11/24): - Update Wisp client and server, which improves error handling - Expose the wisp-client-js version in the API diff --git a/README.md b/README.md index 62336b7..f34e415 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This is an experimental port of [libcurl](https://curl.se/libcurl/) to WebAssemb - [Javascript API](#javascript-api) * [Importing the Library](#importing-the-library) * [Making HTTP Requests](#making-http-requests) + * [Creating New HTTP Sessions](#creating-new-http-sessions) * [Creating WebSocket Connections](#creating-websocket-connections) * [Using TLS Sockets](#using-tls-sockets) * [Changing the Network Transport](#changing-the-network-transport) @@ -94,9 +95,10 @@ console.log(await r.text()); Most of the standard Fetch API's features are supported, with the exception of: - CORS enforcement -- Sending credentials/cookies automatically - Caching +Sending cookies is supported, but they will not be automatically sent unless you create a new HTTP session, which is covered in the next section. + The response may contain multiple HTTP headers with the same name, which the `Headers` object isn't able to properly represent. If this matters to you, use `response.raw_headers`, which is an array of key value pairs, instead of `response.headers`. There is support for streaming the response body using a `ReadableStream`, as well as canceling requests using an `AbortSignal`. All requests made using this method share the same connection pool, which has a limit of 50 active TCP connections. ### Creating New HTTP Sessions: @@ -105,7 +107,13 @@ To create new sessions for HTTP requests, use the `libcurl.HTTPSession` class. T The valid HTTP session settings are: - `enable_cookies` - A boolean which indicate whether or not cookies should be persisted within the session. -- `cookie_jar` - A blob containing the data in the cookie jar file. This should have been exported from a previous session. +- `cookie_jar` - A string containing the data in the cookie jar file. This should have been exported from a previous session. For more information on the format for this file, see the [curl documentation](https://curl.se/docs/http-cookies.html). + +Each HTTP session has the following methods available: +- `fetch` - Identical to the `libcurl.fetch` function but only creates connections in this session. +- `set_connections` - Set the connection limits. This takes two arguments, the first being the limit for the connection cache, and the second being the max number of active connections. +- `export_cookies` - Export any cookies which were recorded in the session. This will return an empty string if cookies are disabled or no cookies have been set yet. +- `close` - Close all connections and clean up the session. You must call this after you are done using the session, otherwise it will leak memory. ### Creating WebSocket Connections: To use WebSockets, create a `libcurl.CurlWebSocket` object, which takes the following arguments: @@ -123,7 +131,9 @@ The following callbacks are available: - `CurlWebSocket.onclose` - Called when the websocket is cleanly closed with no error. - `CurlWebSocket.onerror` - Called when the websocket encounters an unexpected error. The [error code](https://curl.se/libcurl/c/libcurl-errors.html) is passed to the first argument of the function. -The `CurlWebSocket.send` function can be used to send data to the websocket. The only argument is the data that is to be sent, which must be either a string or a `Uint8Array`. +The `CurlWebSocket.send` function can be used to send data to the websocket. The only argument is the data that is to be sent, which must be either a string or a `Uint8Array`. + +You can call `CurlWebSocket.close` to close and clean up the websocket. ```js let ws = new libcurl.CurlWebSocket("wss://echo.websocket.org", [], {verbose: 1}); @@ -161,6 +171,8 @@ The callbacks work similarly to the `libcurl.CurlWebSocket` object, with the mai The `TLSSocket.send` function can be used to send data to the socket. The only argument is the data that is to be sent, which must be a `Uint8Array`. +You can use the `TLSSocket.close` function to close the socket. + ```js let socket = new libcurl.TLSSocket("ading.dev", 443, {verbose: 1}); socket.onopen = () => { diff --git a/client/exported_funcs.txt b/client/exported_funcs.txt index 240c534..dbcb9ae 100644 --- a/client/exported_funcs.txt +++ b/client/exported_funcs.txt @@ -14,6 +14,7 @@ get_error_str http_set_options http_get_info +http_set_cookie_jar websocket_set_options recv_from_websocket @@ -30,7 +31,4 @@ tls_socket_set_options recv_from_socket send_to_socket -ftp_set_options -ftp_set_cmd - free \ No newline at end of file diff --git a/client/javascript/http.js b/client/javascript/http.js index 25db8cb..7a1f3fc 100644 --- a/client/javascript/http.js +++ b/client/javascript/http.js @@ -1,8 +1,41 @@ class HTTPSession extends CurlSession { - constructor(options) { + constructor(options={}) { super(); this.options = options; + this.set_connections(50, 40); + this.import_cookies(); + } + + import_cookies() { + if (this.options.enable_cookies) { + this.cookie_filename = `/cookies_${Math.random()}.txt`; + if (this.options.cookie_jar) { + FS.writeFile(this.cookie_filename, this.options.cookie_jar); + } + } + } + + export_cookies() { + if (!this.cookie_filename) return ""; + + try { + return FS.readFile(this.cookie_filename, {encoding: "utf8"}); + } + catch (e) { + if (e.errno === 44) return ""; + throw e; + } + } + + close() { + if (this.cookie_filename) { + try { + FS.unlink(this.cookie_filename); + } + catch (e) {} + } + super.close(); } request_async(url, params, body) { @@ -38,6 +71,10 @@ class HTTPSession extends CurlSession { http_handle = this.stream_response(url, headers_callback, finish_callback, params.signal); c_func(_http_set_options, [http_handle, params_json, body, body_length]); + if (this.cookie_filename && params.credentials !== "omit") { + c_func(_http_set_cookie_jar, [http_handle, this.cookie_filename]); + } + this.start_request(http_handle); }); } diff --git a/client/javascript/session.js b/client/javascript/session.js index 04b569d..1c4948f 100644 --- a/client/javascript/session.js +++ b/client/javascript/session.js @@ -146,8 +146,6 @@ class CurlSession { headers_callback(stream); } - console.log(error); - try { stream_controller.close(); } diff --git a/client/libcurl/http.c b/client/libcurl/http.c index ffdbc3e..ace91c0 100644 --- a/client/libcurl/http.c +++ b/client/libcurl/http.c @@ -1,5 +1,6 @@ #include #include +#include #include "cjson/cJSON.h" #include "curl/curl.h" @@ -65,6 +66,11 @@ void http_set_options(CURL* http_handle, const char* json_params, const char* bo request_info->headers_list = headers_list; } +void http_set_cookie_jar(CURL* http_handle, const char* filename) { + curl_easy_setopt(http_handle, CURLOPT_COOKIEFILE, filename); + curl_easy_setopt(http_handle, CURLOPT_COOKIEJAR, filename); +} + char* http_get_info(CURL* http_handle) { struct RequestInfo *request_info = get_request_info(http_handle); diff --git a/client/tools/wolfssl.sh b/client/tools/wolfssl.sh index ca40dca..d470e84 100755 --- a/client/tools/wolfssl.sh +++ b/client/tools/wolfssl.sh @@ -16,7 +16,7 @@ git clone -b v5.6.6-stable --depth=1 https://github.com/wolfSSL/wolfssl wolfssl cd wolfssl autoreconf -fi -CFLAGS="-Oz -DSP_WORD_SIZE=32" emconfigure ./configure --prefix=$PREFIX --enable-curl --enable-static --disable-shared --host=i686-linux --disable-examples --disable-asm --enable-sni --enable-alpn --enable-truncatedhmac --enable-oldtls --enable-tlsv12 --enable-all-crypto --disable-asyncthreads --disable-threadlocal --enable-tlsx +CFLAGS="-Oz -DSP_WORD_SIZE=32 -DWOLFSSL_NO_ATOMICS" emconfigure ./configure --prefix=$PREFIX --enable-curl --enable-static --disable-shared --host=i686-linux --disable-examples --disable-asm --enable-sni --enable-alpn --enable-truncatedhmac --enable-oldtls --enable-tlsv12 --enable-all-crypto --disable-asyncthreads --disable-threadlocal --enable-tlsx emmake make -j$CORE_COUNT make install