mirror of
https://github.com/cesanta/mongoose.git
synced 2024-12-27 15:01:03 +08:00
Merge pull request #568 from cesanta/fossamerge
Merge dev branch code named Fossa as next stable Mongoose
This commit is contained in:
commit
e565977481
10
LICENSE
10
LICENSE
@ -2,15 +2,15 @@ Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
Copyright (c) 2013-2015 Cesanta Software Limited
|
||||
All rights reserved
|
||||
|
||||
This code is dual-licensed: you can redistribute it and/or modify
|
||||
This software is dual-licensed: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation. For the terms of this
|
||||
license, see <http://www.gnu.org/licenses>.
|
||||
license, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
You are free to use this code under the terms of the GNU General
|
||||
You are free to use this software under the terms of the GNU General
|
||||
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
Alternatively, you can license this code under a commercial
|
||||
license, as set out in <http://cesanta.com/>.
|
||||
Alternatively, you can license this software under a commercial
|
||||
license, as set out in <https://www.cesanta.com/license>.
|
||||
|
113
README.md
113
README.md
@ -2,20 +2,23 @@
|
||||
|
||||
[![Join the chat at https://gitter.im/cesanta/mongoose](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cesanta/mongoose?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Mongoose is an embedded HTTP and WebSocket library that can turn anything
|
||||
into a web server in 5 minutes by adding a few lines of C/C++ code.
|
||||
On the market since 2004 with over 1 million cumulative downloads,
|
||||
it's simplicity and flexibility has made it the top choice for
|
||||
embedded software engineers.
|
||||
![](https://img.shields.io/badge/license-GPL_2-green.svg "License")
|
||||
|
||||
Mongoose Binary is built on top of Mongoose Library which is used to serve Web
|
||||
GUI on embedded devices, implement RESTful services, RPC frameworks (e.g.
|
||||
JSON-RPC), handle telemetry data exchange and perform many other tasks. You'll find
|
||||
it used across various industries including aerospace, manufacturing, finance,
|
||||
research, automotive, gaming, IT and many more.
|
||||
[Mongoose](https://www.cesanta.com/mongoose) is a
|
||||
multi-protocol networking library written in C.
|
||||
It provides easy to use event-driven interface that allows to implement
|
||||
network protocols or scalable network applications with little effort.
|
||||
Mongoose helps developers to manage the complexity of network programming
|
||||
and let them concentrate on the logic, saving time and money.
|
||||
|
||||
> "Nothing overdone. Nothing less. So unbelievably easy to use. Just how good
|
||||
> software should be!" - Pritin Tyagaraj, SAP
|
||||
Mongoose has built-in support for several protocols, like
|
||||
HTTP, Websocket, MQTT, mDNS. Example applications include
|
||||
Websocket-based chat server, JSON-RPC server,
|
||||
database server with RESTful API, MQTT broker, netcat with SSL and hexdump,
|
||||
Raspberry PI camera video feed + led control, and more.
|
||||
|
||||
Mongoose is ideal for the embedded environments, it has been designed as
|
||||
an open source platform for connecting devices and bringing them online.
|
||||
|
||||
[Download Mongoose Source Code here](http://hubs.ly/H0150FK0)
|
||||
|
||||
@ -23,21 +26,31 @@ Are you an embedded developer? Working on an embedded task?
|
||||
Check out our [embedded development products](http://hubs.ly/H0150sY0)
|
||||
to make the right choice for your project.
|
||||
|
||||
# Technical Specification
|
||||
# Features
|
||||
|
||||
- Works on Windows, Mac, UNIX/Linux, iPhone, Android eCos, QNX
|
||||
and many other platforms
|
||||
- CGI, SSI, SSL, Digest auth, Websocket, WEbDAV, Resumed download,
|
||||
URL rewrite, file blacklist
|
||||
- Custom error pages, Virtual hosts, IP-based ACL, Windows service,
|
||||
HTTP/HTTPS client
|
||||
- Simple and clean
|
||||
[embedding API](https://github.com/cesanta/mongoose/blob/master/mongoose.h).
|
||||
The source is in single
|
||||
[mongoose.c](https://github.com/cesanta/mongoose/blob/master/mongoose.c) file
|
||||
to make embedding easy
|
||||
- Extremely lightweight, has a core of under 40kB and tiny runtime footprint
|
||||
- Asynchronous, non-blocking core supporting single- or multi-threaded usage
|
||||
* Cross-platform: works on Linux/UNIX, QNX, eCos, Windows, Android, iPhone, etc
|
||||
* Single-threaded, asynchronous, non-blocking core with simple event-based API
|
||||
* Builtin protocols:
|
||||
- plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
|
||||
- HTTP client, HTTP server
|
||||
- Websocket client, Websocket server
|
||||
- JSON-RPC client, JSON-RPC server
|
||||
- MQTT client, MQTT broker
|
||||
- CoAP client, CoAP server
|
||||
- DNS client, DNS server, async DNS resolver
|
||||
* Tiny static and run-time footprint
|
||||
* Source code is both ISO C and ISO C++ compliant
|
||||
* Very easy to integrate: just copy
|
||||
[mongoose.c](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c) and
|
||||
[mongoose.h](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.h)
|
||||
files to your build tree
|
||||
* Extensively tested and production-ready, trusted by many blue chip businesses
|
||||
|
||||
# Examples & Documentation
|
||||
|
||||
- [User Guide](https://docs.cesanta.com/mongoose) - Detailed User Guide and API reference
|
||||
- [examples](examples) - Collection of well-commented examples. To build any example,
|
||||
go into respective directory and type `make`
|
||||
|
||||
# Dashboard Example
|
||||
|
||||
@ -47,43 +60,17 @@ and many other platforms
|
||||
](https://www.cesanta.com/contact)
|
||||
|
||||
|
||||
# Contributions
|
||||
|
||||
# Licensing
|
||||
People who have agreed to the
|
||||
[Cesanta CLA](https://docs.cesanta.com/contributors_la.shtml)
|
||||
can make contributions. Note that the CLA isn't a copyright
|
||||
_assigment_ but rather a copyright _license_.
|
||||
You retain the copyright on your contributions.
|
||||
|
||||
Cesanta made Mongoose open source under GPLv2 for a reason. We are all
|
||||
developers here and appreciate easy access to code and therefore seamless
|
||||
integration. It's great to be able to play around with the software before
|
||||
committing to it.
|
||||
# License
|
||||
|
||||
However, the GPLv2 open source license does not permit incorporating the
|
||||
software into non-open source programs. In order to comply with GPLv2 licensing
|
||||
you need to open the source code of your end product fully or alternatively
|
||||
purchase a commercial license.
|
||||
|
||||
[Enquire about commercial licensing here](https://www.cesanta.com/contact)
|
||||
|
||||
# Documentation
|
||||
|
||||
- [Embedding Guide](https://github.com/cesanta/mongoose/blob/master/docs/Embed.md)
|
||||
- [Config Options Reference](https://github.com/cesanta/mongoose/blob/master/docs/Options.md)
|
||||
- [API Reference](https://github.com/cesanta/mongoose/blob/master/docs/API.md)
|
||||
- [Android Build Tutorial](https://docs.cesanta.com/AndroidBuild.shtml)
|
||||
- [Release Notes](https://github.com/cesanta/mongoose/blob/master/docs/ReleaseNotes.md)
|
||||
|
||||
# Mongoose Binary
|
||||
|
||||
This is our easy to use web server for web developers (PHP, Ruby, Python, etc)
|
||||
and web designers. Available in three editions to suit your needs: free, pro
|
||||
(USD 5) and dev edition (from USD 8). To install, simply download, double-click
|
||||
to start and run browser - that's all!
|
||||
|
||||
[Download Mongoose Binary here](https://www.cesanta.com/mongoose)
|
||||
|
||||
# Other products by Cesanta
|
||||
|
||||
- [Smart.js](https://github.com/cesanta/smart.js) - Generic, hardware independent, full-stack IoT software platform
|
||||
- [Fossa](http://github.com/cesanta/fossa) - Multi-protocol networking library
|
||||
- [V7](https://github.com/cesanta/v7) - Embedded JavaScript engine
|
||||
- [Frozen](https://github.com/cesanta/frozen) - JSON parser and generator
|
||||
- [SLRE](https://github.com/cesanta/slre) - Super Light Regular Expression
|
||||
library
|
||||
Mongoose is released under
|
||||
[GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
|
||||
Businesses have an option to get non-restrictive, royalty-free commercial
|
||||
license and professional support from [Cesanta](https://www.cesanta.com).
|
||||
|
@ -1,20 +1,15 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
SUBDIRS = $(sort $(filter-out csharp/, $(dir $(wildcard */))))
|
||||
SUBDIRS = $(sort $(dir $(wildcard */)))
|
||||
X = $(SUBDIRS)
|
||||
ifdef WINDIR
|
||||
# appending the Winsock2 library at the end of the compiler
|
||||
# invocation
|
||||
CFLAGS_EXTRA += -lws2_32
|
||||
endif
|
||||
|
||||
.PHONY: $(SUBDIRS)
|
||||
|
||||
all: $(SUBDIRS)
|
||||
|
||||
$(SUBDIRS):
|
||||
@$(MAKE) CFLAGS_EXTRA="$(CFLAGS_EXTRA)" -C $@
|
||||
@$(MAKE) -C $@
|
||||
|
||||
clean:
|
||||
for d in $(SUBDIRS) ; do $(MAKE) -C $$d clean ; done
|
||||
|
25
examples/api_server/Makefile
Normal file
25
examples/api_server/Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
PROG = api_server
|
||||
SOURCES = $(PROG).c sqlite3.c db_plugin_sqlite.c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -pthread $(CFLAGS_EXTRA)
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Linux)
|
||||
CFLAGS += -ldl -lm
|
||||
endif
|
||||
endif
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I.. /MD /Fe$@
|
||||
|
||||
test: $(PROG)
|
||||
sh unit_test.sh $$(pwd)/$(PROG)
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
104
examples/api_server/api_server.c
Normal file
104
examples/api_server/api_server.c
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "db_plugin.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
static int s_sig_num = 0;
|
||||
static void *s_db_handle = NULL;
|
||||
static const char *s_db_path = "api_server.db";
|
||||
static const struct mg_str s_get_method = NS_STR("GET");
|
||||
static const struct mg_str s_put_method = NS_STR("PUT");
|
||||
static const struct mg_str s_delele_method = NS_STR("DELETE");
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_sig_num = sig_num;
|
||||
}
|
||||
|
||||
static int has_prefix(const struct mg_str *uri, const struct mg_str *prefix) {
|
||||
return uri->len > prefix->len && memcmp(uri->p, prefix->p, prefix->len) == 0;
|
||||
}
|
||||
|
||||
static int is_equal(const struct mg_str *s1, const struct mg_str *s2) {
|
||||
return s1->len == s2->len && memcmp(s1->p, s2->p, s2->len) == 0;
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
static const struct mg_str api_prefix = NS_STR("/api/v1");
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
struct mg_str key;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (has_prefix(&hm->uri, &api_prefix)) {
|
||||
key.p = hm->uri.p + api_prefix.len;
|
||||
key.len = hm->uri.len - api_prefix.len;
|
||||
if (is_equal(&hm->method, &s_get_method)) {
|
||||
db_op(nc, hm, &key, s_db_handle, API_OP_GET);
|
||||
} else if (is_equal(&hm->method, &s_put_method)) {
|
||||
db_op(nc, hm, &key, s_db_handle, API_OP_SET);
|
||||
} else if (is_equal(&hm->method, &s_delele_method)) {
|
||||
db_op(nc, hm, &key, s_db_handle, API_OP_DEL);
|
||||
} else {
|
||||
mg_printf(nc, "%s",
|
||||
"HTTP/1.0 501 Not Implemented\r\n"
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
}
|
||||
} else {
|
||||
mg_serve_http(nc, hm, s_http_server_opts); /* Serve static content */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
int i;
|
||||
|
||||
/* Open listening socket */
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
s_http_server_opts.document_root = "web_root";
|
||||
|
||||
/* Parse command line arguments */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-D") == 0) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-f") == 0) {
|
||||
s_db_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "-r") == 0) {
|
||||
s_http_server_opts.document_root = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
/* Open database */
|
||||
if ((s_db_handle = db_open(s_db_path)) == NULL) {
|
||||
fprintf(stderr, "Cannot open DB [%s]\n", s_db_path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Run event loop until signal is received */
|
||||
printf("Starting RESTful server on port %s\n", s_http_port);
|
||||
while (s_sig_num == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
mg_mgr_free(&mgr);
|
||||
db_close(&s_db_handle);
|
||||
|
||||
printf("Exiting on signal %d\n", s_sig_num);
|
||||
|
||||
return 0;
|
||||
}
|
19
examples/api_server/db_plugin.h
Normal file
19
examples/api_server/db_plugin.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef DB_PLUGIN_HEADER_DEFINED
|
||||
#define DB_PLUGIN_HEADER_DEFINED
|
||||
|
||||
#include "../../mongoose.h"
|
||||
|
||||
void *db_open(const char *db_path);
|
||||
void db_close(void **db_handle);
|
||||
|
||||
enum { API_OP_GET, API_OP_SET, API_OP_DEL };
|
||||
|
||||
void db_op(struct mg_connection *nc, const struct http_message *hm,
|
||||
const struct mg_str *key, void *db, int op);
|
||||
|
||||
#endif /* DB_PLUGIN_HEADER_DEFINED */
|
107
examples/api_server/db_plugin_sqlite.c
Normal file
107
examples/api_server/db_plugin_sqlite.c
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "db_plugin.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
void *db_open(const char *db_path) {
|
||||
sqlite3 *db = NULL;
|
||||
if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE |
|
||||
SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) == SQLITE_OK) {
|
||||
sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS kv(key PRIMARY KEY, val);",
|
||||
0, 0, 0);
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
void db_close(void **db_handle) {
|
||||
if (db_handle != NULL && *db_handle != NULL) {
|
||||
sqlite3_close(*db_handle);
|
||||
*db_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void op_set(struct mg_connection *nc, const struct http_message *hm,
|
||||
const struct mg_str *key, void *db) {
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
char value[200];
|
||||
const struct mg_str *body = hm->query_string.len > 0 ?
|
||||
&hm->query_string : &hm->body;
|
||||
|
||||
mg_get_http_var(body, "value", value, sizeof(value));
|
||||
if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO kv VALUES (?, ?);",
|
||||
-1, &stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, key->p, key->len, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, value, strlen(value), SQLITE_STATIC);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
|
||||
}
|
||||
|
||||
static void op_get(struct mg_connection *nc, const struct http_message *hm,
|
||||
const struct mg_str *key, void *db) {
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
const char *data = NULL;
|
||||
int result;
|
||||
(void) hm;
|
||||
|
||||
if (sqlite3_prepare_v2(db, "SELECT val FROM kv WHERE key = ?;",
|
||||
-1, &stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, key->p, key->len, SQLITE_STATIC);
|
||||
result = sqlite3_step(stmt);
|
||||
data = (char *) sqlite3_column_text(stmt, 0);
|
||||
if ((result == SQLITE_OK || result == SQLITE_ROW) && data != NULL) {
|
||||
mg_printf(nc, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Content-Length: %d\r\n\r\n%s",
|
||||
(int) strlen(data), data);
|
||||
} else {
|
||||
mg_printf(nc, "%s", "HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
mg_printf(nc, "%s", "HTTP/1.1 500 Server Error\r\n"
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void op_del(struct mg_connection *nc, const struct http_message *hm,
|
||||
const struct mg_str *key, void *db) {
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int result;
|
||||
(void) hm;
|
||||
|
||||
if (sqlite3_prepare_v2(db, "DELETE FROM kv WHERE key = ?;",
|
||||
-1, &stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, key->p, key->len, SQLITE_STATIC);
|
||||
result = sqlite3_step(stmt);
|
||||
if (result == SQLITE_OK || result == SQLITE_ROW) {
|
||||
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
|
||||
} else {
|
||||
mg_printf(nc, "%s", "HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
mg_printf(nc, "%s", "HTTP/1.1 500 Server Error\r\n"
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void db_op(struct mg_connection *nc, const struct http_message *hm,
|
||||
const struct mg_str *key, void *db, int op) {
|
||||
switch (op) {
|
||||
case API_OP_GET: op_get(nc, hm, key, db); break;
|
||||
case API_OP_SET: op_set(nc, hm, key, db); break;
|
||||
case API_OP_DEL: op_del(nc, hm, key, db); break;
|
||||
default:
|
||||
mg_printf(nc, "%s", "HTTP/1.0 501 Not Implemented\r\n"
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
140456
examples/api_server/sqlite3.c
Normal file
140456
examples/api_server/sqlite3.c
Normal file
File diff suppressed because it is too large
Load Diff
7245
examples/api_server/sqlite3.h
Normal file
7245
examples/api_server/sqlite3.h
Normal file
File diff suppressed because it is too large
Load Diff
35
examples/api_server/unit_test.sh
Normal file
35
examples/api_server/unit_test.sh
Normal file
@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
|
||||
PROG=$1
|
||||
PORT=${2:-8000} # If second param is given, this is load balancer port
|
||||
DB_FILE=/tmp/_$$.db
|
||||
URL=http://127.0.0.1:$PORT/api/v1
|
||||
|
||||
cleanup() {
|
||||
rm -rf $DB_FILE
|
||||
kill -9 $PID >/dev/null 2>&1
|
||||
}
|
||||
|
||||
#set -x
|
||||
trap cleanup EXIT
|
||||
|
||||
cleanup
|
||||
$PROG -f $DB_FILE &
|
||||
PID=$!
|
||||
|
||||
#sleep 1
|
||||
curl -s -X PUT -d 'value=123' $URL/foo
|
||||
curl -s -X PUT -d 'value=success' $URL/bar/baz
|
||||
|
||||
# Fetch existing key
|
||||
RESULT=$(curl -s $URL/bar/baz)
|
||||
test "$RESULT" = "success" || exit 1
|
||||
|
||||
# Delete it
|
||||
curl -s -X DELETE $URL/bar/baz
|
||||
|
||||
# Make sure it's deleted - GET must result in 404
|
||||
RESULT=$(curl -s -i $URL/bar/baz | head -1 | tr -d '\r')
|
||||
test "$RESULT" = "HTTP/1.1 404 Not Found" || exit 1
|
||||
|
||||
exit 0
|
@ -2,32 +2,37 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>WebSocket Test</title>
|
||||
<title>RESTful API demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #cde; margin: 0;
|
||||
padding: 0; font: 14px Helvetica, Arial, sans-serif;
|
||||
}
|
||||
* { outline: none; }
|
||||
body {
|
||||
background-color: #789; margin: 0;
|
||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
background-color: #fff; border-radius: 1em;
|
||||
}
|
||||
label { display: inline-block; min-width: 7em; }
|
||||
input { border: 1px solid #ccc; padding: 0.4em; margin: 0 0 10px 0; }
|
||||
input { border: 1px solid #ccc; padding: 0.2em; }
|
||||
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||
@media (max-width: 700px) {
|
||||
body { background-color: #fff; }
|
||||
div.content {
|
||||
width: auto; margin: 0 auto; border-radius: 0; padding: 1em;
|
||||
}
|
||||
div.content { width: auto; margin: 0 auto; padding: 1em; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||
<script language="javascript" type="text/javascript">
|
||||
jQuery(function() {
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Mongoose Cookie Base Authentication</h1>
|
||||
<p>This is an index page. Authentication succeeded.</p>
|
||||
<h1>API server</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
2
examples/arduino_restful_client/Makefile
Normal file
2
examples/arduino_restful_client/Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
# This "makefile" is only intended to prevent errors during main makefile execution
|
||||
all:
|
46
examples/arduino_restful_client/README.md
Normal file
46
examples/arduino_restful_client/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Arduino Restful Client
|
||||
|
||||
This example demonstrates how to use [Mongoose](https://www.cesanta.com/mongoose) to send HTTP commands from Arduino.
|
||||
|
||||
Example sends free memory size and current board uptime, but it can be modified to send any user-specific data.
|
||||
|
||||
At the moment this example supports [Arduino Mega 2560](http://arduino.cc/en/Main/ArduinoBoardMega2560) board (and compatible) with either W5100-based
|
||||
network shield (like [Arduino Ethernet Shield](http://arduino.cc/en/Main/ArduinoEthernetShield)) or [CC3000](http://www.ti.com/product/cc3000)-based WIFI Shield.
|
||||
|
||||
## Build and run instructions:
|
||||
|
||||
###To run with Arduino Ethernet (W5100) shield:
|
||||
1. Add (Sketch->Add file...) the following files to sketch:
|
||||
- /mongoose/mongoose.h
|
||||
- /mongoose/mongoose.c
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
3. Buils and start (in console) /Users/alex/Projects/mongoose/examples/restful_server example
|
||||
4. Make `board_ip` and `board_mac` variables suitable for your network and board
|
||||
5. Change IP address in `s_target_address` variable to IP address of host running restful_server
|
||||
6. Uncomment line `#include <Ethernet.h>`
|
||||
7. Compile and flash sketch
|
||||
8. restful_server will start to show current uptime and free memory size (with 5 seconds interval)
|
||||
|
||||
###To run with Adafruit WiFi (CC3000) shield:
|
||||
1. Add (Sketch->Add files...) the following files to sketch:
|
||||
- /mongoose/mongoose.h
|
||||
- /mongoose/mongoose.c
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
2. Import Adafruit CC3000 library for mongoose
|
||||
(select Sketch->Import Library...->Add library... and point
|
||||
/mongoose/platforms/arduino_wifi_CC3000/adafruit_CC3000_lib_mongoose folder)
|
||||
3. Buils and start (in console) /Users/alex/Projects/mongoose/examples/restful_server example
|
||||
4. Make the following variables suitable for your network
|
||||
- `board_ip`
|
||||
- `subnet_mask`
|
||||
- `gateway`
|
||||
- `dns`
|
||||
- `wlan_ssid`
|
||||
- `wlan_pwd`
|
||||
- `wlan_security`
|
||||
5. Change IP address in `s_target_address` variable to IP address of host running restful_server
|
||||
6. Uncomment line `#include <Adafruit_CC3000.h>`
|
||||
8. Compile and flash sketch
|
||||
9. restful_server will start to show current uptime and free memory size (with 5 seconds interval)
|
141
examples/arduino_restful_client/arduino_restful_client.ino
Normal file
141
examples/arduino_restful_client/arduino_restful_client.ino
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Build and run instructions:
|
||||
* To run with Arduino Ethernet (W5100) shield:
|
||||
* -----------------------------------------------------------
|
||||
* 1. Add (Sketch->Add file...) the following files to sketch:
|
||||
* - /mongoose/mongoose.h
|
||||
* - /mongoose/mongoose.c
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
* 2. Buils and run in console /Users/alex/Projects/mongoose/examples/restful_server example
|
||||
* 3. Make board_ip and board_mac variables suitable for your network and board
|
||||
* 4. Uncomment line #include <Ethernet.h>
|
||||
* 5. Change IP address in s_target_address variable to IP address of host running restful_server
|
||||
* 6. Compile & flash sketch
|
||||
* 7. restful_server server will start to show current uptime and free memory size (with 5 second interval)
|
||||
*
|
||||
* To run with Adafruit WiFi (CC3000) shield:
|
||||
* -----------------------------------------------------------
|
||||
* 1. Add (Sketch->Add files...) the following files to sketch:
|
||||
* - /mongoose/mongoose.h
|
||||
* - /mongoose/mongoose.c
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
* 2. Import Adafruit CC3000 library for mongoose (select Sketch->Import Library...->Add library... and point
|
||||
* /mongoose/platforms/arduino_wifi_CC3000/adafruit_CC3000_lib_mongoose folder
|
||||
* 3. Buils and run in console /Users/alex/Projects/mongoose/examples/restful_server example
|
||||
* 4. Make the following variables suitable for your network
|
||||
* - board_ip
|
||||
* - subnet_mask
|
||||
* - gateway
|
||||
* - dns
|
||||
* - wlan_ssid
|
||||
* - wlan_pwd
|
||||
* - wlan_security
|
||||
* 5. Uncomment line #include <Adafruit_CC3000.h>
|
||||
* 6. Compile & flash sketch
|
||||
* 7. restful_server server will start to show current uptime and free memory size (with 5 second interval) *
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//#include <Ethernet.h>
|
||||
//#include <Adafruit_CC3000.h>
|
||||
#include <SPI.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
// CHANGE THESE VARIABLES
|
||||
// NB: Devices with the same address must not end up on the same network.
|
||||
// Use MAC address provided by device manufacturer (e.g. on a sticker).
|
||||
// If there isn't one, use a random one from the locally administered range.
|
||||
// See http://en.wikipedia.org/wiki/MAC_address for details.
|
||||
static uint8_t board_mac[] = {
|
||||
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
|
||||
};
|
||||
static uint8_t board_ip[] = {192, 168, 10, 177};
|
||||
|
||||
#ifdef WIFI_CC3000
|
||||
static uint8_t subnet_mask[] = {255, 255, 255, 0};
|
||||
static uint8_t gateway[] = {192, 168, 10, 254};
|
||||
static uint8_t dmg_ip[] = {192, 168, 10, 254};
|
||||
|
||||
static const char *wlan_ssid = "mynetwork";
|
||||
static const char *wlan_pwd = "mypassword";
|
||||
static int wlan_security = WLAN_SEC_WPA2;
|
||||
#endif
|
||||
|
||||
static const char *s_target_address = "192.168.10.3:8000";
|
||||
|
||||
/////////////////////////////////////////////
|
||||
|
||||
static const char *s_request = "/printcontent";
|
||||
|
||||
static uint32_t IP2U32(uint8_t* iparr) {
|
||||
return ((uint32_t)iparr[0] << 24) | ((uint32_t)iparr[1] << 16) | (iparr[2] << 8) | (iparr[3]);
|
||||
}
|
||||
|
||||
static int get_data_to_send(char* buf, int buf_size) {
|
||||
// Adding data to send
|
||||
// It could be any sensor data, now just put uptime & free memory size here
|
||||
return snprintf(buf, buf_size, "Uptime: %lus Free memory: %db",
|
||||
millis()/1000, get_freememsize());
|
||||
}
|
||||
static void rfc_ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
int connect_status;
|
||||
|
||||
switch (ev) {
|
||||
case NS_CONNECT:
|
||||
connect_status = * (int *) ev_data;
|
||||
|
||||
if (connect_status == 0) {
|
||||
char buf[100];
|
||||
int len = get_data_to_send(buf, sizeof(buf));
|
||||
mg_printf(nc, "POST %s HTTP/1.0\r\nHost: %s\r\nContent-Lenght: %d"
|
||||
"\r\n\r\n%s", s_request, s_target_address, len, buf);
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
} else {
|
||||
nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct mg_mgr mgr;
|
||||
static struct mg_connection *nc;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(9600);
|
||||
Serial.println("Initialization...");
|
||||
#if defined(ETHERNET_W5100)
|
||||
avr_netinit(board_mac, board_ip);
|
||||
#elif defined(WIFI_CC3000)
|
||||
if (avr_netinit(wlan_ssid, wlan_pwd, wlan_security, IP2U32(board_ip),
|
||||
IP2U32(subnet_mask), IP2U32(gateway), IP2U32(dmg_ip)) != 0) {
|
||||
Serial.println("Initialization error, check network settings");
|
||||
return;
|
||||
};
|
||||
#endif
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
Serial.println("Initialization done");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
nc = mg_connect(&mgr, s_target_address, rfc_ev_handler);
|
||||
if (nc != NULL) {
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
}
|
||||
|
||||
uint32_t time_to_finish = millis() + 5000;
|
||||
while (millis() < time_to_finish) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
}
|
||||
|
2
examples/arduino_restful_server/Makefile
Normal file
2
examples/arduino_restful_server/Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
# This "makefile" is only intended to prevent errors during main makefile execution
|
||||
all:
|
45
examples/arduino_restful_server/README.md
Normal file
45
examples/arduino_restful_server/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Arduino Restful Server
|
||||
|
||||
This example demonstrates how to use [Mongoose](https://www.cesanta.com/mongoose) to control Arduino
|
||||
using HTTP requests.
|
||||
Example just blinks by LED when Mongoose receives HTTP command, but it can be modified to execute any user-specific code.
|
||||
|
||||
At the moment this example supports [Arduino Mega 2560](http://arduino.cc/en/Main/ArduinoBoardMega2560) board (and compatible) with either W5100-based
|
||||
network shield (like [Arduino Ethernet Shield](http://arduino.cc/en/Main/ArduinoEthernetShield)) or [CC3000](http://www.ti.com/product/cc3000)-based WIFI Shield.
|
||||
|
||||
## Build and run instructions:
|
||||
|
||||
###To run with Arduino Ethernet (W5100) shield:
|
||||
1. Add (Sketch->Add file...) the following files to sketch:
|
||||
- /mongoose/mongoose.h
|
||||
- /mongoose/mongoose.c
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
2. Make `board_ip` and `board_mac` variables suitable for your network and board
|
||||
3. Uncomment line `#include <Ethernet.h>`
|
||||
4. Compile and flash sketch
|
||||
5. Run `curl http://<board_ip/blink`
|
||||
LED attached to PIN 13 will blink and board free memory size and board uptime will be displayed.
|
||||
|
||||
|
||||
###To run with Adafruit WiFi (CC3000) shield:
|
||||
1. Add (Sketch->Add files...) the following files to sketch:
|
||||
- /mongoose/mongoose.h
|
||||
- /mongoose/mongoose.c
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
- /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
2. Import Adafruit CC3000 library for mongoose
|
||||
(select Sketch->Import Library...->Add library... and point
|
||||
/mongoose/platforms/arduino_wifi_CC3000/adafruit_CC3000_lib_mongoose folder)
|
||||
3. Make the following variables suitable for your network
|
||||
- `board_ip`
|
||||
- `subnet_mask`
|
||||
- `gateway`
|
||||
- `dns`
|
||||
- `wlan_ssid`
|
||||
- `wlan_pwd`
|
||||
- `wlan_security`
|
||||
5. Uncomment line `#include <Adafruit_CC3000.h>`
|
||||
4. Compile and flash sketch
|
||||
5. Run curl `http://<board_ip/blink`
|
||||
LED attached to PIN 13 will blink and board free memory size and board uptime will be displayed.
|
133
examples/arduino_restful_server/arduino_restful_server.ino
Normal file
133
examples/arduino_restful_server/arduino_restful_server.ino
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Build and run instructions:
|
||||
* To run with Arduino Ethernet (W5100) shield:
|
||||
* -----------------------------------------------------------
|
||||
* 1. Add (Sketch->Add file...) the following files to sketch:
|
||||
* - /mongoose/mongoose.h
|
||||
* - /mongoose/mongoose.c
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
* 2. Make board_ip and board_mac variables suitable for your network and board
|
||||
* 3. Uncomment line #include <Ethernet.h>
|
||||
* 4. Compile & flash sketch
|
||||
* 5. Run curl http://<board_ip/blink
|
||||
* LED attached to PIN 13 will blink and board free memory size and uptime will responsed
|
||||
*
|
||||
* To run with Adafruit WiFi (CC3000) shield:
|
||||
* -----------------------------------------------------------
|
||||
* 1. Add (Sketch->Add files...) the following files to sketch:
|
||||
* - /mongoose/mongoose.h
|
||||
* - /mongoose/mongoose.c
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.h
|
||||
* - /mongoose/platforms/arduino_ethernet_W5100/avrsupport.cpp
|
||||
* 2. Import Adafruit CC3000 library for mongoose (select Sketch->Import Library...->Add library... and point
|
||||
* /mongoose/platforms/arduino_wifi_CC3000/adafruit_CC3000_lib_mongoose folder
|
||||
* 3. Make the following variables suitable for your network
|
||||
* - board_ip
|
||||
* - subnet_mask
|
||||
* - gateway
|
||||
* - dns
|
||||
* - wlan_ssid
|
||||
* - wlan_pwd
|
||||
* - wlan_security
|
||||
* 5. Uncomment line #include <Adafruit_CC3000.h>
|
||||
* 4. Compile & flash sketch
|
||||
* 5. Run curl http://<board_ip/blink
|
||||
* LED attached to PIN 13 will blink and board free memory size and uptime will responsed
|
||||
*
|
||||
*/
|
||||
|
||||
//#include <Ethernet.h>
|
||||
//#include <Adafruit_CC3000.h>
|
||||
#include <SPI.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
// CHANGE THESE VARIABLES
|
||||
// NB: Devices with the same address must not end up on the same network.
|
||||
// Use MAC address provided by device manufacturer (e.g. on a sticker).
|
||||
// If there isn't one, use a random one from the locally administered range.
|
||||
// See http://en.wikipedia.org/wiki/MAC_address for details.
|
||||
static uint8_t board_mac[] = {
|
||||
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
|
||||
};
|
||||
|
||||
static uint8_t board_ip[] = {192, 168, 10, 8};
|
||||
|
||||
#ifdef WIFI_CC3000
|
||||
static uint8_t subnet_mask[] = {255, 255, 255, 0};
|
||||
static uint8_t gateway[] = {192, 168, 10, 254};
|
||||
static uint8_t dmg_ip[] = {192, 168, 10, 254};
|
||||
|
||||
static const char *wlan_ssid = "mynetwork";
|
||||
static const char *wlan_pwd = "mypassword";
|
||||
static int wlan_security = WLAN_SEC_WPA2;
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////
|
||||
|
||||
static const char *s_http_port = "60000";
|
||||
|
||||
static uint32_t IP2U32(uint8_t* iparr) {
|
||||
return ((uint32_t)iparr[0] << 24) | ((uint32_t)iparr[1] << 16) | (iparr[2] << 8) | (iparr[3]);
|
||||
}
|
||||
|
||||
static void rfs_ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
char buf[100];
|
||||
int clen;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/blink") == 0) {
|
||||
blink(1, 500);
|
||||
}
|
||||
|
||||
clen = snprintf(buf, sizeof(buf),
|
||||
"Free memory size: %d Uptime: %d",
|
||||
(int)get_freememsize(), (int)time(NULL));
|
||||
|
||||
mg_printf_http_chunk(nc, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Transfer-Encoding: chunked\r\n\r\n"
|
||||
"%s",
|
||||
clen, buf);
|
||||
|
||||
mg_send_http_chunk(nc, "", 0);
|
||||
break;
|
||||
case NS_SEND:
|
||||
nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct mg_connection *nc;
|
||||
static struct mg_mgr mgr;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.println("Initialization...");
|
||||
#if defined(ETHERNET_W5100)
|
||||
avr_netinit(board_mac, board_ip);
|
||||
#elif defined(WIFI_CC3000)
|
||||
if (avr_netinit(wlan_ssid, wlan_pwd, wlan_security, IP2U32(board_ip),
|
||||
IP2U32(subnet_mask), IP2U32(gateway), IP2U32(dmg_ip)) != 0) {
|
||||
Serial.println("Initialization error, check network settings");
|
||||
return;
|
||||
};
|
||||
#endif
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, rfs_ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
Serial.println("Initialization done");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = array_vars
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
run: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
$(PROG): $(SOURCES) Makefile
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
win:
|
||||
wine cl $(SOURCES) /MD /nologo /DNDEBUG /O1 /I../.. /Fe$(PROG).exe
|
||||
wine $(PROG).exe
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib *.gc*
|
@ -1,45 +0,0 @@
|
||||
// Copyright (c) 2014 Cesanta Software
|
||||
// All rights reserved
|
||||
//
|
||||
// This example demostrates how to use array get variables using mg_get_n_var
|
||||
// $Date: 2014-09-09 22:20:23 UTC $
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
case MG_REQUEST:
|
||||
{
|
||||
mg_printf_data(conn, "Hello! Requested URI is [%s] ", conn->uri);
|
||||
char buffer[1024];
|
||||
int i, ret;
|
||||
for(i=0; (ret = mg_get_var_n(conn, "foo[]", buffer, 1024, i)) > 0; i++)
|
||||
mg_printf_data(conn, "\nfoo[%d] = %s", i, buffer);
|
||||
|
||||
return MG_TRUE;
|
||||
}
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server;
|
||||
|
||||
// Create and configure the server
|
||||
server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
// Serve request. Hit Ctrl-C to terminate the program
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
|
||||
// Cleanup, and free server instance
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = big_upload
|
||||
CFLAGS = -W -Wall -pthread -I../.. -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,84 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int handle_request(struct mg_connection *conn) {
|
||||
if (strcmp(conn->uri, "/upload") == 0) {
|
||||
FILE *fp = (FILE *) conn->connection_param;
|
||||
if (fp != NULL) {
|
||||
fwrite(conn->content, 1, conn->content_len, fp); // Write last bits
|
||||
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n\r\n"
|
||||
"Written %ld of POST data to a temp file:\n\n",
|
||||
(long) ftell(fp));
|
||||
|
||||
// Temp file will be destroyed after fclose(), do something with the
|
||||
// data here -- for example, parse it and extract uploaded files.
|
||||
// As an example, we just echo the whole POST buffer back to the client.
|
||||
rewind(fp);
|
||||
mg_send_file_data(conn, fileno(fp));
|
||||
return MG_MORE; // Tell Mongoose reply is not completed yet
|
||||
} else {
|
||||
mg_printf_data(conn, "%s", "Had no data to write...");
|
||||
return MG_TRUE; // Tell Mongoose we're done with this request
|
||||
}
|
||||
} else {
|
||||
mg_printf_data(conn, "%s",
|
||||
"<html><body>Upload example."
|
||||
"<form method=\"POST\" action=\"/upload\" "
|
||||
" enctype=\"multipart/form-data\">"
|
||||
"<input type=\"file\" name=\"file\" /> <br/>"
|
||||
"<input type=\"submit\" value=\"Upload\" />"
|
||||
"</form></body></html>");
|
||||
return MG_TRUE; // Tell mongoose to close this connection
|
||||
}
|
||||
}
|
||||
|
||||
// Mongoose sends MG_RECV for every received POST chunk.
|
||||
// When last POST chunk is received, Mongoose sends MG_REQUEST, then MG_CLOSE.
|
||||
static int handle_recv(struct mg_connection *conn) {
|
||||
FILE *fp = (FILE *) conn->connection_param;
|
||||
|
||||
// Open temporary file where we going to write data
|
||||
if (fp == NULL && ((conn->connection_param = fp = tmpfile())) == NULL) {
|
||||
return -1; // Close connection on error
|
||||
}
|
||||
|
||||
// Return number of bytes written to a temporary file: that is how many
|
||||
// bytes we want to discard from the receive buffer
|
||||
return fwrite(conn->content, 1, conn->content_len, fp);
|
||||
}
|
||||
|
||||
// Make sure we free all allocated resources
|
||||
static int handle_close(struct mg_connection *conn) {
|
||||
if (conn->connection_param != NULL) {
|
||||
fclose((FILE *) conn->connection_param);
|
||||
conn->connection_param = NULL;
|
||||
}
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
case MG_REQUEST: return handle_request(conn);
|
||||
case MG_RECV: return handle_recv(conn);
|
||||
case MG_CLOSE: return handle_close(conn);
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
|
||||
mg_destroy_server(&server);
|
||||
return 0;
|
||||
}
|
4
examples/captive_dns_server/Makefile
Normal file
4
examples/captive_dns_server/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
PROG = captive_dns_server
|
||||
MODULE_CFLAGS=-DNS_ENABLE_DNS_SERVER
|
||||
|
||||
include ../rules.mk
|
82
examples/captive_dns_server/captive_dns_server.c
Normal file
82
examples/captive_dns_server/captive_dns_server.c
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* Try it out with:
|
||||
* $ dig -t A www.google.com -4 @localhost -p 5533
|
||||
*/
|
||||
|
||||
#include "../../mongoose.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int s_exit_flag = 0;
|
||||
static in_addr_t s_our_ip_addr;
|
||||
static const char *s_listening_addr = "udp://:5533";
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct mg_dmg_message *msg;
|
||||
struct mg_dmg_resource_record *rr;
|
||||
struct mg_dmg_reply reply;
|
||||
int i;
|
||||
|
||||
switch (ev) {
|
||||
case NS_DNS_MESSAGE:
|
||||
msg = (struct mg_dmg_message *) ev_data;
|
||||
reply = mg_dmg_create_reply(&nc->send_mbuf, msg);
|
||||
|
||||
for (i = 0; i < msg->num_questions; i++) {
|
||||
rr = &msg->questions[i];
|
||||
if (rr->rtype == NS_DNS_A_RECORD) {
|
||||
mg_dmg_reply_record(&reply, rr, NULL, rr->rtype, 3600,
|
||||
&s_our_ip_addr, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't set the error flag even if there were no answers
|
||||
* maching the NS_DNS_A_RECORD query type.
|
||||
* This indicates that we have (syntetic) answers for NS_DNS_A_RECORD.
|
||||
* See http://goo.gl/QWvufr for a distinction between NXDOMAIN and NODATA.
|
||||
*/
|
||||
|
||||
mg_dmg_send_reply(nc, &reply);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
int i;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
s_our_ip_addr = inet_addr("127.0.0.1");
|
||||
|
||||
/* Parse command line arguments */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-D") == 0) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
|
||||
s_listening_addr = argv[++i];
|
||||
} else {
|
||||
s_our_ip_addr = inet_addr(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Listening on '%s'\n", s_listening_addr);
|
||||
if ((nc = mg_bind(&mgr, s_listening_addr, ev_handler)) == NULL) {
|
||||
fprintf(stderr, "cannot bind to socket\n");
|
||||
exit(1);
|
||||
}
|
||||
mg_set_protocol_dns(nc);
|
||||
|
||||
while (s_exit_flag == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
14
examples/coap_client/Makefile
Normal file
14
examples/coap_client/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = coap_client
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA) -DNS_ENABLE_COAP
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
72
examples/coap_client/coap_client.c
Normal file
72
examples/coap_client/coap_client.c
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* This program sends CoAP CON-message to server (coap.me by default)
|
||||
* and waits for answer.
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_time_to_exit = 0;
|
||||
static char* s_default_address = "udp://coap.me:5683";
|
||||
|
||||
static void coap_handler(struct mg_connection *nc, int ev, void *p) {
|
||||
switch (ev) {
|
||||
case NS_CONNECT: {
|
||||
struct mg_coap_message cm;
|
||||
uint32_t res;
|
||||
|
||||
memset(&cm, 0, sizeof(cm));
|
||||
cm.msg_id = 1;
|
||||
cm.msg_type = NS_COAP_MSG_CON;
|
||||
printf("Sending CON...\n");
|
||||
res = mg_coap_send_message(nc, &cm);
|
||||
if (res == 0) {
|
||||
printf("Sent CON with msg_id = %d\n", cm.msg_id);
|
||||
} else {
|
||||
printf("Error: %d\n", res);
|
||||
s_time_to_exit = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NS_COAP_ACK:
|
||||
case NS_COAP_RST: {
|
||||
struct mg_coap_message *cm = (struct mg_coap_message *)p;
|
||||
printf("ACK/RST for message with msg_id = %d received\n",
|
||||
cm->msg_id);
|
||||
s_time_to_exit = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
char *address = s_default_address;
|
||||
|
||||
if (argc > 1) {
|
||||
address = argv[1];
|
||||
}
|
||||
|
||||
printf("Using %s as CoAP server\n", address);
|
||||
|
||||
mg_mgr_init(&mgr, 0);
|
||||
|
||||
nc = mg_connect(&mgr, address, coap_handler);
|
||||
if (nc == NULL) {
|
||||
printf("Unable to connect to %s\n", address);
|
||||
return -1;
|
||||
}
|
||||
|
||||
mg_set_protocol_coap(nc);
|
||||
|
||||
while (!s_time_to_exit) {
|
||||
mg_mgr_poll(&mgr, 1);
|
||||
}
|
||||
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
14
examples/coap_server/Makefile
Normal file
14
examples/coap_server/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = coap_server
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA) -DNS_ENABLE_COAP
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
73
examples/coap_server/coap_server.c
Normal file
73
examples/coap_server/coap_server.c
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* This program listens on 5683 for CoAP messages,
|
||||
* sends ACK is nessesary and dump everything received.
|
||||
* It is possible to use ../coap_client to send message.
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static char* s_default_address = "udp://:5683";
|
||||
static int s_sig_received = 0;
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_sig_received = sig_num;
|
||||
}
|
||||
|
||||
static void coap_handler(struct mg_connection *nc, int ev, void *p) {
|
||||
switch (ev) {
|
||||
case NS_COAP_CON: {
|
||||
uint32_t res;
|
||||
struct mg_coap_message *cm = (struct mg_coap_message *)p;
|
||||
printf("CON with msg_id = %d received\n", cm->msg_id);
|
||||
res = mg_coap_send_ack(nc, cm->msg_id);
|
||||
if (res == 0) {
|
||||
printf("Successfully sent ACK for message with msg_id = %d\n",
|
||||
cm->msg_id);
|
||||
} else {
|
||||
printf("Error: %d\n", res);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NS_COAP_NOC:
|
||||
case NS_COAP_ACK:
|
||||
case NS_COAP_RST: {
|
||||
struct mg_coap_message *cm = (struct mg_coap_message *)p;
|
||||
printf("ACK/RST/NOC with msg_id = %d received\n",
|
||||
cm->msg_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
mg_mgr_init(&mgr, 0);
|
||||
|
||||
nc = mg_bind(&mgr, s_default_address, coap_handler);
|
||||
if (nc == NULL) {
|
||||
printf("Unable to start listener at %s\n", s_default_address);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Listening for CoAP messages at %s\n", s_default_address);
|
||||
|
||||
mg_set_protocol_coap(nc);
|
||||
|
||||
while (!s_sig_received) {
|
||||
mg_mgr_poll(&mgr, 1);
|
||||
}
|
||||
|
||||
printf("Exiting on signal %d\n", s_sig_received);
|
||||
|
||||
mg_mgr_free(&mgr);
|
||||
return 0;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = cookie_auth
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,97 +0,0 @@
|
||||
// Copyright (c) 2014 Cesanta Software
|
||||
// All rights reserved
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_login_uri = "/login.html";
|
||||
static const char *s_secret = ":-)"; // Must be known only to server
|
||||
|
||||
static void generate_ssid(const char *user_name, const char *expiration_date,
|
||||
char *ssid, size_t ssid_size) {
|
||||
char hash[33];
|
||||
mg_md5(hash, user_name, ":", expiration_date, ":", s_secret, NULL);
|
||||
snprintf(ssid, ssid_size, "%s|%s|%s", user_name, expiration_date, hash);
|
||||
}
|
||||
|
||||
static int check_auth(struct mg_connection *conn) {
|
||||
char ssid[100], calculated_ssid[100], name[100], expire[100];
|
||||
|
||||
// Always authenticate requests to login page
|
||||
if (strcmp(conn->uri, s_login_uri) == 0) {
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
// Look for session ID in the Cookie.
|
||||
// That session ID can be validated against the database that stores
|
||||
// current active sessions.
|
||||
mg_parse_header(mg_get_header(conn, "Cookie"), "ssid", ssid, sizeof(ssid));
|
||||
if (sscanf(ssid, "%[^|]|%[^|]|", name, expire) == 2) {
|
||||
generate_ssid(name, expire, calculated_ssid, sizeof(calculated_ssid));
|
||||
if (strcmp(ssid, calculated_ssid) == 0) {
|
||||
return MG_TRUE; // Authenticate
|
||||
}
|
||||
}
|
||||
|
||||
// Auth failed, do NOT authenticate, redirect to login page
|
||||
mg_printf(conn, "HTTP/1.1 302 Moved\r\nLocation: %s\r\n\r\n", s_login_uri);
|
||||
return MG_FALSE;
|
||||
}
|
||||
|
||||
static int check_login_form_submission(struct mg_connection *conn) {
|
||||
char name[100], password[100], ssid[100], expire[100], expire_epoch[100];
|
||||
|
||||
mg_get_var(conn, "name", name, sizeof(name));
|
||||
mg_get_var(conn, "password", password, sizeof(password));
|
||||
|
||||
// A real authentication mechanism should be employed here.
|
||||
// Also, the whole site should be served through HTTPS.
|
||||
if (strcmp(name, "Joe") == 0 && strcmp(password, "Doe") == 0) {
|
||||
// Generate expiry date
|
||||
time_t t = time(NULL) + 3600; // Valid for 1 hour
|
||||
snprintf(expire_epoch, sizeof(expire_epoch), "%lu", (unsigned long) t);
|
||||
strftime(expire, sizeof(expire), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
|
||||
generate_ssid(name, expire_epoch, ssid, sizeof(ssid));
|
||||
// Set "session id" cookie, there could be some data encoded in it.
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 302 Moved\r\n"
|
||||
"Set-Cookie: ssid=%s; expire=\"%s\"; http-only; HttpOnly;\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Location: /\r\n\r\n",
|
||||
ssid, expire);
|
||||
return MG_TRUE;
|
||||
}
|
||||
return MG_FALSE;
|
||||
}
|
||||
|
||||
static int serve_request(struct mg_connection *conn) {
|
||||
if (strcmp(conn->uri, s_login_uri) == 0 &&
|
||||
strcmp(conn->request_method, "POST") == 0) {
|
||||
return check_login_form_submission(conn);
|
||||
}
|
||||
return MG_FALSE; // Serve files in the document_root
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH: return check_auth(conn);
|
||||
case MG_REQUEST: return serve_request(conn);
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
mg_set_option(server, "document_root", ".");
|
||||
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>WebSocket Test</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #cde; margin: 0;
|
||||
padding: 0; font: 14px Helvetica, Arial, sans-serif;
|
||||
}
|
||||
* { outline: none; }
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
background-color: #fff; border-radius: 1em;
|
||||
}
|
||||
label { display: inline-block; min-width: 7em; }
|
||||
input { border: 1px solid #ccc; padding: 0.4em; margin: 0 0 10px 0; }
|
||||
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||
@media (max-width: 700px) {
|
||||
body { background-color: #fff; }
|
||||
div.content {
|
||||
width: auto; margin: 0 auto; border-radius: 0; padding: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Mongoose Cookie Based Authentication</h1>
|
||||
<p>Use name "Joe", password "Doe" to login.</p>
|
||||
<form method="POST">
|
||||
<div>
|
||||
<label>Name:</label>
|
||||
<input type="text" name="name"/>
|
||||
</div><div>
|
||||
<label>Password:</label>
|
||||
<input type="password" name="password"/>
|
||||
</div><div>
|
||||
<input type="submit" value="Login"/>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
@ -1,43 +0,0 @@
|
||||
// This file is part of mongoose web server project,
|
||||
// https://github.com/cesanta/mongoose
|
||||
|
||||
using System;
|
||||
|
||||
public class Program {
|
||||
static private int EventHandler(IntPtr conn_ptr, int ev) {
|
||||
MongooseConnection conn = (MongooseConnection)
|
||||
System.Runtime.InteropServices.Marshal.PtrToStructure(
|
||||
conn_ptr , typeof(MongooseConnection));
|
||||
|
||||
if (ev == 102) {
|
||||
// MG_AUTH
|
||||
return 1;
|
||||
} else if (ev == 103) {
|
||||
// MG_REQUEST
|
||||
Mongoose.send_data(conn_ptr, "Hello from C#!\n");
|
||||
Mongoose.send_data(conn_ptr, "URI: " + conn.uri + "\n");
|
||||
Mongoose.send_data(conn_ptr, "HTTP Headers:\n");
|
||||
|
||||
for (int i = 0; i < conn.num_headers; i++) {
|
||||
IntPtr name = conn.http_headers[i].name;
|
||||
IntPtr val = conn.http_headers[i].value;
|
||||
System.Runtime.InteropServices.Marshal.PtrToStringAnsi(name);
|
||||
Mongoose.send_data(conn_ptr, " " +
|
||||
System.Runtime.InteropServices.Marshal.PtrToStringAnsi(name) + ": " +
|
||||
System.Runtime.InteropServices.Marshal.PtrToStringAnsi(val) + "\n");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void Main() {
|
||||
Mongoose web_server = new Mongoose(".", "9001",
|
||||
new MongooseEventHandler(EventHandler));
|
||||
|
||||
Console.WriteLine("Mongoose started, press Ctrl-C to exit.");
|
||||
for (;;) {
|
||||
web_server.poll(1000);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// This file is part of mongoose web server project,
|
||||
// https://github.com/cesanta/mongoose
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)] public struct MongooseHeader {
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public IntPtr name;
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public IntPtr value;
|
||||
};
|
||||
|
||||
// mongoose.h :: struct mg_connection
|
||||
[StructLayout(LayoutKind.Sequential)] public struct MongooseConnection {
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string request_method;
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string uri;
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string http_version;
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string query_string;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray,SizeConst=48)] public char[] remote_ip;
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string local_ip;
|
||||
[MarshalAs(UnmanagedType.U2)] public short remote_port;
|
||||
[MarshalAs(UnmanagedType.U2)] public short local_port;
|
||||
|
||||
[MarshalAs(UnmanagedType.SysInt)] public int num_headers;
|
||||
[MarshalAs(UnmanagedType.ByValArray,SizeConst=30)]
|
||||
public MongooseHeader[] http_headers;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public IntPtr content;
|
||||
[MarshalAs(UnmanagedType.SysInt)] public int content_len;
|
||||
|
||||
[MarshalAs(UnmanagedType.SysInt)] public int is_websocket;
|
||||
[MarshalAs(UnmanagedType.SysInt)] public int status_code;
|
||||
[MarshalAs(UnmanagedType.SysInt)] public int wsbits;
|
||||
};
|
||||
|
||||
public delegate int MongooseEventHandler(IntPtr c, int ev);
|
||||
|
||||
public class Mongoose {
|
||||
public const string dll_ = "mongoose";
|
||||
private IntPtr server_;
|
||||
|
||||
[DllImport(dll_)] private static extern IntPtr
|
||||
mg_create_server(IntPtr user_data, MongooseEventHandler eh);
|
||||
[DllImport(dll_)] private static extern int
|
||||
mg_poll_server(IntPtr server, int milli);
|
||||
[DllImport(dll_)] private static extern IntPtr
|
||||
mg_set_option(IntPtr server, string name, string value);
|
||||
[DllImport(dll_)] public static extern int
|
||||
mg_send_data(IntPtr conn, string data, int length);
|
||||
|
||||
public Mongoose(string document_root,
|
||||
string listening_port,
|
||||
MongooseEventHandler event_handler) {
|
||||
server_ = mg_create_server(IntPtr.Zero, event_handler);
|
||||
mg_set_option(server_, "document_root", document_root);
|
||||
mg_set_option(server_, "listening_port", listening_port);
|
||||
}
|
||||
|
||||
public static int send_data(IntPtr conn, string data) {
|
||||
return mg_send_data(conn, data, data.Length);
|
||||
}
|
||||
|
||||
public void poll(int milli) {
|
||||
mg_poll_server(server_, milli);
|
||||
}
|
||||
|
||||
// TODO: add destructor and call mg_destroy_server()
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = digest_auth
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,36 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
|
||||
if (ev == MG_AUTH) {
|
||||
int result = MG_FALSE; // Not authorized
|
||||
FILE *fp;
|
||||
|
||||
// To populate passwords file, do
|
||||
// mongoose -A my_passwords.txt mydomain.com admin admin
|
||||
if ((fp = fopen("my_passwords.txt", "r")) != NULL) {
|
||||
result = mg_authorize_digest(conn, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return MG_FALSE;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
mg_set_option(server, "document_root", ".");
|
||||
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = file_upload
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
run: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
$(PROG): $(SOURCES) Makefile
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
win:
|
||||
wine cl $(SOURCES) /MD /nologo /DNDEBUG /O1 /I../.. /Fe$(PROG).exe
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib *.gc*
|
@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2004-2012 Sergey Lyubka
|
||||
// This file is a part of mongoose project, http://github.com/valenok/mongoose
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int send_index_page(struct mg_connection *conn) {
|
||||
const char *data;
|
||||
int data_len, n1, n2;
|
||||
char var_name[100], file_name[100];
|
||||
|
||||
mg_printf_data(conn, "%s",
|
||||
"<html><body>Upload example."
|
||||
"<form method=\"POST\" action=\"/handle_post_request\" "
|
||||
" enctype=\"multipart/form-data\">"
|
||||
"<input type=\"file\" name=\"file1\" /> <br/>"
|
||||
"<input type=\"file\" name=\"file2\" /> <br/>"
|
||||
"<input type=\"submit\" value=\"Upload\" />"
|
||||
"</form>");
|
||||
|
||||
n1 = n2 = 0;
|
||||
while ((n2 = mg_parse_multipart(conn->content + n1, conn->content_len - n1,
|
||||
var_name, sizeof(var_name), file_name,
|
||||
sizeof(file_name), &data, &data_len)) > 0) {
|
||||
mg_printf_data(conn, "var: %s, file_name: %s, size: %d bytes<br>",
|
||||
var_name, file_name, data_len);
|
||||
n1 += n2;
|
||||
}
|
||||
|
||||
mg_printf_data(conn, "%s", "</body></html>");
|
||||
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
case MG_REQUEST: return send_index_page(conn);
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server;
|
||||
|
||||
// Create and configure the server
|
||||
server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
// Serve request. Hit Ctrl-C to terminate the program
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
|
||||
// Cleanup, and free server instance
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = form_submit
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,62 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *html_form =
|
||||
"<html><body>POST example."
|
||||
"<form method=\"POST\" action=\"/handle_post_request\">"
|
||||
"Input 1: <input type=\"text\" name=\"input_1\" /> <br/>"
|
||||
"Input 2: <input type=\"text\" name=\"input_2\" /> <br/>"
|
||||
"<input type=\"submit\" />"
|
||||
"</form></body></html>";
|
||||
|
||||
static void send_reply(struct mg_connection *conn) {
|
||||
char var1[500], var2[500];
|
||||
|
||||
if (strcmp(conn->uri, "/handle_post_request") == 0) {
|
||||
// User has submitted a form, show submitted data and a variable value
|
||||
// Parse form data. var1 and var2 are guaranteed to be NUL-terminated
|
||||
mg_get_var(conn, "input_1", var1, sizeof(var1));
|
||||
mg_get_var(conn, "input_2", var2, sizeof(var2));
|
||||
|
||||
// Send reply to the client, showing submitted form values.
|
||||
// POST data is in conn->content, data length is in conn->content_len
|
||||
mg_send_header(conn, "Content-Type", "text/plain");
|
||||
mg_printf_data(conn,
|
||||
"Submitted data: [%.*s]\n"
|
||||
"Submitted data length: %d bytes\n"
|
||||
"input_1: [%s]\n"
|
||||
"input_2: [%s]\n",
|
||||
conn->content_len, conn->content,
|
||||
conn->content_len, var1, var2);
|
||||
} else {
|
||||
// Show HTML form.
|
||||
mg_send_data(conn, html_form, strlen(html_form));
|
||||
}
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
if (ev == MG_REQUEST) {
|
||||
send_reply(conn);
|
||||
return MG_TRUE;
|
||||
} else if (ev == MG_AUTH) {
|
||||
return MG_TRUE;
|
||||
} else {
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
||||
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = hello_world
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
run: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
$(PROG): $(SOURCES) Makefile
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
win:
|
||||
wine cl $(SOURCES) /MD /nologo /DNDEBUG /O1 /I../.. /Fe$(PROG).exe
|
||||
wine $(PROG).exe
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib *.gc*
|
@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2014 Cesanta Software
|
||||
// All rights reserved
|
||||
//
|
||||
// This example demostrates basic use of Mongoose embedded web server.
|
||||
// $Date: 2014-09-09 22:20:23 UTC $
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
case MG_REQUEST:
|
||||
mg_printf_data(conn, "Hello! Requested URI is [%s]", conn->uri);
|
||||
return MG_TRUE;
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server;
|
||||
|
||||
// Create and configure the server
|
||||
server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
// Serve request. Hit Ctrl-C to terminate the program
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
|
||||
// Cleanup, and free server instance
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = http_client
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -DNS_ENABLE_SSL -lssl -lcrypto -pthread $(CFLAGS_EXTRA)
|
||||
|
||||
unix: $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp *.o *.lib
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
||||
|
@ -1,82 +1,70 @@
|
||||
// Copyright (c) 2014 Cesanta Software
|
||||
// All rights reserved
|
||||
//
|
||||
// This example demostrates how to connect to the remote Web server,
|
||||
// download data, process it and send back a reply.
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* This program fetches HTTP URLs.
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_received_signal = 0;
|
||||
static struct mg_server *s_server = NULL;
|
||||
static const char *s_remote_addr = "glosbe.com:80";
|
||||
static int s_exit_flag = 0;
|
||||
static int s_show_headers = 0;
|
||||
static const char *s_show_headers_opt = "--show-headers";
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_received_signal = sig_num;
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
struct mg_connection *client, *orig;
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case MG_AUTH:
|
||||
return MG_TRUE;
|
||||
|
||||
case MG_CONNECT:
|
||||
// Send request to the remote host.
|
||||
// TODO(lsm): handle connect error here.
|
||||
mg_printf(conn, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n",
|
||||
"/gapi/translate?from=eng&dest=fra&format=json&phrase=cat",
|
||||
s_remote_addr);
|
||||
return MG_TRUE;
|
||||
|
||||
case MG_REPLY:
|
||||
// Send reply to the original connection
|
||||
orig = (struct mg_connection *) conn->connection_param;
|
||||
mg_send_status(orig, conn->status_code);
|
||||
mg_send_header(orig, "Content-Type", "text/plain");
|
||||
mg_send_data(orig, conn->content, conn->content_len);
|
||||
mg_send_data(orig, "", 0); // Last chunk: mark the end of reply
|
||||
|
||||
// Disconnect connections
|
||||
orig->connection_param = NULL;
|
||||
conn->connection_param = NULL;
|
||||
return MG_TRUE;
|
||||
|
||||
case MG_REQUEST:
|
||||
if ((client = mg_connect(s_server, s_remote_addr)) != NULL) {
|
||||
// Interconnect requests
|
||||
client->connection_param = conn;
|
||||
conn->connection_param = client;
|
||||
return MG_MORE;
|
||||
} else {
|
||||
mg_printf_data(conn, "%s", "cannot send API request");
|
||||
return MG_TRUE;
|
||||
case NS_CONNECT:
|
||||
if (* (int *) ev_data != 0) {
|
||||
fprintf(stderr, "connect() failed: %s\n", strerror(* (int *) ev_data));
|
||||
s_exit_flag = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case NS_HTTP_REPLY:
|
||||
nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
if (s_show_headers) {
|
||||
fwrite(hm->message.p, 1, hm->message.len, stdout);
|
||||
} else {
|
||||
fwrite(hm->body.p, 1, hm->body.len, stdout);
|
||||
}
|
||||
putchar('\n');
|
||||
s_exit_flag = 1;
|
||||
break;
|
||||
default:
|
||||
return MG_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
s_server = mg_create_server(NULL, ev_handler);
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
int i;
|
||||
|
||||
mg_set_option(s_server, "listening_port", "8080");
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
// Setup signal handlers
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
printf("Listening on port %s\n", mg_get_option(s_server, "listening_port"));
|
||||
while (s_received_signal == 0) {
|
||||
mg_poll_server(s_server, 1000);
|
||||
/* Process command line arguments */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], s_show_headers_opt) == 0) {
|
||||
s_show_headers = 1;
|
||||
} else if (strcmp(argv[i], "--hexdump") == 0 && i + 1 < argc) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mg_destroy_server(&s_server);
|
||||
printf("Existing on signal %d\n", s_received_signal);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
if (i + 1 != argc) {
|
||||
fprintf(stderr, "Usage: %s [%s] [--hexdump <file>] <URL>\n",
|
||||
argv[0], s_show_headers_opt);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mg_connect_http(&mgr, ev_handler, argv[i], NULL, NULL);
|
||||
|
||||
while (s_exit_flag == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
14
examples/json_rpc_server/Makefile
Normal file
14
examples/json_rpc_server/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = json_rpc_server
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
67
examples/json_rpc_server/json_rpc_server.c
Normal file
67
examples/json_rpc_server/json_rpc_server.c
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* To test this server, do
|
||||
* $ curl -d '{"id":1,method:"sum",params:[22,33]}' 127.0.0.1:8000
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
|
||||
static int rpc_sum(char *buf, int len, struct mg_rpc_request *req) {
|
||||
double sum = 0;
|
||||
int i;
|
||||
|
||||
if (req->params[0].type != JSON_TYPE_ARRAY) {
|
||||
return mg_rpc_create_std_error(buf, len, req,
|
||||
JSON_RPC_INVALID_PARAMS_ERROR);
|
||||
}
|
||||
|
||||
for (i = 0; i < req->params[0].num_desc; i++) {
|
||||
if (req->params[i + 1].type != JSON_TYPE_NUMBER) {
|
||||
return mg_rpc_create_std_error(buf, len, req,
|
||||
JSON_RPC_INVALID_PARAMS_ERROR);
|
||||
}
|
||||
sum += strtod(req->params[i + 1].ptr, NULL);
|
||||
}
|
||||
return mg_rpc_create_reply(buf, len, req, "f", sum);
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
static const char *methods[] = { "sum", NULL };
|
||||
static mg_rpc_handler_t handlers[] = { rpc_sum, NULL };
|
||||
char buf[100];
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
mg_rpc_dispatch(hm->body.p, hm->body.len, buf, sizeof(buf),
|
||||
methods, handlers);
|
||||
mg_printf(nc, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n"
|
||||
"Content-Type: application/json\r\n\r\n%s",
|
||||
(int) strlen(buf), buf);
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
|
||||
printf("Starting JSON-RPC server on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
10
examples/load_balancer/Dockerfile
Normal file
10
examples/load_balancer/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM cesanta/mongoose
|
||||
|
||||
COPY load_balancer.c /mongoose/
|
||||
WORKDIR /mongoose
|
||||
RUN mkdir /mongoose/certs; \
|
||||
sed -i 's:#include "../../mongoose.h":#include "mongoose.h":' load_balancer.c; \
|
||||
cc load_balancer.c mongoose.c -o load_balancer -W -Wall -pthread -DNS_ENABLE_SSL -lssl -lcrypto
|
||||
EXPOSE 8000
|
||||
VOLUME ["/mongoose/certs"]
|
||||
ENTRYPOINT ["/mongoose/load_balancer"]
|
39
examples/load_balancer/Makefile
Normal file
39
examples/load_balancer/Makefile
Normal file
@ -0,0 +1,39 @@
|
||||
# To build with SSL under windows, do:
|
||||
# wine make load_balancer.exe SSL=openssl # OpenSSL build
|
||||
# wine make load_balancer.exe SSL=krypton # Krypton build
|
||||
|
||||
PROG = load_balancer
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -pthread $(CFLAGS_EXTRA)
|
||||
|
||||
ifeq ($(SSL), openssl)
|
||||
OPENSSL_PATH = ./openssl-0.9.8
|
||||
CFLAGS_EXTRA += -DNS_ENABLE_SSL -I$(OPENSSL_PATH)/include
|
||||
CFLAGS_EXTRA += /link /libpath:$(OPENSSL_PATH)/lib ssleay32.lib libeay32.lib
|
||||
endif
|
||||
|
||||
ifeq ($(SSL), krypton)
|
||||
KRYPTON_PATH = ../../../krypton
|
||||
CFLAGS_EXTRA += -DNS_ENABLE_SSL $(KRYPTON_PATH)/krypton.c -I$(KRYPTON_PATH)
|
||||
endif
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I.. /MD /Fe$@ /DNS_ENABLE_THREADS advapi32.lib $(CFLAGS_EXTRA)
|
||||
|
||||
test: $(PROG)
|
||||
$(MAKE) -C ../api_server
|
||||
sh unit_test.sh $$(pwd)/$(PROG)
|
||||
|
||||
docker-build:
|
||||
docker build -t cesanta/load_balancer .
|
||||
|
||||
docker-push:
|
||||
docker push cesanta/load_balancer
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
44
examples/load_balancer/README.md
Normal file
44
examples/load_balancer/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Mongoose-based HTTP load balancer
|
||||
|
||||
## Configuration
|
||||
|
||||
Load balancer is configured with command-line flags.
|
||||
|
||||
### Global flags
|
||||
|
||||
* `-p port` – TCP port to listen on. Default: 8000.
|
||||
* `-l log_file` – path to the log file. Default: none.
|
||||
* `-s ssl_cert` – path to SSL certificate. Default: none.
|
||||
|
||||
### Backend configuration
|
||||
|
||||
Main flag is `-b uri_prefix host_port` – it adds a new backend for a given
|
||||
URI prefix. Example: `-b /stuff/ 127.0.0.1:8080` will route all requests that
|
||||
start with '/stuff/' to a backend at port 8080 on localhost. There is a special
|
||||
syntax for `uri_prefix` that allows you to change the URIs that get passed to
|
||||
backends:
|
||||
|
||||
* `-b /stuff/=/ 127.0.0.1:8080` – for '/stuff/thing' backend will see '/thing'.
|
||||
* `-b /stuff/=/other/ 127.0.0.1:8080` – '/stuff/thing' => '/other/thing'.
|
||||
|
||||
Also there are few per-backend flags that can be placed before `-b` and apply
|
||||
only to the next backend:
|
||||
|
||||
* `-r` – instead of proxying requests load balancer will reply with 302
|
||||
redirect.
|
||||
* `-v vhost` – match not only URI prefix but 'Host:' header as well.
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
load_balancer -s path/to/cert.pem \
|
||||
-v example.com -b /site/=/ 127.0.0.1:8080 \
|
||||
-b /static/ 127.0.0.1:8081 \
|
||||
-b /static/ 127.0.0.1:8082
|
||||
```
|
||||
|
||||
In this example requests to 'example.com/site/' will be forwarded to the
|
||||
backend on port 8080 with '/site' prefix stripped off and requests to
|
||||
'/static/' on any virtual host will be balanced in round-robin fashion between
|
||||
backends on ports 8081 and 8082.
|
||||
|
637
examples/load_balancer/load_balancer.c
Normal file
637
examples/load_balancer/load_balancer.c
Normal file
@ -0,0 +1,637 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "../../mongoose.h"
|
||||
#include <sys/queue.h>
|
||||
|
||||
#define MAX_IDLE_CONNS 5
|
||||
#define CONN_IDLE_TIMEOUT 30
|
||||
|
||||
struct http_backend;
|
||||
|
||||
struct be_conn {
|
||||
struct http_backend *be;
|
||||
struct mg_connection *nc;
|
||||
time_t idle_deadline;
|
||||
|
||||
STAILQ_ENTRY(be_conn) conns;
|
||||
};
|
||||
|
||||
STAILQ_HEAD(be_conn_list_head, be_conn);
|
||||
struct http_backend {
|
||||
const char *vhost; /* NULL if any host */
|
||||
const char *uri_prefix; /* URI prefix, e.g. "/api/v1/", "/static/" */
|
||||
const char *uri_prefix_replacement; /* if not NULL, will replace uri_prefix in
|
||||
requests to backends */
|
||||
const char *host_port; /* Backend address */
|
||||
int redirect; /* if true redirect instead of proxy */
|
||||
int usage_counter; /* Number of times this backend was chosen */
|
||||
|
||||
struct be_conn_list_head conns;
|
||||
int num_conns;
|
||||
};
|
||||
|
||||
struct peer {
|
||||
struct mg_connection *nc;
|
||||
int64_t body_len; /* Size of the HTTP body to forward */
|
||||
int64_t body_sent; /* Number of bytes already forwarded */
|
||||
struct {
|
||||
/* Headers have been sent, no more headers. */
|
||||
unsigned int headers_sent : 1;
|
||||
unsigned int keep_alive : 1;
|
||||
} flags;
|
||||
};
|
||||
|
||||
struct conn_data {
|
||||
struct be_conn *be_conn; /* Chosen backend */
|
||||
struct peer client; /* Client peer */
|
||||
struct peer backend; /* Backend peer */
|
||||
time_t last_activity;
|
||||
};
|
||||
|
||||
static const char *s_error_500 = "HTTP/1.1 500 Failed\r\n";
|
||||
static const char *s_content_len_0 = "Content-Length: 0\r\n";
|
||||
static const char *s_connection_close = "Connection: close\r\n";
|
||||
static const char *s_http_port = "8000";
|
||||
static struct http_backend s_vhost_backends[100], s_default_backends[100];
|
||||
static int s_num_vhost_backends = 0, s_num_default_backends = 0;
|
||||
static int s_sig_num = 0;
|
||||
static int s_backend_keepalive = 0;
|
||||
static FILE *s_log_file = NULL;
|
||||
#ifdef NS_ENABLE_SSL
|
||||
const char *s_ssl_cert = NULL;
|
||||
#endif
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data);
|
||||
static void write_log(const char *fmt, ...);
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_sig_num = sig_num;
|
||||
}
|
||||
|
||||
static void send_http_err(struct mg_connection *nc, const char *err_line) {
|
||||
mg_printf(nc, "%s%s%s\r\n", err_line, s_content_len_0, s_connection_close);
|
||||
}
|
||||
|
||||
static void respond_with_error(struct conn_data *conn, const char *err_line) {
|
||||
struct mg_connection *nc = conn->client.nc;
|
||||
int headers_sent = conn->client.flags.headers_sent;
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p nc=%p respond_with_error %d\n", conn, nc, headers_sent);
|
||||
#endif
|
||||
if (nc == NULL) return;
|
||||
if (!headers_sent) {
|
||||
send_http_err(nc, err_line);
|
||||
conn->client.flags.headers_sent = 1;
|
||||
}
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
}
|
||||
|
||||
static int has_prefix(const struct mg_str *uri, const char *prefix) {
|
||||
size_t prefix_len = strlen(prefix);
|
||||
return uri->len >= prefix_len && memcmp(uri->p, prefix, prefix_len) == 0;
|
||||
}
|
||||
|
||||
static int matches_vhost(const struct mg_str *host, const char *vhost) {
|
||||
size_t vhost_len;
|
||||
if (vhost == NULL) {
|
||||
return 1;
|
||||
}
|
||||
vhost_len = strlen(vhost);
|
||||
return host->len == vhost_len && memcmp(host->p, vhost, vhost_len) == 0;
|
||||
}
|
||||
|
||||
static void write_log(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
if (s_log_file != NULL) {
|
||||
va_start(ap, fmt);
|
||||
vfprintf(s_log_file, fmt, ap);
|
||||
fflush(s_log_file);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
static struct http_backend *choose_backend_from_list(
|
||||
struct http_message *hm, struct http_backend *backends, int num_backends) {
|
||||
int i;
|
||||
struct mg_str vhost = {"", 0};
|
||||
const struct mg_str *host = mg_get_http_header(hm, "host");
|
||||
if (host != NULL) vhost = *host;
|
||||
|
||||
const char *vhost_end = vhost.p;
|
||||
|
||||
while (vhost_end < vhost.p + vhost.len && *vhost_end != ':') {
|
||||
vhost_end++;
|
||||
}
|
||||
vhost.len = vhost_end - vhost.p;
|
||||
|
||||
struct http_backend *chosen = NULL;
|
||||
for (i = 0; i < num_backends; i++) {
|
||||
struct http_backend *be = &backends[i];
|
||||
if (has_prefix(&hm->uri, be->uri_prefix) &&
|
||||
matches_vhost(&vhost, be->vhost) &&
|
||||
(chosen == NULL ||
|
||||
/* Prefer most specific URI prefixes */
|
||||
strlen(be->uri_prefix) > strlen(chosen->uri_prefix) ||
|
||||
/* Among prefixes of the same length chose the least used. */
|
||||
(strlen(be->uri_prefix) == strlen(chosen->uri_prefix) &&
|
||||
be->usage_counter < chosen->usage_counter))) {
|
||||
chosen = be;
|
||||
}
|
||||
}
|
||||
|
||||
return chosen;
|
||||
}
|
||||
|
||||
static struct http_backend *choose_backend(struct http_message *hm) {
|
||||
struct http_backend *chosen =
|
||||
choose_backend_from_list(hm, s_vhost_backends, s_num_vhost_backends);
|
||||
|
||||
/* Nothing was chosen for this vhost, look for vhost == NULL backends. */
|
||||
if (chosen == NULL) {
|
||||
chosen = choose_backend_from_list(hm, s_default_backends,
|
||||
s_num_default_backends);
|
||||
}
|
||||
|
||||
if (chosen != NULL) chosen->usage_counter++;
|
||||
|
||||
return chosen;
|
||||
}
|
||||
|
||||
static void forward_body(struct peer *src, struct peer *dst) {
|
||||
struct mbuf *src_io = &src->nc->recv_mbuf;
|
||||
if (src->body_sent < src->body_len) {
|
||||
size_t to_send = src->body_len - src->body_sent;
|
||||
if (src_io->len < to_send) {
|
||||
to_send = src_io->len;
|
||||
}
|
||||
mg_send(dst->nc, src_io->buf, to_send);
|
||||
src->body_sent += to_send;
|
||||
mbuf_remove(src_io, to_send);
|
||||
}
|
||||
#ifdef DEBUG
|
||||
write_log("forward_body %p (ka=%d) -> %p sent %d of %d\n", src->nc,
|
||||
src->flags.keep_alive, dst->nc, src->body_sent, src->body_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void forward(struct conn_data *conn, struct http_message *hm,
|
||||
struct peer *src_peer, struct peer *dst_peer) {
|
||||
struct mg_connection *src = src_peer->nc;
|
||||
struct mg_connection *dst = dst_peer->nc;
|
||||
struct mbuf *io = &src->recv_mbuf;
|
||||
int i;
|
||||
int is_request = (src_peer == &conn->client);
|
||||
src_peer->body_len = hm->body.len;
|
||||
struct http_backend *be = conn->be_conn->be;
|
||||
|
||||
if (is_request) {
|
||||
/* Write rewritten request line. */
|
||||
size_t trim_len = strlen(be->uri_prefix);
|
||||
mg_printf(dst, "%.*s%s%.*s\r\n", (int) (hm->uri.p - io->buf), io->buf,
|
||||
be->uri_prefix_replacement,
|
||||
(int) (hm->proto.p + hm->proto.len - (hm->uri.p + trim_len)),
|
||||
hm->uri.p + trim_len);
|
||||
} else {
|
||||
/* Reply line goes without modification */
|
||||
mg_printf(dst, "%.*s %d %.*s\r\n", (int) hm->proto.len, hm->proto.p,
|
||||
(int) hm->resp_code, (int) hm->resp_status_msg.len,
|
||||
hm->resp_status_msg.p);
|
||||
}
|
||||
|
||||
/* Headers. */
|
||||
for (i = 0; i < NS_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
|
||||
struct mg_str hn = hm->header_names[i];
|
||||
struct mg_str hv = hm->header_values[i];
|
||||
#ifdef NS_ENABLE_SSL
|
||||
/*
|
||||
* If we terminate SSL and backend redirects to local HTTP port,
|
||||
* strip protocol to let client use HTTPS.
|
||||
* TODO(lsm): web page content may also contain local HTTP references,
|
||||
* they need to be rewritten too.
|
||||
*/
|
||||
if (mg_vcasecmp(&hn, "Location") == 0 && s_ssl_cert != NULL) {
|
||||
size_t hlen = strlen(be->host_port);
|
||||
const char *hp = be->host_port, *p = memchr(hp, ':', hlen);
|
||||
|
||||
if (p == NULL) {
|
||||
p = hp + hlen;
|
||||
}
|
||||
|
||||
if (mg_ncasecmp(hv.p, "http://", 7) == 0 &&
|
||||
mg_ncasecmp(hv.p + 7, hp, (p - hp)) == 0) {
|
||||
mg_printf(dst, "Location: %.*s\r\n", (int) (hv.len - (7 + (p - hp))),
|
||||
hv.p + 7 + (p - hp));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* We always rewrite the connection header depending on the settings. */
|
||||
if (mg_vcasecmp(&hn, "Connection") == 0) continue;
|
||||
|
||||
mg_printf(dst, "%.*s: %.*s\r\n", (int) hn.len, hn.p, (int) hv.len, hv.p);
|
||||
}
|
||||
|
||||
/* Emit the connection header. */
|
||||
const char *connection_mode = "close";
|
||||
if (dst_peer == &conn->backend) {
|
||||
if (s_backend_keepalive) connection_mode = "keep-alive";
|
||||
} else {
|
||||
if (conn->client.flags.keep_alive) connection_mode = "keep-alive";
|
||||
}
|
||||
mg_printf(dst, "Connection: %s\r\n", connection_mode);
|
||||
|
||||
mg_printf(dst, "%s", "\r\n");
|
||||
|
||||
mbuf_remove(io, hm->body.p - hm->message.p); /* We've forwarded headers */
|
||||
dst_peer->flags.headers_sent = 1;
|
||||
|
||||
forward_body(src_peer, dst_peer);
|
||||
}
|
||||
|
||||
struct be_conn *get_conn(struct http_backend *be) {
|
||||
if (STAILQ_EMPTY(&be->conns)) return NULL;
|
||||
struct be_conn *result = STAILQ_FIRST(&be->conns);
|
||||
STAILQ_REMOVE_HEAD(&be->conns, conns);
|
||||
be->num_conns--;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* choose_backend parses incoming HTTP request and routes it to the appropriate
|
||||
* backend. It assumes that clients don't do HTTP pipelining, handling only
|
||||
* one request request for each connection. To give a hint to backend about
|
||||
* this it inserts "Connection: close" header into each forwarded request.
|
||||
*/
|
||||
static int connect_backend(struct conn_data *conn, struct http_message *hm) {
|
||||
struct mg_connection *nc = conn->client.nc;
|
||||
struct http_backend *be = choose_backend(hm);
|
||||
|
||||
write_log("%.*s %.*s backend=%s\n", (int) hm->method.len, hm->method.p,
|
||||
(int) hm->uri.len, hm->uri.p, be->host_port);
|
||||
|
||||
if (be == NULL) return 0;
|
||||
if (be->redirect != 0) {
|
||||
mg_printf(nc, "HTTP/1.1 302 Found\r\nLocation: %s\r\n\r\n", be->host_port);
|
||||
return 1;
|
||||
}
|
||||
struct be_conn *bec = get_conn(be);
|
||||
if (bec != NULL) {
|
||||
bec->nc->handler = ev_handler;
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p to %p (%s) reusing bec=%p\n", conn, be, be->host_port,
|
||||
bec);
|
||||
#endif
|
||||
} else {
|
||||
bec = malloc(sizeof(*conn->be_conn));
|
||||
memset(bec, 0, sizeof(*bec));
|
||||
bec->nc = mg_connect(nc->mgr, be->host_port, ev_handler);
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p new conn to %p (%s) bec=%p\n", conn, be, be->host_port,
|
||||
bec);
|
||||
#endif
|
||||
if (bec->nc == NULL) {
|
||||
free(bec);
|
||||
write_log("Connection to [%s] failed\n", be->host_port);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
bec->be = be;
|
||||
conn->be_conn = bec;
|
||||
conn->backend.nc = bec->nc;
|
||||
conn->backend.nc->user_data = conn;
|
||||
mg_set_protocol_http_websocket(conn->backend.nc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_keep_alive(struct http_message *hm) {
|
||||
const struct mg_str *connection_header = mg_get_http_header(hm, "Connection");
|
||||
if (connection_header == NULL) {
|
||||
/* HTTP/1.1 connections are keep-alive by default. */
|
||||
if (mg_vcasecmp(&hm->proto, "HTTP/1.1") != 0) return 0;
|
||||
} else if (mg_vcasecmp(connection_header, "keep-alive") != 0) {
|
||||
return 0;
|
||||
}
|
||||
// We must also have Content-Length.
|
||||
return mg_get_http_header(hm, "Content-Length") != NULL;
|
||||
}
|
||||
|
||||
static void idle_backend_handler(struct mg_connection *nc, int ev,
|
||||
void *ev_data) {
|
||||
(void) ev_data; /* Unused. */
|
||||
struct be_conn *bec = nc->user_data;
|
||||
const time_t now = time(NULL);
|
||||
#ifdef DEBUG
|
||||
write_log("%d idle bec=%p nc=%p ev=%d deadline=%d\n", now, bec, nc, ev,
|
||||
bec->idle_deadline);
|
||||
#endif
|
||||
switch (ev) {
|
||||
case NS_POLL: {
|
||||
if (bec->idle_deadline > 0 && now > bec->idle_deadline) {
|
||||
#ifdef DEBUG
|
||||
write_log("bec=%p nc=%p closing due to idleness\n", bec, bec->nc);
|
||||
#endif
|
||||
bec->nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NS_CLOSE: {
|
||||
#ifdef DEBUG
|
||||
write_log("bec=%p closed\n", bec);
|
||||
#endif
|
||||
if (bec->idle_deadline > 0) {
|
||||
STAILQ_REMOVE(&bec->be->conns, bec, be_conn, conns);
|
||||
}
|
||||
free(bec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void release_backend(struct conn_data *conn) {
|
||||
/* Disassociate the backend, put back on the pool. */
|
||||
struct be_conn *bec = conn->be_conn;
|
||||
conn->be_conn = NULL;
|
||||
if (bec->nc == NULL) {
|
||||
free(bec);
|
||||
memset(&conn->backend, 0, sizeof(conn->backend));
|
||||
return;
|
||||
}
|
||||
struct http_backend *be = bec->be;
|
||||
bec->nc->user_data = bec;
|
||||
bec->nc->handler = idle_backend_handler;
|
||||
if (conn->backend.flags.keep_alive) {
|
||||
bec->idle_deadline = time(NULL) + CONN_IDLE_TIMEOUT;
|
||||
STAILQ_INSERT_TAIL(&be->conns, bec, conns);
|
||||
#ifdef DEBUG
|
||||
write_log("bec=%p becoming idle\n", bec);
|
||||
#endif
|
||||
be->num_conns++;
|
||||
while (be->num_conns > MAX_IDLE_CONNS) {
|
||||
bec = STAILQ_FIRST(&be->conns);
|
||||
STAILQ_REMOVE_HEAD(&be->conns, conns);
|
||||
be->num_conns--;
|
||||
bec->idle_deadline = 0;
|
||||
bec->nc->flags = NSF_CLOSE_IMMEDIATELY;
|
||||
#ifdef DEBUG
|
||||
write_log("bec=%p evicted\n", bec);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
bec->idle_deadline = 0;
|
||||
bec->nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
memset(&conn->backend, 0, sizeof(conn->backend));
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct conn_data *conn = (struct conn_data *) nc->user_data;
|
||||
const time_t now = time(NULL);
|
||||
#ifdef DEBUG
|
||||
write_log("%d conn=%p nc=%p ev=%d ev_data=%p bec=%p bec_nc=%p\n", now, conn,
|
||||
nc, ev, ev_data, conn != NULL ? conn->be_conn : NULL,
|
||||
conn != NULL && conn->be_conn != NULL ? conn->be_conn->nc : NULL);
|
||||
#endif
|
||||
|
||||
if (conn == NULL) {
|
||||
if (ev == NS_ACCEPT) {
|
||||
conn = calloc(1, sizeof(*conn));
|
||||
if (conn == NULL) {
|
||||
send_http_err(nc, s_error_500);
|
||||
} else {
|
||||
memset(conn, 0, sizeof(*conn));
|
||||
nc->user_data = conn;
|
||||
conn->client.nc = nc;
|
||||
conn->client.body_len = -1;
|
||||
conn->backend.body_len = -1;
|
||||
conn->last_activity = now;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev != NS_POLL) conn->last_activity = now;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST: { /* From client */
|
||||
assert(conn != NULL);
|
||||
assert(conn->be_conn == NULL);
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
conn->client.flags.keep_alive = is_keep_alive(hm);
|
||||
|
||||
if (!connect_backend(conn, hm)) {
|
||||
respond_with_error(conn, s_error_500);
|
||||
break;
|
||||
}
|
||||
|
||||
if (conn->backend.nc == NULL) {
|
||||
/* This is a redirect, we're done. */
|
||||
conn->client.nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
break;
|
||||
}
|
||||
|
||||
forward(conn, hm, &conn->client, &conn->backend);
|
||||
break;
|
||||
}
|
||||
|
||||
case NS_CONNECT: { /* To backend */
|
||||
assert(conn != NULL);
|
||||
assert(conn->be_conn != NULL);
|
||||
int status = *(int *) ev_data;
|
||||
if (status != 0) {
|
||||
write_log("Error connecting to %s: %d (%s)\n",
|
||||
conn->be_conn->be->host_port, status, strerror(status));
|
||||
/* TODO(lsm): mark backend as defunct, try it later on */
|
||||
respond_with_error(conn, s_error_500);
|
||||
conn->be_conn->nc = NULL;
|
||||
release_backend(conn);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NS_HTTP_REPLY: { /* From backend */
|
||||
assert(conn != NULL);
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
conn->backend.flags.keep_alive = s_backend_keepalive && is_keep_alive(hm);
|
||||
forward(conn, hm, &conn->backend, &conn->client);
|
||||
release_backend(conn);
|
||||
if (!conn->client.flags.keep_alive) {
|
||||
conn->client.nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p remains open\n", conn);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NS_POLL: {
|
||||
assert(conn != NULL);
|
||||
if (now - conn->last_activity > CONN_IDLE_TIMEOUT &&
|
||||
conn->backend.nc == NULL /* not waiting for backend */) {
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p has been idle for too long\n", conn);
|
||||
conn->client.nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NS_CLOSE: {
|
||||
assert(conn != NULL);
|
||||
if (nc == conn->client.nc) {
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p nc=%p client closed, body_sent=%d\n", conn, nc,
|
||||
conn->backend.body_sent);
|
||||
#endif
|
||||
conn->client.nc = NULL;
|
||||
if (conn->backend.nc != NULL) {
|
||||
conn->backend.nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
} else if (nc == conn->backend.nc) {
|
||||
#ifdef DEBUG
|
||||
write_log("conn=%p nc=%p backend closed\n", conn, nc);
|
||||
#endif
|
||||
conn->backend.nc = NULL;
|
||||
if (conn->client.nc != NULL &&
|
||||
(conn->backend.body_len < 0 ||
|
||||
conn->backend.body_sent < conn->backend.body_len)) {
|
||||
write_log("Backend %s disconnected.\n", conn->be_conn->be->host_port);
|
||||
respond_with_error(conn, s_error_500);
|
||||
}
|
||||
}
|
||||
if (conn->client.nc == NULL && conn->backend.nc == NULL) {
|
||||
free(conn);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print_usage_and_exit(const char *prog_name) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-D debug_dump_file] [-p http_port] [-l log] [-k]"
|
||||
#if NS_ENABLE_SSL
|
||||
"[-s ssl_cert] "
|
||||
#endif
|
||||
"<[-r] [-v vhost] -b uri_prefix[=replacement] host_port> ... \n",
|
||||
prog_name);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
int i, redirect = 0;
|
||||
const char *vhost = NULL;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
/* Parse command line arguments */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-D") == 0) {
|
||||
mgr.hexdump_file = argv[i + 1];
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-k") == 0) {
|
||||
s_backend_keepalive = 1;
|
||||
} else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
|
||||
if (strcmp(argv[i + 1], "-") == 0) {
|
||||
s_log_file = stdout;
|
||||
} else {
|
||||
s_log_file = fopen(argv[i + 1], "a");
|
||||
if (s_log_file == NULL) {
|
||||
perror("fopen");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-p") == 0) {
|
||||
s_http_port = argv[i + 1];
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
|
||||
redirect = 1;
|
||||
} else if (strcmp(argv[i], "-v") == 0 && i + 1 < argc) {
|
||||
if (strcmp(argv[i + 1], "") == 0) {
|
||||
vhost = NULL;
|
||||
} else {
|
||||
vhost = argv[i + 1];
|
||||
}
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-b") == 0 && i + 2 < argc) {
|
||||
struct http_backend *be =
|
||||
vhost != NULL ? &s_vhost_backends[s_num_vhost_backends++]
|
||||
: &s_default_backends[s_num_default_backends++];
|
||||
STAILQ_INIT(&be->conns);
|
||||
char *r = NULL;
|
||||
be->vhost = vhost;
|
||||
be->uri_prefix = argv[i + 1];
|
||||
be->host_port = argv[i + 2];
|
||||
be->redirect = redirect;
|
||||
be->uri_prefix_replacement = be->uri_prefix;
|
||||
if ((r = strchr(be->uri_prefix, '=')) != NULL) {
|
||||
*r = '\0';
|
||||
be->uri_prefix_replacement = r + 1;
|
||||
}
|
||||
printf(
|
||||
"Adding backend for %s%s : %s "
|
||||
"[redirect=%d,prefix_replacement=%s]\n",
|
||||
be->vhost == NULL ? "" : be->vhost, be->uri_prefix, be->host_port,
|
||||
be->redirect, be->uri_prefix_replacement);
|
||||
vhost = NULL;
|
||||
redirect = 0;
|
||||
i += 2;
|
||||
#ifdef NS_ENABLE_SSL
|
||||
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
|
||||
s_ssl_cert = argv[++i];
|
||||
#endif
|
||||
} else {
|
||||
print_usage_and_exit(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Open listening socket */
|
||||
if ((nc = mg_bind(&mgr, s_http_port, ev_handler)) == NULL) {
|
||||
fprintf(stderr, "mg_bind(%s) failed\n", s_http_port);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#if NS_ENABLE_SSL
|
||||
if (s_ssl_cert != NULL) {
|
||||
const char *err_str = mg_set_ssl(nc, s_ssl_cert, NULL);
|
||||
if (err_str != NULL) {
|
||||
fprintf(stderr, "Error loading SSL cert: %s\n", err_str);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
|
||||
if (s_num_vhost_backends + s_num_default_backends == 0) {
|
||||
print_usage_and_exit(argv[0]);
|
||||
}
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
/* Run event loop until signal is received */
|
||||
printf("Starting LB on port %s\n", s_http_port);
|
||||
while (s_sig_num == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
printf("Exiting on signal %d\n", s_sig_num);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
21
examples/load_balancer/unit_test.sh
Normal file
21
examples/load_balancer/unit_test.sh
Normal file
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
PROG=$1
|
||||
PORT=8002
|
||||
|
||||
cleanup() {
|
||||
kill -9 $PID >/dev/null 2>&1
|
||||
}
|
||||
|
||||
#set -x
|
||||
trap cleanup EXIT
|
||||
|
||||
cleanup
|
||||
$PROG -p $PORT -b /api/ 127.0.0.1:8000 &
|
||||
PID=$!
|
||||
|
||||
# Perform api_server unit test through the load balancer by passing
|
||||
# load balancer port to the unit test script
|
||||
(cd ../api_server && make && sh unit_test.sh ./api_server $PORT)
|
||||
|
||||
exit $?
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = mjpg_streamer
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,105 +0,0 @@
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static void send_file(struct mg_connection *conn, const char *path) {
|
||||
char buf[1024];
|
||||
struct stat st;
|
||||
int n;
|
||||
FILE *fp;
|
||||
|
||||
if (stat(path, &st) == 0 && (fp = fopen(path, "rb")) != NULL) {
|
||||
mg_printf(conn, "--w00t\r\nContent-Type: image/jpeg\r\n"
|
||||
"Content-Length: %lu\r\n\r\n", (unsigned long) st.st_size);
|
||||
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
|
||||
mg_write(conn, buf, n);
|
||||
}
|
||||
fclose(fp);
|
||||
mg_write(conn, "\r\n", 2);
|
||||
}
|
||||
}
|
||||
|
||||
struct conn_state {
|
||||
int file_index;
|
||||
time_t last_poll;
|
||||
};
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
const char **file_names = (const char **) conn->server_param;
|
||||
struct conn_state *state;
|
||||
time_t now = time(NULL);
|
||||
|
||||
switch (ev) {
|
||||
|
||||
case MG_AUTH:
|
||||
return MG_TRUE;
|
||||
|
||||
case MG_REQUEST:
|
||||
if (strcmp(conn->uri, "/stream") != 0) {
|
||||
mg_send_header(conn, "Content-Type", "text/html");
|
||||
mg_printf_data(conn, "%s",
|
||||
"Go to <a href=/stream>/stream</a> for MJPG stream");
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
mg_printf(conn, "%s",
|
||||
"HTTP/1.0 200 OK\r\n" "Cache-Control: no-cache\r\n"
|
||||
"Pragma: no-cache\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
|
||||
"Connection: close\r\nContent-Type: multipart/x-mixed-replace; "
|
||||
"boundary=--w00t\r\n\r\n");
|
||||
|
||||
send_file(conn, file_names[0]);
|
||||
|
||||
state = (struct conn_state *) malloc(sizeof(*state));
|
||||
conn->connection_param = state;
|
||||
state->file_index = 1; // First file is already sent
|
||||
state->last_poll = time(NULL);
|
||||
return MG_MORE;
|
||||
|
||||
case MG_POLL:
|
||||
state = (struct conn_state *) conn->connection_param;
|
||||
|
||||
if (state != NULL && now > state->last_poll) {
|
||||
if (file_names[state->file_index] != NULL) {
|
||||
send_file(conn, file_names[state->file_index]);
|
||||
state->file_index++;
|
||||
if (file_names[state->file_index] == NULL) {
|
||||
return MG_TRUE; // No more images, close connection
|
||||
}
|
||||
}
|
||||
state->last_poll = now;
|
||||
}
|
||||
return MG_FALSE;
|
||||
|
||||
case MG_CLOSE:
|
||||
free(conn->connection_param);
|
||||
conn->connection_param = NULL;
|
||||
return MG_FALSE;
|
||||
|
||||
default:
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_server *server;
|
||||
|
||||
if (argc < 3) {
|
||||
printf("Usage: %s image1.jpg image2.jpg ...\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
server = mg_create_server(&argv[1], ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
14
examples/mqtt_broker/Makefile
Normal file
14
examples/mqtt_broker/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = mqtt_broker
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread -DNS_ENABLE_SSL -DNS_ENABLE_MQTT_BROKER -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /DNS_ENABLE_SSL /DNS_ENABLE_MQTT_BROKER /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
43
examples/mqtt_broker/mqtt_broker.c
Normal file
43
examples/mqtt_broker/mqtt_broker.c
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
* This software is dual-licensed: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation. For the terms of this
|
||||
* license, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You are free to use this software under the terms of the GNU General
|
||||
* Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* Alternatively, you can license this software under a commercial
|
||||
* license, as set out in <https://www.cesanta.com/license>.
|
||||
*/
|
||||
|
||||
#include "../../mongoose.h"
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
const char *address = "0.0.0.0:1883";
|
||||
struct mg_connection *nc;
|
||||
struct mg_mqtt_broker brk;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
mg_mqtt_broker_init(&brk, NULL);
|
||||
|
||||
if ((nc = mg_bind(&mgr, address, mg_mqtt_broker)) == NULL) {
|
||||
fprintf(stderr, "mg_bind(%s) failed\n", address);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
nc->user_data = &brk;
|
||||
|
||||
/*
|
||||
* TODO: Add a HTTP status page that shows current sessions
|
||||
* and subscriptions
|
||||
*/
|
||||
|
||||
for(;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
}
|
14
examples/mqtt_client/Makefile
Normal file
14
examples/mqtt_client/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = mqtt_client
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread -DNS_ENABLE_SSL -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /DNS_ENABLE_SSL /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
86
examples/mqtt_client/mqtt_client.c
Normal file
86
examples/mqtt_client/mqtt_client.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
* This software is dual-licensed: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation. For the terms of this
|
||||
* license, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You are free to use this software under the terms of the GNU General
|
||||
* Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* Alternatively, you can license this software under a commercial
|
||||
* license, as set out in <https://www.cesanta.com/license>.
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
struct mg_mqtt_topic_expression topic_expressions[] = {
|
||||
{"/stuff", 0}
|
||||
};
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
|
||||
struct mg_mqtt_message *msg = (struct mg_mqtt_message *)p;
|
||||
(void) nc;
|
||||
|
||||
#if 0
|
||||
if (ev != NS_POLL)
|
||||
printf("USER HANDLER GOT %d\n", ev);
|
||||
#endif
|
||||
|
||||
switch (ev) {
|
||||
case NS_CONNECT:
|
||||
mg_set_protocol_mqtt(nc);
|
||||
mg_send_mqtt_handshake(nc, "dummy");
|
||||
break;
|
||||
case NS_MQTT_CONNACK:
|
||||
if (msg->connack_ret_code != NS_MQTT_CONNACK_ACCEPTED) {
|
||||
printf("Got mqtt connection error: %d\n", msg->connack_ret_code);
|
||||
exit(1);
|
||||
}
|
||||
printf("Subscribing to '/stuff'\n");
|
||||
mg_mqtt_subscribe(nc, topic_expressions, sizeof(topic_expressions)/sizeof(*topic_expressions), 42);
|
||||
break;
|
||||
case NS_MQTT_PUBACK:
|
||||
printf("Message publishing acknowledged (msg_id: %d)\n", msg->message_id);
|
||||
break;
|
||||
case NS_MQTT_SUBACK:
|
||||
printf("Subscription acknowledged, forwarding to '/test'\n");
|
||||
break;
|
||||
case NS_MQTT_PUBLISH:
|
||||
{
|
||||
#if 0
|
||||
char hex[1024] = {0};
|
||||
mg_hexdump(nc->recv_mbuf.buf, msg->payload.len, hex, sizeof(hex));
|
||||
printf("Got incoming message %s:\n%s", msg->topic, hex);
|
||||
#else
|
||||
printf("Got incoming message %s: %.*s\n", msg->topic, (int)msg->payload.len, msg->payload.p);
|
||||
#endif
|
||||
|
||||
printf("Forwarding to /test\n");
|
||||
mg_mqtt_publish(nc, "/test", 65, NS_MQTT_QOS(0), msg->payload.p, msg->payload.len);
|
||||
}
|
||||
break;
|
||||
case NS_CLOSE:
|
||||
printf("Connection closed\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
const char *address = "localhost:1883";
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
if (mg_connect(&mgr, address, ev_handler) == NULL) {
|
||||
fprintf(stderr, "mg_connect(%s) failed\n", address);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for(;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = multi_threaded_server
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 -DMONGOOSE_ENABLE_THREADS $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,40 +0,0 @@
|
||||
#include "mongoose.h"
|
||||
|
||||
// Start a browser and hit refresh couple of times. The replies will
|
||||
// come from both server instances.
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
if (ev == MG_REQUEST) {
|
||||
mg_send_header(conn, "Content-Type", "text/plain");
|
||||
mg_printf_data(conn, "This is a reply from server instance # %s",
|
||||
(char *) conn->server_param);
|
||||
return MG_TRUE;
|
||||
} else if (ev == MG_AUTH) {
|
||||
return MG_TRUE;
|
||||
} else {
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void *serve(void *server) {
|
||||
for (;;) mg_poll_server((struct mg_server *) server, 1000);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server1, *server2;
|
||||
|
||||
server1 = mg_create_server((void *) "1", ev_handler);
|
||||
server2 = mg_create_server((void *) "2", ev_handler);
|
||||
|
||||
// Make both server1 and server2 listen on the same sockets
|
||||
mg_set_option(server1, "listening_port", "8080");
|
||||
mg_copy_listeners(server1, server2);
|
||||
|
||||
// server1 goes to separate thread, server 2 runs in main thread.
|
||||
// IMPORTANT: NEVER LET DIFFERENT THREADS HANDLE THE SAME SERVER.
|
||||
mg_start_thread(serve, server1);
|
||||
mg_start_thread(serve, server2);
|
||||
getchar();
|
||||
|
||||
return 0;
|
||||
}
|
18
examples/multithreaded_restful_server/Makefile
Normal file
18
examples/multithreaded_restful_server/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
PROG = multithreaded_restful_server
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
APP_FLAGS = -DNS_ENABLE_THREADS $(CFLAGS_EXTRA)
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
APP_FLAGS += advapi32.lib
|
||||
endif
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ -W -Wall -I../.. -pthread $(APP_FLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@ $(APP_FLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
// This example shows how to handle long, blocking requests by
|
||||
// handing off computation to different threads. Here, each
|
||||
// request spawns a new thread. In a production scenario, a thread
|
||||
// pools can be used for efficiency, if required.
|
||||
// Long computation is simulated by sleeping for a random interval.
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
|
||||
static void ev_handler(struct mg_connection *c, int ev, void *p) {
|
||||
if (ev == NS_HTTP_REQUEST) {
|
||||
struct http_message *hm = (struct http_message *) p;
|
||||
char reply[100];
|
||||
|
||||
/* Simulate long calculation */
|
||||
sleep(3);
|
||||
|
||||
/* Send the reply */
|
||||
snprintf(reply, sizeof(reply), "{ \"uri\": \"%.*s\" }\n",
|
||||
(int) hm->uri.len, hm->uri.p);
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"\r\n"
|
||||
"%s",
|
||||
(int) strlen(reply), reply);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
|
||||
/* For each new connection, execute ev_handler in a separate thread */
|
||||
mg_enable_multithreading(nc);
|
||||
|
||||
printf("Starting multi-threaded server on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 3000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
14
examples/netcat/Makefile
Normal file
14
examples/netcat/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = nc
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread -DNS_ENABLE_SSL -DNS_ENABLE_THREADS -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /DNS_ENABLE_SSL /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
140
examples/netcat/nc.c
Normal file
140
examples/netcat/nc.c
Normal file
@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2014 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// This software is dual-licensed: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation. For the terms of this
|
||||
// license, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// You are free to use this software under the terms of the GNU General
|
||||
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU General Public License for more details.
|
||||
//
|
||||
// Alternatively, you can license this software under a commercial
|
||||
// license, as set out in <https://www.cesanta.com/license>.
|
||||
//
|
||||
// $Date: 2014-09-28 05:04:41 UTC $
|
||||
|
||||
// This file implements "netcat" utility with SSL and traffic hexdump.
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static sig_atomic_t s_received_signal = 0;
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_received_signal = sig_num;
|
||||
}
|
||||
|
||||
static void show_usage_and_exit(const char *prog_name) {
|
||||
fprintf(stderr, "%s\n", "Copyright (c) 2014 CESANTA SOFTWARE");
|
||||
fprintf(stderr, "%s\n", "Usage:");
|
||||
fprintf(stderr, " %s\n [-d debug_file] [-l] [tcp|ssl]://[ip:]port[:cert][:ca_cert]",
|
||||
prog_name);
|
||||
fprintf(stderr, "%s\n", "Examples:");
|
||||
fprintf(stderr, " %s\n -d hexdump.txt ssl://google.com:443", prog_name);
|
||||
fprintf(stderr, " %s\n -l ssl://443:ssl_cert.pem", prog_name);
|
||||
fprintf(stderr, " %s\n -l tcp://8080", prog_name);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void on_stdin_read(struct mg_connection *nc, int ev, void *p) {
|
||||
int ch = * (int *) p;
|
||||
|
||||
(void) ev;
|
||||
|
||||
if (ch < 0) {
|
||||
// EOF is received from stdin. Schedule the connection to close
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
if (nc->send_mbuf.len <= 0) {
|
||||
nc->flags |= NSF_CLOSE_IMMEDIATELY;
|
||||
}
|
||||
} else {
|
||||
// A character is received from stdin. Send it to the connection.
|
||||
unsigned char c = (unsigned char) ch;
|
||||
mg_send(nc, &c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void *stdio_thread_func(void *param) {
|
||||
struct mg_mgr *mgr = (struct mg_mgr *) param;
|
||||
int ch;
|
||||
|
||||
// Read stdin until EOF character by character, sending them to the mgr
|
||||
while ((ch = getchar()) != EOF) {
|
||||
mg_broadcast(mgr, on_stdin_read, &ch, sizeof(ch));
|
||||
}
|
||||
s_received_signal = 1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
|
||||
(void) p;
|
||||
|
||||
switch (ev) {
|
||||
case NS_ACCEPT:
|
||||
case NS_CONNECT:
|
||||
mg_start_thread(stdio_thread_func, nc->mgr);
|
||||
break;
|
||||
|
||||
case NS_CLOSE:
|
||||
s_received_signal = 1;
|
||||
break;
|
||||
|
||||
case NS_RECV:
|
||||
fwrite(nc->recv_mbuf.buf, 1, nc->recv_mbuf.len, stdout);
|
||||
mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
int i, is_listening = 0;
|
||||
const char *address = NULL;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
// Parse command line options
|
||||
for (i = 1; i < argc && argv[i][0] == '-'; i++) {
|
||||
if (strcmp(argv[i], "-l") == 0) {
|
||||
is_listening = 1;
|
||||
} else if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else {
|
||||
show_usage_and_exit(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 == argc) {
|
||||
address = argv[i];
|
||||
} else {
|
||||
show_usage_and_exit(argv[0]);
|
||||
}
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if (is_listening) {
|
||||
if (mg_bind(&mgr, address, ev_handler) == NULL) {
|
||||
fprintf(stderr, "mg_bind(%s) failed\n", address);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (mg_connect(&mgr, address, ev_handler) == NULL) {
|
||||
fprintf(stderr, "mg_connect(%s) failed\n", address);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while (s_received_signal == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = proxy_server
|
||||
FLAGS = -I../.. -DNS_ENABLE_SSL
|
||||
CFLAGS = -W -Wall -g -O0 -pthread -lssl -DMONGOOSE_ENABLE_THREADS $(FLAGS) $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
unix: $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,202 +0,0 @@
|
||||
// Copyright (c) 2014 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// To build and run this example:
|
||||
// git clone https://github.com/cesanta/net_skeleton.git
|
||||
// git clone https://github.com/cesanta/mongoose.git
|
||||
// cd mongoose/examples
|
||||
// make proxy
|
||||
// ./proxy
|
||||
//
|
||||
// Configure your browser to use localhost:2014 as a proxy for all protocols
|
||||
// Then, navigate to https://cesanta.com
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define sleep(x) Sleep((x) * 1000)
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_received_signal = 0;
|
||||
static struct mg_server *s_server = NULL;
|
||||
|
||||
#define SSE_CONNECTION ((void *) 1)
|
||||
|
||||
static void elog(int do_exit, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
if (do_exit) exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_received_signal = sig_num;
|
||||
}
|
||||
|
||||
static int sse_push(struct mg_connection *conn, enum mg_event ev) {
|
||||
if (ev == MG_POLL && conn->connection_param == SSE_CONNECTION) {
|
||||
mg_printf(conn, "data: %s\r\n\r\n", (const char *) conn->callback_param);
|
||||
}
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
static void *sse_pusher_thread_func(void *param) {
|
||||
while (s_received_signal == 0) {
|
||||
mg_wakeup_server_ex(s_server, sse_push, "%lu %s",
|
||||
(unsigned long) time(NULL), (const char *) param);
|
||||
sleep(1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return: 1 if regular file, 2 if directory, 0 if not found
|
||||
static int exists(const char *path) {
|
||||
struct stat st;
|
||||
return stat(path, &st) != 0 ? 0 : S_ISDIR(st.st_mode) == 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
// Return: 1 if regular file, 2 if directory, 0 if not found
|
||||
static int is_local_file(const char *uri, char *path, size_t path_len) {
|
||||
snprintf(path, path_len, "%s/%s",
|
||||
mg_get_option(s_server, "document_root"), uri);
|
||||
return exists(path);
|
||||
}
|
||||
|
||||
static int try_to_serve_locally(struct mg_connection *conn) {
|
||||
char path[500], buf[2000];
|
||||
int n, res;
|
||||
FILE *fp = NULL;
|
||||
|
||||
if ((res = is_local_file(conn->uri, path, sizeof(path))) == 2) {
|
||||
strncat(path, "/index.html", sizeof(path) - strlen(path) - 1);
|
||||
res = exists(path);
|
||||
printf("PATH: [%s]\n", path);
|
||||
}
|
||||
if (res == 0) return MG_FALSE;
|
||||
|
||||
if ((fp = fopen(path, "rb")) != NULL) {
|
||||
printf("Serving [%s] locally \n", path);
|
||||
mg_send_header(conn, "Connection", "close");
|
||||
mg_send_header(conn, "Content-Type", mg_get_mime_type(path, "text/plain"));
|
||||
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
|
||||
mg_send_data(conn, buf, n);
|
||||
}
|
||||
mg_send_data(conn, "", 0);
|
||||
fclose(fp);
|
||||
}
|
||||
return fp == NULL ? MG_FALSE : MG_TRUE;
|
||||
}
|
||||
|
||||
static int is_resource_present_locally(const char *uri) {
|
||||
char path[500];
|
||||
return is_local_file(uri, path, sizeof(path)) || strcmp(uri, "/api/sse") == 0;
|
||||
}
|
||||
|
||||
static int proxy_event_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
static const char target_url[] = "http://cesanta.com";
|
||||
static int target_url_size = sizeof(target_url) - 1;
|
||||
const char *host;
|
||||
|
||||
switch (ev) {
|
||||
case MG_REQUEST:
|
||||
host = mg_get_header(conn, "Host");
|
||||
printf("[%s] [%s] [%s]\n", conn->request_method, conn->uri,
|
||||
host == NULL ? "" : host);
|
||||
if (strstr(conn->uri, "/qqq") != NULL) s_received_signal = SIGTERM;
|
||||
|
||||
// Proxied HTTPS requests use "CONNECT foo.com:443"
|
||||
// Proxied HTTP requests use "GET http://..... "
|
||||
// Serve requests for target_url from the local FS.
|
||||
if (memcmp(conn->uri, target_url, target_url_size) == 0 &&
|
||||
is_resource_present_locally(conn->uri + target_url_size)) {
|
||||
conn->uri += target_url_size; // Leave only path in the URI
|
||||
}
|
||||
|
||||
if (strcmp(conn->uri, "/api/sse") == 0) {
|
||||
conn->connection_param = SSE_CONNECTION;
|
||||
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: text/event-stream\r\n"
|
||||
"Cache-Control: no-cache\r\n\r\n");
|
||||
return MG_MORE;
|
||||
}
|
||||
|
||||
if (host != NULL && strstr(host, "cesanta") != NULL) {
|
||||
return try_to_serve_locally(conn);
|
||||
}
|
||||
|
||||
// Enable man-in-the-middle SSL mode for oracle.com
|
||||
if (!strcmp(conn->request_method, "CONNECT") &&
|
||||
!strcmp(host, "oracle.com")) {
|
||||
mg_terminate_ssl(conn, "ssl_cert.pem"); // MUST return MG_MORE after
|
||||
return MG_MORE;
|
||||
}
|
||||
|
||||
return MG_FALSE;
|
||||
case MG_AUTH:
|
||||
return MG_TRUE;
|
||||
default:
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void setopt(struct mg_server *s, const char *opt, const char *val) {
|
||||
const char *err_msg = mg_set_option(s, opt, val);
|
||||
if (err_msg != NULL) {
|
||||
elog(1, "Error setting [%s]: [%s]", opt, err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *port = "2014", *dump = NULL, *root = "proxy_web_root";
|
||||
int i;
|
||||
|
||||
// Parse command line options
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-port") == 0 && i + 1 < argc) {
|
||||
port = argv[++i];
|
||||
} else if (strcmp(argv[i], "-root") == 0 && i + 1 < argc) {
|
||||
root = argv[++i];
|
||||
} else if (strcmp(argv[i], "-dump") == 0 && i + 1 < argc) {
|
||||
dump = argv[++i];
|
||||
} else {
|
||||
elog(1, "Usage: %s [-cert FILE] [-ca_cert FILE] [-port PORT]", argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
// Create and configure proxy server
|
||||
s_server = mg_create_server(NULL, &proxy_event_handler);
|
||||
setopt(s_server, "enable_proxy", "yes");
|
||||
setopt(s_server, "document_root", root);
|
||||
setopt(s_server, "listening_port", port);
|
||||
setopt(s_server, "hexdump_file", dump);
|
||||
|
||||
// Start two SSE pushing threads
|
||||
mg_start_thread(sse_pusher_thread_func, (void *) "sse_pusher_thread_1");
|
||||
mg_start_thread(sse_pusher_thread_func, (void *) "sse_pusher_thread_2");
|
||||
|
||||
// Start serving in the main thread
|
||||
printf("Starting on port %s\n", mg_get_option(s_server, "listening_port"));
|
||||
while (s_received_signal == 0) {
|
||||
mg_poll_server(s_server, 1000);
|
||||
}
|
||||
printf("Existing on signal %d\n", s_received_signal);
|
||||
mg_destroy_server(&s_server);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>App1 Index</title>
|
||||
<style>
|
||||
img { height: 40px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>App1 index page. Served locally from the the proxy server filesystem</h1>
|
||||
|
||||
<p>image that references non-existent local resource. Forwarded to
|
||||
the 'real' proxy target:</p>
|
||||
<img src="http://cesanta.com/images/logo.png" />
|
||||
|
||||
<p>Google logo via HTTPS (external resource, served by remote host):</p>
|
||||
<img src="https://www.google.ie/images/srpr/logo11w.png" />
|
||||
|
||||
<p>Same image via HTTP:</p>
|
||||
<img src="http://www.google.ie/images/srpr/logo11w.png" />
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,37 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>App2 Index</title>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Using secure websocket connection, wss://
|
||||
var ws = new WebSocket('wss://echo.websocket.org');
|
||||
var div = document.getElementById('events');
|
||||
ws.onmessage = function(ev) {
|
||||
var el = document.createElement('div');
|
||||
el.innerHTML = 'websocket message: ' + ev.data;
|
||||
div.appendChild(el);
|
||||
// Keep only last 5 messages in the list
|
||||
while (div.childNodes.length > 5) div.removeChild(div.firstChild);
|
||||
};
|
||||
|
||||
// Send random stuff to the websocket connection periodically.
|
||||
// websocket server much echo that stuff back.
|
||||
window.setInterval(function() {
|
||||
var d = new Date();
|
||||
ws.send(d.toString());
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>App2 index page. Served locally from the
|
||||
the proxy's filesystem.</h1>
|
||||
<p>
|
||||
Following div shows proxy forwarding of websocket connection, served by
|
||||
ws://echo.websocket.org:
|
||||
</p>
|
||||
|
||||
<div id="events"></div>
|
||||
</body>
|
||||
</html>
|
@ -1,29 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title> proxy index </title>
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
var es = new EventSource("/api/sse");
|
||||
var div = document.getElementById('events');
|
||||
es.onmessage = function(ev) {
|
||||
var el = document.createElement('div');
|
||||
el.innerHTML = 'sse message: ' + ev.data;
|
||||
div.appendChild(el);
|
||||
// Keep only last 5 messages in the list
|
||||
while (div.childNodes.length > 5) div.removeChild(div.firstChild);
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1> proxy index page.</h1>
|
||||
<ul>
|
||||
<li><a href="app1">App1</a> - App1 root</li>
|
||||
<li><a href="app2">App2</a> - App2 root</li>
|
||||
</ul>
|
||||
|
||||
<h2>SSE pushes, done by separate threads at random times:</h2>
|
||||
<div id="events"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,50 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
|
||||
hMSGCcSV6y32hzhqR5lvTViaQez+xhc58NZRu+OUgEhodRBW/vAOjpz/xdMz5HaC
|
||||
EhP3E9W1pkitVseS8B5rrgJo1BfCGai1fPav1nutPq2Kj7vMy24+g460Lonf6ln1
|
||||
di4aTIRtAqXtUU6RFpPJP35PkCXbTK65O8HJSxxt/XtfoezHCU5+UIwmZGYx46UB
|
||||
Wzg3IfK6bGPSiHU3pdiTol0uMPt/GUK+x4NyZJ4/ImsNAicRwMBdja4ywHKXJehH
|
||||
gXBthsVIHbL21x+4ibsg9eVM/XioTV6tW3IrdwIDAQABAoIBACFfdLutmkQFBcRN
|
||||
HAJNNHmmsyr0vcUOVnXTFyYeDXV67qxrYHQlOHe6LqIpKq1Mon7O2kYMnWvooFAP
|
||||
trOnsS6L+qaTYJdYg2TKjgo4ubw1hZXytyB/mdExuaMSkgMgtpia+tB5lD+V+LxN
|
||||
x1DesZ+veFMO3Zluyckswt4qM5yVa04YFrt31H0E1rJfIen61lidXIKYmHHWuRxK
|
||||
SadjFfbcqJ6P9ZF22BOkleg5Fm5NaxJmyQynOWaAkSZa5w1XySFfRjRfsbDr64G6
|
||||
+LSG8YtRuvfxnvUNhynVPHcpE40eiPo6v8Ho6yZKXpV5klCKciodXAORsswSoGJa
|
||||
N3nnu/ECgYEA6Yb2rM3QUEPIALdL8f/OzZ1GBSdiQB2WSAxzl9pR/dLF2H+0pitS
|
||||
to0830mk92ppVmRVD3JGxYDRZQ56tlFXyGaCzJBMRIcsotAhBoNbjV0i9n5bLJYf
|
||||
BmjU9yvWcgsTt0tr3B0FrtYyp2tCvwHqlxvFpFdUCj2oRw2uGpkhmNkCgYEA03M6
|
||||
WxFhsix3y6eVCVvShfbLBSOqp8l0qiTEty+dgVQcWN4CO/5eyaZXKxlCG9KMmKxy
|
||||
Yx+YgxZrDhfaZ0cxhHGPRKEAxM3IKwT2C8/wCaSiLWXZZpTifnSD99vtOt4wEfrG
|
||||
+AghNd5kamFiM9tU0AyvhJc2vdJFuXrfeC7ntM8CgYBGDA+t4cZcbRhu7ow/OKYF
|
||||
kulP3nJgHP/Y+LMrl3cEldZ2jEfZmCElVNQvfd2XwTl7injhOzvzPiKRF3jDez7D
|
||||
g8w0JAxceddvttJRK9GoY4l7OoeKpjUELSnEQkf+yUfOsTbXPXVY7jMfeNL6jE6b
|
||||
qN7t3qv8rmXtejMBE3G6cQKBgGR5W2BMiRSlxqKx1cKlrApV87BUe1HRCyuR3xuA
|
||||
d6Item7Lx1oEi7vb242yKdSYnpApWQ06xTh83Y/Ly87JaIEbiM0+h+P8OEIg0F1a
|
||||
iB+86AcUX1I8KseVy+Np0HbpfwP8GrFfA5DaRPK7pXMopEtby8cAJ1XZZaI1/ZvZ
|
||||
BebHAoGAcQU9WvCkT+nIp9FpXfBybYUsvgkaizMIqp66/l3GYgYAq8p1VLGvN4v5
|
||||
ec0dW58SJrCpqsM3NP78DtEzQf9OOsk+FsjBFzDU2RkeUreyt2/nQBj/2mN/+hEy
|
||||
hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMB4XDTA4MTIwNzEwMjUyMloXDTE4MTIwNTEwMjUyMlowRTELMAkG
|
||||
A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
|
||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AMDjWizj+xHXoKo0bkCkg187x5hTGXwbn44RC4TY6OSrC+Inh4TEhgnElest9oc4
|
||||
akeZb01YmkHs/sYXOfDWUbvjlIBIaHUQVv7wDo6c/8XTM+R2ghIT9xPVtaZIrVbH
|
||||
kvAea64CaNQXwhmotXz2r9Z7rT6tio+7zMtuPoOOtC6J3+pZ9XYuGkyEbQKl7VFO
|
||||
kRaTyT9+T5Al20yuuTvByUscbf17X6HsxwlOflCMJmRmMeOlAVs4NyHyumxj0oh1
|
||||
N6XYk6JdLjD7fxlCvseDcmSePyJrDQInEcDAXY2uMsBylyXoR4FwbYbFSB2y9tcf
|
||||
uIm7IPXlTP14qE1erVtyK3cCAwEAATANBgkqhkiG9w0BAQQFAAOCAQEAW4yZdqpB
|
||||
oIdiuXRosr86Sg9FiMg/cn+2OwQ0QIaA8ZBwKsc+wIIHEgXCS8J6316BGQeUvMD+
|
||||
plNe0r4GWzzmlDMdobeQ5arPRB89qd9skE6pAMdLg3FyyfEjz3A0VpskolW5VBMr
|
||||
P5R7uJ1FLgH12RyAjZCWYcCRqEMOffqvyMCH6oAjyDmQOA5IssRKX/HsHntSH/HW
|
||||
W7slTcP45ty1b44Nq22/ubYk0CJRQgqKOIQ3cLgPomN1jNFQbAbfVTaK1DpEysrQ
|
||||
5V8a8gNW+3sVZmV6d1Mj3pN2Le62wUKuV2g6BNU7iiwcoY8HI68aRxz2hVMS+t5f
|
||||
SEGI4JSxV56lYg==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MEYCQQD+ef8hZ4XbdoyIpJyCTF2UrUEfX6mYDvxuS5O1UNYcslUqlj6JkA11e/yS
|
||||
6DK8Z86W6mSj5CEk4IjbyEOECXH7AgEC
|
||||
-----END DH PARAMETERS-----
|
14
examples/publish_subscribe/Makefile
Normal file
14
examples/publish_subscribe/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = publish_subscribe
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -DNS_ENABLE_THREADS -pthread $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
112
examples/publish_subscribe/publish_subscribe.c
Normal file
112
examples/publish_subscribe/publish_subscribe.c
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright (c) 2014 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// This software is dual-licensed: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation. For the terms of this
|
||||
// license, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// You are free to use this software under the terms of the GNU General
|
||||
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU General Public License for more details.
|
||||
//
|
||||
// Alternatively, you can license this software under a commercial
|
||||
// license, as set out in <https://www.cesanta.com/license>.
|
||||
//
|
||||
// $Date: 2014-09-28 05:04:41 UTC $
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static void *stdin_thread(void *param) {
|
||||
int ch, sock = * (int *) param;
|
||||
while ((ch = getchar()) != EOF) {
|
||||
unsigned char c = (unsigned char) ch;
|
||||
send(sock, &c, 1, 0); // Forward all types characters to the socketpair
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void server_handler(struct mg_connection *nc, int ev, void *p) {
|
||||
(void) p;
|
||||
if (ev == NS_RECV) {
|
||||
// Push received message to all ncections
|
||||
struct mbuf *io = &nc->recv_mbuf;
|
||||
struct mg_connection *c;
|
||||
|
||||
for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
|
||||
mg_send(c, io->buf, io->len);
|
||||
}
|
||||
mbuf_remove(io, io->len);
|
||||
}
|
||||
}
|
||||
|
||||
static void client_handler(struct mg_connection *conn, int ev, void *p) {
|
||||
struct mbuf *io = &conn->recv_mbuf;
|
||||
(void) p;
|
||||
|
||||
if (ev == NS_CONNECT) {
|
||||
if (conn->flags & NSF_CLOSE_IMMEDIATELY) {
|
||||
printf("%s\n", "Error connecting to server!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("%s\n", "Connected to server. Type a message and press enter.");
|
||||
} else if (ev == NS_RECV) {
|
||||
if (conn->flags & NSF_USER_1) {
|
||||
// Received data from the stdin, forward it to the server
|
||||
struct mg_connection *c = (struct mg_connection *) conn->user_data;
|
||||
mg_send(c, io->buf, io->len);
|
||||
mbuf_remove(io, io->len);
|
||||
} else {
|
||||
// Received data from server connection, print it
|
||||
fwrite(io->buf, io->len, 1, stdout);
|
||||
mbuf_remove(io, io->len);
|
||||
}
|
||||
} else if (ev == NS_CLOSE) {
|
||||
// Connection has closed, most probably cause server has stopped
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s <port> <client|server>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (strcmp(argv[2], "client") == 0) {
|
||||
int fds[2];
|
||||
struct mg_connection *ioconn, *server_conn;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
// Connect to the pubsub server
|
||||
server_conn = mg_connect(&mgr, argv[1], client_handler);
|
||||
if (server_conn == NULL) {
|
||||
fprintf(stderr, "Cannot connect to port %s\n", argv[1]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Create a socketpair and give one end to the thread that reads stdin
|
||||
mg_socketpair(fds, SOCK_STREAM);
|
||||
mg_start_thread(stdin_thread, &fds[1]);
|
||||
|
||||
// The other end of a pair goes inside the server
|
||||
ioconn = mg_add_sock(&mgr, fds[0], client_handler);
|
||||
ioconn->flags |= NSF_USER_1; // Mark this so we know this is a stdin
|
||||
ioconn->user_data = server_conn;
|
||||
|
||||
} else {
|
||||
// Server code path
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
mg_bind(&mgr, argv[1], server_handler);
|
||||
printf("Starting pubsub server on port %s\n", argv[1]);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
18
examples/raspberry_pi_mjpeg_led/Makefile
Normal file
18
examples/raspberry_pi_mjpeg_led/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
NS=../../mongoose.c
|
||||
FLAGS = ../../mongoose.c -I../..
|
||||
CFLAGS=-W -Wall -DNS_ENABLE_THREADS -pthread $(CFLAGS_EXTRA)
|
||||
PROGS = device_side cloud_side
|
||||
|
||||
all: $(PROGS)
|
||||
|
||||
device_side: Makefile device_side.c $(NS)
|
||||
$(CC) device_side.c $(FLAGS) -o $@ $(CFLAGS)
|
||||
|
||||
cloud_side: Makefile cloud_side.c $(NS)
|
||||
$(CC) cloud_side.c $(FLAGS) -o $@ $(CFLAGS)
|
||||
|
||||
device_side.exe: Makefile device_side.c $(NS)
|
||||
cl device_side.c $(FLAGS) /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROGS)
|
72
examples/raspberry_pi_mjpeg_led/README.adoc
Normal file
72
examples/raspberry_pi_mjpeg_led/README.adoc
Normal file
@ -0,0 +1,72 @@
|
||||
= Raspberry Pi camera/LED demo
|
||||
|
||||
== Overview
|
||||
|
||||
The link:/[demo] consists of web app providing access to a webcam and a LED attached to a RaspberryPi.
|
||||
The device is assumed to have a limited bandwidth towards the server hosting the web app.
|
||||
|
||||
== Objective
|
||||
|
||||
The demo shows how to use websockets to communicate bidirectionally with an embedded device using standard protocols.
|
||||
|
||||
It also shows that it's possible to use Smart.c to develop also the cloud endpoint and expose WebSocket and RESTful APIs
|
||||
easy to integreate with modern web stacks.
|
||||
|
||||
== How it works
|
||||
|
||||
image::docs/arch.png[]
|
||||
|
||||
There are two components, once with runs on the device (`device_side`) and one that runs on a stronger machine
|
||||
and with more bandwidth (`cloud_side`).
|
||||
|
||||
The device app connects to the cloud app via websocket and sends a new jpeg frame as fast as the underlying `raspistill` camera
|
||||
grabbing application can handle. The device automatically attempts reconnecting.
|
||||
|
||||
The cloud side serves the webapp static pages and serves an MPJEG image on `/mpjg`.
|
||||
The MPJEG image handler blocks all the clients until a JPEG frame arrives via websocket
|
||||
and then every client will recieve a copy of the frame.
|
||||
|
||||
The web app can turn on and off the LED via a RESTful api accessible via the `/api` handler.
|
||||
|
||||
== Installation
|
||||
|
||||
=== Server side
|
||||
|
||||
----
|
||||
git clone https://github.com/cesanta/mongoose
|
||||
cd mongoose/examples/web_demo
|
||||
make cloud_side && ./cloud_side 0.0.0.0:8080
|
||||
----
|
||||
|
||||
=== Raspberry Pi
|
||||
|
||||
The instructions provided here are tailored for the Raspbian distribution.
|
||||
|
||||
==== Dependencies
|
||||
|
||||
jpegoptim::
|
||||
apt-get install jpegoptim
|
||||
|
||||
camera::
|
||||
run raspi-config and enable camera
|
||||
|
||||
==== LED
|
||||
|
||||
In order to access the led on your link:http://www.qdh.org.uk/wordpress/?page_id=15[HotPi]
|
||||
board you need to export the gpio pins:
|
||||
|
||||
----
|
||||
for i in 22 23 24; do
|
||||
echo $i >/sys/class/gpio/export
|
||||
echo out >/sys/class/gpio/gpio$i/direction
|
||||
chgrp pi /sys/class/gpio/gpio$i/value
|
||||
done
|
||||
----
|
||||
|
||||
==== Build and run
|
||||
|
||||
----
|
||||
git clone https://github.com/cesanta/mongoose
|
||||
cd mongoose/examples/web_demo
|
||||
make device_side && ./device_side yourserver:8080
|
||||
----
|
143
examples/raspberry_pi_mjpeg_led/cloud_side.c
Normal file
143
examples/raspberry_pi_mjpeg_led/cloud_side.c
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the cloud endpoint of the Raspberry Pi camera/LED example
|
||||
* of the Mongoose networking library.
|
||||
* It is a simple web server, serving both static files, a REST API handler,
|
||||
* and a WebSocket handler.
|
||||
*/
|
||||
#include "mongoose.h"
|
||||
|
||||
static struct mg_serve_http_opts web_root_opts;
|
||||
|
||||
/*
|
||||
* Forwards the jpeg frame data to all open mjpeg connections.
|
||||
*
|
||||
* Incoming messages follow a very simple binary frame format:
|
||||
* 4 bytes: timestamp (in network byte order)
|
||||
* n bytes: jpeg payload
|
||||
*
|
||||
* The timestamp is used to compute a lag.
|
||||
* It's done in a quite stupid way as it requires the device clock
|
||||
* to be synchronized with the cloud endpoint.
|
||||
*/
|
||||
static void push_frame_to_clients(struct mg_mgr *mgr,
|
||||
const struct websocket_message *wm) {
|
||||
struct mg_connection *nc;
|
||||
/*
|
||||
* mjpeg connections are tagged with the NSF_USER_2 flag so we can find them
|
||||
* my scanning the connection list provided by the mongoose manager.
|
||||
*/
|
||||
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
|
||||
if (!(nc->flags & NSF_USER_2)) continue; // Ignore un-marked requests
|
||||
|
||||
mg_printf(nc, "--w00t\r\nContent-Type: image/jpeg\r\n"
|
||||
"Content-Length: %lu\r\n\r\n", (unsigned long) wm->size);
|
||||
mg_send(nc, wm->data, wm->size);
|
||||
mg_send(nc, "\r\n", 2);
|
||||
printf("Image pushed to %p\n", nc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Forwards API payload to the device, by scanning through
|
||||
* all the connections to find those that are tagged as WebSocket.
|
||||
*/
|
||||
static void send_command_to_the_device(struct mg_mgr *mgr,
|
||||
const struct mg_str *cmd) {
|
||||
struct mg_connection *nc;
|
||||
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
|
||||
if (!(nc->flags & NSF_IS_WEBSOCKET)) continue; // Ignore non-websocket requests
|
||||
|
||||
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, cmd->p, cmd->len);
|
||||
printf("Sent API command [%.*s] to %p\n", (int) cmd->len, cmd->p, nc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Main event handler. Receives data events and dispatches to
|
||||
* the appropriate handler function.
|
||||
*
|
||||
* 1. RESTful API requests are handled by send_command_to_the_device.
|
||||
* 2. requests to /mpeg are established and left open waiting for data to arrive
|
||||
* from WebSocket.
|
||||
* 3. WebSocket frames are handled by push_frame_to_clients.
|
||||
* 4. All other connections are passed to the mg_serve_http handler
|
||||
* which serves static files.
|
||||
*/
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct websocket_message *wm = (struct websocket_message *) ev_data;
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/mjpg") == 0) {
|
||||
nc->flags |= NSF_USER_2; /* Set a mark on image requests */
|
||||
mg_printf(nc, "%s",
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Pragma: no-cache\r\n"
|
||||
"Expires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Type: multipart/x-mixed-replace; "
|
||||
"boundary=--w00t\r\n\r\n");
|
||||
} else if (mg_vcmp(&hm->uri, "/api") == 0 && hm->body.len > 0) {
|
||||
/*
|
||||
* RESTful API call. HTTP message body should be a JSON message.
|
||||
* We should parse it and take appropriate action.
|
||||
* In our case, simply forward that call to the device.
|
||||
*/
|
||||
printf("API CALL: [%.*s] [%.*s]\n", (int) hm->method.len, hm->method.p,
|
||||
(int) hm->body.len, hm->body.p);
|
||||
send_command_to_the_device(nc->mgr, &hm->body);
|
||||
mg_printf(nc, "HTTP/1.0 200 OK\nContent-Length: 0\n\n");
|
||||
} else {
|
||||
/* Delegate to the static web server handler for all other paths. */
|
||||
mg_serve_http(nc, hm, web_root_opts);
|
||||
}
|
||||
break;
|
||||
case NS_WEBSOCKET_FRAME:
|
||||
printf("Got websocket frame, size %lu\n", (unsigned long) wm->size);
|
||||
push_frame_to_clients(nc->mgr, wm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <listening_addr>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Listening on: [%s]\n", argv[1]);
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
/*
|
||||
* mg_bind() creates a listening connection on a given ip:port and
|
||||
* with an attached event handler.
|
||||
* The event handler will only trigger TCP events until the http
|
||||
* protocol handler is installed.
|
||||
*/
|
||||
if ((nc = mg_bind(&mgr, argv[1], ev_handler)) == NULL) {
|
||||
fprintf(stderr, "Error binding to %s\n", argv[1]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
web_root_opts.document_root = "./web_root";
|
||||
|
||||
/*
|
||||
* We explicitly hand over control to the Mongoose manager
|
||||
* in this event loop and we can easily multiplex other activities.
|
||||
*/
|
||||
for(;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
168
examples/raspberry_pi_mjpeg_led/device_side.c
Normal file
168
examples/raspberry_pi_mjpeg_led/device_side.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the device endpoint of the Raspberry Pi camera/LED example
|
||||
* of the Mongoose networking library.
|
||||
* It is a simple websocket client, sending jpeg frames obtained from the
|
||||
* RPi camera and receiving JSON commands through the same WebSocket channel
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_poll_interval_ms = 100;
|
||||
static int s_still_period = 100;
|
||||
static int s_vertical_flip = 0;
|
||||
static int s_width = 320;
|
||||
static int s_height = 180;
|
||||
static const char *s_mjpg_file = "/var/run/shm/cam.jpg";
|
||||
|
||||
static struct mg_connection *client;
|
||||
|
||||
/*
|
||||
* Check if there is a new image available and
|
||||
* send it to the cloud endpoint if the send buffer is not too full.
|
||||
* The image is moved in a new file by the jpeg optimizer function;
|
||||
* this ensures that we will detect a new frame when raspistill writes
|
||||
* it's output file.
|
||||
*/
|
||||
static void send_mjpg_frame(struct mg_connection *nc, const char *file_path) {
|
||||
static int skipped_frames = 0;
|
||||
struct stat st;
|
||||
FILE *fp;
|
||||
|
||||
/* Check file modification time. */
|
||||
if (stat(file_path, &st) == 0) {
|
||||
/* Skip the frame if there is too much unsent data. */
|
||||
if (nc->send_mbuf.len > 256) skipped_frames++;
|
||||
|
||||
/* Read new mjpg frame into a buffer */
|
||||
fp = fopen(file_path, "rb");
|
||||
char buf[st.st_size];
|
||||
fread(buf, 1, sizeof(buf), fp);
|
||||
fclose(fp);
|
||||
|
||||
/*
|
||||
* Delete the file so we can detect when raspistill creates a new one.
|
||||
* mtime granularity is only 1s.
|
||||
*/
|
||||
unlink(file_path);
|
||||
|
||||
/* Send those buffer through the websocket connection */
|
||||
mg_send_websocket_frame(nc, WEBSOCKET_OP_BINARY, buf, sizeof(buf));
|
||||
printf("Sent mjpg frame, %lu bytes after skippping %d frames\n", (unsigned long) sizeof(buf), skipped_frames);
|
||||
skipped_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn on or off the LED.
|
||||
* The LED in this example is an RGB led, so all the colors have to be set.
|
||||
*/
|
||||
static void set_led(int v) {
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "for i in 22 23 24; do"
|
||||
" echo %d >/sys/class/gpio/gpio$i/value; done", v);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse control JSON and perform command:
|
||||
* for now only LED on/off is supported.
|
||||
*/
|
||||
static void perform_control_command(const char* data, size_t len) {
|
||||
struct json_token toks[200], *onoff;
|
||||
parse_json(data, len, toks, sizeof(toks));
|
||||
onoff = find_json_token(toks, "onoff");
|
||||
set_led(strncmp("[\"on\"]", onoff->ptr, onoff->len) == 0);
|
||||
}
|
||||
|
||||
/* Main event handler. Sends websocket frames and receives control commands */
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct websocket_message *wm = (struct websocket_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_CONNECT:
|
||||
printf("Reconnect: %s\n", * (int *) ev_data == 0 ? "ok" : "failed");
|
||||
if (* (int *) ev_data == 0) {
|
||||
/*
|
||||
* Tune the tcp send buffer size, so that we can skip frames
|
||||
* when the connection is congested. This helps maintaining a
|
||||
* reasonable latency.
|
||||
*/
|
||||
int sndbuf_size = 512;
|
||||
if(setsockopt(nc->sock, SOL_SOCKET, SO_SNDBUF,
|
||||
(void *) &sndbuf_size, sizeof(int)) == -1) {
|
||||
perror("failed to tune TCP send buffer size\n");
|
||||
}
|
||||
|
||||
mg_send_websocket_handshake(nc, "/stream", NULL);
|
||||
}
|
||||
break;
|
||||
case NS_CLOSE:
|
||||
printf("Connection %p closed\n", nc);
|
||||
client = NULL;
|
||||
break;
|
||||
case NS_POLL:
|
||||
send_mjpg_frame(nc, s_mjpg_file);
|
||||
break;
|
||||
case NS_WEBSOCKET_FRAME:
|
||||
printf("Got control command: [%.*s]\n", (int) wm->size, wm->data);
|
||||
perform_control_command((const char*)wm->data, wm->size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This thread regenerates s_mjpg_file every s_poll_interval_ms milliseconds.
|
||||
* It is Raspberry PI specific, change this function on other systems.
|
||||
*/
|
||||
static void *generate_mjpg_data_thread_func(void *param) {
|
||||
char cmd[400];
|
||||
(void) param;
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "raspistill -w %d -h %d -n -q 100 -tl %d "
|
||||
"-t 999999999 -v %s -o %s >/dev/null 2>&1", s_width, s_height,
|
||||
s_still_period, s_vertical_flip ? "-vf" : "", s_mjpg_file);
|
||||
|
||||
for(;;) {
|
||||
int ret = system(cmd);
|
||||
if (WIFSIGNALED(ret)) exit(1);
|
||||
sleep(1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
char *addr = argv[1];
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_address>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Start separate thread that generates MJPG data */
|
||||
mg_start_thread(generate_mjpg_data_thread_func, NULL);
|
||||
|
||||
printf("Streaming [%s] to [%s]\n", s_mjpg_file, addr);
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
for(;;) {
|
||||
mg_mgr_poll(&mgr, s_poll_interval_ms);
|
||||
|
||||
/* Reconnect if disconnected */
|
||||
if (!client) {
|
||||
sleep(1); /* limit reconnections frequency */
|
||||
printf("Reconnecting to %s...\n", addr);
|
||||
client = mg_connect(&mgr, addr, ev_handler);
|
||||
if (client) mg_set_protocol_http_websocket(client);
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
BIN
examples/raspberry_pi_mjpeg_led/docs/arch.png
Normal file
BIN
examples/raspberry_pi_mjpeg_led/docs/arch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
533
examples/raspberry_pi_mjpeg_led/web_root/doc.html
Normal file
533
examples/raspberry_pi_mjpeg_led/web_root/doc.html
Normal file
@ -0,0 +1,533 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="generator" content="Asciidoctor 1.5.1">
|
||||
<title>Raspberry Pi camera/LED demo</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400">
|
||||
<style>
|
||||
/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
|
||||
/* Remove the comments around the @import statement below when using this as a custom stylesheet */
|
||||
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400";*/
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
|
||||
audio,canvas,video{display:inline-block}
|
||||
audio:not([controls]){display:none;height:0}
|
||||
[hidden],template{display:none}
|
||||
script{display:none!important}
|
||||
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
|
||||
body{margin:0}
|
||||
a{background:transparent}
|
||||
a:focus{outline:thin dotted}
|
||||
a:active,a:hover{outline:0}
|
||||
h1{font-size:2em;margin:.67em 0}
|
||||
abbr[title]{border-bottom:1px dotted}
|
||||
b,strong{font-weight:bold}
|
||||
dfn{font-style:italic}
|
||||
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
|
||||
mark{background:#ff0;color:#000}
|
||||
code,kbd,pre,samp{font-family:monospace;font-size:1em}
|
||||
pre{white-space:pre-wrap}
|
||||
q{quotes:"\201C" "\201D" "\2018" "\2019"}
|
||||
small{font-size:80%}
|
||||
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sup{top:-.5em}
|
||||
sub{bottom:-.25em}
|
||||
img{border:0}
|
||||
svg:not(:root){overflow:hidden}
|
||||
figure{margin:0}
|
||||
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{border:0;padding:0}
|
||||
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
|
||||
button,input{line-height:normal}
|
||||
button,select{text-transform:none}
|
||||
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
|
||||
button[disabled],html input[disabled]{cursor:default}
|
||||
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
|
||||
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
|
||||
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
|
||||
textarea{overflow:auto;vertical-align:top}
|
||||
table{border-collapse:collapse;border-spacing:0}
|
||||
*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
|
||||
html,body{font-size:100%}
|
||||
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto}
|
||||
a:hover{cursor:pointer}
|
||||
img,object,embed{max-width:100%;height:auto}
|
||||
object,embed{height:100%}
|
||||
img{-ms-interpolation-mode:bicubic}
|
||||
#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none!important}
|
||||
.left{float:left!important}
|
||||
.right{float:right!important}
|
||||
.text-left{text-align:left!important}
|
||||
.text-right{text-align:right!important}
|
||||
.text-center{text-align:center!important}
|
||||
.text-justify{text-align:justify!important}
|
||||
.hide{display:none}
|
||||
.antialiased,body{-webkit-font-smoothing:antialiased}
|
||||
img{display:inline-block;vertical-align:middle}
|
||||
textarea{height:auto;min-height:50px}
|
||||
select{width:100%}
|
||||
p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}
|
||||
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
|
||||
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
|
||||
a{color:#2156a5;text-decoration:underline;line-height:inherit}
|
||||
a:hover,a:focus{color:#1d4b8f}
|
||||
a img{border:none}
|
||||
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
|
||||
p aside{font-size:.875em;line-height:1.35;font-style:italic}
|
||||
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
|
||||
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
|
||||
h1{font-size:2.125em}
|
||||
h2{font-size:1.6875em}
|
||||
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
|
||||
h4,h5{font-size:1.125em}
|
||||
h6{font-size:1em}
|
||||
hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
|
||||
em,i{font-style:italic;line-height:inherit}
|
||||
strong,b{font-weight:bold;line-height:inherit}
|
||||
small{font-size:60%;line-height:inherit}
|
||||
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
|
||||
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
|
||||
ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em}
|
||||
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
|
||||
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
|
||||
ul.square{list-style-type:square}
|
||||
ul.circle{list-style-type:circle}
|
||||
ul.disc{list-style-type:disc}
|
||||
ul.no-bullet{list-style:none}
|
||||
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
|
||||
dl dt{margin-bottom:.3125em;font-weight:bold}
|
||||
dl dd{margin-bottom:1.25em}
|
||||
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
|
||||
abbr{text-transform:none}
|
||||
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
|
||||
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
|
||||
blockquote cite:before{content:"\2014 \0020"}
|
||||
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
|
||||
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
|
||||
@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
|
||||
h1{font-size:2.75em}
|
||||
h2{font-size:2.3125em}
|
||||
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
|
||||
h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
|
||||
table thead,table tfoot{background:#f7f8f7;font-weight:bold}
|
||||
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
|
||||
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
|
||||
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
|
||||
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
|
||||
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
|
||||
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
|
||||
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
|
||||
.clearfix:after,.float-group:after{clear:both}
|
||||
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
|
||||
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
|
||||
.keyseq{color:rgba(51,51,51,.8)}
|
||||
kbd{display:inline-block;color:rgba(0,0,0,.8);font-size:.75em;line-height:1.4;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:-.15em .15em 0 .15em;padding:.2em .6em .2em .5em;vertical-align:middle;white-space:nowrap}
|
||||
.keyseq kbd:first-child{margin-left:0}
|
||||
.keyseq kbd:last-child{margin-right:0}
|
||||
.menuseq,.menu{color:rgba(0,0,0,.8)}
|
||||
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
|
||||
b.button:before{content:"[";padding:0 3px 0 2px}
|
||||
b.button:after{content:"]";padding:0 2px 0 3px}
|
||||
p a>code:hover{color:rgba(0,0,0,.9)}
|
||||
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
|
||||
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
|
||||
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
|
||||
#content{margin-top:1.25em}
|
||||
#content:before{content:none}
|
||||
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
|
||||
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
|
||||
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
|
||||
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
|
||||
#header .details span:first-child{margin-left:-.125em}
|
||||
#header .details span.email a{color:rgba(0,0,0,.85)}
|
||||
#header .details br{display:none}
|
||||
#header .details br+span:before{content:"\00a0\2013\00a0"}
|
||||
#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
|
||||
#header .details br+span#revremark:before{content:"\00a0|\00a0"}
|
||||
#header #revnumber{text-transform:capitalize}
|
||||
#header #revnumber:after{content:"\00a0"}
|
||||
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
|
||||
#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
|
||||
#toc>ul{margin-left:.125em}
|
||||
#toc ul.sectlevel0>li>a{font-style:italic}
|
||||
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
|
||||
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
|
||||
#toc a{text-decoration:none}
|
||||
#toc a:active{text-decoration:underline}
|
||||
#toctitle{color:#7a2518;font-size:1.2em}
|
||||
@media only screen and (min-width:768px){#toctitle{font-size:1.375em}
|
||||
body.toc2{padding-left:15em;padding-right:0}
|
||||
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
|
||||
#toc.toc2 #toctitle{margin-top:0;font-size:1.2em}
|
||||
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
|
||||
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
|
||||
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
|
||||
body.toc2.toc-right{padding-left:0;padding-right:15em}
|
||||
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
|
||||
#toc.toc2{width:20em}
|
||||
#toc.toc2 #toctitle{font-size:1.375em}
|
||||
#toc.toc2>ul{font-size:.95em}
|
||||
#toc.toc2 ul ul{padding-left:1.25em}
|
||||
body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
|
||||
#content #toc>:first-child{margin-top:0}
|
||||
#content #toc>:last-child{margin-bottom:0}
|
||||
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
|
||||
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
|
||||
.sect1{padding-bottom:.625em}
|
||||
@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed}
|
||||
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
|
||||
#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
|
||||
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
|
||||
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
|
||||
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
|
||||
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
|
||||
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
|
||||
table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}
|
||||
.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}
|
||||
table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}
|
||||
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
|
||||
.admonitionblock>table td.icon{text-align:center;width:80px}
|
||||
.admonitionblock>table td.icon img{max-width:none}
|
||||
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
|
||||
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
|
||||
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
|
||||
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
|
||||
.exampleblock>.content>:first-child{margin-top:0}
|
||||
.exampleblock>.content>:last-child{margin-bottom:0}
|
||||
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
|
||||
.sidebarblock>:first-child{margin-top:0}
|
||||
.sidebarblock>:last-child{margin-bottom:0}
|
||||
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
|
||||
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
|
||||
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
|
||||
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
|
||||
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
|
||||
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
|
||||
@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
|
||||
.listingblock pre.highlightjs{padding:0}
|
||||
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
|
||||
.listingblock pre.prettyprint{border-width:0}
|
||||
.listingblock>.content{position:relative}
|
||||
.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
|
||||
.listingblock:hover code[data-lang]:before{display:block}
|
||||
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
|
||||
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
|
||||
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
|
||||
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0}
|
||||
table.pyhltable td.code{padding-left:.75em;padding-right:0}
|
||||
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
|
||||
pre.pygments .lineno{display:inline-block;margin-right:.25em}
|
||||
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
|
||||
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
|
||||
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
|
||||
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
|
||||
.quoteblock blockquote{margin:0;padding:0;border:0}
|
||||
.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
|
||||
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
|
||||
.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
|
||||
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
|
||||
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
|
||||
.quoteblock .quoteblock blockquote:before{display:none}
|
||||
.verseblock{margin:0 1em 1.25em 1em}
|
||||
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
|
||||
.verseblock pre strong{font-weight:400}
|
||||
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
|
||||
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
|
||||
.quoteblock .attribution br,.verseblock .attribution br{display:none}
|
||||
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.05em;color:rgba(0,0,0,.6)}
|
||||
.quoteblock.abstract{margin:0 0 1.25em 0;display:block}
|
||||
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
|
||||
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
|
||||
table.tableblock{max-width:100%;border-collapse:separate}
|
||||
table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}
|
||||
table.spread{width:100%}
|
||||
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
|
||||
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
|
||||
table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0}
|
||||
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
|
||||
table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0}
|
||||
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0}
|
||||
table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0}
|
||||
table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0}
|
||||
table.frame-all{border-width:1px}
|
||||
table.frame-sides{border-width:0 1px}
|
||||
table.frame-topbot{border-width:1px 0}
|
||||
th.halign-left,td.halign-left{text-align:left}
|
||||
th.halign-right,td.halign-right{text-align:right}
|
||||
th.halign-center,td.halign-center{text-align:center}
|
||||
th.valign-top,td.valign-top{vertical-align:top}
|
||||
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
|
||||
th.valign-middle,td.valign-middle{vertical-align:middle}
|
||||
table thead th,table tfoot th{font-weight:bold}
|
||||
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
|
||||
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
|
||||
p.tableblock>code:only-child{background:none;padding:0}
|
||||
p.tableblock{font-size:1em}
|
||||
td>div.verse{white-space:pre}
|
||||
ol{margin-left:1.75em}
|
||||
ul li ol{margin-left:1.5em}
|
||||
dl dd{margin-left:1.125em}
|
||||
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
|
||||
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
|
||||
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
|
||||
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
|
||||
ul.checklist li>p:first-child>.fa-check-square-o:first-child,ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
|
||||
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{position:relative;top:1px}
|
||||
ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
|
||||
ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}
|
||||
ul.inline>li>*{display:block}
|
||||
.unstyled dl dt{font-weight:400;font-style:normal}
|
||||
ol.arabic{list-style-type:decimal}
|
||||
ol.decimal{list-style-type:decimal-leading-zero}
|
||||
ol.loweralpha{list-style-type:lower-alpha}
|
||||
ol.upperalpha{list-style-type:upper-alpha}
|
||||
ol.lowerroman{list-style-type:lower-roman}
|
||||
ol.upperroman{list-style-type:upper-roman}
|
||||
ol.lowergreek{list-style-type:lower-greek}
|
||||
.hdlist>table,.colist>table{border:0;background:none}
|
||||
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
|
||||
td.hdlist1{padding-right:.75em;font-weight:bold}
|
||||
td.hdlist1,td.hdlist2{vertical-align:top}
|
||||
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
|
||||
.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1}
|
||||
.colist>table tr>td:last-of-type{padding:.25em 0}
|
||||
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
|
||||
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
|
||||
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
|
||||
.imageblock>.title{margin-bottom:0}
|
||||
.imageblock.thumb,.imageblock.th{border-width:6px}
|
||||
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
|
||||
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
|
||||
.image.left{margin-right:.625em}
|
||||
.image.right{margin-left:.625em}
|
||||
a.image{text-decoration:none}
|
||||
span.footnote,span.footnoteref{vertical-align:super;font-size:.875em}
|
||||
span.footnote a,span.footnoteref a{text-decoration:none}
|
||||
span.footnote a:active,span.footnoteref a:active{text-decoration:underline}
|
||||
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
|
||||
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}
|
||||
#footnotes .footnote{padding:0 .375em;line-height:1.3;font-size:.875em;margin-left:1.2em;text-indent:-1.2em;margin-bottom:.2em}
|
||||
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}
|
||||
#footnotes .footnote:last-of-type{margin-bottom:0}
|
||||
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
|
||||
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
|
||||
.gist .file-data>table td.line-data{width:99%}
|
||||
div.unbreakable{page-break-inside:avoid}
|
||||
.big{font-size:larger}
|
||||
.small{font-size:smaller}
|
||||
.underline{text-decoration:underline}
|
||||
.overline{text-decoration:overline}
|
||||
.line-through{text-decoration:line-through}
|
||||
.aqua{color:#00bfbf}
|
||||
.aqua-background{background-color:#00fafa}
|
||||
.black{color:#000}
|
||||
.black-background{background-color:#000}
|
||||
.blue{color:#0000bf}
|
||||
.blue-background{background-color:#0000fa}
|
||||
.fuchsia{color:#bf00bf}
|
||||
.fuchsia-background{background-color:#fa00fa}
|
||||
.gray{color:#606060}
|
||||
.gray-background{background-color:#7d7d7d}
|
||||
.green{color:#006000}
|
||||
.green-background{background-color:#007d00}
|
||||
.lime{color:#00bf00}
|
||||
.lime-background{background-color:#00fa00}
|
||||
.maroon{color:#600000}
|
||||
.maroon-background{background-color:#7d0000}
|
||||
.navy{color:#000060}
|
||||
.navy-background{background-color:#00007d}
|
||||
.olive{color:#606000}
|
||||
.olive-background{background-color:#7d7d00}
|
||||
.purple{color:#600060}
|
||||
.purple-background{background-color:#7d007d}
|
||||
.red{color:#bf0000}
|
||||
.red-background{background-color:#fa0000}
|
||||
.silver{color:#909090}
|
||||
.silver-background{background-color:#bcbcbc}
|
||||
.teal{color:#006060}
|
||||
.teal-background{background-color:#007d7d}
|
||||
.white{color:#bfbfbf}
|
||||
.white-background{background-color:#fafafa}
|
||||
.yellow{color:#bfbf00}
|
||||
.yellow-background{background-color:#fafa00}
|
||||
span.icon>.fa{cursor:default}
|
||||
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
|
||||
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}
|
||||
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
|
||||
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}
|
||||
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
|
||||
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
|
||||
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
|
||||
.conum[data-value] *{color:#fff!important}
|
||||
.conum[data-value]+b{display:none}
|
||||
.conum[data-value]:after{content:attr(data-value)}
|
||||
pre .conum[data-value]{position:relative;top:-.125em}
|
||||
b.conum *{color:inherit!important}
|
||||
.conum:not([data-value]):empty{display:none}
|
||||
h1,h2{letter-spacing:-.01em}
|
||||
dt,th.tableblock,td.content{text-rendering:optimizeLegibility}
|
||||
p,td.content{letter-spacing:-.01em}
|
||||
p strong,td.content strong{letter-spacing:-.005em}
|
||||
p,blockquote,dt,td.content{font-size:1.0625rem}
|
||||
p{margin-bottom:1.25rem}
|
||||
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
|
||||
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
|
||||
.print-only{display:none!important}
|
||||
@media print{@page{margin:1.25cm .75cm}
|
||||
*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
|
||||
a{color:inherit!important;text-decoration:underline!important}
|
||||
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
|
||||
a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
|
||||
abbr[title]:after{content:" (" attr(title) ")"}
|
||||
pre,blockquote,tr,img{page-break-inside:avoid}
|
||||
thead{display:table-header-group}
|
||||
img{max-width:100%!important}
|
||||
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
|
||||
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
|
||||
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
|
||||
#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
|
||||
.sect1{padding-bottom:0!important}
|
||||
.sect1+.sect1{border:0!important}
|
||||
#header>h1:first-child{margin-top:1.25rem}
|
||||
body.book #header{text-align:center}
|
||||
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}
|
||||
body.book #header .details{border:0!important;display:block;padding:0!important}
|
||||
body.book #header .details span:first-child{margin-left:0!important}
|
||||
body.book #header .details br{display:block}
|
||||
body.book #header .details br+span:before{content:none!important}
|
||||
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
|
||||
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
|
||||
.listingblock code[data-lang]:before{display:block}
|
||||
#footer{background:none!important;padding:0 .9375em}
|
||||
#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}
|
||||
.hide-on-print{display:none!important}
|
||||
.print-only{display:block!important}
|
||||
.hide-for-print{display:none!important}
|
||||
.show-for-print{display:inherit!important}}
|
||||
</style>
|
||||
</head>
|
||||
<body class="article">
|
||||
<div id="header">
|
||||
<h1>Raspberry Pi camera/LED demo</h1>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div class="sect1">
|
||||
<h2 id="_overview">Overview</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>The <a href="/">demo</a> consists of web app providing access to a webcam and a LED attached to a RaspberryPi.
|
||||
The device is assumed to have a limited bandwidth towards the server hosting the web app.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_objective">Objective</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>The demo shows how to use websockets to communicate bidirectionally with an embedded device using standard protocols.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>It also shows that it’s possible to use Smart.c to develop also the cloud endpoint and expose WebSocket and RESTful APIs
|
||||
easy to integreate with modern web stacks.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_how_it_works">How it works</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="imageblock">
|
||||
<div class="content">
|
||||
<img src="docs/arch.png" alt="arch">
|
||||
</div>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>There are two components, once with runs on the device (<code>device_side</code>) and one that runs on a stronger machine
|
||||
and with more bandwidth (<code>cloud_side</code>).</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The device app connects to the cloud app via websocket and sends a new jpeg frame as fast as the underlying <code>raspistill</code> camera
|
||||
grabbing application can handle. The device automatically attempts reconnecting.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The cloud side serves the webapp static pages and serves an MPJEG image on <code>/mpjg</code>.
|
||||
The MPJEG image handler blocks all the clients until a JPEG frame arrives via websocket
|
||||
and then every client will recieve a copy of the frame.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The web app can turn on and off the LED via a RESTful api accessible via the <code>/api</code> handler.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_installation">Installation</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="sect2">
|
||||
<h3 id="_server_side">Server side</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>git clone https://github.com/cesanta/mongoose
|
||||
cd mongoose/examples/web_demo
|
||||
make cloud_side && ./cloud_side 0.0.0.0:8080</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_raspberry_pi">Raspberry Pi</h3>
|
||||
<div class="paragraph">
|
||||
<p>The instructions provided here are tailored for the Raspbian distribution.</p>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
<h4 id="_dependencies">Dependencies</h4>
|
||||
<div class="dlist">
|
||||
<dl>
|
||||
<dt class="hdlist1">jpegoptim</dt>
|
||||
<dd>
|
||||
<p>apt-get install jpegoptim</p>
|
||||
</dd>
|
||||
<dt class="hdlist1">camera</dt>
|
||||
<dd>
|
||||
<p>run raspi-config and enable camera</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
<h4 id="_led">LED</h4>
|
||||
<div class="paragraph">
|
||||
<p>In order to access the led on your <a href="http://www.qdh.org.uk/wordpress/?page_id=15">HotPi</a>
|
||||
board you need to export the gpio pins:</p>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>for i in 22 23 24; do
|
||||
echo $i >/sys/class/gpio/export
|
||||
echo out >/sys/class/gpio/gpio$i/direction
|
||||
chgrp pi /sys/class/gpio/gpio$i/value
|
||||
done</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
<h4 id="_build_and_run">Build and run</h4>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>git clone https://github.com/cesanta/mongoose
|
||||
cd mongoose/examples/web_demo
|
||||
make device_side && ./device_side yourserver:8080</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2014-11-03 21:30:06 GMT
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
1
examples/raspberry_pi_mjpeg_led/web_root/docs/arch.png
Symbolic link
1
examples/raspberry_pi_mjpeg_led/web_root/docs/arch.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../docs/arch.png
|
15
examples/raspberry_pi_mjpeg_led/web_root/framework7.min.css
vendored
Executable file
15
examples/raspberry_pi_mjpeg_led/web_root/framework7.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
18
examples/raspberry_pi_mjpeg_led/web_root/framework7.min.js
vendored
Executable file
18
examples/raspberry_pi_mjpeg_led/web_root/framework7.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
108
examples/raspberry_pi_mjpeg_led/web_root/index.html
Normal file
108
examples/raspberry_pi_mjpeg_led/web_root/index.html
Normal file
@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Required meta tags-->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<link rel="stylesheet" href="framework7.min.css">
|
||||
|
||||
<title>Smart.c mjpg example</title>
|
||||
<style type="text/css">
|
||||
.image img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Status bar overlay for full screen mode (PhoneGap) -->
|
||||
<div class="statusbar-overlay"></div>
|
||||
|
||||
<!-- Views -->
|
||||
<div class="views">
|
||||
<!-- Your main view, should have "view-main" class -->
|
||||
<div class="view view-main">
|
||||
<!-- Top Navbar-->
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<!-- We need cool sliding animation on title element, so we have additional "sliding" class -->
|
||||
<div class="left">
|
||||
<a href="docs/docs/doc.html" class="link" onclick="location='docs/docs/doc.html'">
|
||||
<span>About</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="center sliding">Remote Camera</div>
|
||||
<div class="right sliding">
|
||||
<a href="https://github.com/cesanta/mongoose/tree/master/examples/raspberry_pi_mjpeg_led" class="link" onclick="location='https://github.com/cesanta/mongoose/tree/master/examples/raspberry_pi_mjpeg_led'">
|
||||
<span>Github</span>
|
||||
<i class="icon icon-next"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pages container, because we use fixed-through navbar and toolbar, it has additional appropriate classes-->
|
||||
<div class="pages navbar-through toolbar-through">
|
||||
<!-- Page, "data-page" contains page name -->
|
||||
<div data-page="index" class="page">
|
||||
<!-- Scrollable page content -->
|
||||
<div class="page-content">
|
||||
<div class="content-block-title">Camera View</div>
|
||||
<div class="content-block image">
|
||||
<img src="/mjpg">
|
||||
</div>
|
||||
|
||||
<div class="content-block-title">Device Control</div>
|
||||
<div class="list-block">
|
||||
|
||||
<form action="/api" method="GET" enctype="application/json"
|
||||
id="form-control">
|
||||
<ul>
|
||||
|
||||
<!-- Switch (Checkbox) -->
|
||||
<li>
|
||||
<div class="item-content">
|
||||
<div class="item-inner">
|
||||
<div class="item-title label">LED on/off</div>
|
||||
<div class="item-input">
|
||||
<label class="label-switch">
|
||||
<input type="checkbox" name="onoff">
|
||||
<div class="checkbox"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="framework7.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var myApp = new Framework7({
|
||||
pushState: true,
|
||||
swipePanel: 'left',
|
||||
// ... other parameters
|
||||
});
|
||||
Dom7(document).on('change', '#form-control', function(ev) {
|
||||
var data = myApp.formToJSON('#form-control');
|
||||
var json = JSON.stringify(data);
|
||||
Dom7.ajax({
|
||||
url: '/api',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: json
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = restful_api
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,51 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_no_cache_header =
|
||||
"Cache-Control: max-age=0, post-check=0, "
|
||||
"pre-check=0, no-store, no-cache, must-revalidate\r\n";
|
||||
|
||||
static void handle_restful_call(struct mg_connection *conn) {
|
||||
char n1[100], n2[100];
|
||||
|
||||
// Get form variables
|
||||
mg_get_var(conn, "n1", n1, sizeof(n1));
|
||||
mg_get_var(conn, "n2", n2, sizeof(n2));
|
||||
|
||||
mg_printf_data(conn, "{ \"result\": %lf }", strtod(n1, NULL) + strtod(n2, NULL));
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
case MG_REQUEST:
|
||||
if (!strcmp(conn->uri, "/api/sum")) {
|
||||
handle_restful_call(conn);
|
||||
return MG_TRUE;
|
||||
}
|
||||
mg_send_file(conn, "index.html", s_no_cache_header);
|
||||
return MG_MORE;
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server;
|
||||
|
||||
// Create and configure the server
|
||||
server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8000");
|
||||
|
||||
// Serve request. Hit Ctrl-C to terminate the program
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
|
||||
// Cleanup, and free server instance
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
14
examples/restful_client/Makefile
Normal file
14
examples/restful_client/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
PROG = restful_client
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
56
examples/restful_client/restful_client.c
Normal file
56
examples/restful_client/restful_client.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
/* RESTful server host and request URI */
|
||||
static const char *s_target_address = "ajax.googleapis.com:80";
|
||||
static const char *s_request = "/ajax/services/search/web?v=1.0&q=cesanta";
|
||||
|
||||
static int s_exit_flag = 0;
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
int connect_status;
|
||||
|
||||
switch (ev) {
|
||||
case NS_CONNECT:
|
||||
connect_status = * (int *) ev_data;
|
||||
if (connect_status == 0) {
|
||||
printf("Connected to %s, sending request...\n", s_target_address);
|
||||
mg_printf(nc, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n",
|
||||
s_request, s_target_address);
|
||||
} else {
|
||||
printf("Error connecting to %s: %s\n",
|
||||
s_target_address, strerror(connect_status));
|
||||
s_exit_flag = 1;
|
||||
}
|
||||
break;
|
||||
case NS_HTTP_REPLY:
|
||||
printf("Got reply:\n%.*s\n", (int) hm->body.len, hm->body.p);
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
s_exit_flag = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_connect(&mgr, s_target_address, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
|
||||
printf("Starting RESTful client against %s\n", s_target_address);
|
||||
while (s_exit_flag == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
20
examples/restful_server/Makefile
Normal file
20
examples/restful_server/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
PROG = restful_server
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA)
|
||||
|
||||
ifeq ($(SSL), openssl)
|
||||
CFLAGS += -DNS_ENABLE_SSL -lssl -lcrypto -lcrypto
|
||||
else ifeq ($(SSL), krypton)
|
||||
CFLAGS += -DNS_ENABLE_SSL ../../../krypton/krypton.c
|
||||
endif
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /DNS_ENABLE_THREADS /Fe$@ advapi32.lib
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
@ -5,10 +5,11 @@
|
||||
<title>RESTful API demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
* { outline: none; font: 16px/1.4 Helvetica, Arial, sans-serif; }
|
||||
* { outline: none; }
|
||||
body {
|
||||
background-color: #cde; margin: 0;
|
||||
background-color: #789; margin: 0;
|
||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
@ -23,13 +24,13 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||
<script language="javascript" type="text/javascript">
|
||||
jQuery(function() {
|
||||
|
||||
$(document).on('keyup', '#n1, #n2', function() {
|
||||
$.ajax({
|
||||
url: '/api/sum',
|
||||
url: '/api/v1/sum',
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
data: { n1: $('#n1').val(), n2: $('#n2').val() },
|
||||
@ -47,10 +48,10 @@
|
||||
<h1>RESTful API demo.</h1>
|
||||
|
||||
<p>
|
||||
This page demonstrates how Mongoose web server could be used to implement
|
||||
RESTful APIs. Enter numbers below, and press Submit. Browser will send
|
||||
two numbers to <tt>/api/sum</tt> URI, Mongoose calclulates the sum of
|
||||
two and returns the result.
|
||||
This page demonstrates how Mongoose could be used to implement
|
||||
RESTful APIs. Start typing numbers into the text fields below.
|
||||
Browser sends two numbers to <tt>/api/v1/sum</tt> URI.
|
||||
Web server calclulates the sum and returns the result.
|
||||
</p>
|
||||
|
||||
<div>
|
114
examples/restful_server/restful_server.c
Normal file
114
examples/restful_server/restful_server.c
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
|
||||
static void handle_sum_call(struct mg_connection *nc, struct http_message *hm) {
|
||||
char n1[100], n2[100];
|
||||
double result;
|
||||
|
||||
/* Get form variables */
|
||||
mg_get_http_var(&hm->body, "n1", n1, sizeof(n1));
|
||||
mg_get_http_var(&hm->body, "n2", n2, sizeof(n2));
|
||||
|
||||
/* Send headers */
|
||||
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
|
||||
/* Compute the result and send it back as a JSON object */
|
||||
result = strtod(n1, NULL) + strtod(n2, NULL);
|
||||
mg_printf_http_chunk(nc, "{ \"result\": %lf }", result);
|
||||
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/api/v1/sum") == 0) {
|
||||
handle_sum_call(nc, hm); /* Handle RESTful call */
|
||||
} else if (mg_vcmp(&hm->uri, "/printcontent") == 0) {
|
||||
char buf[100] = {0};
|
||||
memcpy(buf, hm->body.p,
|
||||
sizeof(buf) - 1 < hm->body.len? sizeof(buf) - 1 : hm->body.len);
|
||||
printf("%s\n", buf);
|
||||
} else {
|
||||
mg_serve_http(nc, hm, s_http_server_opts); /* Serve static content */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
int i;
|
||||
char *cp;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
/* Process command line options to customize HTTP server */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-D") == 0 && i + 1 < argc) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) {
|
||||
s_http_server_opts.document_root = argv[++i];
|
||||
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
|
||||
s_http_port = argv[++i];
|
||||
} else if (strcmp(argv[i], "-a") == 0 && i + 1 < argc) {
|
||||
s_http_server_opts.auth_domain = argv[++i];
|
||||
} else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
|
||||
s_http_server_opts.global_auth_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
|
||||
s_http_server_opts.per_directory_auth_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
|
||||
s_http_server_opts.url_rewrites = argv[++i];
|
||||
#ifndef NS_DISABLE_CGI
|
||||
} else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
|
||||
s_http_server_opts.cgi_interpreter = argv[++i];
|
||||
#endif
|
||||
#ifdef NS_ENABLE_SSL
|
||||
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
|
||||
const char *ssl_cert = argv[++i];
|
||||
const char *err_str = mg_set_ssl(nc, ssl_cert, NULL);
|
||||
if (err_str != NULL) {
|
||||
fprintf(stderr, "Error loading SSL cert: %s\n", err_str);
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* Set HTTP server options */
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
if (nc == NULL) {
|
||||
fprintf(stderr, "Error starting server on port %s\n", s_http_port);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
s_http_server_opts.document_root = ".";
|
||||
s_http_server_opts.enable_directory_listing = "yes";
|
||||
|
||||
/* Use current binary directory as document root */
|
||||
if (argc > 0 && ((cp = strrchr(argv[0], '/')) != NULL ||
|
||||
(cp = strrchr(argv[0], '/')) != NULL)) {
|
||||
*cp = '\0';
|
||||
s_http_server_opts.document_root = argv[0];
|
||||
}
|
||||
|
||||
printf("Starting RESTful server on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
20
examples/restful_server_s3/Makefile
Normal file
20
examples/restful_server_s3/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
PROG = restful_server_s3
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA)
|
||||
|
||||
ifeq ($(SSL), openssl)
|
||||
CFLAGS += -DNS_ENABLE_SSL -lssl -lcrypto -lcrypto
|
||||
else ifeq ($(SSL), krypton)
|
||||
CFLAGS += -DNS_ENABLE_SSL ../../../krypton/krypton.c
|
||||
endif
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /MD /DNS_ENABLE_THREADS /Fe$@ advapi32.lib
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
31
examples/restful_server_s3/README.md
Normal file
31
examples/restful_server_s3/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
RESTful server with Amazon S3 upload example
|
||||
============================================
|
||||
|
||||
This example demonstrates how Mongoose could be used to implement a RESTful
|
||||
service that uses another RESTful service to handle it's own API call.
|
||||
This example takes form data and uploads it as a file to Amazon S3.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Amazon S3 account security credentials: Access Key ID and Secret Access
|
||||
Key ID. Get them from the Amazon IAM console.
|
||||
- Amazon S3 bucket.
|
||||
|
||||
## Building and running the example
|
||||
|
||||
$ git clone https://github.com/cesanta/mongoose.git
|
||||
$ cd mongoose/examples/restful_server_s3
|
||||
$ make
|
||||
$ ./restful_server_s3 -a ACCESS_KEY_ID -s SECRET_ACCESS_KEY_ID
|
||||
Starting RESTful server on port 8000
|
||||
|
||||
Then, open a browser on `http://localhost:8000`
|
||||
|
||||
Note: If you're getting a *Temporary Redirect* error, look what is the
|
||||
Endpoint value is. It's likely that you have something like
|
||||
`BUCKET_NAME.S3_ZONE.amazonaws.com`.
|
||||
Change the *Host* field to `S3_ZONE.amazonaws.com and retry`.
|
||||
|
||||
## Screenshot
|
||||
|
||||
![](https://docs.cesanta.com/images/mongoose_s3_example.png)
|
97
examples/restful_server_s3/index.html
Normal file
97
examples/restful_server_s3/index.html
Normal file
@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>RESTful API demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
* { outline: none; }
|
||||
body {
|
||||
background-color: #789; margin: 0;
|
||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
background-color: #fff; border-radius: 1em;
|
||||
}
|
||||
label { display: inline-block; min-width: 12em; }
|
||||
input { border: 1px solid #ccc; padding: 0.2em; min-width: 20em; }
|
||||
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||
#result {
|
||||
background: #ffc; min-height: 3em; border: 1px solid #ccc;
|
||||
white-space: pre-wrap; font-size: 85%;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body { background-color: #fff; }
|
||||
div.content { width: auto; margin: 0 auto; padding: 1em; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||
<script language="javascript" type="text/javascript">
|
||||
jQuery(function() {
|
||||
|
||||
$(document).on('click', '#upload', function() {
|
||||
var data = {};
|
||||
$('#upload_form [name]').each(function(index, el) {
|
||||
data[$(el).attr('name')] = $(el).val();
|
||||
});
|
||||
$('#result').text('');
|
||||
$.ajax({
|
||||
url: '/upload',
|
||||
method: 'POST',
|
||||
dataType: 'html',
|
||||
data: data,
|
||||
success: function(data) {
|
||||
$('#result').text(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>RESTful server with Amazon S3 integration demo.</h1>
|
||||
|
||||
<p>
|
||||
This page demonstrates how Mongoose could be used to implement
|
||||
a RESTful service that uses another RESTful service to handle
|
||||
it's own API call. This example takes form data and uploads it as a file
|
||||
to Amazon S3.
|
||||
Open S3 console and create a bucket for testing. Fill out correct
|
||||
bucket name in the fields below.
|
||||
</p>
|
||||
|
||||
<p>If you're getting a "Temporary Redirect" error, look what is the
|
||||
<b>Endpoint</b> value is. It's likely that you have something like
|
||||
<b>BUCKET_NAME.S3_ZONE.amazonaws.com</b>. Change
|
||||
the <b>Host</b> field to <b>S3_ZONE.amazonaws.com</b> and retry.
|
||||
</p>
|
||||
|
||||
<div id="upload_form">
|
||||
<div>
|
||||
<label>Bucket name:</label> <input type="text" name="bucket"
|
||||
value="my_uploads"/>
|
||||
</div><div>
|
||||
<label>File name:</label> <input type="text" name="file_name"
|
||||
value="myfile.txt"/>
|
||||
</div><div>
|
||||
<label>File content:</label> <input type="text" name="file_data"
|
||||
value=":-)"/>
|
||||
</div><div>
|
||||
<label>Host:</label> <input type="text" name="host"
|
||||
value="s3.amazonaws.com"/>
|
||||
</div><div>
|
||||
<label></label>
|
||||
<button id="upload">Upload</button>
|
||||
</div><div>
|
||||
<label>Result:</label> <pre id="result"> </pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
180
examples/restful_server_s3/restful_server_s3.c
Normal file
180
examples/restful_server_s3/restful_server_s3.c
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
static const char *s_access_key_id = NULL;
|
||||
static const char *s_secret_access_key = NULL;
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
|
||||
static void send_error_result(struct mg_connection *nc, const char *msg) {
|
||||
mg_printf_http_chunk(nc, "Error: %s", msg);
|
||||
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
|
||||
}
|
||||
|
||||
static void link_conns(struct mg_connection *nc1, struct mg_connection *nc2) {
|
||||
nc1->user_data = nc2;
|
||||
nc2->user_data = nc1;
|
||||
}
|
||||
|
||||
static void unlink_conns(struct mg_connection *nc1) {
|
||||
struct mg_connection *nc2 = (struct mg_connection *) nc1->user_data;
|
||||
if (nc1->user_data != NULL) {
|
||||
nc1->user_data = NULL;
|
||||
nc2->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* S3 client handler */
|
||||
static void s3_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
struct mg_connection *nc2 = (struct mg_connection *) nc->user_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REPLY:
|
||||
if (nc2 != NULL) {
|
||||
mg_printf_http_chunk(nc2, "Error: %.*s", (int)hm->message.len,
|
||||
hm->message.p);
|
||||
mg_send_http_chunk(nc2, "", 0);
|
||||
}
|
||||
unlink_conns(nc);
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
break;
|
||||
case NS_CLOSE:
|
||||
unlink_conns(nc);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void send_s3_request(struct mg_connection *nc, const char *file_name,
|
||||
const char *file_data, const char *host,
|
||||
const char *bucket) {
|
||||
char host_port[100];
|
||||
struct mg_connection *s3_conn;
|
||||
|
||||
snprintf(host_port, sizeof(host_port), "%s:80", host);
|
||||
s3_conn = mg_connect(nc->mgr, host_port, s3_handler);
|
||||
|
||||
if (s3_conn == NULL) {
|
||||
send_error_result(nc, "s3 connection failed");
|
||||
} else {
|
||||
const char *content_type = "text/plain", *method = "PUT";
|
||||
char date[100], to_sign[500], signature[100], sha1[20], req[1000];
|
||||
time_t now = time(NULL);
|
||||
|
||||
link_conns(nc, s3_conn);
|
||||
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
|
||||
mg_set_protocol_http_websocket(s3_conn);
|
||||
|
||||
/* Prepare S3 authorization header */
|
||||
snprintf(to_sign, sizeof(to_sign), "%s\n\n%s\n%s\n/%s/%s", method,
|
||||
content_type, date, bucket, file_name);
|
||||
hmac_sha1((unsigned char *)s_secret_access_key, strlen(s_secret_access_key),
|
||||
(unsigned char *)to_sign, strlen(to_sign), (unsigned char *)sha1);
|
||||
mg_base64_encode((unsigned char *) sha1, sizeof(sha1), signature);
|
||||
snprintf(req, sizeof(req),
|
||||
"%s /%s HTTP/1.1\r\n"
|
||||
"Host: %s.%s\r\n"
|
||||
"Date: %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %lu\r\n"
|
||||
"Authorization: AWS %s:%s\r\n"
|
||||
"\r\n",
|
||||
method, file_name, bucket, host, date, content_type,
|
||||
(unsigned long)strlen(file_data), s_access_key_id, signature);
|
||||
mg_printf(s3_conn, "%s%s", req, file_data);
|
||||
/* S3 request sent, wait for a reply */
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_api_call(struct mg_connection *nc, struct http_message *hm) {
|
||||
char file_name[100], file_data[100], host[100], bucket[100];
|
||||
|
||||
/* Get form variables */
|
||||
mg_get_http_var(&hm->body, "file_name", file_name, sizeof(file_name));
|
||||
mg_get_http_var(&hm->body, "file_data", file_data, sizeof(file_data));
|
||||
mg_get_http_var(&hm->body, "host", host, sizeof(host));
|
||||
mg_get_http_var(&hm->body, "bucket", bucket, sizeof(bucket));
|
||||
|
||||
/* Send headers */
|
||||
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
|
||||
/* Send body */
|
||||
if (file_name[0] == '\0' || file_data[0] == '\0' || bucket[0] == '\0') {
|
||||
send_error_result(nc, "bad input");
|
||||
} else {
|
||||
send_s3_request(nc, file_name, file_data, host, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
/* Server handler */
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/upload") == 0) {
|
||||
handle_api_call(nc, hm); /* Handle RESTful call */
|
||||
} else {
|
||||
mg_serve_http(nc, hm, s_http_server_opts); /* Serve static content */
|
||||
}
|
||||
break;
|
||||
case NS_CLOSE:
|
||||
unlink_conns(nc);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
int i;
|
||||
char *cp;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
s_http_server_opts.document_root = ".";
|
||||
s_http_server_opts.enable_directory_listing = "yes";
|
||||
|
||||
/* Use current binary directory as document root */
|
||||
if (argc > 0 && ((cp = strrchr(argv[0], '/')) != NULL ||
|
||||
(cp = strrchr(argv[0], '/')) != NULL)) {
|
||||
*cp = '\0';
|
||||
s_http_server_opts.document_root = argv[0];
|
||||
}
|
||||
|
||||
/* Process command line options to customize HTTP server */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-D") == 0 && i + 1 < argc) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
|
||||
s_http_port = argv[++i];
|
||||
} else if (strcmp(argv[i], "-a") == 0 && i + 1 < argc) {
|
||||
s_access_key_id = argv[++i];
|
||||
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
|
||||
s_secret_access_key = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
if (s_access_key_id == NULL || s_secret_access_key == NULL) {
|
||||
fprintf(stderr, "Usage: %s -a access_key_id -s s_secret_access_key "
|
||||
"[-p port] [-D hexdump_file]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Starting RESTful server on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
13
examples/rules.mk
Normal file
13
examples/rules.mk
Normal file
@ -0,0 +1,13 @@
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread -DNS_ENABLE_SSL -DNS_ENABLE_IPV6 -DNS_ENABLE_THREADS -lssl -lcrypto $(CFLAGS_EXTRA) $(MODULE_CFLAGS)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /DNS_ENABLE_SSL /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
@ -1,21 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = send_file
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
run: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
$(PROG): $(SOURCES) Makefile
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
win:
|
||||
wine cl $(SOURCES) /MD /nologo /DNDEBUG /O1 /I../.. /Fe$(PROG).exe
|
||||
wine $(PROG).exe
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib *.gc*
|
@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2014 Cesanta Software
|
||||
// All rights reserved
|
||||
//
|
||||
// This example demostrates how to send arbitrary files to the client.
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_REQUEST:
|
||||
mg_send_file(conn, "send_file.c", NULL); // Also could be a dir, or CGI
|
||||
return MG_MORE; // It is important to return MG_MORE after mg_send_file!
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) mg_poll_server(server, 1000);
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2015 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = server_data_push
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 -DMONGOOSE_ENABLE_THREADS $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
*
|
||||
* In this example we have a background data producer that periodically
|
||||
* produces a piece of data, which is then distributed to all connected
|
||||
* WebSocket clients.
|
||||
*
|
||||
* Data producing thread is outside of the event loop so synchronization
|
||||
* using a mutex is required.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static pthread_mutex_t s_data_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int s_data_version = 0;
|
||||
static char s_data[100];
|
||||
|
||||
void *data_producer(void *arg) {
|
||||
(void) arg;
|
||||
fprintf(stderr, "Data producer running\n");
|
||||
srand(time(NULL));
|
||||
while (1) {
|
||||
pthread_mutex_lock(&s_data_lock);
|
||||
snprintf(s_data, sizeof(s_data), "The lucky number is %d.", rand() % 100);
|
||||
s_data_version++;
|
||||
pthread_mutex_unlock(&s_data_lock);
|
||||
sleep(1 + rand() % 10);
|
||||
}
|
||||
}
|
||||
|
||||
struct conn_state {
|
||||
int data_version;
|
||||
/* More state goes here. */
|
||||
};
|
||||
|
||||
void maybe_send_data(struct mg_connection *conn) {
|
||||
struct conn_state *cs = (struct conn_state *) conn->connection_param;
|
||||
if (cs == NULL) return; /* Not connected yet. */
|
||||
pthread_mutex_lock(&s_data_lock);
|
||||
if (cs->data_version != s_data_version) {
|
||||
mg_websocket_printf(conn, WEBSOCKET_OPCODE_TEXT, "%s\n", s_data);
|
||||
cs->data_version = s_data_version;
|
||||
}
|
||||
pthread_mutex_unlock(&s_data_lock);
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH:
|
||||
return MG_TRUE; /* Authenticated. */
|
||||
case MG_WS_HANDSHAKE:
|
||||
return MG_FALSE; /* Let Mongoose complete the handshake. */
|
||||
case MG_WS_CONNECT:
|
||||
fprintf(stderr, "%s:%u joined\n", conn->remote_ip, conn->remote_port);
|
||||
conn->connection_param = calloc(1, sizeof(struct conn_state));
|
||||
mg_websocket_printf(conn, WEBSOCKET_OPCODE_TEXT, "Hi %p!\n", conn);
|
||||
maybe_send_data(conn);
|
||||
return MG_FALSE; /* Keep the connection open. */
|
||||
case MG_POLL:
|
||||
maybe_send_data(conn);
|
||||
return MG_FALSE; /* Keep the connection open. */
|
||||
case MG_CLOSE:
|
||||
fprintf(stderr, "%s:%u went away\n", conn->remote_ip, conn->remote_port);
|
||||
free(conn->connection_param);
|
||||
conn->connection_param = NULL;
|
||||
return MG_TRUE;
|
||||
default:
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const char *listen_port = "8080";
|
||||
struct mg_server *server;
|
||||
const char *err;
|
||||
|
||||
server = mg_create_server(NULL, ev_handler);
|
||||
err = mg_set_option(server, "listening_port", listen_port);
|
||||
if (err != NULL) {
|
||||
fprintf(stderr, "Error setting up listener on %s: %s\n", listen_port, err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
mg_start_thread(data_producer, NULL);
|
||||
|
||||
printf("Listening on %s\n", listen_port);
|
||||
while (1) {
|
||||
mg_poll_server(server, 100);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
11
examples/settings_panel_for_a_device/Makefile
Normal file
11
examples/settings_panel_for_a_device/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
PROG = settings_panel
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -DNS_ENABLE_SSL -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
81
examples/settings_panel_for_a_device/settings_panel.c
Normal file
81
examples/settings_panel_for_a_device/settings_panel.c
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
struct device_settings {
|
||||
char setting1[100];
|
||||
char setting2[100];
|
||||
};
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
static struct device_settings s_settings = { "value1", "value2" };
|
||||
|
||||
static void handle_save(struct mg_connection *nc, struct http_message *hm) {
|
||||
/* Get form variables and store settings values */
|
||||
mg_get_http_var(&hm->body, "setting1", s_settings.setting1,
|
||||
sizeof(s_settings.setting1));
|
||||
mg_get_http_var(&hm->body, "setting2", s_settings.setting2,
|
||||
sizeof(s_settings.setting2));
|
||||
|
||||
/* Send response */
|
||||
mg_printf(nc, "HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n%.*s",
|
||||
(unsigned long) hm->body.len, (int) hm->body.len, hm->body.p);
|
||||
}
|
||||
|
||||
static void handle_ssi_call(struct mg_connection *nc, const char *param) {
|
||||
if (strcmp(param, "setting1") == 0) {
|
||||
mg_printf_html_escape(nc, "%s", s_settings.setting1);
|
||||
} else if (strcmp(param, "setting2") == 0) {
|
||||
mg_printf_html_escape(nc, "%s", s_settings.setting2);
|
||||
}
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/save") == 0) {
|
||||
handle_save(nc, hm); /* Handle RESTful call */
|
||||
} else {
|
||||
mg_serve_http(nc, hm, s_http_server_opts); /* Serve static content */
|
||||
}
|
||||
break;
|
||||
case NS_SSI_CALL:
|
||||
handle_ssi_call(nc, ev_data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
char *p, path[512];
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
s_http_server_opts.document_root = "./web_root";
|
||||
s_http_server_opts.auth_domain = "example.com";
|
||||
//mgr.hexdump_file = "/dev/stdout";
|
||||
|
||||
/* If our current directory */
|
||||
if (argc > 0 && (p = strrchr(argv[0], '/'))) {
|
||||
snprintf(path, sizeof(path), "%.*s/web_root", (int)(p - argv[0]), argv[0]);
|
||||
s_http_server_opts.document_root = path;
|
||||
}
|
||||
|
||||
printf("Starting device configurator on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
BIN
examples/settings_panel_for_a_device/web_root/fossa.jpg
Normal file
BIN
examples/settings_panel_for_a_device/web_root/fossa.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
104
examples/settings_panel_for_a_device/web_root/index.shtml
Normal file
104
examples/settings_panel_for_a_device/web_root/index.shtml
Normal file
@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>RESTful API demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
* { outline: none; }
|
||||
body {
|
||||
background-color: #789; margin: 0;
|
||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
background-color: #fff; border-radius: 1em;
|
||||
}
|
||||
code { background: #eee; padding: 0 0.3em; border-radius: 0.2em; }
|
||||
label { display: inline-block; min-width: 5em; }
|
||||
input { border: 1px solid #ccc; padding: 0.2em; margin-right: 2em; }
|
||||
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||
@media (max-width: 900px) {
|
||||
div.content { width: auto; margin: 2em; padding: 1em; }
|
||||
}
|
||||
fieldset { border: 1px solid #ccc; padding: 1em; }
|
||||
#result {
|
||||
background: #cfc; border: 1px solid #ccc; padding: 2px 1em;
|
||||
white-space: pre-wrap; font-size: 85%; display: none; text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="jquery-1.11.3.min.js"></script>
|
||||
<script language="javascript" type="text/javascript">
|
||||
jQuery(function() {
|
||||
|
||||
$(document).on('submit', '#settings_form', function() {
|
||||
var data = {};
|
||||
$('#settings_form [name]').each(function(index, el) {
|
||||
data[$(el).attr('name')] = $(el).val();
|
||||
});
|
||||
$('#result').text('');
|
||||
$.ajax({
|
||||
url: '/save',
|
||||
method: 'POST',
|
||||
dataType: 'html',
|
||||
data: data,
|
||||
success: function(data) {
|
||||
$('#result').text('saved').show().fadeOut(2000);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<img src="mongoose.jpg" style="float:right; height: 50px; border-radius: 3px;">
|
||||
<h1>Device Configurator demo.</h1>
|
||||
|
||||
<p>
|
||||
This page demonstrates how Mongoose could be used to implement
|
||||
device settings page.</p>
|
||||
|
||||
<h3>How to show device parameters on the page</h3>
|
||||
<p>This page has embedded
|
||||
<code><!--#call parameter_name --></code> blocks. For each such
|
||||
block, mongoose triggers <code>NS_SSI_CALL</code> event, passing
|
||||
<code>parameter_name</code> string as an event parameter. A callback
|
||||
then can print some content, which will replace the
|
||||
<code><!--#call parameter_name --></code> block.
|
||||
Take a look at <code>handle_ssi_call()</code> to see how that is done.
|
||||
</p>
|
||||
|
||||
<h3>How to save updated values</h3>
|
||||
<p>When Save button is clicked, this page makes Ajax call and passes
|
||||
values to the backend in a POST request. Backend extracts values from
|
||||
the POST request and updates the configuration. Take a look at
|
||||
<code>handle_save()</code> to see how that is done.</p>
|
||||
|
||||
|
||||
<p>You can change values, click Save button, refresh this page - make sure
|
||||
settings values stay intact between refreshes. </p>
|
||||
|
||||
<form method="POST" id="settings_form">
|
||||
<fieldset>
|
||||
<legend>Device settings</legend>
|
||||
<label>Setting 1:</label> <input type="text"
|
||||
name="setting1" value="<!--#call setting1 -->" >
|
||||
<label>Setting 2:</label> <input type="text"
|
||||
name="setting2" value="<!--#call setting2 -->" >
|
||||
</fieldset>
|
||||
|
||||
<div style="margin: 1em 0;">
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
<div id="result"> </div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
5
examples/settings_panel_for_a_device/web_root/jquery-1.11.3.min.js
vendored
Normal file
5
examples/settings_panel_for_a_device/web_root/jquery-1.11.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user