diff --git a/src/http.c b/src/http.c index 8c49f71b..f8d7ca6c 100644 --- a/src/http.c +++ b/src/http.c @@ -170,7 +170,8 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d if (connection->body_length == connection->content_length) { - tf_http_request_t request = + tf_http_request_t* request = tf_malloc(sizeof(tf_http_request_t)); + *request = (tf_http_request_t) { .connection = connection, .phase = k_http_callback_phase_headers_received, @@ -180,7 +181,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d .headers_count = connection->headers_length, .user_data = connection->user_data, }; - connection->callback(&request); + connection->callback(request); _http_reset_connection(connection); } } @@ -387,8 +388,15 @@ static const char* _http_status_text(int status) { switch (status) { + case 101: return "Switching Protocols"; case 200: return "OK"; + case 303: return "See other"; + case 304: return "Not Modified"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 403: return "Forbidden"; case 404: return "File not found"; + case 500: return "Internal server error"; default: return "Unknown"; } } @@ -455,6 +463,7 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header *shutdown_request = (uv_shutdown_t) { .data = request }; uv_shutdown(shutdown_request, (uv_stream_t*)&request->connection->tcp, _http_on_shutdown); } + tf_free(request); } size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data) diff --git a/src/httpd.js.c b/src/httpd.js.c index 998d2439..f26293cb 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -6,7 +6,14 @@ #include "task.h" #include "util.js.h" +#include "picohttpparser.h" + +#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32) +#include +#endif + static JSClassID _httpd_class_id; +static JSClassID _httpd_response_class_id; typedef struct _http_handler_data_t { @@ -14,14 +21,111 @@ typedef struct _http_handler_data_t JSValue callback; } http_handler_data_t; +static JSValue _httpd_response_write_head(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + JS_SetPropertyStr(context, this_val, "response_status", JS_DupValue(context, argv[0])); + JS_SetPropertyStr(context, this_val, "response_headers", JS_DupValue(context, argv[1])); + return JS_UNDEFINED; +} + +static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_response_class_id); + size_t length = 0; + const void* data = NULL; + JSValue buffer; + if (JS_IsString(argv[0])) + { + data = JS_ToCStringLen(context, &length, argv[0]); + } + else if ((data = tf_util_try_get_array_buffer(context, &length, argv[0])) != 0) + { + } + else + { + size_t offset; + size_t size; + size_t element_size; + buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &size, &element_size); + if (!JS_IsException(buffer)) + { + data = tf_util_try_get_array_buffer(context, &length, buffer); + } + } + JSValue response_status = JS_GetPropertyStr(context, this_val, "response_status"); + int status = 0; + JS_ToInt32(context, &status, response_status); + JS_FreeValue(context, response_status); + + const char** headers = NULL; + int headers_length = 0; + JSValue response_headers = JS_GetPropertyStr(context, this_val, "response_headers"); + JSPropertyEnum* ptab; + uint32_t plen; + JS_GetOwnPropertyNames(context, &ptab, &plen, response_headers, JS_GPN_STRING_MASK); + headers = alloca(sizeof(const char*) * plen * 2); + headers_length = plen; + for (uint32_t i = 0; i < plen; ++i) + { + JSValue key = JS_AtomToString(context, ptab[i].atom); + JSPropertyDescriptor desc; + JSValue key_value = JS_NULL; + if (JS_GetOwnProperty(context, &desc, response_headers, ptab[i].atom) == 1) + { + key_value = desc.value; + JS_FreeValue(context, desc.setter); + JS_FreeValue(context, desc.getter); + } + headers[i * 2 + 0] = JS_ToCString(context, key); + headers[i * 2 + 1] = JS_ToCString(context, key_value); + } + + tf_http_respond(request, status, headers, headers_length, data, length); + + for (int i = 0; i < headers_length * 2; i++) + { + JS_FreeCString(context, headers[i]); + } + for (uint32_t i = 0; i < plen; ++i) + { + JS_FreeAtom(context, ptab[i].atom); + } + js_free(context, ptab); + JS_FreeValue(context, buffer); + return JS_UNDEFINED; +} + static void _httpd_callback(tf_http_request_t* request) { - tf_printf("httpd_callback!\n"); http_handler_data_t* data = request->user_data; - JSValue response = JS_Call(data->context, data->callback, JS_UNDEFINED, 0, NULL); - tf_printf("%d %d\n", JS_IsUndefined(response), JS_IsException(response)); - tf_util_report_error(data->context, response); - JS_FreeValue(data->context, response); + JSContext* context = data->context; + JSValue request_object = JS_NewObject(context); + JS_SetPropertyStr(context, request_object, "uri", JS_NewString(context, request->path)); + JSValue headers = JS_NewObject(context); + JS_SetPropertyStr(context, request_object, "headers", headers); + for (int i = 0; i < request->headers_count; i++) + { + JS_SetPropertyStr(context, headers, request->headers[i].name, JS_NewString(context, request->headers[i].value)); + } + + JSValue client = JS_NewObject(context); + JS_SetPropertyStr(context, client, "tls", JS_FALSE); + JS_SetPropertyStr(context, request_object, "client", client); + + JSValue response_object = JS_NewObjectClass(context, _httpd_response_class_id); + JS_SetOpaque(response_object, request); + JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2)); + JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1)); + JSValue args[] = + { + request_object, + response_object, + }; + JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args); + tf_util_report_error(context, response); + JS_FreeValue(context, request_object); + JS_FreeValue(context, response_object); + JS_FreeValue(context, response); } static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) @@ -31,7 +135,6 @@ static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, J http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); *data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; tf_http_add_handler(http, pattern, _httpd_callback, data); - tf_printf("HTTPD_ALL: %s\n", pattern); JS_FreeCString(context, pattern); return JS_UNDEFINED; @@ -59,6 +162,7 @@ void _httpd_finalizer(JSRuntime* runtime, JSValue value) void tf_httpd_register(JSContext* context) { JS_NewClassID(&_httpd_class_id); + JS_NewClassID(&_httpd_response_class_id); JSClassDef def = { .class_name = "Httpd",