-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
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: $(SPROG) $(CPROG) # Default target. Build and run program | ||
$(RUN) ./$(SPROG) $(SARGS) | ||
|
||
$(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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// 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://0.0.0.0: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) { | ||
// 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_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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../mongoose.c |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../mongoose.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// 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; // store 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 { | ||
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"); | ||
} else if (hm.body.len > (size_t) s_max_size) { | ||
mg_http_reply(c, 400, "", "Too long\n"); | ||
} else { | ||
char fpath[MG_PATH_MAX]; | ||
char *fname = strndup(hm.uri.ptr + 8, hm.uri.len - 8); // 8: /upload/ | ||
if (fname[0] == '\0') { | ||
mg_http_reply(c, 400, "", "Name required\n"); | ||
} else if (!mg_path_is_sane(fname)) { | ||
mg_http_reply(c, 400, "", "Invalid path\n"); | ||
} else { | ||
snprintf(fpath, MG_PATH_MAX, "%s%c%s", s_upld_dir, MG_DIRSEP, fname); | ||
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); | ||
} 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); | ||
if (data[1] >= data[0]) { | ||
mg_fs_close(fd); | ||
mg_http_reply(c, 200, "", "ok\n"); | ||
} | ||
} | ||
} | ||
free(fname); | ||
} | ||
} 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; | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<title>File Transfer</title> | ||
</head> | ||
<body> | ||
<p style="font-size:100px">😃</p> | ||
</body> | ||
</html> |