Skip to content

Commit

Permalink
Merge pull request #2634 from cesanta/MG_EV_HTTP_HDRS
Browse files Browse the repository at this point in the history
Introduce MG_EV_HTTP_HDRS
  • Loading branch information
scaprile authored Mar 1, 2024
2 parents 73a3897 + cec06eb commit 00db56f
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 39 deletions.
71 changes: 40 additions & 31 deletions examples/file-upload-single-post/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,60 @@
// 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) {
struct mg_mgr mgr;

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);
Expand Down
7 changes: 4 additions & 3 deletions mongoose.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion mongoose.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
3 changes: 2 additions & 1 deletion src/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
7 changes: 4 additions & 3 deletions src/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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)
Expand Down

0 comments on commit 00db56f

Please sign in to comment.