Skip to content

Commit 76193fe

Browse files
committed
Add example
1 parent 922d148 commit 76193fe

File tree

8 files changed

+371
-0
lines changed

8 files changed

+371
-0
lines changed

examples/file-transfer/Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
SPROG ?= server # Program we are building
2+
CPROG ?= client # Program we are building
3+
DELETE = rm -rf # Command to remove files
4+
SOUT ?= -o $(SPROG) # Compiler argument for output file
5+
COUT ?= -o $(CPROG) # Compiler argument for output file
6+
SSOURCES = server.c mongoose.c # Source code files
7+
CSOURCES = client.c mongoose.c # Source code files
8+
CFLAGS = -W -Wall -Wextra -g -I. # Build options
9+
10+
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
11+
#CFLAGS_MONGOOSE += -DMG_ENABLE_LINES
12+
13+
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
14+
SPROG ?= server.exe # Use .exe suffix for the binary
15+
CPROG ?= client.exe # Use .exe suffix for the binary
16+
CC = gcc # Use MinGW gcc compiler
17+
CFLAGS += -lws2_32 # Link against Winsock library
18+
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
19+
SOUT ?= -o $(SPROG) # Build output
20+
COUT ?= -o $(CPROG) # Build output
21+
endif
22+
23+
all: $(SPROG) $(CPROG) # Default target. Build and run program
24+
$(RUN) ./$(SPROG) $(SARGS)
25+
26+
$(SPROG): $(SSOURCES) # Build program from sources
27+
$(CC) $(SSOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(SOUT)
28+
29+
$(CPROG): $(CSOURCES) # Build program from sources
30+
$(CC) $(CSOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(COUT)
31+
32+
clean: # Cleanup. Delete built program and all build artifacts
33+
$(DELETE) $(SPROG) $(CPROG) *.o *.obj *.exe *.dSYM

examples/file-transfer/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# File Transfer
2+
3+
This example contains minimal HTTP client and server.
4+
5+
The client uploads a file to the server in a single POST, shaping traffic to send small data chunks.
6+
7+
The server manually processes requests in order to be able to write as soon as data arrives, to avoid buffering a whole (possibly huge) file not fitting in RAM.
8+
9+
Uploads are authenticated using Basic Auth. Both client and server have a default user/pass and can be configured using the command line.
10+
11+
The server can also accept regular uploads from any HTTP client, for example curl:
12+
13+
```sh
14+
curl -su user:pass http://localhost:8090/upload/foo.txt --data-binary @Makefile
15+
```
16+
17+
- Follow the [Build Tools](../tools/) tutorial to setup your development environment.
18+
- Start a terminal in this project directory; and build the example:
19+
20+
```sh
21+
cd mongoose/examples/file-transfer
22+
make clean all
23+
```
24+
25+
- Manually start the server, either in background (to reuse the same terminal window) or in foreground; in which case you'll need another terminal to run the client. The server will listen at all interfaces in port 8090
26+
27+
```sh
28+
./server
29+
6332b7 2 server.c:157:main Mongoose version : v7.12
30+
6332b7 2 server.c:158:main Listening on : http://0.0.0.0:8090
31+
6332b7 2 server.c:159:main Web root : [/home/mongoose/examples/file-transfer/web_root]
32+
6332b7 2 server.c:160:main Uploading to : [/home/mongoose/examples/file-transfer/upload]
33+
```
34+
35+
- Manually run the client to send a file, default is to send it as "foo.txt" to the server in localhost at port 8090
36+
37+
```sh
38+
./client -f Makefile
39+
ok
40+
```
41+
42+
Default operation is to assume hardcoded username and password. Call both server and client with no arguments to see usage instructions
43+
44+
See detailed tutorials at
45+
https://mongoose.ws/tutorials/file-uploads/
46+
https://mongoose.ws/tutorials/http-server/
47+
https://mongoose.ws/tutorials/http-client/

examples/file-transfer/client.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) 2021 Cesanta Software Limited
2+
// All rights reserved
3+
//
4+
// Example HTTP client. Connect to `s_url`, send request, wait for a response,
5+
// print the response and exit.
6+
// You can change `s_url` from the command line by executing: ./example YOUR_URL
7+
//
8+
// To enable SSL/TLS, , see https://mongoose.ws/tutorials/tls/#how-to-build
9+
10+
#include "mongoose.h"
11+
12+
static int s_debug_level = MG_LL_INFO;
13+
static const char *s_user = "user";
14+
static const char *s_pass = "pass";
15+
static const char *s_fname = NULL;
16+
static struct mg_fd *fd; // file descriptor
17+
static size_t fsize;
18+
static const char *s_url = "http://0.0.0.0:8090/upload/foo.txt";
19+
static const uint64_t s_timeout_ms = 1500; // Connect timeout in milliseconds
20+
21+
// Print HTTP response and signal that we're done
22+
static void fn(struct mg_connection *c, int ev, void *ev_data) {
23+
if (ev == MG_EV_OPEN) {
24+
// Connection created. Store connect expiration time in c->data
25+
*(uint64_t *) c->data = mg_millis() + s_timeout_ms;
26+
} else if (ev == MG_EV_POLL) {
27+
if (mg_millis() > *(uint64_t *) c->data &&
28+
(c->is_connecting || c->is_resolving)) {
29+
mg_error(c, "Connect timeout");
30+
}
31+
} else if (ev == MG_EV_CONNECT) {
32+
// Connected to server. Extract host name from URL
33+
struct mg_str host = mg_url_host(s_url);
34+
// Send request
35+
MG_DEBUG(("Connected, send request"));
36+
mg_printf(c,
37+
"POST %s HTTP/1.0\r\n"
38+
"Host: %.*s\r\n"
39+
"Content-Type: octet-stream\r\n"
40+
"Content-Length: %d\r\n",
41+
mg_url_uri(s_url), (int) host.len, host.ptr, fsize);
42+
mg_http_bauth(c, s_user, s_pass); // Add Basic auth header
43+
mg_printf(c, "%s", "\r\n"); // End HTTP headers
44+
} else if (ev == MG_EV_WRITE && c->send.len < MG_IO_SIZE) {
45+
uint8_t *buf = alloca(MG_IO_SIZE);
46+
size_t len = MG_IO_SIZE - c->send.len;
47+
len = fsize < len ? fsize : len;
48+
fd->fs->rd(fd->fd, buf, len);
49+
mg_send(c, buf, len);
50+
fsize -= len;
51+
MG_DEBUG(("sent %u bytes", len));
52+
} else if (ev == MG_EV_HTTP_MSG) {
53+
// Response is received. Print it
54+
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
55+
printf("%.*s", (int) hm->body.len, hm->body.ptr);
56+
c->is_draining = 1; // Tell mongoose to close this connection
57+
mg_fs_close(fd);
58+
*(bool *) c->fn_data = true; // Tell event loop to stop
59+
} else if (ev == MG_EV_ERROR) {
60+
mg_fs_close(fd);
61+
*(bool *) c->fn_data = true; // Error, tell event loop to stop
62+
}
63+
}
64+
65+
static void usage(const char *prog) {
66+
fprintf(stderr,
67+
"File Transfer client based on Mongoose v.%s\n"
68+
"Usage: %s -f NAME OPTIONS\n"
69+
" -u NAME - user name, default: '%s'\n"
70+
" -p PWD - password, default: '%s'\n"
71+
" -U URL - Full server URL, including destination file name; "
72+
"default: '%s'\n"
73+
" -f NAME - File to send\n"
74+
" -v LEVEL - debug level, from 0 to 4, default: %d\n",
75+
MG_VERSION, prog, s_user, s_pass, s_url, s_debug_level);
76+
exit(EXIT_FAILURE);
77+
}
78+
79+
int main(int argc, char *argv[]) {
80+
struct mg_mgr mgr; // Event manager
81+
bool done = false; // Event handler flips it to true
82+
time_t mtime;
83+
int i;
84+
85+
// Parse command-line flags
86+
for (i = 1; i < argc; i++) {
87+
if (strcmp(argv[i], "-f") == 0) {
88+
s_fname = argv[++i];
89+
} else if (strcmp(argv[i], "-u") == 0) {
90+
s_user = argv[++i];
91+
} else if (strcmp(argv[i], "-p") == 0) {
92+
s_pass = argv[++i];
93+
} else if (strcmp(argv[i], "-U") == 0) {
94+
s_url = argv[++i];
95+
} else if (strcmp(argv[i], "-v") == 0) {
96+
s_debug_level = atoi(argv[++i]);
97+
} else {
98+
usage(argv[0]);
99+
}
100+
}
101+
if (s_fname == NULL) usage(argv[0]);
102+
mg_fs_posix.st(s_fname, &fsize, &mtime);
103+
if (fsize == 0 ||
104+
(fd = mg_fs_open(&mg_fs_posix, s_fname, MG_FS_READ)) == NULL) {
105+
MG_ERROR(("open failed: %d", errno));
106+
exit(EXIT_FAILURE);
107+
}
108+
109+
mg_log_set(s_debug_level);
110+
mg_mgr_init(&mgr); // Initialise event manager
111+
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
112+
while (!done) mg_mgr_poll(&mgr, 50); // Event manager loops until 'done'
113+
mg_mgr_free(&mgr); // Free resources
114+
return 0;
115+
}

examples/file-transfer/mongoose.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../mongoose.c

examples/file-transfer/mongoose.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../mongoose.h

examples/file-transfer/server.c

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright (c) 2024 Cesanta Software Limited
2+
// All rights reserved
3+
4+
#include <signal.h>
5+
#include "mongoose.h"
6+
7+
static int s_debug_level = MG_LL_INFO;
8+
static int s_max_size = 10000;
9+
static const char *s_root_dir = "web_root";
10+
static const char *s_upld_dir = "upload";
11+
static const char *s_listening_address = "http://0.0.0.0:8090";
12+
static const char *s_user = "user";
13+
static const char *s_pass = "pass";
14+
15+
// Handle interrupts, like Ctrl-C
16+
static int s_signo;
17+
static void signal_handler(int signo) {
18+
s_signo = signo;
19+
}
20+
21+
static bool authuser(struct mg_http_message *hm) {
22+
char user[256], pass[256];
23+
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
24+
if (strcmp(user, s_user) == 0 && strcmp(pass, s_pass) == 0) return true;
25+
return false;
26+
}
27+
28+
// Streaming upload example. Demonstrates how to use MG_EV_READ events
29+
// to get large payload in smaller chunks. To test, use curl utility:
30+
static void cb(struct mg_connection *c, int ev, void *ev_data) {
31+
if (ev == MG_EV_READ) {
32+
// Parse the incoming data ourselves. If we can parse the request,
33+
// store two size_t variables in c->data: expected len and recv len.
34+
size_t *data = (size_t *) c->data;
35+
struct mg_fd *fd = (struct mg_fd *) c->fn_data; // store file descriptor
36+
if (data[0]) { // Already parsed, receiving body
37+
data[1] += c->recv.len;
38+
MG_DEBUG(("Got chunk len %lu, %lu total", c->recv.len, data[1]));
39+
fd->fs->wr(fd->fd, c->recv.buf, c->recv.len);
40+
c->recv.len = 0; // And cleanup the receive buffer. Streaming!
41+
if (data[1] >= data[0]) {
42+
mg_fs_close(fd);
43+
mg_http_reply(c, 200, "", "ok\n");
44+
}
45+
} else {
46+
struct mg_http_message hm;
47+
int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
48+
if (n < 0) mg_error(c, "Bad response");
49+
if (n > 0) {
50+
if (mg_http_match_uri(&hm, "/upload/#")) {
51+
if (!authuser(&hm)) {
52+
mg_http_reply(c, 403, "", "Denied\n");
53+
} else if (hm.body.len > (size_t) s_max_size) {
54+
mg_http_reply(c, 400, "", "Too long\n");
55+
} else {
56+
char fpath[MG_PATH_MAX];
57+
char *fname = strndup(hm.uri.ptr + 8, hm.uri.len - 8); // 8: /upload/
58+
if (fname[0] == '\0') {
59+
mg_http_reply(c, 400, "", "Name required\n");
60+
} else if (!mg_path_is_sane(fname)) {
61+
mg_http_reply(c, 400, "", "Invalid path\n");
62+
} else {
63+
snprintf(fpath, MG_PATH_MAX, "%s%c%s", s_upld_dir, MG_DIRSEP, fname);
64+
MG_DEBUG(("Got request, chunk len %lu", c->recv.len - n));
65+
if ((fd = mg_fs_open(&mg_fs_posix, fpath, MG_FS_WRITE)) == NULL) {
66+
mg_http_reply(c, 400, "", "open failed: %d", errno);
67+
} else {
68+
c->fn_data = fd;
69+
c->recv.len -= n; // remove headers
70+
data[0] = hm.body.len;
71+
data[1] = c->recv.len;
72+
if (c->recv.len) fd->fs->wr(fd->fd, c->recv.buf + n, c->recv.len);
73+
if (data[1] >= data[0]) {
74+
mg_fs_close(fd);
75+
mg_http_reply(c, 200, "", "ok\n");
76+
}
77+
}
78+
}
79+
free(fname);
80+
}
81+
} else {
82+
struct mg_http_serve_opts opts = {0};
83+
opts.root_dir = s_root_dir;
84+
mg_http_serve_dir(c, &hm, &opts);
85+
}
86+
}
87+
}
88+
}
89+
(void) ev_data;
90+
}
91+
92+
static void usage(const char *prog) {
93+
fprintf(stderr,
94+
"File Transfer server based on Mongoose v.%s\n"
95+
"Usage: %s OPTIONS\n"
96+
" -u NAME - user name, default: '%s'\n"
97+
" -p PWD - password, default: '%s'\n"
98+
" -d DIR - directory to serve, default: '%s'\n"
99+
" -D DIR - directory to store uploads, default: '%s'\n"
100+
" -s SIZE - maximum allowed file size, default: '%d'\n"
101+
" -l ADDR - listening address, default: '%s'\n"
102+
" -v LEVEL - debug level, from 0 to 4, default: %d\n",
103+
MG_VERSION, prog, s_user, s_pass, s_root_dir, s_upld_dir, s_max_size,
104+
s_listening_address, s_debug_level);
105+
exit(EXIT_FAILURE);
106+
}
107+
108+
int main(int argc, char *argv[]) {
109+
char spath[MG_PATH_MAX] = ".";
110+
char upath[MG_PATH_MAX] = ".";
111+
struct mg_mgr mgr;
112+
int i;
113+
114+
// Parse command-line flags
115+
for (i = 1; i < argc; i++) {
116+
if (strcmp(argv[i], "-d") == 0) {
117+
s_root_dir = argv[++i];
118+
} else if (strcmp(argv[i], "-D") == 0) {
119+
s_upld_dir = argv[++i];
120+
} else if (strcmp(argv[i], "-u") == 0) {
121+
s_user = argv[++i];
122+
} else if (strcmp(argv[i], "-p") == 0) {
123+
s_pass = argv[++i];
124+
} else if (strcmp(argv[i], "-l") == 0) {
125+
s_listening_address = argv[++i];
126+
} else if (strcmp(argv[i], "-v") == 0) {
127+
s_debug_level = atoi(argv[++i]);
128+
} else if (strcmp(argv[i], "-s") == 0) {
129+
s_max_size = atoi(argv[++i]);
130+
} else {
131+
usage(argv[0]);
132+
}
133+
}
134+
135+
// Root directory must not contain double dots. Make it absolute
136+
// Do the conversion only if the root dir spec does not contain overrides
137+
if (strchr(s_root_dir, ',') == NULL) {
138+
realpath(s_root_dir, spath);
139+
s_root_dir = spath;
140+
}
141+
if (strchr(s_upld_dir, ',') == NULL) {
142+
realpath(s_upld_dir, upath);
143+
s_upld_dir = upath;
144+
}
145+
146+
// Initialise stuff
147+
signal(SIGINT, signal_handler);
148+
signal(SIGTERM, signal_handler);
149+
mg_log_set(s_debug_level);
150+
mg_mgr_init(&mgr);
151+
if (mg_http_listen(&mgr, s_listening_address, cb, NULL) == NULL) {
152+
MG_ERROR(("Cannot listen on %s.", s_listening_address));
153+
exit(EXIT_FAILURE);
154+
}
155+
156+
// Start infinite event loop
157+
MG_INFO(("Mongoose version : v%s", MG_VERSION));
158+
MG_INFO(("Listening on : %s", s_listening_address));
159+
MG_INFO(("Web root : [%s]", s_root_dir));
160+
MG_INFO(("Uploading to : [%s]", s_upld_dir));
161+
while (s_signo == 0) mg_mgr_poll(&mgr, 1000);
162+
mg_mgr_free(&mgr);
163+
MG_INFO(("Exiting on signal %d", s_signo));
164+
return 0;
165+
}

examples/file-transfer/upload/README.md

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>File Transfer</title>
5+
</head>
6+
<body>
7+
<p style="font-size:100px">&#128515;</p>
8+
</body>
9+
</html>

0 commit comments

Comments
 (0)