From cec06eb25c261632677ba94c67a04eafdf96bd7b Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Fri, 1 Mar 2024 23:29:12 +0000 Subject: [PATCH] Introduce MG_EV_HTTP_HDRS --- examples/file-upload-single-post/main.c | 71 ++++++++++++++----------- mongoose.c | 7 +-- mongoose.h | 3 +- src/event.h | 3 +- src/http.c | 7 +-- 5 files changed, 52 insertions(+), 39 deletions(-) diff --git a/examples/file-upload-single-post/main.c b/examples/file-upload-single-post/main.c index c3be1382c8..cb208d9fc2 100644 --- a/examples/file-upload-single-post/main.c +++ b/examples/file-upload-single-post/main.c @@ -2,43 +2,52 @@ // All rights reserved // // Streaming upload example. Demonstrates how to use MG_EV_READ events -// to get large payload in smaller chunks. To test, use curl utility: +// to save a large file without buffering it fully in memory. // // curl http://localhost:8000/upload?name=a.txt --data-binary @large_file.txt #include "mongoose.h" -// HTTP request handler function. It implements the following endpoints: -// /upload - Saves the next file chunk -// all other URI - serves web_root/ directory -static void fn(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { - // Parse the incoming data ourselves. If we can parse the request, - // store two size_t variables in the c->data: expected len and recv len. - size_t *data = (size_t *) c->data; - if (data[0]) { // Already parsed, simply print received data - data[1] += c->recv.len; - MG_INFO(("Got chunk len %lu, %lu total", c->recv.len, data[1])); - c->recv.len = 0; // And cleanup the receive buffer. Streaming! - if (data[1] >= data[0]) mg_http_reply(c, 200, "", "ok\n"); - } else { - struct mg_http_message hm; - int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); - if (n < 0) mg_error(c, "Bad response"); - if (n > 0) { - if (mg_http_match_uri(&hm, "/upload")) { - MG_INFO(("Got chunk len %lu", c->recv.len - n)); - data[0] = hm.body.len; - data[1] = c->recv.len - n; - if (data[1] >= data[0]) mg_http_reply(c, 200, "", "ok\n"); - } else { - struct mg_http_serve_opts opts = {.root_dir = "web_root"}; - mg_http_serve_dir(c, &hm, &opts); - } - } +static void handle_uploads(struct mg_connection *c, int ev, void *ev_data) { + size_t *data = (size_t *) c->data; + + // Catch /upload requests early, without buffering whole body: + if (ev == MG_EV_HTTP_HDRS) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/upload"), NULL)) { + // When we receive MG_EV_HTTP_HDRS event, that means we've received all + // HTTP headers but not necessarily full HTTP body. We save HTTP body + // length in data[0]: + // data[0] contains expected number of bytes + // data[1] contains received number of bytes + data[0] = hm->body.len; // Store number of bytes we expect + mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers + c->pfn = NULL; // Silence HTTP protocol handler, we'll use MG_EV_READ + } + } + + // Catch uploaded file data for both MG_EV_READ and MG_EV_HTTP_HDRS + if (data[0] > 0 && c->recv.len > 0) { + data[1] += c->recv.len; + // MG_DEBUG(("Got chunk len %lu, %lu total", c->recv.len, data[1])); + c->recv.len = 0; // Delete received data + if (data[1] >= data[0]) { + // Uploaded everything. Send response back + MG_INFO(("Uploaded %lu bytes", data[1])); + mg_http_reply(c, 200, NULL, "%lu ok\n", data[1]); + c->is_draining = 1; // Close us when response gets sent } } - (void) ev_data; +} + +static void fn(struct mg_connection *c, int ev, void *ev_data) { + handle_uploads(c, ev, ev_data); + + // Non-upload requests, we serve normally + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_serve_opts opts = {.root_dir = "web_root"}; + mg_http_serve_dir(c, ev_data, &opts); + } } int main(void) { @@ -46,7 +55,7 @@ int main(void) { mg_mgr_init(&mgr); mg_log_set(MG_LL_DEBUG); // Set debug log level - mg_listen(&mgr, "http://localhost:8000", fn, NULL); + mg_http_listen(&mgr, "http://localhost:8000", fn, NULL); for (;;) mg_mgr_poll(&mgr, 50); mg_mgr_free(&mgr); diff --git a/mongoose.c b/mongoose.c index 59acc0daab..93b4ca3586 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3219,8 +3219,9 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { c->recv.len = 0; return; } - if (n == 0) break; // Request is not buffered yet - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length + if (n == 0) break; // Request is not buffered yet + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); } @@ -3237,7 +3238,7 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { bool is_response = mg_ncasecmp(hm.method.ptr, "HTTP/", 5) == 0; bool require_content_len = false; if (!is_response && (mg_vcasecmp(&hm.method, "POST") == 0 || - mg_vcasecmp(&hm.method, "PUT") == 0)) { + mg_vcasecmp(&hm.method, "PUT") == 0)) { // POST and PUT should include an entity body. Therefore, they should // contain a Content-length header. Other requests can also contain a // body, but their content has no defined semantics (RFC 7231) diff --git a/mongoose.h b/mongoose.h index 65dbb39309..62d3f46414 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2113,7 +2113,8 @@ enum { MG_EV_READ, // Data received from socket long *bytes_read MG_EV_WRITE, // Data written to socket long *bytes_written MG_EV_CLOSE, // Connection closed NULL - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message * + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message * + MG_EV_HTTP_MSG, // Full HTTP request/response struct mg_http_message * MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * diff --git a/src/event.h b/src/event.h index 1241437925..0a08dc337b 100644 --- a/src/event.h +++ b/src/event.h @@ -17,7 +17,8 @@ enum { MG_EV_READ, // Data received from socket long *bytes_read MG_EV_WRITE, // Data written to socket long *bytes_written MG_EV_CLOSE, // Connection closed NULL - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message * + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message * + MG_EV_HTTP_MSG, // Full HTTP request/response struct mg_http_message * MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * diff --git a/src/http.c b/src/http.c index 1ffbf911a0..93ab4100a3 100644 --- a/src/http.c +++ b/src/http.c @@ -1003,8 +1003,9 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { c->recv.len = 0; return; } - if (n == 0) break; // Request is not buffered yet - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length + if (n == 0) break; // Request is not buffered yet + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); } @@ -1021,7 +1022,7 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { bool is_response = mg_ncasecmp(hm.method.ptr, "HTTP/", 5) == 0; bool require_content_len = false; if (!is_response && (mg_vcasecmp(&hm.method, "POST") == 0 || - mg_vcasecmp(&hm.method, "PUT") == 0)) { + mg_vcasecmp(&hm.method, "PUT") == 0)) { // POST and PUT should include an entity body. Therefore, they should // contain a Content-length header. Other requests can also contain a // body, but their content has no defined semantics (RFC 7231)