fix tls socket, add documentation

This commit is contained in:
ading2210 2024-03-07 12:16:28 -05:00
parent 155d5ea5b6
commit 53a474430a
4 changed files with 125 additions and 46 deletions

View file

@ -51,7 +51,7 @@ document.addEventListener("libcurl_load", ()=>{
}); });
``` ```
Alternatively, the `libcurl.onload` callback can be used. You may also use the, the `libcurl.onload` callback, which can be useful for running libcurl.js inside a web worker.
```js ```js
libcurl.onload = () => { libcurl.onload = () => {
console.log("libcurl.js ready!"); console.log("libcurl.js ready!");
@ -76,7 +76,35 @@ Most of the standard Fetch API's features are supported, with the exception of:
Note that there is a hard limit of 50 active TCP connections due to emscripten limitations. Note that there is a hard limit of 50 active TCP connections due to emscripten limitations.
### Creating WebSocket Connections: ### Creating WebSocket Connections:
To use WebSockets, create a `libcurl.WebSocket` object, which works identically to the regular [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. To use WebSockets, create a `libcurl.CurlWebSocket` object, which takes the following arguments:
- `url` - The Websocket URL.
- `protocols` - A optional list of websocket subprotocols, as an array of strings.
- `options` - An optional object with extra settings to pass to curl.
The valid WebSocket options are:
- `headers` - HTTP request headers for the websocket handshake.
- `verbose` - A boolean flag that toggles the verbose libcurl output. This verbose output will be passed to the function defined in `libcurl.stderr`, which is `console.warn` by default.
The following callbacks are available:
- `CurlWebSocket.onopen` - Called when the websocket is successfully connected.
- `CurlWebSocket.message` - Called when a websocket message is received from the server. The data is passed to the first argument of the function, and it will be either a `Uint8Array` or a string, depending on the type of message.
- `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`.
```js
let ws = new libcurl.CurlWebSocket("wss://echo.websocket.org", [], {verbose: 1});
ws.onopen = () => {
console.log("ws connected!");
ws.send("hello".repeat(100));
};
ws.onmessage = (data) => {
console.log(data);
};
```
You can also use the `libcurl.WebSocket` object, which works identically to the regular [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object. It uses the same arguments as the simpler `CurlWebSocket` API.
```js ```js
let ws = new libcurl.WebSocket("wss://echo.websocket.org"); let ws = new libcurl.WebSocket("wss://echo.websocket.org");
ws.addEventListener("open", () => { ws.addEventListener("open", () => {
@ -88,7 +116,32 @@ ws.addEventListener("message", (event) => {
}); });
``` ```
### Changing the Websocket URL: ### Using TLS Sockets:
Raw TLS sockets can be created with the `libcurl.TLSSocket` class, which takes the following arguments:
- `host` - The hostname to connect to.
- `port` - The TCP port to connect to.
- `options` - An optional object with extra settings to pass to curl.
The valid TLS socket options are:
- `verbose` - A boolean flag that toggles the verbose libcurl output.
The callbacks work similarly to the `libcurl.CurlWebSocket` object, with the main difference being that the `onmessage` callback always returns a `Uint8Array`.
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`.
```js
let socket = new libcurl.TLSSocket("ading.dev", 443, {verbose: 1});
socket.onopen = () => {
console.log("socket connected!");
let str = "GET /all HTTP/1.1\r\nHost: ading.dev\r\nConnection: close\r\n\r\n";
socket.send(new TextEncoder().encode(str));
};
socket.onmessage = (data) => {
console.log(new TextDecoder().decode(data));
};
```
### Changing the Websocket Proxy URL:
You can change the URL of the websocket proxy by using `libcurl.set_websocket`. You can change the URL of the websocket proxy by using `libcurl.set_websocket`.
```js ```js
libcurl.set_websocket("ws://localhost:6001/"); libcurl.set_websocket("ws://localhost:6001/");
@ -96,7 +149,7 @@ libcurl.set_websocket("ws://localhost:6001/");
If the websocket proxy URL is not set and one of the other API functions is called, an error will be thrown. Note that this URL must end with a trailing slash. If the websocket proxy URL is not set and one of the other API functions is called, an error will be thrown. Note that this URL must end with a trailing slash.
### Getting Libcurl's Output: ### Getting Libcurl's Output:
If you want more information about a connection, you can pass the `_libcurl_verbose` argument to the `libcurl.fetch` function. If you want more information about a connection, you can pass the `_libcurl_verbose` argument to the `libcurl.fetch` function. These are the same messages that you would see if you ran `curl -v` on the command line.
```js ```js
await libcurl.fetch("https://example.com", {_libcurl_verbose: 1}); await libcurl.fetch("https://example.com", {_libcurl_verbose: 1});
``` ```

View file

@ -193,12 +193,9 @@ async function libcurl_fetch(url, params={}) {
function set_websocket_url(url) { function set_websocket_url(url) {
websocket_url = url; websocket_url = url;
if (!Module.websocket && ENVIRONMENT_IS_WEB) { if (Module.websocket) {
document.addEventListener("libcurl_load", () => { Module.websocket.url = url;
set_websocket_url(url);
});
} }
else Module.websocket.url = url;
} }
function get_version() { function get_version() {

View file

@ -1,11 +1,21 @@
//currently broken //currently broken
class TLSSocket { class TLSSocket {
constructor(hostname, port, debug) { constructor(hostname, port, options={}) {
check_loaded(true);
this.hostname = hostname; this.hostname = hostname;
this.port = port; this.port = port;
this.url = `https://${hostname}:${port}`; this.url = `https://${hostname}:${port}`;
this.debug = debug; this.options = options;
this.onopen = () => {};
this.onerror = () => {};
this.onmessage = () => {};
this.onclose = () => {};
this.connected = false;
this.event_loop = null;
this.connect(); this.connect();
} }
@ -13,48 +23,56 @@ class TLSSocket {
connect() { connect() {
let data_callback = () => {}; let data_callback = () => {};
let finish_callback = (error, response_info) => { let finish_callback = (error, response_info) => {
this.status = this.OPEN;
if (error === 0) { if (error === 0) {
this.open_callback(); this.connected = true;
this.recv_loop(); this.event_loop = setInterval(() => {
let data = this.recv();
if (data != null) this.onmessage(data);
}, 0);
this.onopen();
} }
else { else {
this.cleanup(error); this.cleanup(error);
} }
} }
let options = { let request_options = {
_connect_only: 1, _connect_only: 1,
} }
if (this.debug) options._libcurl_verbose = 1; if (this.options.verbose) {
request_options._libcurl_verbose = 1;
}
this.http_handle = perform_request(this.url, options, data_callback, finish_callback, null); this.http_handle = perform_request(this.url, request_options, data_callback, finish_callback, null);
} }
custom_recv() { recv() {
let buffer_size = 64*1024; let buffer_size = 64*1024;
let result_ptr = _recv_from_socket(this.http_handle, buffer_size); let result_ptr = _recv_from_socket(this.http_handle, buffer_size);
let data_ptr = _get_result_buffer(result_ptr); let data_ptr = _get_result_buffer(result_ptr);
let result_code = _get_result_code(result_ptr); let result_code = _get_result_code(result_ptr);
let result_closed = _get_result_closed(result_ptr);
if (result_code == 0) { //CURLE_OK - data received if (result_code === 0 && !result_closed) { //CURLE_OK - data received
if (_get_result_closed(result_ptr)) {
this.close_callback();
return;
}
let data_size = _get_result_size(result_ptr); let data_size = _get_result_size(result_ptr);
let data_heap = Module.HEAPU8.subarray(data_ptr, data_ptr + data_size); let data_heap = Module.HEAPU8.subarray(data_ptr, data_ptr + data_size);
let data = new Uint8Array(data_heap); let data = new Uint8Array(data_heap);
this.onmessage(data)
}
else if (result_code === 0 && result_closed) {
this.cleanup();
}
let message_event = new MessageEvent("message", {data: data}); else if (result_code != 81) {
this.dispatchEvent(message_event); this.cleanup(result_code);
} }
_free(data_ptr); _free(data_ptr);
_free(result_ptr); _free(result_ptr);
} }
custom_send(data_array) { send(data_array) {
if (!this.connected) return;
let data_ptr = allocate_array(data_array); let data_ptr = allocate_array(data_array);
let data_len = data_array.length; let data_len = data_array.length;
_send_to_socket(this.http_handle, data_ptr, data_len); _send_to_socket(this.http_handle, data_ptr, data_len);
@ -63,12 +81,20 @@ class TLSSocket {
cleanup(error=false) { cleanup(error=false) {
if (this.http_handle) _cleanup_handle(this.http_handle); if (this.http_handle) _cleanup_handle(this.http_handle);
else return;
clearInterval(this.event_loop); clearInterval(this.event_loop);
this.close_callback(error); this.connected = false;
if (error) {
this.onerror(error);
}
else {
this.onclose();
}
} }
custom_close() { close() {
this.cleanup(); this.cleanup();
this.status = this.CLOSED;
} }
} }

View file

@ -33,7 +33,6 @@ class CurlWebSocket {
this.onopen(); this.onopen();
} }
else { else {
this.status = this.CLOSED;
this.cleanup(error); this.cleanup(error);
} }
} }
@ -54,16 +53,14 @@ class CurlWebSocket {
let result_ptr = _recv_from_websocket(this.http_handle, buffer_size); let result_ptr = _recv_from_websocket(this.http_handle, buffer_size);
let data_ptr = _get_result_buffer(result_ptr); let data_ptr = _get_result_buffer(result_ptr);
let result_code = _get_result_code(result_ptr); let result_code = _get_result_code(result_ptr);
let result_closed = _get_result_closed(result_ptr);
let returned_data = null; let returned_data = null;
function free_result() { //CURLE_OK - data received
_free(data_ptr); if (result_code === 0 && !result_closed) {
_free(result_ptr);
}
if (result_code === 0) { //CURLE_OK - data received
if (_get_result_closed(result_ptr)) { if (_get_result_closed(result_ptr)) {
free_result(); _free(data_ptr);
_free(result_ptr);
this.cleanup(); this.cleanup();
return returned_data; return returned_data;
} }
@ -85,18 +82,26 @@ class CurlWebSocket {
} }
} }
} }
//CURLE_GOT_NOTHING, CURLE_RECV_ERROR, CURLE_SEND_ERROR - socket closed // websocket was cleanly closed by the server
else if (result_code === 52 || result_code === 55 || result_code === 56) { else if (result_code === 0 && result_closed) {
this.cleanup(); this.cleanup();
} }
//code is not CURLE_AGAIN - an error must have occurred
else if (result_code !== 81) {
this.cleanup(result_code);
}
free_result(); _free(data_ptr);
_free(result_ptr);
return returned_data; return returned_data;
} }
cleanup(error=false) { cleanup(error=0) {
if (this.http_handle) _cleanup_handle(this.http_handle); if (this.http_handle) _cleanup_handle(this.http_handle);
else return;
clearInterval(this.event_loop); clearInterval(this.event_loop);
this.connected = false; this.connected = false;
@ -110,9 +115,7 @@ class CurlWebSocket {
send(data) { send(data) {
let is_text = typeof data === "string"; let is_text = typeof data === "string";
if (!this.connected) { if (!this.connected) return;
throw new DOMException("websocket not connected");
}
if (is_text) { if (is_text) {
data = new TextEncoder().encode(data); data = new TextEncoder().encode(data);