mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-12 10:13:29 +00:00
Merge pull request #3703 from cesanta/up
Replace mg_http_start_* with streaming uploads
This commit is contained in:
+203
-150
@@ -977,6 +977,7 @@ bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
|
||||
|
||||
|
||||
|
||||
|
||||
#define MG_NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
|
||||
#define MG_JSON_HEADERS "Content-Type: application/json\r\n" MG_NO_CACHE_HEADERS
|
||||
|
||||
@@ -1340,31 +1341,75 @@ static inline void mg_log_http_req(struct mg_connection *c,
|
||||
c->send.buf + n));
|
||||
}
|
||||
|
||||
static void mg_dash_ota_cb(struct mg_connection *c, const char *errmsg) {
|
||||
mg_http_reply(c, errmsg ? 500 : 200, NULL, errmsg ? errmsg : "ok\n");
|
||||
c->is_draining = 1;
|
||||
}
|
||||
// State of one file upload to a dashboard file array. Owned by the upload
|
||||
// callback, which frees it in the end call
|
||||
struct mg_dash_upload {
|
||||
struct mg_mgr *mgr; // Manager, to notify WebSocket clients
|
||||
struct mg_dash *dash; // Dashboard whose file arrays get notified
|
||||
struct mg_fd *fd; // Destination file, opened by the request handler
|
||||
size_t expected; // Number of body bytes to receive
|
||||
size_t received; // Number of body bytes written
|
||||
};
|
||||
|
||||
static void mg_dash_upload_cb(struct mg_connection *c, const char *errmsg) {
|
||||
if (errmsg) {
|
||||
mg_http_reply(c, 500, NULL, "%s\n", errmsg);
|
||||
} else {
|
||||
// mg_http_start_upload() repurposes c->data for its own bookkeeping,
|
||||
// so the field set can't be cached there. Re-derive the dashboard from
|
||||
// c->fn_data instead, and notify every file-backed array: the upload
|
||||
// could belong to any of them, and re-querying get_dir() per recipient
|
||||
// is what mg_dash_send_change() does anyway (directories can be
|
||||
// user-specific)
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
struct mg_field_set *fs;
|
||||
mg_http_reply(c, 200, NULL, "ok\n");
|
||||
for (fs = dash->sets; fs != NULL; fs = fs->next) {
|
||||
if (fs->get_dir == NULL) continue;
|
||||
*fs->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
||||
mg_dash_send_change(c->mgr, fs);
|
||||
// Upload callback for mg_http_stream_body(). Notifies clients only if the whole
|
||||
// body was written. A dropped or failed upload sends no notification
|
||||
static bool mg_dash_upload_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
struct mg_dash_upload *up = (struct mg_dash_upload *) *p;
|
||||
bool ok;
|
||||
if (hm != NULL) return true; // Start, the file is already open
|
||||
if (data != NULL) { // Next chunk
|
||||
ok = up->fd->fs->wr(up->fd->fd, data->buf, data->len) == data->len;
|
||||
if (ok) up->received += data->len;
|
||||
return ok;
|
||||
}
|
||||
ok = up->received == up->expected; // End
|
||||
mg_fs_close(up->fd);
|
||||
if (ok) {
|
||||
// Notify every file-backed array: the upload could belong to any of them,
|
||||
// and re-querying get_dir() per recipient is what mg_dash_send_change()
|
||||
// does anyway (directories can be user-specific)
|
||||
struct mg_field_set *set;
|
||||
for (set = up->dash->sets; set != NULL; set = set->next) {
|
||||
if (set->get_dir == NULL) continue;
|
||||
*set->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
||||
mg_dash_send_change(up->mgr, set);
|
||||
}
|
||||
}
|
||||
c->is_draining = 1;
|
||||
mg_free(up);
|
||||
*p = NULL;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Opens dir/name and starts streaming the request body into it. Returns true
|
||||
// if streaming started, otherwise the error reply has been sent already
|
||||
static bool mg_dash_start_upload(struct mg_connection *c,
|
||||
struct mg_http_message *hm,
|
||||
struct mg_dash *dash, struct mg_fs *fs,
|
||||
const char *dir, struct mg_str name) {
|
||||
char path[MG_PATH_MAX];
|
||||
struct mg_dash_upload *up = NULL;
|
||||
struct mg_fd *fd = NULL;
|
||||
size_t n = mg_snprintf(path, sizeof(path), "%s%c%.*s", dir, MG_DIRSEP,
|
||||
(int) name.len, name.buf);
|
||||
if (n < sizeof(path)) fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
||||
if (fd != NULL) up = (struct mg_dash_upload *) mg_calloc(1, sizeof(*up));
|
||||
if (up == NULL) {
|
||||
if (fd != NULL) mg_fs_close(fd);
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Open failed\n");
|
||||
return false;
|
||||
}
|
||||
up->mgr = c->mgr;
|
||||
up->dash = dash;
|
||||
up->fd = fd;
|
||||
up->expected = hm->body.len;
|
||||
if (mg_http_stream_body(c, hm, mg_dash_upload_cb, up)) {
|
||||
return true; // The callback owns up and fd now
|
||||
}
|
||||
mg_fs_close(fd); // Chunked body or unexpected method: not streamed
|
||||
mg_free(up);
|
||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad request\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
||||
@@ -1606,6 +1651,21 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
|
||||
}
|
||||
}
|
||||
|
||||
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
|
||||
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
|
||||
static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
if (hm != NULL) { // Start
|
||||
if (!mg_ota_begin(hm->body.len)) return false;
|
||||
*p = (void *) (ptrdiff_t) 1;
|
||||
return true;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
return mg_ota_write(data->buf, data->len);
|
||||
}
|
||||
if (*p == NULL) return false; // End. Begin failed, nothing to finish
|
||||
*p = NULL;
|
||||
return mg_ota_end();
|
||||
}
|
||||
|
||||
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
||||
@@ -1632,7 +1692,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
mg_handle_login(c, u);
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||
mg_http_start_ota(c, hm, mg_dash_ota_cb);
|
||||
// Authenticated above. If streaming started, the stream handler owns
|
||||
// the connection and c->data, so skip the marker check below
|
||||
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts) &&
|
||||
(mg_strcasecmp(hm->method, mg_str("POST")) == 0 ||
|
||||
mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) {
|
||||
@@ -1661,9 +1723,10 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (mg_dash_start_upload(c, hm, dash, fs, dir, name)) {
|
||||
return; // The stream handler owns the connection and c->data
|
||||
} else {
|
||||
mg_http_start_upload(c, hm, name, mg_str(dir), fs,
|
||||
mg_dash_upload_cb);
|
||||
d->marker = CONN_HANDLED; // Error reply has been sent
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3621,7 +3684,6 @@ struct mg_health mg_health_record MG_HEALTH_RAM;
|
||||
|
||||
|
||||
|
||||
|
||||
static int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
|
||||
int diff = 0;
|
||||
if (len > 0) do {
|
||||
@@ -4638,137 +4700,96 @@ int mg_http_status(const struct mg_http_message *hm) {
|
||||
return atoi(hm->uri.buf);
|
||||
}
|
||||
|
||||
struct mg_upload_priv {
|
||||
size_t expected;
|
||||
size_t received;
|
||||
struct mg_fd *fd; // non-NULL: file upload; NULL: OTA
|
||||
void (*fn)(struct mg_connection *, const char *);
|
||||
// State of a streamed request or response body, kept in c->data. The callback
|
||||
// is kept in c->fn_data, which is free because we own the connection
|
||||
struct mg_upload_state {
|
||||
size_t expected; // Number of body bytes to receive
|
||||
size_t received; // Number of body bytes received so far
|
||||
void *user_data; // Passed to cb, free for cb to use
|
||||
bool failed; // Set when cb returned false
|
||||
};
|
||||
|
||||
static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
if (p->fn == NULL) return;
|
||||
if (ev == MG_EV_READ && c->recv.len > 0) {
|
||||
size_t alignment = 512;
|
||||
size_t left = p->expected > p->received ? p->expected - p->received : 0;
|
||||
size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment)
|
||||
: left;
|
||||
bool ok = true;
|
||||
if (aligned > 0) {
|
||||
if (p->fd != NULL) {
|
||||
ok = p->fd->fs->wr(p->fd->fd, c->recv.buf, aligned) == aligned;
|
||||
} else {
|
||||
ok = mg_ota_write(c->recv.buf, aligned);
|
||||
}
|
||||
}
|
||||
p->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned);
|
||||
if (!ok) {
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else mg_ota_end();
|
||||
p->fn(c, "write error");
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
} else if (p->received >= p->expected) {
|
||||
const char *errmsg = NULL;
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else if (!mg_ota_end()) errmsg = "OTA finalize failed";
|
||||
p->fn(c, errmsg);
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else mg_ota_end();
|
||||
p->fn(c, ev == MG_EV_ERROR ? (const char *) ev_data : "connection closed");
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
}
|
||||
// Event handler installed by mg_http_stream_body(). Feeds the body to the
|
||||
// callback, then replies (servers only). It does not restore the previous
|
||||
// handler: the connection is closed after the reply.
|
||||
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
||||
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
|
||||
(ptrdiff_t) c->fn_data;
|
||||
(void) ev_data;
|
||||
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
||||
if (us->received < us->expected) {
|
||||
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
|
||||
us->received = us->expected; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
||||
fn(NULL, NULL, &us->user_data);
|
||||
}
|
||||
} else if (ev == MG_EV_READ && !c->is_draining) {
|
||||
if (c->recv.len > 0 && us->received < us->expected) {
|
||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
||||
size_t left = us->expected - us->received, r = c->recv.len;
|
||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||
struct mg_str data = mg_str_n((char *) c->recv.buf, aligned);
|
||||
if (data.len > 0 && !us->failed) {
|
||||
us->failed = !fn(NULL, &data, &us->user_data);
|
||||
}
|
||||
us->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
|
||||
}
|
||||
if (us->received >= us->expected) {
|
||||
// Finished upload. Always call cb, even after error, so it can clean up
|
||||
us->failed |= !fn(NULL, NULL, &us->user_data);
|
||||
if (!c->is_client) {
|
||||
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
|
||||
us->received, us->failed ? "fail" : "ok");
|
||||
}
|
||||
c->is_draining = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_upload_default_cb(struct mg_connection *c, const char *status) {
|
||||
MG_INFO(("%lu %s", c->id, status ? status : "ok"));
|
||||
mg_http_reply(c, status ? 500 : 200, "", "%s\n", status ? status : "ok");
|
||||
}
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*cb)(struct mg_http_message *, struct mg_str *,
|
||||
void **user_data),
|
||||
void *user_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
|
||||
bool handled = false;
|
||||
|
||||
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_str name, struct mg_str dir,
|
||||
struct mg_fs *fs,
|
||||
void (*fn)(struct mg_connection *, const char *)) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
char path[MG_PATH_MAX];
|
||||
struct mg_fd *fd;
|
||||
if (fn == NULL) fn = mg_upload_default_cb;
|
||||
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
|
||||
if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; }
|
||||
mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf,
|
||||
MG_DIRSEP, (int) name.len, name.buf);
|
||||
fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
||||
if (fd == NULL) { fn(c, "open failed"); return; }
|
||||
p->expected = hm->body.len;
|
||||
p->received = 0;
|
||||
p->fd = fd;
|
||||
p->fn = fn;
|
||||
c->fn = mg_upload_handler;
|
||||
c->pfn_data = strdup(path);
|
||||
c->pfn = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len);
|
||||
mg_call(c, MG_EV_READ, &c->recv.len);
|
||||
}
|
||||
|
||||
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void (*fn)(struct mg_connection *, const char *)) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
if (fn == NULL) fn = mg_upload_default_cb;
|
||||
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
|
||||
if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; }
|
||||
p->expected = hm->body.len;
|
||||
p->received = 0;
|
||||
p->fd = NULL;
|
||||
p->fn = fn;
|
||||
c->fn = mg_upload_handler;
|
||||
c->pfn = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len);
|
||||
mg_call(c, MG_EV_READ, &c->recv.len);
|
||||
}
|
||||
|
||||
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
|
||||
struct mg_str uri_pattern,
|
||||
void (*cb)(struct mg_http_message *, struct mg_str *,
|
||||
void **user_data)) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
|
||||
// Catch upload requests early, without buffering whole body
|
||||
// When we receive MG_EV_HTTP_HDRS event, that means we've received all
|
||||
// HTTP headers but not necessarily full HTTP body
|
||||
if (ev == MG_EV_HTTP_HDRS &&
|
||||
(mg_strcmp(hm->method, mg_str("POST")) == 0 ||
|
||||
mg_strcmp(hm->method, mg_str("PUT")) == 0) &&
|
||||
hm->body.len != (size_t) ~0 && mg_match(hm->uri, uri_pattern, NULL)) {
|
||||
c->pfn = NULL; // Silence HTTP protocol handler, we'll use MG_EV_READ
|
||||
c->pfn_data = (void *) (ptrdiff_t) hm->body.len; // Record expected len
|
||||
cb(hm, NULL, &c->fn_data); // Call handler before deleting headers
|
||||
// Catch upload requests early, without buffering whole body. Called on the
|
||||
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
|
||||
// the full HTTP body
|
||||
if (hm->body.len == (size_t) ~0 ||
|
||||
(te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0)) {
|
||||
// Unknown length or chunked, cannot stream. Leave it to the caller
|
||||
} else if (!c->is_client &&
|
||||
mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
||||
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
||||
// Not an upload request. Leave it to the caller
|
||||
} else if (sizeof(*us) > sizeof(c->data)) {
|
||||
if (!c->is_client) { // Nobody to reply to on a client connection
|
||||
c->pfn = NULL;
|
||||
mg_http_reply(c, 500, NULL, "data too small\n");
|
||||
c->is_draining = 1;
|
||||
cb(NULL, NULL, &user_data); // Let cb release user_data, start skipped
|
||||
handled = true;
|
||||
}
|
||||
} else {
|
||||
c->pfn = NULL; // Silence HTTP protocol handler
|
||||
memset(us, 0, sizeof(*us));
|
||||
us->expected = hm->body.len; // Store number of bytes we expect
|
||||
us->user_data = user_data;
|
||||
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
|
||||
if (mg_http_get_header(hm, "Expect") != NULL) {
|
||||
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
|
||||
c->fn = mg_stream_handler; // We own the connection from now
|
||||
c->fn_data = (void *) (ptrdiff_t) cb;
|
||||
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (c->pfn == NULL && ev != MG_EV_OPEN) {
|
||||
if (c->pfn_data != NULL && c->recv.len > 0) {
|
||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
||||
size_t left = (size_t) (ptrdiff_t) c->pfn_data, r = c->recv.len;
|
||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||
struct mg_str data = mg_str_n((char *) c->recv.buf, aligned);
|
||||
if (data.len) cb(NULL, &data, &c->fn_data);
|
||||
c->pfn_data = (void *) (ptrdiff_t) (left - aligned);
|
||||
c->recv.len -= aligned;
|
||||
}
|
||||
if (c->pfn_data == NULL && c->is_draining == 0) {
|
||||
// Finished upload
|
||||
mg_http_reply(c, 200, NULL, "ok\n");
|
||||
c->is_draining = 1;
|
||||
cb(NULL, NULL, &c->fn_data);
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
static bool is_hex_digit(int c) {
|
||||
@@ -11007,6 +11028,9 @@ static struct mg_ota_state {
|
||||
size_t size;
|
||||
uint8_t sha256[32];
|
||||
void (*fn)(const char *error_message);
|
||||
size_t received; // Firmware bytes written so far
|
||||
bool open; // mg_ota_begin() succeeded, mg_ota_end() is pending
|
||||
const char *err; // First error during download, NULL if none
|
||||
} *s_ota;
|
||||
|
||||
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data);
|
||||
@@ -11127,11 +11151,38 @@ static void status_fn(const char *errmsg) {
|
||||
if (errmsg) MG_ERROR(("OTA failed: %s", errmsg));
|
||||
}
|
||||
|
||||
static void status_fn_2(struct mg_connection *c, const char *errmsg) {
|
||||
if (s_ota) s_ota->fn(errmsg);
|
||||
// Firmware download callback for mg_http_stream_body(). Writes chunks to the
|
||||
// OTA target and reports the result via s_ota->fn(). The end call also happens
|
||||
// when the connection drops before the whole body arrived.
|
||||
static bool s_firmware_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
bool finalized = false, ok;
|
||||
(void) p;
|
||||
if (s_ota == NULL) return false;
|
||||
if (hm != NULL) { // Start
|
||||
s_ota->received = 0;
|
||||
s_ota->open = mg_ota_begin(hm->body.len);
|
||||
if (!s_ota->open) s_ota->err = "OTA begin failed";
|
||||
return s_ota->open;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
if (!mg_ota_write(data->buf, data->len)) {
|
||||
s_ota->err = "OTA write failed";
|
||||
return false;
|
||||
}
|
||||
s_ota->received += data->len;
|
||||
return true;
|
||||
}
|
||||
// End
|
||||
if (s_ota->open) finalized = mg_ota_end();
|
||||
if (s_ota->err == NULL && s_ota->received != s_ota->size) {
|
||||
s_ota->err = "Connection closed";
|
||||
}
|
||||
if (s_ota->err == NULL && !finalized) s_ota->err = "OTA finalize failed";
|
||||
ok = s_ota->err == NULL;
|
||||
s_ota->fn(s_ota->err);
|
||||
mg_free(s_ota);
|
||||
s_ota = NULL;
|
||||
mg_http_reply(c, errmsg ? 500 : 200, "", "%s\n", errmsg ? errmsg : "ok");
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
@@ -11162,7 +11213,9 @@ static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
|
||||
} else {
|
||||
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
|
||||
mg_http_start_ota(c, hm, status_fn_2);
|
||||
if (!mg_http_stream_body(c, hm, s_firmware_cb, NULL)) {
|
||||
mg_error(c, "Cannot stream firmware");
|
||||
}
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
s_ota->fn((char *) ev_data);
|
||||
|
||||
+22
-30
@@ -2793,20 +2793,6 @@ void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t);
|
||||
long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_fs *fs, const char *dir, size_t max_size);
|
||||
|
||||
// Streams the raw request body into dir/name via fs. Non-blocking: installs an
|
||||
// internal handler and returns immediately. fn is called with NULL on success
|
||||
// or an error string on failure.
|
||||
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_str name, struct mg_str dir,
|
||||
struct mg_fs *fs,
|
||||
void (*fn)(struct mg_connection *, const char *));
|
||||
|
||||
// Starts an OTA firmware update from an HTTP POST upload.
|
||||
// Calls mg_ota_begin/write/end internally; fn is called with NULL on success,
|
||||
// or an error string on failure.
|
||||
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void (*fn)(struct mg_connection *, const char *));
|
||||
|
||||
// Sends a 401 Unauthorized response with a Basic Auth WWW-Authenticate challenge.
|
||||
void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
|
||||
|
||||
@@ -2825,7 +2811,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
|
||||
// Full examples:
|
||||
// tutorials/http/file-upload-html-form, tutorials/http/http-server
|
||||
// Related APIs:
|
||||
// mg_http_listen(), mg_http_start_upload()
|
||||
// mg_http_listen(), mg_http_stream_body()
|
||||
// Notes:
|
||||
// Call from an MG_EV_HTTP_MSG handler after the full request body is
|
||||
// received. part.name, part.filename, and part.body are zero-copy slices into
|
||||
@@ -2836,27 +2822,33 @@ size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
|
||||
int mg_http_status(const struct mg_http_message *hm);
|
||||
|
||||
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
|
||||
// Call it from your connection handler on every event.
|
||||
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
|
||||
// with mg_match(). Returns true if it took over the connection, false if the
|
||||
// request is not POST/PUT, is chunked, or has no Content-Length.
|
||||
//
|
||||
// Callback upload_fn(hm, data, user_data) is called:
|
||||
// - at start: hm is set, data is NULL. Open the file, set user_data.
|
||||
// - per chunk: hm is NULL, data is set. Chunks are 512 bytes aligned
|
||||
// except the last one.
|
||||
// - at end: both NULL. Close the file.
|
||||
// After the last chunk, "200 ok" is sent and the connection is drained.
|
||||
// - at start: hm is set. Open the file, set *user_data.
|
||||
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
||||
// again after a failure.
|
||||
// - at end: both NULL. Always called once, also when the connection drops
|
||||
// early, so release resources here. A truncated body looks like a complete
|
||||
// one: validate before committing.
|
||||
// Return true on success, false on error.
|
||||
// Example:
|
||||
// mg_http_stream_body(c, ev, ev_data, mg_str("/fs/*"), upload);
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL));
|
||||
// }
|
||||
// Full example:
|
||||
// tutorials/http/file-upload-single-post
|
||||
// Related APIs:
|
||||
// mg_http_start_upload(), mg_http_start_ota()
|
||||
// mg_http_listen(), mg_http_next_multipart()
|
||||
// Notes:
|
||||
// Requires Content-Length, chunked bodies are not streamed. user_data is
|
||||
// &c->fn_data. While streaming, c->pfn is NULL
|
||||
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
|
||||
struct mg_str uri_pattern,
|
||||
void (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data));
|
||||
// After it returns true, this function hijacks the connection. Existing event
|
||||
// handler not called, hm is invalid, c->data and c->fn_data are modified
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data),
|
||||
void *user_data);
|
||||
|
||||
|
||||
void mg_http_serve_ssi(struct mg_connection *c, const char *root,
|
||||
@@ -4738,7 +4730,7 @@ extern void mg_mqtt_poll(struct mg_mgr *);
|
||||
// This is a low-level OTA API, not intended to be called directly by users.
|
||||
// Users are provided with a higher-level API:
|
||||
// - defining MG_OTA_URL enables HTTP or HTTPS pull-based updates
|
||||
// - mg_http_start_ota() enables push-based updates
|
||||
// - mg_http_stream_body() enables push-based updates
|
||||
//
|
||||
// However, it is possible to use the mg_ota_* API directly. Below is the
|
||||
// example code of a function that is called repeatedly. It expects the
|
||||
|
||||
+88
-25
@@ -1,5 +1,6 @@
|
||||
#include "dash.h"
|
||||
#include "http.h"
|
||||
#include "ota.h"
|
||||
#include "util.h"
|
||||
|
||||
#define MG_NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
|
||||
@@ -365,31 +366,75 @@ static inline void mg_log_http_req(struct mg_connection *c,
|
||||
c->send.buf + n));
|
||||
}
|
||||
|
||||
static void mg_dash_ota_cb(struct mg_connection *c, const char *errmsg) {
|
||||
mg_http_reply(c, errmsg ? 500 : 200, NULL, errmsg ? errmsg : "ok\n");
|
||||
c->is_draining = 1;
|
||||
}
|
||||
// State of one file upload to a dashboard file array. Owned by the upload
|
||||
// callback, which frees it in the end call
|
||||
struct mg_dash_upload {
|
||||
struct mg_mgr *mgr; // Manager, to notify WebSocket clients
|
||||
struct mg_dash *dash; // Dashboard whose file arrays get notified
|
||||
struct mg_fd *fd; // Destination file, opened by the request handler
|
||||
size_t expected; // Number of body bytes to receive
|
||||
size_t received; // Number of body bytes written
|
||||
};
|
||||
|
||||
static void mg_dash_upload_cb(struct mg_connection *c, const char *errmsg) {
|
||||
if (errmsg) {
|
||||
mg_http_reply(c, 500, NULL, "%s\n", errmsg);
|
||||
} else {
|
||||
// mg_http_start_upload() repurposes c->data for its own bookkeeping,
|
||||
// so the field set can't be cached there. Re-derive the dashboard from
|
||||
// c->fn_data instead, and notify every file-backed array: the upload
|
||||
// could belong to any of them, and re-querying get_dir() per recipient
|
||||
// is what mg_dash_send_change() does anyway (directories can be
|
||||
// user-specific)
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
struct mg_field_set *fs;
|
||||
mg_http_reply(c, 200, NULL, "ok\n");
|
||||
for (fs = dash->sets; fs != NULL; fs = fs->next) {
|
||||
if (fs->get_dir == NULL) continue;
|
||||
*fs->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
||||
mg_dash_send_change(c->mgr, fs);
|
||||
// Upload callback for mg_http_stream_body(). Notifies clients only if the whole
|
||||
// body was written. A dropped or failed upload sends no notification
|
||||
static bool mg_dash_upload_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
struct mg_dash_upload *up = (struct mg_dash_upload *) *p;
|
||||
bool ok;
|
||||
if (hm != NULL) return true; // Start, the file is already open
|
||||
if (data != NULL) { // Next chunk
|
||||
ok = up->fd->fs->wr(up->fd->fd, data->buf, data->len) == data->len;
|
||||
if (ok) up->received += data->len;
|
||||
return ok;
|
||||
}
|
||||
ok = up->received == up->expected; // End
|
||||
mg_fs_close(up->fd);
|
||||
if (ok) {
|
||||
// Notify every file-backed array: the upload could belong to any of them,
|
||||
// and re-querying get_dir() per recipient is what mg_dash_send_change()
|
||||
// does anyway (directories can be user-specific)
|
||||
struct mg_field_set *set;
|
||||
for (set = up->dash->sets; set != NULL; set = set->next) {
|
||||
if (set->get_dir == NULL) continue;
|
||||
*set->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
||||
mg_dash_send_change(up->mgr, set);
|
||||
}
|
||||
}
|
||||
c->is_draining = 1;
|
||||
mg_free(up);
|
||||
*p = NULL;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Opens dir/name and starts streaming the request body into it. Returns true
|
||||
// if streaming started, otherwise the error reply has been sent already
|
||||
static bool mg_dash_start_upload(struct mg_connection *c,
|
||||
struct mg_http_message *hm,
|
||||
struct mg_dash *dash, struct mg_fs *fs,
|
||||
const char *dir, struct mg_str name) {
|
||||
char path[MG_PATH_MAX];
|
||||
struct mg_dash_upload *up = NULL;
|
||||
struct mg_fd *fd = NULL;
|
||||
size_t n = mg_snprintf(path, sizeof(path), "%s%c%.*s", dir, MG_DIRSEP,
|
||||
(int) name.len, name.buf);
|
||||
if (n < sizeof(path)) fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
||||
if (fd != NULL) up = (struct mg_dash_upload *) mg_calloc(1, sizeof(*up));
|
||||
if (up == NULL) {
|
||||
if (fd != NULL) mg_fs_close(fd);
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Open failed\n");
|
||||
return false;
|
||||
}
|
||||
up->mgr = c->mgr;
|
||||
up->dash = dash;
|
||||
up->fd = fd;
|
||||
up->expected = hm->body.len;
|
||||
if (mg_http_stream_body(c, hm, mg_dash_upload_cb, up)) {
|
||||
return true; // The callback owns up and fd now
|
||||
}
|
||||
mg_fs_close(fd); // Chunked body or unexpected method: not streamed
|
||||
mg_free(up);
|
||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad request\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
||||
@@ -631,6 +676,21 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
|
||||
}
|
||||
}
|
||||
|
||||
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
|
||||
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
|
||||
static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
if (hm != NULL) { // Start
|
||||
if (!mg_ota_begin(hm->body.len)) return false;
|
||||
*p = (void *) (ptrdiff_t) 1;
|
||||
return true;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
return mg_ota_write(data->buf, data->len);
|
||||
}
|
||||
if (*p == NULL) return false; // End. Begin failed, nothing to finish
|
||||
*p = NULL;
|
||||
return mg_ota_end();
|
||||
}
|
||||
|
||||
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
||||
@@ -657,7 +717,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
mg_handle_login(c, u);
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||
mg_http_start_ota(c, hm, mg_dash_ota_cb);
|
||||
// Authenticated above. If streaming started, the stream handler owns
|
||||
// the connection and c->data, so skip the marker check below
|
||||
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts) &&
|
||||
(mg_strcasecmp(hm->method, mg_str("POST")) == 0 ||
|
||||
mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) {
|
||||
@@ -686,9 +748,10 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (mg_dash_start_upload(c, hm, dash, fs, dir, name)) {
|
||||
return; // The stream handler owns the connection and c->data
|
||||
} else {
|
||||
mg_http_start_upload(c, hm, name, mg_str(dir), fs,
|
||||
mg_dash_upload_cb);
|
||||
d->marker = CONN_HANDLED; // Error reply has been sent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+79
-121
@@ -3,7 +3,6 @@
|
||||
#include "fmt.h"
|
||||
#include "log.h"
|
||||
#include "net.h"
|
||||
#include "ota.h"
|
||||
#include "printf.h"
|
||||
#include "ssi.h"
|
||||
#include "util.h"
|
||||
@@ -1025,137 +1024,96 @@ int mg_http_status(const struct mg_http_message *hm) {
|
||||
return atoi(hm->uri.buf);
|
||||
}
|
||||
|
||||
struct mg_upload_priv {
|
||||
size_t expected;
|
||||
size_t received;
|
||||
struct mg_fd *fd; // non-NULL: file upload; NULL: OTA
|
||||
void (*fn)(struct mg_connection *, const char *);
|
||||
// State of a streamed request or response body, kept in c->data. The callback
|
||||
// is kept in c->fn_data, which is free because we own the connection
|
||||
struct mg_upload_state {
|
||||
size_t expected; // Number of body bytes to receive
|
||||
size_t received; // Number of body bytes received so far
|
||||
void *user_data; // Passed to cb, free for cb to use
|
||||
bool failed; // Set when cb returned false
|
||||
};
|
||||
|
||||
static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
if (p->fn == NULL) return;
|
||||
if (ev == MG_EV_READ && c->recv.len > 0) {
|
||||
size_t alignment = 512;
|
||||
size_t left = p->expected > p->received ? p->expected - p->received : 0;
|
||||
size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment)
|
||||
: left;
|
||||
bool ok = true;
|
||||
if (aligned > 0) {
|
||||
if (p->fd != NULL) {
|
||||
ok = p->fd->fs->wr(p->fd->fd, c->recv.buf, aligned) == aligned;
|
||||
} else {
|
||||
ok = mg_ota_write(c->recv.buf, aligned);
|
||||
}
|
||||
}
|
||||
p->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned);
|
||||
if (!ok) {
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else mg_ota_end();
|
||||
p->fn(c, "write error");
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
} else if (p->received >= p->expected) {
|
||||
const char *errmsg = NULL;
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else if (!mg_ota_end()) errmsg = "OTA finalize failed";
|
||||
p->fn(c, errmsg);
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else mg_ota_end();
|
||||
p->fn(c, ev == MG_EV_ERROR ? (const char *) ev_data : "connection closed");
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
}
|
||||
// Event handler installed by mg_http_stream_body(). Feeds the body to the
|
||||
// callback, then replies (servers only). It does not restore the previous
|
||||
// handler: the connection is closed after the reply.
|
||||
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
||||
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
|
||||
(ptrdiff_t) c->fn_data;
|
||||
(void) ev_data;
|
||||
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
||||
if (us->received < us->expected) {
|
||||
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
|
||||
us->received = us->expected; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
||||
fn(NULL, NULL, &us->user_data);
|
||||
}
|
||||
} else if (ev == MG_EV_READ && !c->is_draining) {
|
||||
if (c->recv.len > 0 && us->received < us->expected) {
|
||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
||||
size_t left = us->expected - us->received, r = c->recv.len;
|
||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||
struct mg_str data = mg_str_n((char *) c->recv.buf, aligned);
|
||||
if (data.len > 0 && !us->failed) {
|
||||
us->failed = !fn(NULL, &data, &us->user_data);
|
||||
}
|
||||
us->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
|
||||
}
|
||||
if (us->received >= us->expected) {
|
||||
// Finished upload. Always call cb, even after error, so it can clean up
|
||||
us->failed |= !fn(NULL, NULL, &us->user_data);
|
||||
if (!c->is_client) {
|
||||
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
|
||||
us->received, us->failed ? "fail" : "ok");
|
||||
}
|
||||
c->is_draining = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_upload_default_cb(struct mg_connection *c, const char *status) {
|
||||
MG_INFO(("%lu %s", c->id, status ? status : "ok"));
|
||||
mg_http_reply(c, status ? 500 : 200, "", "%s\n", status ? status : "ok");
|
||||
}
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*cb)(struct mg_http_message *, struct mg_str *,
|
||||
void **user_data),
|
||||
void *user_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
|
||||
bool handled = false;
|
||||
|
||||
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_str name, struct mg_str dir,
|
||||
struct mg_fs *fs,
|
||||
void (*fn)(struct mg_connection *, const char *)) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
char path[MG_PATH_MAX];
|
||||
struct mg_fd *fd;
|
||||
if (fn == NULL) fn = mg_upload_default_cb;
|
||||
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
|
||||
if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; }
|
||||
mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf,
|
||||
MG_DIRSEP, (int) name.len, name.buf);
|
||||
fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
||||
if (fd == NULL) { fn(c, "open failed"); return; }
|
||||
p->expected = hm->body.len;
|
||||
p->received = 0;
|
||||
p->fd = fd;
|
||||
p->fn = fn;
|
||||
c->fn = mg_upload_handler;
|
||||
c->pfn_data = strdup(path);
|
||||
c->pfn = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len);
|
||||
mg_call(c, MG_EV_READ, &c->recv.len);
|
||||
}
|
||||
|
||||
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void (*fn)(struct mg_connection *, const char *)) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
if (fn == NULL) fn = mg_upload_default_cb;
|
||||
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
|
||||
if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; }
|
||||
p->expected = hm->body.len;
|
||||
p->received = 0;
|
||||
p->fd = NULL;
|
||||
p->fn = fn;
|
||||
c->fn = mg_upload_handler;
|
||||
c->pfn = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len);
|
||||
mg_call(c, MG_EV_READ, &c->recv.len);
|
||||
}
|
||||
|
||||
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
|
||||
struct mg_str uri_pattern,
|
||||
void (*cb)(struct mg_http_message *, struct mg_str *,
|
||||
void **user_data)) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
|
||||
// Catch upload requests early, without buffering whole body
|
||||
// When we receive MG_EV_HTTP_HDRS event, that means we've received all
|
||||
// HTTP headers but not necessarily full HTTP body
|
||||
if (ev == MG_EV_HTTP_HDRS &&
|
||||
(mg_strcmp(hm->method, mg_str("POST")) == 0 ||
|
||||
mg_strcmp(hm->method, mg_str("PUT")) == 0) &&
|
||||
hm->body.len != (size_t) ~0 && mg_match(hm->uri, uri_pattern, NULL)) {
|
||||
c->pfn = NULL; // Silence HTTP protocol handler, we'll use MG_EV_READ
|
||||
c->pfn_data = (void *) (ptrdiff_t) hm->body.len; // Record expected len
|
||||
cb(hm, NULL, &c->fn_data); // Call handler before deleting headers
|
||||
// Catch upload requests early, without buffering whole body. Called on the
|
||||
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
|
||||
// the full HTTP body
|
||||
if (hm->body.len == (size_t) ~0 ||
|
||||
(te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0)) {
|
||||
// Unknown length or chunked, cannot stream. Leave it to the caller
|
||||
} else if (!c->is_client &&
|
||||
mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
||||
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
||||
// Not an upload request. Leave it to the caller
|
||||
} else if (sizeof(*us) > sizeof(c->data)) {
|
||||
if (!c->is_client) { // Nobody to reply to on a client connection
|
||||
c->pfn = NULL;
|
||||
mg_http_reply(c, 500, NULL, "data too small\n");
|
||||
c->is_draining = 1;
|
||||
cb(NULL, NULL, &user_data); // Let cb release user_data, start skipped
|
||||
handled = true;
|
||||
}
|
||||
} else {
|
||||
c->pfn = NULL; // Silence HTTP protocol handler
|
||||
memset(us, 0, sizeof(*us));
|
||||
us->expected = hm->body.len; // Store number of bytes we expect
|
||||
us->user_data = user_data;
|
||||
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
|
||||
if (mg_http_get_header(hm, "Expect") != NULL) {
|
||||
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
|
||||
c->fn = mg_stream_handler; // We own the connection from now
|
||||
c->fn_data = (void *) (ptrdiff_t) cb;
|
||||
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (c->pfn == NULL && ev != MG_EV_OPEN) {
|
||||
if (c->pfn_data != NULL && c->recv.len > 0) {
|
||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
||||
size_t left = (size_t) (ptrdiff_t) c->pfn_data, r = c->recv.len;
|
||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||
struct mg_str data = mg_str_n((char *) c->recv.buf, aligned);
|
||||
if (data.len) cb(NULL, &data, &c->fn_data);
|
||||
c->pfn_data = (void *) (ptrdiff_t) (left - aligned);
|
||||
c->recv.len -= aligned;
|
||||
}
|
||||
if (c->pfn_data == NULL && c->is_draining == 0) {
|
||||
// Finished upload
|
||||
mg_http_reply(c, 200, NULL, "ok\n");
|
||||
c->is_draining = 1;
|
||||
cb(NULL, NULL, &c->fn_data);
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
static bool is_hex_digit(int c) {
|
||||
|
||||
+21
-29
@@ -160,20 +160,6 @@ void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t);
|
||||
long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_fs *fs, const char *dir, size_t max_size);
|
||||
|
||||
// Streams the raw request body into dir/name via fs. Non-blocking: installs an
|
||||
// internal handler and returns immediately. fn is called with NULL on success
|
||||
// or an error string on failure.
|
||||
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_str name, struct mg_str dir,
|
||||
struct mg_fs *fs,
|
||||
void (*fn)(struct mg_connection *, const char *));
|
||||
|
||||
// Starts an OTA firmware update from an HTTP POST upload.
|
||||
// Calls mg_ota_begin/write/end internally; fn is called with NULL on success,
|
||||
// or an error string on failure.
|
||||
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void (*fn)(struct mg_connection *, const char *));
|
||||
|
||||
// Sends a 401 Unauthorized response with a Basic Auth WWW-Authenticate challenge.
|
||||
void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
|
||||
|
||||
@@ -192,7 +178,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
|
||||
// Full examples:
|
||||
// tutorials/http/file-upload-html-form, tutorials/http/http-server
|
||||
// Related APIs:
|
||||
// mg_http_listen(), mg_http_start_upload()
|
||||
// mg_http_listen(), mg_http_stream_body()
|
||||
// Notes:
|
||||
// Call from an MG_EV_HTTP_MSG handler after the full request body is
|
||||
// received. part.name, part.filename, and part.body are zero-copy slices into
|
||||
@@ -203,24 +189,30 @@ size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
|
||||
int mg_http_status(const struct mg_http_message *hm);
|
||||
|
||||
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
|
||||
// Call it from your connection handler on every event.
|
||||
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
|
||||
// with mg_match(). Returns true if it took over the connection, false if the
|
||||
// request is not POST/PUT, is chunked, or has no Content-Length.
|
||||
//
|
||||
// Callback upload_fn(hm, data, user_data) is called:
|
||||
// - at start: hm is set, data is NULL. Open the file, set user_data.
|
||||
// - per chunk: hm is NULL, data is set. Chunks are 512 bytes aligned
|
||||
// except the last one.
|
||||
// - at end: both NULL. Close the file.
|
||||
// After the last chunk, "200 ok" is sent and the connection is drained.
|
||||
// - at start: hm is set. Open the file, set *user_data.
|
||||
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
||||
// again after a failure.
|
||||
// - at end: both NULL. Always called once, also when the connection drops
|
||||
// early, so release resources here. A truncated body looks like a complete
|
||||
// one: validate before committing.
|
||||
// Return true on success, false on error.
|
||||
// Example:
|
||||
// mg_http_stream_body(c, ev, ev_data, mg_str("/fs/*"), upload);
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL));
|
||||
// }
|
||||
// Full example:
|
||||
// tutorials/http/file-upload-single-post
|
||||
// Related APIs:
|
||||
// mg_http_start_upload(), mg_http_start_ota()
|
||||
// mg_http_listen(), mg_http_next_multipart()
|
||||
// Notes:
|
||||
// Requires Content-Length, chunked bodies are not streamed. user_data is
|
||||
// &c->fn_data. While streaming, c->pfn is NULL
|
||||
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
|
||||
struct mg_str uri_pattern,
|
||||
void (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data));
|
||||
// After it returns true, this function hijacks the connection. Existing event
|
||||
// handler not called, hm is invalid, c->data and c->fn_data are modified
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data),
|
||||
void *user_data);
|
||||
|
||||
@@ -23,6 +23,9 @@ static struct mg_ota_state {
|
||||
size_t size;
|
||||
uint8_t sha256[32];
|
||||
void (*fn)(const char *error_message);
|
||||
size_t received; // Firmware bytes written so far
|
||||
bool open; // mg_ota_begin() succeeded, mg_ota_end() is pending
|
||||
const char *err; // First error during download, NULL if none
|
||||
} *s_ota;
|
||||
|
||||
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data);
|
||||
@@ -143,11 +146,38 @@ static void status_fn(const char *errmsg) {
|
||||
if (errmsg) MG_ERROR(("OTA failed: %s", errmsg));
|
||||
}
|
||||
|
||||
static void status_fn_2(struct mg_connection *c, const char *errmsg) {
|
||||
if (s_ota) s_ota->fn(errmsg);
|
||||
// Firmware download callback for mg_http_stream_body(). Writes chunks to the
|
||||
// OTA target and reports the result via s_ota->fn(). The end call also happens
|
||||
// when the connection drops before the whole body arrived.
|
||||
static bool s_firmware_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
bool finalized = false, ok;
|
||||
(void) p;
|
||||
if (s_ota == NULL) return false;
|
||||
if (hm != NULL) { // Start
|
||||
s_ota->received = 0;
|
||||
s_ota->open = mg_ota_begin(hm->body.len);
|
||||
if (!s_ota->open) s_ota->err = "OTA begin failed";
|
||||
return s_ota->open;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
if (!mg_ota_write(data->buf, data->len)) {
|
||||
s_ota->err = "OTA write failed";
|
||||
return false;
|
||||
}
|
||||
s_ota->received += data->len;
|
||||
return true;
|
||||
}
|
||||
// End
|
||||
if (s_ota->open) finalized = mg_ota_end();
|
||||
if (s_ota->err == NULL && s_ota->received != s_ota->size) {
|
||||
s_ota->err = "Connection closed";
|
||||
}
|
||||
if (s_ota->err == NULL && !finalized) s_ota->err = "OTA finalize failed";
|
||||
ok = s_ota->err == NULL;
|
||||
s_ota->fn(s_ota->err);
|
||||
mg_free(s_ota);
|
||||
s_ota = NULL;
|
||||
mg_http_reply(c, errmsg ? 500 : 200, "", "%s\n", errmsg ? errmsg : "ok");
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
@@ -178,7 +208,9 @@ static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
|
||||
} else {
|
||||
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
|
||||
mg_http_start_ota(c, hm, status_fn_2);
|
||||
if (!mg_http_stream_body(c, hm, s_firmware_cb, NULL)) {
|
||||
mg_error(c, "Cannot stream firmware");
|
||||
}
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
s_ota->fn((char *) ev_data);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// This is a low-level OTA API, not intended to be called directly by users.
|
||||
// Users are provided with a higher-level API:
|
||||
// - defining MG_OTA_URL enables HTTP or HTTPS pull-based updates
|
||||
// - mg_http_start_ota() enables push-based updates
|
||||
// - mg_http_stream_body() enables push-based updates
|
||||
//
|
||||
// However, it is possible to use the mg_ota_* API directly. Below is the
|
||||
// example code of a function that is called repeatedly. It expects the
|
||||
|
||||
+131
-18
@@ -3104,26 +3104,83 @@ static void uc(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
}
|
||||
|
||||
static int s_su_done;
|
||||
static int s_su_done; // 1: upload ended, -1: upload rejected
|
||||
static int s_su_end; // Number of end calls
|
||||
|
||||
static void su_cb(struct mg_connection *c, const char *errmsg) {
|
||||
s_su_done = errmsg ? -1 : 1;
|
||||
mg_http_reply(c, errmsg ? 400 : 200, "", errmsg ? errmsg : "ok\n");
|
||||
c->is_draining = 1;
|
||||
// mg_http_stream_body() callback, saves the body to a file named as the URI
|
||||
static bool su_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
if (hm != NULL) { // Start
|
||||
char path[100];
|
||||
struct mg_str name = mg_str_n(hm->uri.buf + 1, hm->uri.len - 1);
|
||||
*p = NULL;
|
||||
if (!mg_path_is_sane(name)) {
|
||||
s_su_done = -1;
|
||||
return false;
|
||||
}
|
||||
mg_snprintf(path, sizeof(path), "./%.*s", (int) name.len, name.buf);
|
||||
*p = mg_fs_open(&mg_fs_posix, path, MG_FS_WRITE);
|
||||
return *p != NULL;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
struct mg_fd *fd = (struct mg_fd *) *p;
|
||||
return fd != NULL && fd->fs->wr(fd->fd, data->buf, data->len) == data->len;
|
||||
}
|
||||
s_su_end++; // End, also called when the connection drops
|
||||
if (*p != NULL) mg_fs_close((struct mg_fd *) *p);
|
||||
*p = NULL;
|
||||
if (s_su_done == 0) s_su_done = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void su(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct mg_str name = mg_str_n(hm->uri.buf + 1, hm->uri.len - 1);
|
||||
mg_http_start_upload(c, hm, name, mg_str("."), &mg_fs_posix, su_cb);
|
||||
mg_http_stream_body(c, (struct mg_http_message *) ev_data, su_cb, NULL);
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // Only for requests that were not streamed
|
||||
mg_http_reply(c, 400, "", "not streamed\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void cu(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_CONNECT)
|
||||
mg_printf(c, "POST %s HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n",
|
||||
(char *) c->fn_data);
|
||||
// Client that announces a large body, sends a few bytes and stays silent
|
||||
static void cd(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
mg_printf(c, "POST /su_drop.txt HTTP/1.0\r\nContent-Length: 100000\r\n\r\nfoo\n");
|
||||
}
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
static char s_cr_buf[4096]; // Response body streamed by the client
|
||||
static size_t s_cr_len;
|
||||
static int s_cr_end;
|
||||
|
||||
static bool cr_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
(void) p;
|
||||
if (hm != NULL) {
|
||||
s_cr_len = 0;
|
||||
} else if (data != NULL) {
|
||||
ASSERT(s_cr_len + data->len <= sizeof(s_cr_buf));
|
||||
memcpy(s_cr_buf + s_cr_len, data->buf, data->len);
|
||||
s_cr_len += data->len;
|
||||
} else {
|
||||
s_cr_end++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Client that streams the response body with mg_http_stream_body()
|
||||
static void cr(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
ASSERT(mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb,
|
||||
NULL));
|
||||
}
|
||||
}
|
||||
|
||||
static void cs(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
char body[3000];
|
||||
memset(body, 'x', sizeof(body));
|
||||
mg_http_reply(c, 200, "", "%.*s", (int) sizeof(body), body);
|
||||
}
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
@@ -3141,16 +3198,18 @@ static void test_http_upload(void) {
|
||||
mg_mgr_free(&mgr);
|
||||
ASSERT(mgr.conns == NULL);
|
||||
|
||||
// mg_http_start_upload: successful upload, file content verified
|
||||
// mg_http_stream_body: successful upload, file content verified
|
||||
{
|
||||
char buf[FETCH_BUF_SIZE];
|
||||
struct mg_str fc;
|
||||
remove("su_ok.txt");
|
||||
s_su_done = s_su_end = 0;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://127.0.0.1:12355", su, NULL);
|
||||
ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12355",
|
||||
"POST /su_ok.txt HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n") ==
|
||||
200);
|
||||
ASSERT(s_su_done == 1 && s_su_end == 1);
|
||||
fc = mg_file_read(&mg_fs_posix, "su_ok.txt");
|
||||
ASSERT(mg_strcmp(fc, mg_str("foo\nbar\n")) == 0);
|
||||
mg_free((void *) fc.buf);
|
||||
@@ -3159,14 +3218,68 @@ static void test_http_upload(void) {
|
||||
ASSERT(mgr.conns == NULL);
|
||||
}
|
||||
|
||||
// mg_http_start_upload: path traversal rejected, callback gets error
|
||||
// mg_http_stream_body: path traversal rejected by the callback, "500" sent
|
||||
{
|
||||
s_su_done = 0;
|
||||
char buf[FETCH_BUF_SIZE];
|
||||
s_su_done = s_su_end = 0;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://127.0.0.1:12356", su, NULL);
|
||||
mg_http_connect(&mgr, "http://127.0.0.1:12356", cu, (void *) "/../evil.txt");
|
||||
for (i = 0; i < 50 && !s_su_done; i++) mg_mgr_poll(&mgr, 5);
|
||||
ASSERT(s_su_done == -1);
|
||||
ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12356",
|
||||
"POST /../evil.txt HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n") ==
|
||||
500);
|
||||
ASSERT(s_su_done == -1 && s_su_end == 1);
|
||||
ASSERT(fopen("../evil.txt", "r") == NULL);
|
||||
mg_mgr_free(&mgr);
|
||||
ASSERT(mgr.conns == NULL);
|
||||
}
|
||||
|
||||
// mg_http_stream_body: chunked request is not streamed, caller replies "400"
|
||||
{
|
||||
char buf[FETCH_BUF_SIZE];
|
||||
s_su_done = s_su_end = 0;
|
||||
remove("su_chunked.txt");
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://127.0.0.1:12393", su, NULL);
|
||||
ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12393",
|
||||
"POST /su_chunked.txt HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n\r\n"
|
||||
"3\r\nfoo\r\n0\r\n\r\n") == 400);
|
||||
ASSERT(s_su_done == 0 && s_su_end == 0); // The callback was never called
|
||||
ASSERT(fopen("su_chunked.txt", "r") == NULL);
|
||||
mg_mgr_free(&mgr);
|
||||
ASSERT(mgr.conns == NULL);
|
||||
}
|
||||
|
||||
// mg_http_stream_body: connection drops mid-body, the end call still happens
|
||||
{
|
||||
struct mg_connection *cc;
|
||||
s_su_done = s_su_end = 0;
|
||||
remove("su_drop.txt");
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://127.0.0.1:12391", su, NULL);
|
||||
cc = mg_http_connect(&mgr, "http://127.0.0.1:12391", cd, NULL);
|
||||
ASSERT(cc != NULL);
|
||||
for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 5);
|
||||
ASSERT(s_su_end == 0); // Still waiting for the rest of the body
|
||||
cc->is_closing = 1;
|
||||
for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 5);
|
||||
ASSERT(s_su_end == 1);
|
||||
remove("su_drop.txt");
|
||||
mg_mgr_free(&mgr);
|
||||
ASSERT(mgr.conns == NULL);
|
||||
}
|
||||
|
||||
// mg_http_stream_body on a client connection: response body is streamed
|
||||
{
|
||||
s_cr_end = 0;
|
||||
s_cr_len = 0;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://127.0.0.1:12392", cs, NULL);
|
||||
mg_http_connect(&mgr, "http://127.0.0.1:12392", cr, NULL);
|
||||
for (i = 0; i < 50 && s_cr_end == 0; i++) mg_mgr_poll(&mgr, 5);
|
||||
ASSERT(s_cr_end == 1);
|
||||
ASSERT(s_cr_len == 3000);
|
||||
ASSERT(s_cr_buf[0] == 'x' && s_cr_buf[2999] == 'x');
|
||||
mg_mgr_free(&mgr);
|
||||
ASSERT(mgr.conns == NULL);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ static const char *s_tls_key =
|
||||
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
|
||||
"-----END EC PRIVATE KEY-----\n";
|
||||
|
||||
static void upload(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
static bool upload(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
struct mg_fs *fs = &mg_fs_posix;
|
||||
if (hm != NULL) {
|
||||
char path[MG_PATH_MAX];
|
||||
@@ -48,6 +48,7 @@ static void upload(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
MG_INFO(("UPLOAD END %p", p));
|
||||
if (*p) fs->cl(*p), *p = NULL;
|
||||
}
|
||||
return true; // signal success
|
||||
}
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
@@ -55,13 +56,16 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(s_tls_cert),
|
||||
.key = mg_str(s_tls_key)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
// Handle upload requests to /fs/*
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
|
||||
mg_http_stream_body(c, hm, upload, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG && c->pfn != NULL) {
|
||||
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
}
|
||||
|
||||
// Handle upload requests to /fs/*
|
||||
mg_http_stream_body(c, ev, ev_data, mg_str("/fs/*"), upload);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
|
||||
@@ -12,7 +12,7 @@ SOURCES = main.c hal.c
|
||||
SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s
|
||||
SOURCES += mongoose/mongoose.c
|
||||
|
||||
all build example: firmware.signed.bin
|
||||
all build example: firmware.bin
|
||||
|
||||
mongoose/mongoose.c mongoose/mongoose.h:
|
||||
cp ../../../../mongoose.[ch] mongoose/
|
||||
@@ -25,9 +25,6 @@ firmware.bin: firmware.elf
|
||||
@echo
|
||||
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
|
||||
|
||||
sign firmware.signed.bin: firmware.bin
|
||||
node ../../../../resources/sign.js sign firmware.bin private.pem
|
||||
|
||||
flash: firmware.bin
|
||||
STM32_Programmer_CLI -c port=swd mode=UR -w firmware.elf -hardRst
|
||||
|
||||
|
||||
@@ -181,23 +181,7 @@ static inline void hal_system_init(void) {
|
||||
__ISB();
|
||||
}
|
||||
|
||||
// Enable TAMP/RTC APB clock, backup domain write access, and RTC/TAMP kernel
|
||||
// clock (LSI). Idempotent: skips RTC init if already enabled after a reset.
|
||||
// Must be called before accessing TAMP backup registers or IWDG.
|
||||
static inline void hal_backup_domain_init(void) {
|
||||
RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN; // enable TAMP/RTC APB clock
|
||||
PWR->DBPCR |= PWR_DBPCR_DBP; // enable backup domain write access
|
||||
if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0) { // init only once (not warm reset)
|
||||
RCC->BDCR |= RCC_BDCR_LSION; // enable LSI
|
||||
while ((RCC->BDCR & RCC_BDCR_LSIRDY) == 0) (void) 0; // wait ready
|
||||
RCC->BDCR |= (2U << RCC_BDCR_RTCSEL_Pos); // LSI as clock source
|
||||
RCC->BDCR |= RCC_BDCR_RTCEN; // enable RTC/TAMP clock
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_clock_init(void) {
|
||||
hal_backup_domain_init();
|
||||
|
||||
// Set flash latency. RM0481, section 7.11.1, section 7.3.4 table 37
|
||||
CLRSET(FLASH->ACR, (FLASH_ACR_WRHIGHFREQ_Msk | FLASH_ACR_LATENCY_Msk),
|
||||
FLASH_ACR_LATENCY_5WS | FLASH_ACR_WRHIGHFREQ_1);
|
||||
|
||||
@@ -41,31 +41,13 @@ bool mg_random(void *buf, size_t len) {
|
||||
}
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
// Start OTA on headers so body chunks stream directly to flash (no RAM buffer)
|
||||
if (mg_match(hm->uri, mg_str("/api/ota/update"), NULL)) {
|
||||
mg_http_start_ota(c, hm, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
|
||||
} else if (mg_match(hm->uri, mg_str("/api/ota/commit"), NULL)) {
|
||||
c->data[0] = 1;
|
||||
mg_http_reply(c, 200, "", "ok\n");
|
||||
} else if (mg_match(hm->uri, mg_str("/api/ota/rollback"), NULL)) {
|
||||
c->data[0] = 2;
|
||||
mg_http_reply(c, 200, "", "ok\n");
|
||||
} else {
|
||||
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", hal_get_tick());
|
||||
}
|
||||
} else if (ev == MG_EV_CLOSE && c->data[0] == 1) {
|
||||
MG_OTA_STATE_SET(MG_OTA_CONFIRMED);
|
||||
NVIC_SystemReset();
|
||||
} else if (ev == MG_EV_CLOSE && c->data[0] == 2) {
|
||||
MG_OTA_STATE_SET(MG_OTA_CONFIRMED);
|
||||
MG_OTA_ROLLBACK();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +56,6 @@ int main(void) {
|
||||
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
|
||||
MG_OTA_BOOT_CHECK(); // Must be called after clock init
|
||||
|
||||
hal_rng_init();
|
||||
hal_gpio_output(LED_1);
|
||||
hal_gpio_output(LED_2);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
// See https://mongoose.ws/documentation/#build-options
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
#define MG_OTA MG_OTA_STM32H5
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
#define MG_IO_SIZE 2048
|
||||
|
||||
@@ -13,39 +12,6 @@
|
||||
#define MG_ENABLE_PACKED_FS 1
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
|
||||
#define MG_OTA_PUBLIC_KEY \
|
||||
{ \
|
||||
0x19, 0x7f, 0xed, 0x81, 0x8b, 0xb7, 0xb2, 0xa5, 0xde, 0xe0, 0xde, \
|
||||
0x04, 0x83, 0x60, 0xc8, 0x69, 0x61, 0x39, 0x30, 0x1f, 0xc1, 0x52, \
|
||||
0x11, 0x4b, 0xb7, 0xa4, 0xd7, 0x52, 0x03, 0x36, 0xc6, 0xc1, 0xbf, \
|
||||
0x22, 0x75, 0x04, 0x49, 0xf8, 0x46, 0x47, 0xec, 0x20, 0xd6, 0x8d, \
|
||||
0x48, 0x29, 0x7d, 0x63, 0xb3, 0xf7, 0x1f, 0x58, 0x44, 0x82, 0xcc, \
|
||||
0x7a, 0x4b, 0x5e, 0x68, 0x98, 0x24, 0x1f, 0x86, 0xf4, \
|
||||
}
|
||||
|
||||
// OTA rollback timer via IWDG. PR=8 → /1024 on H563 (RM0481 §52.4.2).
|
||||
// Fixed 10 s hang-detection window: RLR = 10 * 32000 / 1024 = 312.
|
||||
// PVU/RVU must clear before writing RLR/starting (RM0481 §52.4.3).
|
||||
// mg_ota_poll() feeds the watchdog every 500ms while in MG_OTA_TESTING state.
|
||||
#define MG_OTA_ROLLBACK_TIMER_START() \
|
||||
do { \
|
||||
IWDG->KR = 0x5555U; /* unlock */ \
|
||||
IWDG->PR = 8U; /* /1024 */ \
|
||||
while (IWDG->SR & IWDG_SR_PVU) (void) 0; /* wait PVU */ \
|
||||
IWDG->RLR = 312U; /* 10 s */ \
|
||||
while (IWDG->SR & IWDG_SR_RVU) (void) 0; /* wait RVU */ \
|
||||
IWDG->KR = 0xCCCCU; /* start */ \
|
||||
IWDG->KR = 0xAAAAU; /* reload RLR */ \
|
||||
} while (0)
|
||||
#define MG_OTA_ROLLBACK_TIMER_FEED() (IWDG->KR = 0xAAAAU)
|
||||
|
||||
// OTA state in TAMP backup register 0 (survives all resets, clears on POR).
|
||||
// Requires hal_backup_domain_init() (called from hal_clock_init) to have run.
|
||||
#define MG_OTA_STATE_GET() (RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, PWR->DBPCR |= PWR_DBPCR_DBP, \
|
||||
(TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R)
|
||||
|
||||
// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 4
|
||||
// #define MG_TCPIP_PHY_ADDR 0 // PHY address
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgto6awcVrVzHfgSaD
|
||||
WFiy6JrXahab3F5lfGvb7A1XzjShRANCAAQZf+2Bi7eypd7g3gSDYMhpYTkwH8FS
|
||||
EUu3pNdSAzbGwb8idQRJ+EZH7CDWjUgpfWOz9x9YRILMekteaJgkH4b0
|
||||
-----END PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user