Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce MG_EV_HTTP_HDRS #2634

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading