mirror of
https://github.com/cesanta/mongoose.git
synced 2024-12-28 15:40:23 +08:00
commit
dd6458262d
35
examples/file-transfer/Makefile
Normal file
35
examples/file-transfer/Makefile
Normal file
@ -0,0 +1,35 @@
|
||||
SPROG ?= server # Program we are building
|
||||
CPROG ?= client # Program we are building
|
||||
DELETE = rm -rf # Command to remove files
|
||||
SOUT ?= -o $(SPROG) # Compiler argument for output file
|
||||
COUT ?= -o $(CPROG) # Compiler argument for output file
|
||||
SSOURCES = server.c mongoose.c # Source code files
|
||||
CSOURCES = client.c mongoose.c # Source code files
|
||||
CFLAGS = -W -Wall -Wextra -g -I. # Build options
|
||||
|
||||
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
|
||||
#CFLAGS_MONGOOSE += -DMG_ENABLE_LINES
|
||||
|
||||
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
|
||||
SPROG ?= server.exe # Use .exe suffix for the binary
|
||||
CPROG ?= client.exe # Use .exe suffix for the binary
|
||||
CC = gcc # Use MinGW gcc compiler
|
||||
CFLAGS += -lws2_32 # Link against Winsock library
|
||||
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
|
||||
SOUT ?= -o $(SPROG) # Build output
|
||||
COUT ?= -o $(CPROG) # Build output
|
||||
endif
|
||||
|
||||
all: example # Default target. Build all and run server
|
||||
$(RUN) ./$(SPROG) $(SARGS)
|
||||
|
||||
example: $(SPROG) $(CPROG)
|
||||
|
||||
$(SPROG): $(SSOURCES) # Build program from sources
|
||||
$(CC) $(SSOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(SOUT)
|
||||
|
||||
$(CPROG): $(CSOURCES) # Build program from sources
|
||||
$(CC) $(CSOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(COUT)
|
||||
|
||||
clean: # Cleanup. Delete built program and all build artifacts
|
||||
$(DELETE) $(SPROG) $(CPROG) *.o *.obj *.exe *.dSYM
|
47
examples/file-transfer/README.md
Normal file
47
examples/file-transfer/README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# File Transfer
|
||||
|
||||
This example contains minimal HTTP client and server.
|
||||
|
||||
The client uploads a file to the server in a single POST, shaping traffic to send small data chunks.
|
||||
|
||||
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.
|
||||
|
||||
Uploads are authenticated using Basic Auth. Both client and server have a default user/pass and can be configured using the command line. Only authenticated users can upload a file.
|
||||
|
||||
The server can also accept regular uploads from any HTTP client, for example curl:
|
||||
|
||||
```sh
|
||||
curl -su user:pass http://localhost:8090/upload/foo.txt --data-binary @Makefile
|
||||
```
|
||||
|
||||
- Follow the [Build Tools](../tools/) tutorial to setup your development environment.
|
||||
- Start a terminal in this project directory; and build the example:
|
||||
|
||||
```sh
|
||||
cd mongoose/examples/file-transfer
|
||||
make clean all
|
||||
```
|
||||
|
||||
- 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
|
||||
|
||||
```sh
|
||||
./server
|
||||
6332b7 2 server.c:157:main Mongoose version : v7.12
|
||||
6332b7 2 server.c:158:main Listening on : http://0.0.0.0:8090
|
||||
6332b7 2 server.c:159:main Web root : [/home/mongoose/examples/file-transfer/web_root]
|
||||
6332b7 2 server.c:160:main Uploading to : [/home/mongoose/examples/file-transfer/upload]
|
||||
```
|
||||
|
||||
- Manually run the client to send a file, default is to send it as "foo.txt" to the server in localhost at port 8090
|
||||
|
||||
```sh
|
||||
./client -f Makefile
|
||||
ok
|
||||
```
|
||||
|
||||
Default operation is to assume hardcoded username and password. Call both server and client with no arguments to see usage instructions
|
||||
|
||||
See detailed tutorials at
|
||||
https://mongoose.ws/tutorials/file-uploads/
|
||||
https://mongoose.ws/tutorials/http-server/
|
||||
https://mongoose.ws/tutorials/http-client/
|
117
examples/file-transfer/client.c
Normal file
117
examples/file-transfer/client.c
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2021 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Example HTTP client. Connect to `s_url`, send request, wait for a response,
|
||||
// print the response and exit.
|
||||
// You can change `s_url` from the command line by executing: ./example YOUR_URL
|
||||
//
|
||||
// To enable SSL/TLS, , see https://mongoose.ws/tutorials/tls/#how-to-build
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_debug_level = MG_LL_INFO;
|
||||
static const char *s_user = "user";
|
||||
static const char *s_pass = "pass";
|
||||
static const char *s_fname = NULL;
|
||||
static struct mg_fd *fd; // file descriptor
|
||||
static size_t fsize;
|
||||
static const char *s_url = "http://localhost:8090/upload/foo.txt";
|
||||
static const uint64_t s_timeout_ms = 1500; // Connect timeout in milliseconds
|
||||
|
||||
// Print HTTP response and signal that we're done
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
// Connection created. Store connect expiration time in c->data
|
||||
*(uint64_t *) c->data = mg_millis() + s_timeout_ms;
|
||||
} else if (ev == MG_EV_POLL) {
|
||||
if (mg_millis() > *(uint64_t *) c->data &&
|
||||
(c->is_connecting || c->is_resolving)) {
|
||||
mg_error(c, "Connect timeout");
|
||||
}
|
||||
} else if (ev == MG_EV_CONNECT) {
|
||||
// Connected to server. Extract host name from URL
|
||||
struct mg_str host = mg_url_host(s_url);
|
||||
// Send request
|
||||
MG_DEBUG(("Connected, send request"));
|
||||
mg_printf(c,
|
||||
"POST %s HTTP/1.0\r\n"
|
||||
"Host: %.*s\r\n"
|
||||
"Content-Type: octet-stream\r\n"
|
||||
"Content-Length: %d\r\n",
|
||||
mg_url_uri(s_url), (int) host.len, host.ptr, fsize);
|
||||
mg_http_bauth(c, s_user, s_pass); // Add Basic auth header
|
||||
mg_printf(c, "%s", "\r\n"); // End HTTP headers
|
||||
} else if (ev == MG_EV_WRITE && c->send.len < MG_IO_SIZE) {
|
||||
uint8_t *buf = alloca(MG_IO_SIZE);
|
||||
size_t len = MG_IO_SIZE - c->send.len;
|
||||
len = fsize < len ? fsize : len;
|
||||
fd->fs->rd(fd->fd, buf, len);
|
||||
mg_send(c, buf, len);
|
||||
fsize -= len;
|
||||
MG_DEBUG(("sent %u bytes", len));
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
MG_DEBUG(("MSG"));
|
||||
// Response is received. Print it
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
printf("%.*s", (int) hm->body.len, hm->body.ptr);
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
mg_fs_close(fd);
|
||||
*(bool *) c->fn_data = true; // Tell event loop to stop
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
MG_DEBUG(("ERROR"));
|
||||
mg_fs_close(fd);
|
||||
*(bool *) c->fn_data = true; // Error, tell event loop to stop
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"File Transfer client based on Mongoose v.%s\n"
|
||||
"Usage: %s -f NAME OPTIONS\n"
|
||||
" -u NAME - user name, default: '%s'\n"
|
||||
" -p PWD - password, default: '%s'\n"
|
||||
" -U URL - Full server URL, including destination file name; "
|
||||
"default: '%s'\n"
|
||||
" -f NAME - File to send\n"
|
||||
" -v LEVEL - debug level, from 0 to 4, default: %d\n",
|
||||
MG_VERSION, prog, s_user, s_pass, s_url, s_debug_level);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr; // Event manager
|
||||
bool done = false; // Event handler flips it to true
|
||||
time_t mtime;
|
||||
int i;
|
||||
|
||||
// Parse command-line flags
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-f") == 0) {
|
||||
s_fname = argv[++i];
|
||||
} else if (strcmp(argv[i], "-u") == 0) {
|
||||
s_user = argv[++i];
|
||||
} else if (strcmp(argv[i], "-p") == 0) {
|
||||
s_pass = argv[++i];
|
||||
} else if (strcmp(argv[i], "-U") == 0) {
|
||||
s_url = argv[++i];
|
||||
} else if (strcmp(argv[i], "-v") == 0) {
|
||||
s_debug_level = atoi(argv[++i]);
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
}
|
||||
if (s_fname == NULL) usage(argv[0]);
|
||||
mg_fs_posix.st(s_fname, &fsize, &mtime);
|
||||
if (fsize == 0 ||
|
||||
(fd = mg_fs_open(&mg_fs_posix, s_fname, MG_FS_READ)) == NULL) {
|
||||
MG_ERROR(("open failed: %d", errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mg_log_set(s_debug_level);
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
|
||||
while (!done) mg_mgr_poll(&mgr, 50); // Event manager loops until 'done'
|
||||
mg_mgr_free(&mgr); // Free resources
|
||||
return 0;
|
||||
}
|
1
examples/file-transfer/mongoose.c
Symbolic link
1
examples/file-transfer/mongoose.c
Symbolic link
@ -0,0 +1 @@
|
||||
../../mongoose.c
|
1
examples/file-transfer/mongoose.h
Symbolic link
1
examples/file-transfer/mongoose.h
Symbolic link
@ -0,0 +1 @@
|
||||
../../mongoose.h
|
176
examples/file-transfer/server.c
Normal file
176
examples/file-transfer/server.c
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2024 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include <signal.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_debug_level = MG_LL_INFO;
|
||||
static int s_max_size = 10000;
|
||||
static const char *s_root_dir = "web_root";
|
||||
static const char *s_upld_dir = "upload";
|
||||
static const char *s_listening_address = "http://0.0.0.0:8090";
|
||||
static const char *s_user = "user";
|
||||
static const char *s_pass = "pass";
|
||||
|
||||
// Handle interrupts, like Ctrl-C
|
||||
static int s_signo;
|
||||
static void signal_handler(int signo) {
|
||||
s_signo = signo;
|
||||
}
|
||||
|
||||
static bool authuser(struct mg_http_message *hm) {
|
||||
char user[256], pass[256];
|
||||
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
|
||||
if (strcmp(user, s_user) == 0 && strcmp(pass, s_pass) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Streaming upload example. Demonstrates how to use MG_EV_READ events
|
||||
// to get large payload in smaller chunks. To test, use curl utility:
|
||||
static void cb(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 c->data: expected len and recv len.
|
||||
size_t *data = (size_t *) c->data;
|
||||
struct mg_fd *fd = (struct mg_fd *) c->fn_data; // get file descriptor
|
||||
if (data[0]) { // Already parsed, receiving body
|
||||
data[1] += c->recv.len;
|
||||
MG_DEBUG(("Got chunk len %lu, %lu total", c->recv.len, data[1]));
|
||||
fd->fs->wr(fd->fd, c->recv.buf, c->recv.len);
|
||||
c->recv.len = 0; // And cleanup the receive buffer. Streaming!
|
||||
if (data[1] >= data[0]) {
|
||||
mg_fs_close(fd);
|
||||
mg_http_reply(c, 200, "", "ok\n");
|
||||
}
|
||||
} else if(c->is_resp == 0) {
|
||||
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/#")) {
|
||||
if (!authuser(&hm)) {
|
||||
mg_http_reply(c, 403, "", "Denied\n");
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
} else if (hm.body.len > (size_t) s_max_size) {
|
||||
mg_http_reply(c, 400, "", "Too long\n");
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
} else if (hm.uri.len == 8) { // 8: /upload/
|
||||
mg_http_reply(c, 400, "", "Name required\n");
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
} else if (strlen(s_upld_dir) + (hm.uri.len - 8) + 2 >
|
||||
MG_PATH_MAX) { // 2: MG_DIRSEP + NUL
|
||||
mg_http_reply(c, 400, "", "Path is too long\n");
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
} else {
|
||||
char fpath[MG_PATH_MAX];
|
||||
snprintf(fpath, MG_PATH_MAX, "%s%c", s_upld_dir, MG_DIRSEP);
|
||||
strncat(fpath, hm.uri.ptr + 8, hm.uri.len - 8);
|
||||
if (!mg_path_is_sane(fpath)) {
|
||||
mg_http_reply(c, 400, "", "Invalid path\n");
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
} else {
|
||||
MG_DEBUG(("Got request, chunk len %lu", c->recv.len - n));
|
||||
if ((fd = mg_fs_open(&mg_fs_posix, fpath, MG_FS_WRITE)) == NULL) {
|
||||
mg_http_reply(c, 400, "", "open failed: %d", errno);
|
||||
c->is_draining = 1; // Tell mongoose to close this connection
|
||||
} else {
|
||||
c->fn_data = fd;
|
||||
c->recv.len -= n; // remove headers
|
||||
data[0] = hm.body.len;
|
||||
data[1] = c->recv.len;
|
||||
if (c->recv.len)
|
||||
fd->fs->wr(fd->fd, c->recv.buf + n, c->recv.len);
|
||||
c->recv.len = 0; // consume data
|
||||
if (data[1] >= data[0]) {
|
||||
mg_fs_close(fd);
|
||||
mg_http_reply(c, 200, "", "ok\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c->is_resp = 1; // ignore the rest of the body
|
||||
} else {
|
||||
struct mg_http_serve_opts opts = {0};
|
||||
opts.root_dir = s_root_dir;
|
||||
mg_http_serve_dir(c, &hm, &opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
static void usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"File Transfer server based on Mongoose v.%s\n"
|
||||
"Usage: %s OPTIONS\n"
|
||||
" -u NAME - user name, default: '%s'\n"
|
||||
" -p PWD - password, default: '%s'\n"
|
||||
" -d DIR - directory to serve, default: '%s'\n"
|
||||
" -D DIR - directory to store uploads, default: '%s'\n"
|
||||
" -s SIZE - maximum allowed file size, default: '%d'\n"
|
||||
" -l ADDR - listening address, default: '%s'\n"
|
||||
" -v LEVEL - debug level, from 0 to 4, default: %d\n",
|
||||
MG_VERSION, prog, s_user, s_pass, s_root_dir, s_upld_dir, s_max_size,
|
||||
s_listening_address, s_debug_level);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char spath[MG_PATH_MAX] = ".";
|
||||
char upath[MG_PATH_MAX] = ".";
|
||||
struct mg_mgr mgr;
|
||||
int i;
|
||||
|
||||
// Parse command-line flags
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-d") == 0) {
|
||||
s_root_dir = argv[++i];
|
||||
} else if (strcmp(argv[i], "-D") == 0) {
|
||||
s_upld_dir = argv[++i];
|
||||
} else if (strcmp(argv[i], "-u") == 0) {
|
||||
s_user = argv[++i];
|
||||
} else if (strcmp(argv[i], "-p") == 0) {
|
||||
s_pass = argv[++i];
|
||||
} else if (strcmp(argv[i], "-l") == 0) {
|
||||
s_listening_address = argv[++i];
|
||||
} else if (strcmp(argv[i], "-v") == 0) {
|
||||
s_debug_level = atoi(argv[++i]);
|
||||
} else if (strcmp(argv[i], "-s") == 0) {
|
||||
s_max_size = atoi(argv[++i]);
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Root directory must not contain double dots. Make it absolute
|
||||
// Do the conversion only if the root dir spec does not contain overrides
|
||||
if (strchr(s_root_dir, ',') == NULL) {
|
||||
realpath(s_root_dir, spath);
|
||||
s_root_dir = spath;
|
||||
}
|
||||
if (strchr(s_upld_dir, ',') == NULL) {
|
||||
realpath(s_upld_dir, upath);
|
||||
s_upld_dir = upath;
|
||||
}
|
||||
|
||||
// Initialise stuff
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
mg_log_set(s_debug_level);
|
||||
mg_mgr_init(&mgr);
|
||||
if (mg_http_listen(&mgr, s_listening_address, cb, NULL) == NULL) {
|
||||
MG_ERROR(("Cannot listen on %s.", s_listening_address));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Start infinite event loop
|
||||
MG_INFO(("Mongoose version : v%s", MG_VERSION));
|
||||
MG_INFO(("Listening on : %s", s_listening_address));
|
||||
MG_INFO(("Web root : [%s]", s_root_dir));
|
||||
MG_INFO(("Uploading to : [%s]", s_upld_dir));
|
||||
while (s_signo == 0) mg_mgr_poll(&mgr, 1000);
|
||||
mg_mgr_free(&mgr);
|
||||
MG_INFO(("Exiting on signal %d", s_signo));
|
||||
return 0;
|
||||
}
|
0
examples/file-transfer/upload/README.md
Normal file
0
examples/file-transfer/upload/README.md
Normal file
9
examples/file-transfer/web_root/index.html
Normal file
9
examples/file-transfer/web_root/index.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>File Transfer</title>
|
||||
</head>
|
||||
<body>
|
||||
<p style="font-size:100px">😃</p>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user