From 6cb70c6760eebfa80bb8e2bec4cbc70957bf5e1c Mon Sep 17 00:00:00 2001 From: ading2210 Date: Fri, 5 Jan 2024 02:27:18 -0500 Subject: [PATCH] return a response object from libcurl_fetch --- client/build.sh | 4 +- client/errors.h | 1 - client/main.c | 52 ++++++++++++++++++++----- client/main.js | 101 ++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 20 deletions(-) delete mode 100644 client/errors.h diff --git a/client/build.sh b/client/build.sh index 13aaafa..0eb7ddd 100755 --- a/client/build.sh +++ b/client/build.sh @@ -7,8 +7,8 @@ LIB_DIR="build/curl-wasm/lib/" CACERT_FILE="cacert.pem" OUT_FILE="out/libcurl.js" -EXPORTED_FUNCS="_main,_perform_request,_copy_bytes" -RUNTIME_METHODS="addFunction,removeFunction,allocate" +EXPORTED_FUNCS="_main,_perform_request" +RUNTIME_METHODS="addFunction,removeFunction,allocate,ALLOC_NORMAL" COMPILER_OPTIONS="-o $OUT_FILE -lcurl -lssl -lcrypto -lcjson -I $INCLUDE_DIR -L $LIB_DIR" EMSCRIPTEN_OPTIONS="-lwebsocket.js -sWEBSOCKET_URL=wss://debug.ading.dev/ws -sASYNCIFY -sALLOW_TABLE_GROWTH -sEXPORTED_FUNCTIONS=$EXPORTED_FUNCS -sEXPORTED_RUNTIME_METHODS=$RUNTIME_METHODS --preload-file $CACERT_FILE" diff --git a/client/errors.h b/client/errors.h deleted file mode 100644 index e2434ee..0000000 --- a/client/errors.h +++ /dev/null @@ -1 +0,0 @@ -#define ERROR_REDIRECT_DISALLOWED -1 \ No newline at end of file diff --git a/client/main.c b/client/main.c index c54a988..b056028 100644 --- a/client/main.c +++ b/client/main.c @@ -5,10 +5,13 @@ #include "curl/curl.h" #include "cjson/cJSON.h" -#include "errors.h" +#include "curl/easy.h" +#include "curl/header.h" typedef void(*DataCallback)(char* chunk_ptr, int chunk_size); -typedef void(*EndCallback)(int error); +typedef void(*EndCallback)(int error, char* response_json); + +#define ERROR_REDIRECT_DISALLOWED -1 int write_function(void *data, size_t size, size_t nmemb, DataCallback data_callback) { long real_size = size * nmemb; @@ -40,12 +43,15 @@ void perform_request(const char* url, const char* json_params, DataCallback data curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, &write_function); curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, data_callback); + //some default options + curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1); + //parse json options - cJSON* json = cJSON_Parse(json_params); + cJSON* request_json = cJSON_Parse(json_params); cJSON* item = NULL; struct curl_slist* headers_list = NULL; - cJSON_ArrayForEach(item, json) { + cJSON_ArrayForEach(item, request_json) { char* key = item->string; if (strcmp(key, "_libcurl_verbose") == 0) { @@ -81,13 +87,9 @@ void perform_request(const char* url, const char* json_params, DataCallback data else if (strcmp(item->valuestring, "manual") == 0) { curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 0); } - else { - //follow by default - curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1); - } } } - cJSON_Delete(json); + cJSON_Delete(request_json); //add post data if specified if (body != NULL) { @@ -122,14 +124,44 @@ void perform_request(const char* url, const char* json_params, DataCallback data if (abort_on_redirect && response_code / 100 == 3) { error = ERROR_REDIRECT_DISALLOWED; } + + //create new json object with response info + cJSON* response_json = cJSON_CreateObject(); + + cJSON* status_item = cJSON_CreateNumber(response_code); + cJSON_AddItemToObject(response_json, "status", status_item); + + char* response_url; + curl_easy_getinfo(http_handle, CURLINFO_EFFECTIVE_URL, &response_url); + cJSON* url_item = cJSON_CreateString(response_url); + cJSON_AddItemToObject(response_json, "url", url_item); + + cJSON* headers_item = cJSON_CreateObject(); + struct curl_header *prev_header = NULL; + struct curl_header *header = NULL; + while ((header = curl_easy_nextheader(http_handle, CURLH_HEADER, -1, prev_header))) { + cJSON* header_entry = cJSON_CreateString(header->value); + cJSON_AddItemToObject(headers_item, header->name, header_entry); + prev_header = header; + } + cJSON_AddItemToObject(response_json, "headers", headers_item); + + long redirect_count; + curl_easy_getinfo(http_handle, CURLINFO_REDIRECT_COUNT, &redirect_count); + cJSON* redirects_item = cJSON_CreateBool(redirect_count > 0); + cJSON_AddItemToObject(response_json, "redirected", redirects_item); + + char* response_json_str = cJSON_Print(response_json); + cJSON_Delete(response_json); + //clean up curl curl_slist_free_all(headers_list); curl_multi_remove_handle(multi_handle, http_handle); curl_easy_cleanup(http_handle); curl_multi_cleanup(multi_handle); curl_global_cleanup(); - (*end_callback)(error); + (*end_callback)(error, response_json_str); } char* copy_bytes(const char* ptr, const int size) { diff --git a/client/main.js b/client/main.js index bf3a4a8..c464d67 100644 --- a/client/main.js +++ b/client/main.js @@ -1,6 +1,72 @@ const cacert_path = "./out/cacert.pem"; const websocket_url = `wss://${location.hostname}/ws`; +const status_messages = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi-Status", + 208: "Already Reported", + 226: "IM Used", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 306: "Switch Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Content", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required" +} + function is_str(obj) { return typeof obj === 'string' || obj instanceof String; } @@ -15,7 +81,6 @@ function allocate_array(array) { //make emscripten shut up about unsupported syscalls function silence_errs() { - window._err = window.err; window.err = function() { let arg = arguments[0]; @@ -50,14 +115,18 @@ function perform_request(url, params, js_data_callback, js_end_callback, body=nu body_length = body.length; } - let end_callback = (error) => { + let end_callback = (error, response_json_ptr) => { + let response_json = UTF8ToString(response_json_ptr); + let response_info = JSON.parse(response_json); + Module.removeFunction(end_callback_ptr); Module.removeFunction(data_callback_ptr); if (body_ptr) _free(body_ptr); _free(url_ptr); + _free(response_json_ptr); if (error != 0) console.error("request failed with error code " + error); - js_end_callback(error); + js_end_callback(error, response_info); } let data_callback = (chunk_ptr, chunk_size) => { @@ -66,7 +135,7 @@ function perform_request(url, params, js_data_callback, js_end_callback, body=nu js_data_callback(chunk); } - end_callback_ptr = Module.addFunction(end_callback, "vi"); + end_callback_ptr = Module.addFunction(end_callback, "vii"); data_callback_ptr = Module.addFunction(data_callback, "vii"); _perform_request(url_ptr, params_ptr, data_callback_ptr, end_callback_ptr, body_ptr, body_length); _free(params_ptr); @@ -83,6 +152,20 @@ function merge_arrays(arrays) { return new_array; } +function create_response(response_data, response_info) { + response_info.ok = response_info.status >= 200 && response_info.status < 300; + response_info.statusText = status_messages[response_info.status] || ""; + + let response_obj = new Response(response_data, response_info); + for (let key in response_info) { + Object.defineProperty(response_obj, key, { + writable: false, + value: response_info[key] + }); + } + return response_obj; +} + function libcurl_fetch(url, params={}) { let body = null; if (params.body) { @@ -104,10 +187,14 @@ function libcurl_fetch(url, params={}) { let data_callback = (new_data) => { chunks.push(new_data); }; - let finish_callback = () => { + + let finish_callback = (error, response_info) => { + if (error != 0) { + reject("libcurl.js encountered an error: " + error); + } let response_data = merge_arrays(chunks); - let response_str = new TextDecoder().decode(response_data); - resolve(response_str); + let response_obj = create_response(response_data, response_info); + resolve(response_obj); } perform_request(url, params, data_callback, finish_callback, body); })