Use MG_EV_HTTP_CHUNK for streaming upload

This commit is contained in:
Sergey Lyubka committed 2022-02-09 12:24:06 +00:00
1 parent 33666e7d6b
commit 9e634310b4
6 files changed
+99 -142

No files matched your search

+23 -8
View File
@@ -1,5 +1,11 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Streaming upload example. Demonstrates how to use chunked encoding
// to send large payload in smaller chunks. To test, use curl utility:
//
// curl http://localhost:8000/upload \
// --data-binary @my_large_file.txt -H 'Transfer-Encoding: chunked'
#include "mongoose.h"
@@ -7,20 +13,29 @@
// /upload - Saves the next file chunk
// all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/upload")) {
mg_http_upload(c, hm, &mg_fs_posix, "/tmp");
} else {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, hm, &opts);
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (ev == MG_EV_HTTP_CHUNK && mg_http_match_uri(hm, "/upload")) {
LOG(LL_INFO, ("Got chunk len %lu", (unsigned long) hm->chunk.len));
LOG(LL_INFO, ("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
LOG(LL_INFO, ("Chunk data:\n%.*s", (int) hm->chunk.len, hm->chunk.ptr));
mg_http_delete_chunk(c, hm);
if (hm->chunk.len == 0) {
LOG(LL_INFO, ("Last chunk received, sending response"));
mg_http_reply(c, 200, "", "ok (chunked)\n");
}
} else if (ev == MG_EV_HTTP_MSG && mg_http_match_uri(hm, "/upload")) {
LOG(LL_INFO, ("Got all %lu bytes!", (unsigned long) hm->body.len));
LOG(LL_INFO, ("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
LOG(LL_INFO, ("Body:\n%.*s", (int) hm->body.len, hm->body.ptr));
mg_http_reply(c, 200, "", "ok (%lu)\n", (unsigned long) hm->body.len);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, hm, &opts);
}
}
int main(void) {
struct mg_mgr mgr;
struct mg_timer t1;
mg_mgr_init(&mgr);
mg_log_set("3");
-41
View File
@@ -1,41 +0,0 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
// Helper function to display upload status
var setStatus = function(text) {
document.getElementById('el3').innerText = text;
};
// When user clicks on a button, trigger file selection dialog
var button = document.getElementById('el2');
button.onclick = function(ev) {
input.click();
};
// Send a large blob of data chunk by chunk
var sendFileData = function(name, data, chunkSize) {
var sendChunk = function(offset) {
var chunk = data.subarray(offset, offset + chunkSize) || '';
var opts = {method: 'POST', body: chunk};
var url = '/upload?offset=' + offset + '&name=' + encodeURIComponent(name);
setStatus(
'sending bytes ' + offset + '..' + (offset + chunk.length) + ' of ' +
data.length);
fetch(url, opts).then(function(res) {
if (chunk.length > 0) sendChunk(offset + chunk.length);
});
};
sendChunk(0);
};
// If user selected a file, read it into memory and trigger sendFileData()
var input = document.getElementById('el1');
input.onchange = function(ev) {
if (!ev.target.files[0]) return;
var f = ev.target.files[0], r = new FileReader();
r.readAsArrayBuffer(f);
r.onload = function() {
ev.target.value = '';
sendFileData(f.name, new Uint8Array(r.result), 4096);
};
};
+38 -10
View File
@@ -16,21 +16,49 @@
<div id="info">
Mongoose always buffers a full HTTP message before invoking
MG_EV_HTTP_MSG event. Big POST request require of lot
of RAM to buffer everything. Therefore, in order to upload large
files on memory-constrained system, a large file should be send
in small chunks.
<br/><br/>
In this example, JavaScript code on this page sends uploaded
file in 2k chunks using an <code>/upload</code> endpoint.
Uploaded file is stored in <code>/tmp</code> directory by the helper
API function <code>mg_http_upload()</code>
of RAM to buffer everything.
<br><br>
In order to upload large files to a memory-constrained system, use
<code>MG_EV_HTTP_CHUNK</code> on a server side. It fires when
a partial HTTP message has been received (or a chunk-encoded chunk).
Use <code>mg_http_delete_chunk()</code> to release chunk memory.
When 0-sized chunk is received, that's the end of the message.
Use <code>MG_MAX_RECV_BUF_SIZE</code> build constant to limit
maximum chunk size on a server side.
<br><br>
In this example, JavaScript code uses "fetch()" browser API.
Uploaded file is not saved, but rather printed by server side.
</div>
<div id="wrapper">
<input type="file" id="el1" style="display: none"/>
<button id="el2">choose file...</button>
<div id="el3"></div>
<div id="el3" style="margin-top: 1em;"></div>
</div>
</div>
</body>
<script src="app.js"></script>
<script>
// When user clicks on a button, trigger file selection dialog
document.getElementById('el2').onclick = function(ev) {
document.getElementById('el1').click();
};
// If user selected a file, read it into memory and trigger sendFileData()
document.getElementById('el1').onchange = function(ev) {
if (!ev.target.files[0]) return;
var f = ev.target.files[0], r = new FileReader();
r.readAsArrayBuffer(f);
r.onload = function() {
ev.target.value = '';
document.getElementById('el3').innerText = 'Uploading...';
fetch('/upload?name=' + encodeURIComponent(f.name), {
method: 'POST',
body: r.result,
}).then(function(res) {
document.getElementById('el3').innerText = 'Uploaded ' + r.result.byteLength + ' bytes';
});
};
};
</script>
</html>