mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a66ff5278 | ||
|
|
e1dd3f06fe | ||
|
|
32ab35f61f | ||
|
|
8ab96ec74b | ||
|
|
0730590e35 | ||
|
|
f7e0b1331c | ||
|
|
17aa2a7092 | ||
|
|
7d38d05ca7 | ||
|
|
33bad047f0 | ||
|
|
5234b73c29 | ||
|
|
c566e2eba5 | ||
|
|
e565977481 | ||
|
|
8927c9d22b | ||
|
|
28eb251ccd | ||
|
|
b99dc95b1d | ||
|
|
75aadb3e79 | ||
|
|
b6ecd5fb4c | ||
|
|
1e7f55ed84 | ||
|
|
eeadc5ecff | ||
|
|
568ca42988 | ||
|
|
7b758bfc9d | ||
|
|
8ef454b75f | ||
|
|
0a2f2fda5c | ||
|
|
6b440918ec | ||
|
|
a73fce945c | ||
|
|
d349bb3cc7 | ||
|
|
7c4119a287 | ||
|
|
8a71ad99e6 | ||
|
|
41b405d7bf | ||
|
|
30b80dc5ee | ||
|
|
524aa2e586 | ||
|
|
990a1b9ae0 | ||
|
|
4cb0c6864e | ||
|
|
5183693951 | ||
|
|
e47506f957 | ||
|
|
824e4dc0e4 | ||
|
|
d033497714 | ||
|
|
d8028df29d | ||
|
|
564683d25a | ||
|
|
30c0a3f201 | ||
|
|
08fe9ac518 | ||
|
|
14d6f71f82 | ||
|
|
29454e2296 | ||
|
|
d6cbf9f77d | ||
|
|
b9fead4d6e | ||
|
|
c6a04e1719 | ||
|
|
c839a61fea | ||
|
|
c06abbc5f7 | ||
|
|
c8e88e1710 | ||
|
|
bf7a115bf2 | ||
|
|
b8dfb69cf5 | ||
|
|
1cd9715095 | ||
|
|
5c7aeac148 | ||
|
|
09a80628e6 | ||
|
|
2ff2c369f7 | ||
|
|
eef70aaf74 | ||
|
|
89ac97caba | ||
|
|
c00b236c8d | ||
|
|
eacd3f35e0 | ||
|
|
8a2aa72571 | ||
|
|
7da8f273d2 | ||
|
|
a90a5a2b6a | ||
|
|
b5b819b4a3 | ||
|
|
821e2b3a75 | ||
|
|
773efb0745 | ||
|
|
ed1904a75d | ||
|
|
a66663f0f6 | ||
|
|
1104688b02 | ||
|
|
36d95e230e | ||
|
|
8a27a95a2a | ||
|
|
2e63b5a5e5 | ||
|
|
4cbd113d01 | ||
|
|
ba5ae74144 | ||
|
|
f2b40e5fae | ||
|
|
8ed682930b | ||
|
|
86ad7922b0 | ||
|
|
f7476f53c7 | ||
|
|
5d6f7ac8ef | ||
|
|
f16fa5ed55 | ||
|
|
c0ba32f7a6 | ||
|
|
409c12a851 | ||
|
|
bcdcbf20ee | ||
|
|
8fc5b5a55f | ||
|
|
7cf28a5623 | ||
|
|
6668c456ac | ||
|
|
98f2476751 | ||
|
|
faeedb2930 | ||
|
|
c8e8f94ffe | ||
|
|
22aeb6111b | ||
|
|
2d1d2e6534 | ||
|
|
05c88a98fc | ||
|
|
5224afb946 | ||
|
|
3bd79c7cd5 | ||
|
|
c52e0744cb | ||
|
|
6c09c303d0 | ||
|
|
b6fde06990 | ||
|
|
af892c9726 | ||
|
|
df7ad13680 | ||
|
|
057c2d4b42 | ||
|
|
6c9b43bcf5 | ||
|
|
a91438acae | ||
|
|
a6fa51dbc2 | ||
|
|
6ceac6c84c | ||
|
|
288efe3c23 | ||
|
|
5207545a8b | ||
|
|
716d524f9b | ||
|
|
a434bab767 | ||
|
|
e9633a8d2d | ||
|
|
c7b6a6f0ea | ||
|
|
6cdb9f7ed3 | ||
|
|
4502dd0c86 | ||
|
|
95055f6048 | ||
|
|
2c36532ec4 | ||
|
|
f2b0b9c737 | ||
|
|
b3866439a0 | ||
|
|
4f2205a667 | ||
|
|
6cec1d36a8 | ||
|
|
4238370ae1 | ||
|
|
b4924b715e | ||
|
|
7a0385dcac | ||
|
|
44a146faf6 | ||
|
|
53a55da7bc | ||
|
|
bf44277764 | ||
|
|
a2e1da6e30 | ||
|
|
bef3f85ba8 | ||
|
|
eb72a63cca | ||
|
|
f19bbc73cc | ||
|
|
25326b3b12 | ||
|
|
10ee430bfd | ||
|
|
10d0bfdab4 | ||
|
|
6b6637c337 | ||
|
|
878acca213 | ||
|
|
ace9285966 | ||
|
|
cdc167b0cc | ||
|
|
a0dd396b95 | ||
|
|
8082501b9c | ||
|
|
e4ad3010be | ||
|
|
03a82ef848 | ||
|
|
9459d69dcc | ||
|
|
b28662362d | ||
|
|
66450bf3d1 | ||
|
|
478ca05e7a | ||
|
|
8cad0a72f9 | ||
|
|
31178f4d48 | ||
|
|
7a5485493e | ||
|
|
d3cbb160f5 | ||
|
|
45ff2c2c19 | ||
|
|
c3db7b141b | ||
|
|
0a0ea36ace | ||
|
|
72ed954721 | ||
|
|
33e035273a | ||
|
|
223f36c130 | ||
|
|
4c0bc5bd03 | ||
|
|
808a5f6f53 | ||
|
|
404e635665 | ||
|
|
9d7a60a63f | ||
|
|
ba9c74187b | ||
|
|
e9d4847cbb | ||
|
|
ea76de07ee | ||
|
|
5a8c468ff7 | ||
|
|
5b143a6237 | ||
|
|
d244a7fdfd | ||
|
|
300a27de99 | ||
|
|
04a15a0c10 | ||
|
|
f0f6ae05aa | ||
|
|
8a7d535d92 | ||
|
|
e7ac6024de | ||
|
|
9981b99ac8 | ||
|
|
87a36c0c30 | ||
|
|
f2b13761c0 | ||
|
|
9786478a31 | ||
|
|
7d236d7f33 | ||
|
|
e098a7292d | ||
|
|
632b3a38a9 |
No files matched your search
-20
@@ -1,20 +0,0 @@
|
|||||||
language: cpp
|
|
||||||
|
|
||||||
env:
|
|
||||||
matrix:
|
|
||||||
- MONGOOSE_NO_SOCKETPAIR
|
|
||||||
- MONGOOSE_NO_FILESYSTEM
|
|
||||||
- MONGOOSE_NO_AUTH
|
|
||||||
- MONGOOSE_NO_DAV
|
|
||||||
- MONGOOSE_NO_DIRECTORY_LISTING
|
|
||||||
- MONGOOSE_NO_LOGGING
|
|
||||||
- MONGOOSE_NO_SSI
|
|
||||||
- MONGOOSE_NO_DL
|
|
||||||
- MONGOOSE_NO_WEBSOCKET
|
|
||||||
|
|
||||||
compiler:
|
|
||||||
- gcc
|
|
||||||
- clang
|
|
||||||
|
|
||||||
script:
|
|
||||||
- cd test && make
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||||
Copyright (c) 2013 Cesanta Software Limited
|
Copyright (c) 2013-2015 Cesanta Software Limited
|
||||||
All rights reserved
|
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
|
it under the terms of the GNU General Public License version 2 as
|
||||||
published by the Free Software Foundation. For the terms of this
|
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
|
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
See the GNU General Public License for more details.
|
See the GNU General Public License for more details.
|
||||||
|
|
||||||
Alternatively, you can license this code under a commercial
|
Alternatively, you can license this software under a commercial
|
||||||
license, as set out in <http://cesanta.com/>.
|
license, as set out in <https://www.cesanta.com/license>.
|
||||||
@@ -1,86 +1,101 @@
|
|||||||
# <img src="http://cesanta.com/images/mongoose_logo.png" width="64" height="64"> Mongoose Web Server
|
# <img src="http://www.cesanta.com/hubfs/www.cesanta.com/Images/mongoose_library.png" width="64" height="64"> Mongoose - Embedded Web Server
|
||||||
|
|
||||||
Mongoose is the most easy to use web server on the planet. A web server of choice for Web developers (PHP, Ruby, Python, etc) and Web designers.
|
[](https://gitter.im/cesanta/mongoose?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
Mongoose is built on top of Libmongoose embedded library, which can turn
|

|
||||||
anything into a web server in 5 minutes worth of effort and few lines of code.
|
|
||||||
Libmongoose 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 in various different industries including aerospace,
|
|
||||||
manufacturing, finance, research, automotive, gaming, IT.
|
|
||||||
|
|
||||||
|
|
||||||
* [Mailing list](http://groups.google.com/group/mongoose-users)
|
Mongoose is ideal for embedded environments, it has been designed as an open
|
||||||
* [Downloads](http://cesanta.com/mongoose.shtml)
|
source platform for connecting devices and bringing them online.
|
||||||
* [Documentation](http://cesanta.com/docs.shtml)
|
[Mongoose](https://www.cesanta.com/products)
|
||||||
|
has been on the market since 2004 and attracted
|
||||||
|
over 1 million cumulative downloads.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
[Download Mongoose Source Code here](http://hubs.ly/H0150FK0)
|
||||||
|
|
||||||
|
> Nothing overdone. Nothing less. So unbelievably easy to use.
|
||||||
|
> Just how good software should be!
|
||||||
|
> - Pritin Tyagaraj, SAP
|
||||||
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Works on Windows, Mac, UNIX/Linux, iPhone, Android eCos, QNX
|
* Cross-platform: works on Linux/UNIX, QNX, eCos, Windows, Android, iPhone, etc
|
||||||
and many other platforms
|
* Single-threaded, asynchronous, non-blocking core with simple event-based API
|
||||||
- CGI, SSI, SSL, Digest auth, Websocket, WEbDAV, Resumed download,
|
* Builtin protocols:
|
||||||
URL rewrite, file blacklist
|
- plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
|
||||||
- Custom error pages, Virtual hosts, IP-based ACL, Windows service,
|
- HTTP client, HTTP server
|
||||||
HTTP/HTTPS client
|
- Websocket client, Websocket server
|
||||||
- Simple and clean
|
- JSON-RPC client, JSON-RPC server
|
||||||
[embedding API](https://github.com/cesanta/mongoose/blob/master/mongoose.h).
|
- MQTT client, MQTT broker
|
||||||
The source is in single
|
- CoAP client, CoAP server
|
||||||
[mongoose.c](https://github.com/cesanta/mongoose/blob/master/mongoose.c) file
|
- DNS client, DNS server, async DNS resolver
|
||||||
to make embedding easy
|
* Tiny static and run-time footprint
|
||||||
- Extremely lightweight, has a core of under 40kB and tiny runtime footprint
|
* Source code is both ISO C and ISO C++ compliant
|
||||||
- Asynchronous, non-blocking core supporting single- or multi-threaded usage
|
* Very easy to integrate: just copy
|
||||||
- On the market since 2004 with over 1 million cumulative downloads
|
[mongoose.c](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c) and
|
||||||
- Stable, mature and tested, has several man-years invested
|
[mongoose.h](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.h)
|
||||||
in continuous improvement and refinement
|
files to your build tree
|
||||||
|
* Extensively tested and production-ready, trusted by many blue chip businesses
|
||||||
|
|
||||||
# Screenshots
|
# Examples & Documentation
|
||||||
|
|
||||||
Download, double-click to start, run browser -- that's all!
|
- [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
|
||||||

|
|
||||||
|
|
||||||

|
Mongoose is often used to implement device dashboards and real-time
|
||||||

|
data exchange over Websocket. Here is a dashboard example that illustrates
|
||||||
|
the functionality:
|
||||||
|
|
||||||
# Acknowledgements
|

|
||||||
|
|
||||||
Mongoose made better thanks to the contribution of following people:
|
[Developing a new product? Contact us today to discuss how Mongoose can help
|
||||||
|
](https://www.cesanta.com/contact)
|
||||||
|
|
||||||
Arnout Vandecappelle, Benoît Amiaux, Boris Pek, Cody Hanson, Colin Leitner,
|
|
||||||
Daniel Oaks, Eric Bakan, Erik Oomen, Filipp Kovalev, Ger Hobbelt,
|
|
||||||
Hendrik Polczynski, Igor Okulist, Jay, Joe Mucchiello, John Safranek,
|
|
||||||
José Miguel Gonçalves, Shueng Chuan, Katerina Blinova, Konstantin Sorokin,
|
|
||||||
Marin Atanasov, Matt Healy, Mitch Hendrickson, Nigel Stewart, Pavel Khlebovich,
|
|
||||||
Sebastian Reinhard, Stefan Doehla, abadc0de, nullable.type,
|
|
||||||
T.Barmann, D.Hughes, J.C.Sloan, R.Romeo, L.E.Spencer, S.Kotay, R.M.Shorter,
|
|
||||||
W.Mar, J.Wilander, Santa from Memphis, S.Davies, C.Beck,
|
|
||||||
O.M.Vilhunen, C.Radik, G.Woodcock, M.Szczepkowski,
|
|
||||||
Eternal Lands Dev Team, T.Tollet, C.Tangerino, G.Karsai, A.Bourgett,
|
|
||||||
C.Blakemore, D.Fonaryov, T.Andrle, O.IJsselmuiden, R.Womack, M.Tomlinson,
|
|
||||||
A.Slåttå, L.Farrell, J.D.P.Ballestero, V.Albaev, B.Harker, T.Scheffel, H.Klein,
|
|
||||||
R.Merit, T.Bennett, H.Solis, A.Zincenko, M.S., S.Krul, K.Cooke, S.McCallum,
|
|
||||||
F.Morenius, and 10 others.
|
|
||||||
|
|
||||||
# Licensing
|
# Licensing
|
||||||
|
|
||||||
Mongoose is released under commercial and
|
You are using Mongoose under
|
||||||
[GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) open
|
[GPLv2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
source licenses. The GPLv2 open source License does not generally permit
|
and until you commercialize your
|
||||||
incorporating this software into non-open source programs.
|
application, can continue to use Mongoose free of charge.
|
||||||
For those customers who do not wish to comply with the GPLv2 open
|
|
||||||
source license requirements,
|
|
||||||
[Cesanta Software](http://cesanta.com) offers a full,
|
|
||||||
royalty-free commercial license and professional support
|
|
||||||
without any of the GPL restrictions.
|
|
||||||
|
|
||||||
# Other products by Cesanta Software: simple and effective
|
Once you commercialize your product, in order to comply with GPLv2
|
||||||
|
you need to either open the source code of your product fully or
|
||||||
|
alternatively purchase a commercial license.
|
||||||
|
We offer [three levels of commercial licensing](https://www.cesanta.com/products):
|
||||||
|
|
||||||
- [SSL Wrapper](https://github.com/cesanta/ssl_wrapper) - application to
|
- Mongoose Lite: cross platform integration, SSL/TLS, HTTP support
|
||||||
secure network communications
|
- Mongoose: Mongoose Lite + HTTP CGI, HTTP SSI, HTTP Digest Auth,
|
||||||
|
WebSocket support
|
||||||
|
- Mongoose Premium: Mongoose + Plain TCP, UDP, JSON-RPC, MQTT, DNS,
|
||||||
|
CoAP support
|
||||||
|
|
||||||
|
|
||||||
|
[Enquire about commercial licensing here](https://www.cesanta.com/contact)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
- [V7](https://github.com/cesanta/v7) - Embedded JavaScript engine
|
||||||
- [Frozen](https://github.com/cesanta/frozen) - JSON parser and generator
|
- [Frozen](https://github.com/cesanta/frozen) - JSON parser and generator
|
||||||
- [SLRE](https://github.com/cesanta/slre) - Super Light Regular Expression
|
- [SLRE](https://github.com/cesanta/slre) - Super Light Regular Expression
|
||||||
library
|
library
|
||||||
- [Net Skeleton](https://github.com/cesanta/net_skeleton) - framework for
|
|
||||||
building network applications
|
|
||||||
- [SLDR](https://github.com/cesanta/sldr) - Super Light DNS Resolver
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
LOCAL_PATH := $(call my-dir)/../..
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
|
|
||||||
# To build with lua support, uncomment two lines below:
|
|
||||||
#LUA_SOURCES := build/lua_5.2.1.c build/sqlite3.c build/lsqlite3.c
|
|
||||||
#LUA_FLAGS := -I$(LOCAL_PATH)/build -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DUSE_LUA -DUSE_LUA_SQLITE3 -DLUA_COMPAT_ALL -D"getlocaledecpoint() ='.'"
|
|
||||||
|
|
||||||
LOCAL_CFLAGS := -std=c99 -O2 -W -Wall -pthread -pipe $(LUA_FLAGS) $(COPT)
|
|
||||||
LOCAL_MODULE := mongoose
|
|
||||||
LOCAL_SRC_FILES := examples/server.c mongoose.c $(LUA_SOURCES)
|
|
||||||
|
|
||||||
include $(BUILD_EXECUTABLE)
|
|
||||||
-246
@@ -1,246 +0,0 @@
|
|||||||
# Mongoose API Reference
|
|
||||||
|
|
||||||
struct mg_server *mg_create_server(void *server_param);
|
|
||||||
|
|
||||||
Creates web server instance. Returns opaque instance pointer, or NULL if
|
|
||||||
there is not enough memory. `server_param`: Could be any pointer, or NULL.
|
|
||||||
This pointer will be passed
|
|
||||||
to the callback functions as `struct mg_connection::server_param` field.
|
|
||||||
A common use case is to pass `this` pointer of the C++ wrapper class
|
|
||||||
as `user_param`, to let the callback get the pointer to the C++ object.
|
|
||||||
|
|
||||||
Note that this function doesn't make the
|
|
||||||
server instance to serve. Serving is done by `mg_poll_server()` function.
|
|
||||||
Mongoose has single-threaded, event-driven, asynchronous, non-blocking core.
|
|
||||||
When server instance is created, it contains an information about
|
|
||||||
the configuration and the state of each connection.
|
|
||||||
Server instance is capable on listening on only one port. After creation,
|
|
||||||
`struct mg_server` has a list
|
|
||||||
of active connections and configuration parameters.
|
|
||||||
|
|
||||||
Side-effect: on UNIX, `mg_create_server()` ignores SIGPIPE signals. If custom
|
|
||||||
processing is required SIGPIPE, signal handler must be set up after
|
|
||||||
calling `mg_create_server()`.
|
|
||||||
|
|
||||||
Important: Mongoose does not install `SIGCHLD` handler. If CGI is used,
|
|
||||||
`SIGCHLD` handler must be set up to reap CGI zombie processes.
|
|
||||||
|
|
||||||
|
|
||||||
void mg_destroy_server(struct mg_server **server);
|
|
||||||
|
|
||||||
Deallocates web server instance, closes all pending connections, and makes
|
|
||||||
server pointer a NULL pointer.
|
|
||||||
|
|
||||||
const char mg_set_option(struct mg_server *server, const char *name,
|
|
||||||
const char *value);
|
|
||||||
|
|
||||||
Sets a particular server option. Note that at least one option,
|
|
||||||
`listening_port`, must be specified. To serve static files, `document_root`
|
|
||||||
must be specified too. If `document_root` option is left unset, Mongoose
|
|
||||||
will not access filesystem at all. `mg_set_option()` returns NULL if option was
|
|
||||||
set successfully, otherwise it returns human-readable error string. It is
|
|
||||||
allowed to call `mg_set_option()` by the same thread that does
|
|
||||||
`mg_poll_server()` (Mongoose thread) and change server configuration while it
|
|
||||||
is serving, in between `mg_poll_server()` calls.
|
|
||||||
|
|
||||||
int mg_poll_server(struct mg_server *server, int milliseconds);
|
|
||||||
|
|
||||||
Performs one iteration of IO loop by iterating over all
|
|
||||||
active connections, performing `select()` syscall on all sockets with a timeout
|
|
||||||
of `milliseconds`. When `select()` returns, Mongoose
|
|
||||||
does an IO for each socket that has data to be sent or received. Application
|
|
||||||
code must call `mg_poll_server()` in a loop. It is an error to have more then
|
|
||||||
one thread calling `mg_poll_server()`, `mg_set_option()` or any other function
|
|
||||||
that take `struct mg_server *` parameter. Mongoose does not
|
|
||||||
mutex-protect `struct mg_server *`, therefore only single thread
|
|
||||||
(Mongoose thread) should make Mongoose calls.
|
|
||||||
|
|
||||||
`mg_poll_server()` calls user-specified event handler when certain events
|
|
||||||
occur. Sequence of events for the accepted connection is this:
|
|
||||||
|
|
||||||
* `MG_AUTH` - Mongoose asks whether this connection is authorized. If event
|
|
||||||
handler returns `MG_FALSE`, then Mongoose does not serve the request but
|
|
||||||
sends authorization request to the client. If `MG_TRUE` is returned,
|
|
||||||
then Mongoose continues on with the request.
|
|
||||||
* `MG_REQUEST` - Mongoose asks event handler to serve the request. If
|
|
||||||
event handler serves the request by sending a reply,
|
|
||||||
it should return `MG_TRUE`. Otherwise,
|
|
||||||
it should return `MG_FALSE` which tells Mongoose that request is not
|
|
||||||
served and Mongoose should serve it. For example, event handler might
|
|
||||||
choose to serve only RESTful API requests with URIs that start with
|
|
||||||
certain prefix, and let Mongoose serve all static files.
|
|
||||||
If event handler decides to serve the request, but doesn't have
|
|
||||||
all the data at the moment, it should return `MG_MORE`. That tells
|
|
||||||
Mongoose to keep the connection open after callback returns.
|
|
||||||
|
|
||||||
`mg_connection::connection_param` pointer is a placeholder to keep
|
|
||||||
user-specific data. For example, handler could decide to open a DB
|
|
||||||
connection and store DB connection handle in `connection_param`.
|
|
||||||
* `MG_POLL` is sent to every connection on every iteration of
|
|
||||||
`mg_poll_server()`. Event handler should return `MG_FALSE` to ignore
|
|
||||||
this event. If event handler returns `MG_TRUE`, then Mongoose assumes
|
|
||||||
that event handler has finished sending data, and Mongoose will
|
|
||||||
close the connection.
|
|
||||||
* `MG_HTTP_ERROR` sent when Mongoose is about to send HTTP error back
|
|
||||||
to the client. Event handler can choose to send a reply itself, in which
|
|
||||||
case event handler must return `MG_TRUE`. Otherwise, event handler must
|
|
||||||
return `MG_FALSE`.
|
|
||||||
* `MG_CLOSE` is sent when the connection is closed. This event is used
|
|
||||||
to cleanup per-connection state stored in `connection_param`
|
|
||||||
if it was allocated. Event handler return value is ignored.
|
|
||||||
|
|
||||||
Sequence of events for the client connection is this:
|
|
||||||
|
|
||||||
* `MG_CONNECT` sent when Mongoose has connected to the remote host.
|
|
||||||
This event is sent to the connection initiated by `mg_connect()` call.
|
|
||||||
Connection status is held in `mg_connection::status_code`: if zero,
|
|
||||||
then connection was successful, otherwise connection was not established.
|
|
||||||
User should send a request upon successful connection.
|
|
||||||
Event handler should return `MG_TRUE` if connection was successful and
|
|
||||||
HTTP request has been sent. Otherwise, it should send `MG_FALSE`.
|
|
||||||
* `MG_REPLY` is sent when response has been received from the remote host.
|
|
||||||
If event handler sends another request, then it should return `MG_TRUE`.
|
|
||||||
Otherwise it should return `MG_FALSE` and Mongoose will close the connection.
|
|
||||||
* `MG_CLOSE` same as for the accepted connection.
|
|
||||||
|
|
||||||
|
|
||||||
When mongoose buffers in HTTP request and successfully parses it, it sends
|
|
||||||
`MG_REQUEST` event for GET requests immediately. For POST requests,
|
|
||||||
Mongoose delays the call until the whole POST request is buffered in memory.
|
|
||||||
POST data is available to the callback as `struct mg_connection::content`,
|
|
||||||
and POST data length is in `struct mg_connection::content_len`.
|
|
||||||
|
|
||||||
Note that websocket connections are treated the same way. Mongoose buffers
|
|
||||||
websocket frame in memory, and calls event handler when frame is fully
|
|
||||||
buffered. Frame data is available `struct mg_connection::content`, and
|
|
||||||
data length is in `struct mg_connection::content_len`, i.e. very similar to
|
|
||||||
the POST request. `struct mg_connection::is_websocket` flag indicates
|
|
||||||
whether the request is websocket or not. Also, for websocket requests,
|
|
||||||
there is `struct mg_connection::wsbits` field which contains first byte
|
|
||||||
of the websocket frame which URI handler can examine. Note that to
|
|
||||||
reply to the websocket client, `mg_websocket_write()` should be used.
|
|
||||||
To reply to the plain HTTP client, `mg_write_data()` should be used.
|
|
||||||
|
|
||||||
Return value: number of active connections.
|
|
||||||
|
|
||||||
|
|
||||||
const char **mg_get_valid_option_names(void);
|
|
||||||
|
|
||||||
Returns a NULL-terminated array of option names and their default values.
|
|
||||||
There are two entries per option in an array: an option name followed by a
|
|
||||||
default value. A default value could be NULL. A NULL name indicates an end
|
|
||||||
of the array.
|
|
||||||
|
|
||||||
const char *mg_get_option(const struct mg_server *server, const char *name);
|
|
||||||
|
|
||||||
Returns the value of particular configuration parameter. If
|
|
||||||
given parameter name is not valid, NULL is returned. For valid names, return
|
|
||||||
value is guaranteed to be non-NULL. If parameter is not set, zero-length string
|
|
||||||
is returned.
|
|
||||||
|
|
||||||
void mg_wakeup_server_ex(struct mg_server *, mg_handler_t func,
|
|
||||||
const char *fmt, ...);
|
|
||||||
|
|
||||||
Sends string message to a server. Function `func` is called for every active
|
|
||||||
connection. String message is passed in `struct mg_connection::callback_param`.
|
|
||||||
This function is designed to push data to the connected clients, and
|
|
||||||
can be called from any thread. There is a limitation on the length of
|
|
||||||
the message, currently at 8 kilobytes.
|
|
||||||
|
|
||||||
void mg_send_status(struct mg_connection *, int status_code);
|
|
||||||
void mg_send_header(struct mg_connection *, const char *name,
|
|
||||||
const char *value);
|
|
||||||
void mg_send_data(struct mg_connection *, const void *data, int data_len);
|
|
||||||
void mg_printf_data(struct mg_connection *, const char *format, ...);
|
|
||||||
|
|
||||||
These functions are used to construct a response to the client. HTTP response
|
|
||||||
consists of three parts: a status line, zero or more HTTP headers,
|
|
||||||
a response body. Mongoose provides functions for all three parts:
|
|
||||||
* `mg_send_status()` is used to create status line. This function can be
|
|
||||||
called zero or once. If `mg_send_status()` is not called, then Mongoose
|
|
||||||
will send status 200 (success) implicitly.
|
|
||||||
* `mg_send_header()` adds HTTP header to the response. This function could
|
|
||||||
be called zero or more times.
|
|
||||||
* `mg_send_data()` and `mg_printf_data()` are used to send data to the
|
|
||||||
client. Note that Mongoose adds `Transfer-Encoding: chunked` header
|
|
||||||
implicitly, and sends data in chunks. Therefore, it is not necessary to
|
|
||||||
set `Content-Length` header. Note that `mg_send_data()` and
|
|
||||||
`mg_printf_data()` do not send data immediately. Instead, they spool
|
|
||||||
data in memory, and Mongoose sends that data later after URI handler
|
|
||||||
returns. If data to be sent is huge, an URI handler might
|
|
||||||
send data in pieces by saving state in
|
|
||||||
`struct mg_connection::connection_param` variable and returning `0`. Then
|
|
||||||
Mongoose will call a handler repeatedly after each socket write.
|
|
||||||
|
|
||||||
<!-- -->
|
|
||||||
|
|
||||||
void mg_send_file(struct mg_connection *, const char *path);
|
|
||||||
|
|
||||||
Tells Mongoose to serve given file. Mongoose handles file according to
|
|
||||||
it's extensions, i.e. Mongoose will invoke CGI script if `path` has CGI
|
|
||||||
extension, it'll render SSI file if `path` has SSI extension, etc. If `path`
|
|
||||||
points to a directory, Mongoose will show directory listing. If this function
|
|
||||||
is used, no calls to `mg_send*` or `mg_printf*` functions must be made, and
|
|
||||||
event handler must return `MG_MORE`.
|
|
||||||
|
|
||||||
int mg_websocket_write(struct mg_connection* conn, int opcode,
|
|
||||||
const char *data, size_t data_len);
|
|
||||||
|
|
||||||
Similar to `mg_write()`, but wraps the data into a websocket frame with a
|
|
||||||
given websocket `opcode`.
|
|
||||||
|
|
||||||
const char *mg_get_header(const struct mg_connection *, const char *name);
|
|
||||||
|
|
||||||
Get the value of particular HTTP header. This is a helper function.
|
|
||||||
It traverses http_headers array, and if the header is present in the array,
|
|
||||||
returns its value. If it is not present, NULL is returned.
|
|
||||||
|
|
||||||
|
|
||||||
int mg_get_var(const struct mg_connection *conn, const char *var_name,
|
|
||||||
char *buf, size_t buf_len);
|
|
||||||
|
|
||||||
Gets HTTP form variable. Both POST buffer and query string are inspected.
|
|
||||||
Form variable is url-decoded and written to the buffer. On success, this
|
|
||||||
function returns the length of decoded variable. On error, -1 is returned if
|
|
||||||
variable not found, and -2 is returned if destination buffer is too small
|
|
||||||
to hold the variable. Destination buffer is guaranteed to be
|
|
||||||
'\0' - terminated if it is not NULL or zero length.
|
|
||||||
|
|
||||||
int mg_parse_header(const char *hdr, const char *var_name, char *buf,
|
|
||||||
size_t buf_size);
|
|
||||||
|
|
||||||
This function parses HTTP header and fetches given variable's value in a buffer.
|
|
||||||
A header should be like `x=123, y=345, z="other value"`. This function is
|
|
||||||
designed to parse Cookie headers, Authorization headers, and similar. Returns
|
|
||||||
the length of the fetched value, or 0 if variable not found.
|
|
||||||
|
|
||||||
int mg_modify_passwords_file(const char *passwords_file_name,
|
|
||||||
const char *domain,
|
|
||||||
const char *user,
|
|
||||||
const char *password);
|
|
||||||
|
|
||||||
Add, edit or delete the entry in the passwords file.
|
|
||||||
This function allows an application to manipulate .htpasswd files on the
|
|
||||||
fly by adding, deleting and changing user records. This is one of the
|
|
||||||
several ways of implementing authentication on the server side.
|
|
||||||
If password is not NULL, entry is added (or modified if already exists).
|
|
||||||
If password is NULL, entry is deleted.
|
|
||||||
Return: 1 on success, 0 on error.
|
|
||||||
|
|
||||||
|
|
||||||
int mg_parse_multipart(const char *buf, int buf_len,
|
|
||||||
char *var_name, int var_name_len,
|
|
||||||
char *file_name, int file_name_len,
|
|
||||||
const char **data, int *data_len);
|
|
||||||
|
|
||||||
Parses a buffer that contains multipart form data. Stores chunk name
|
|
||||||
in a `var_name` buffer. If chunk is an uploaded file, then `file_name`
|
|
||||||
will have a file name. `data` and `data_len` will point to the chunk data.
|
|
||||||
Returns number of bytes to skip to the next chunk.
|
|
||||||
|
|
||||||
struct mg_connection *mg_connect(struct mg_server *server,
|
|
||||||
const char *host, int port, int use_ssl);
|
|
||||||
|
|
||||||
Create connection to the remote host. Returns `NULL` on error, non-null
|
|
||||||
if the connection has been scheduled for connection. Upon a connection,
|
|
||||||
Mongoose will send `MG_CONNECT` event to the event handler.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Mongoose Build on Android
|
|
||||||
|
|
||||||
This is a small guide to help you run mongoose on Android. Currently it is
|
|
||||||
tested on the HTC Wildfire. If you have managed to run it on other devices
|
|
||||||
as well, please comment or drop an email in the mailing list.
|
|
||||||
Note : You dont need root access to run mongoose on Android.
|
|
||||||
|
|
||||||
- Clone Mongoose Git repo
|
|
||||||
- Download the Android NDK from [http://developer.android.com/tools/sdk/ndk/index.html](http://developer.android.com/tools/sdk/ndk/index.html)
|
|
||||||
- Run `/path-to-ndk/ndk-build -C /path-to-mongoose/examples`
|
|
||||||
That should generate mongoose/lib/armeabi/mongoose
|
|
||||||
- Using the adb tool (you need to have Android SDK installed for that),
|
|
||||||
push the generated mongoose binary to `/data/local` folder on device.
|
|
||||||
- From adb shell, navigate to `/data/local` and execute `./mongoose`.
|
|
||||||
- To test if the server is running fine, visit your web-browser and
|
|
||||||
navigate to `http://127.0.0.1:8080` You should see the `Index of /` page.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
- `jni` stands for Java Native Interface. Read up on Android NDK if you want
|
|
||||||
to know how to interact with the native C functions of mongoose in Android
|
|
||||||
Java applications.
|
|
||||||
- TODO: A Java application that interacts with the native binary or a
|
|
||||||
shared library.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
How To Create Basic Website With Mongoose
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
## 1. Create a directory which will contain your website files. For example, on drive `C:\`, create a directory called `my_website`:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 2. Inside `my_website` directory, create a new file called "index". This will be the default web page shown when the website is visited.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 3. Open index file with your favorite editor (for example, Notepad) and enter some HTML code:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 4. Save this file as `index.html`:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## 5. Download Mongoose executable from http://cesanta.com/mongoose.shtml and copy the executable inside `my_website` directory:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 6. Double-click mongoose executable. An icon will appear on a system tray in the bottom right corner of the desktop:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 7. Click on the mongoose icon and choose "Go to my address" menu:
|
|
||||||

|
|
||||||
|
|
||||||
## 8. A browser will popup displaying `index.html` file. Now, you can expand your website by adding more content.
|
|
||||||
|
|
||||||

|
|
||||||
-171
@@ -1,171 +0,0 @@
|
|||||||
# Mongoose Embedding Guide
|
|
||||||
|
|
||||||
Embedding Mongoose is done in two steps:
|
|
||||||
|
|
||||||
1. Copy
|
|
||||||
[mongoose.c](https://raw.github.com/cesanta/mongoose/master/mongoose.c) and
|
|
||||||
[mongoose.h](https://raw.github.com/cesanta/mongoose/master/mongoose.h)
|
|
||||||
to your application's source tree and include them in the build.
|
|
||||||
2. Somewhere in the application code, call `mg_create_server()` to create
|
|
||||||
a server, configure it with `mg_set_option()` and loop with
|
|
||||||
`mg_poll_server()` until done. Call `mg_destroy_server()` to cleanup.
|
|
||||||
|
|
||||||
Here's a minimal application `app.c` that embeds mongoose:
|
|
||||||
|
|
||||||
#include "mongoose.h"
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
struct mg_server *server = mg_create_server(NULL, NULL);
|
|
||||||
mg_set_option(server, "document_root", "."); // Serve current directory
|
|
||||||
mg_set_option(server, "listening_port", "8080"); // Open port 8080
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
mg_poll_server(server, 1000); // Infinite loop, Ctrl-C to stop
|
|
||||||
}
|
|
||||||
mg_destroy_server(&server);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
To compile it, put `mongoose.c`, `mongoose.h` and `app.c` into one
|
|
||||||
folder, start terminal on UNIX or Visual Studio command line prompt on Windows,
|
|
||||||
and run the following command:
|
|
||||||
|
|
||||||
cc app.c mongoose.c -pthread -o app # on Unix
|
|
||||||
cl.exe app.c mongoose.c /TC /MD # on Windows
|
|
||||||
|
|
||||||
When run, this simple application opens port 8080 and serves static files,
|
|
||||||
CGI files and lists directory content in the current working directory.
|
|
||||||
|
|
||||||
It is possible to generate HTML page content. Mongoose can call user-defined
|
|
||||||
function when certain events occur.
|
|
||||||
That function is called _an event handler_, and it is the second parameter
|
|
||||||
to `mg_create_server()` function. Here is the example event handler function:
|
|
||||||
|
|
||||||
int event_handler(struct mg_connection *conn, enum mg_event ev) {
|
|
||||||
switch (ev) {
|
|
||||||
case MG_AUTH: return MG_TRUE;
|
|
||||||
default: return MG_FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Event handler is called by Mongoose with `struct mg_connection *`
|
|
||||||
pointer and an event number. `struct mg_connection *conn`
|
|
||||||
has all information about the request: HTTP headers, POST or websocket
|
|
||||||
data buffer, etcetera. `enum mg_event ev` tells which exactly event is sent.
|
|
||||||
For each event, an event handler returns a value which tells Mongoose how
|
|
||||||
to behave.
|
|
||||||
|
|
||||||
The sequence of events for every connection is this:
|
|
||||||
|
|
||||||
* `MG_AUTH` - Mongoose asks whether this connection is authorized. If event
|
|
||||||
handler returns `MG_FALSE`, then Mongoose does not serve the request but
|
|
||||||
sends authorization request to the client. If `MG_TRUE` is returned,
|
|
||||||
then Mongoose continues on with the request.
|
|
||||||
* `MG_REQUEST` - Mongoose asks event handler to serve the request. If
|
|
||||||
event handler serves the request by sending a reply,
|
|
||||||
it should return `MG_TRUE`. Otherwise,
|
|
||||||
it should return `MG_FALSE` which tells Mongoose that request is not
|
|
||||||
served and Mongoose should serve it. For example, event handler might
|
|
||||||
choose to serve only RESTful API requests with URIs that start with
|
|
||||||
certain prefix, and let Mongoose serve all static files.
|
|
||||||
If event handler decides to serve the request, but doesn't have
|
|
||||||
all the data at the moment, it should return `MG_MORE`. That tells
|
|
||||||
Mongoose to keep the connection open after callback returns.
|
|
||||||
|
|
||||||
`mg_connection::connection_param` pointer is a placeholder to keep
|
|
||||||
user-specific data. For example, handler could decide to open a DB
|
|
||||||
connection and store DB connection handle in `connection_param`.
|
|
||||||
* `MG_POLL` is sent to every connection on every iteration of
|
|
||||||
`mg_poll_server()`. Event handler should return `MG_FALSE` to ignore
|
|
||||||
this event. If event handler returns `MG_TRUE`, then Mongoose assumes
|
|
||||||
that event handler has finished sending data, and Mongoose will
|
|
||||||
close the connection.
|
|
||||||
* `MG_HTTP_ERROR` sent when Mongoose is about to send HTTP error back
|
|
||||||
to the client. Event handler can choose to send a reply itself, in which
|
|
||||||
case event handler must return `MG_TRUE`. Otherwise, event handler must
|
|
||||||
return `MG_FALSE`
|
|
||||||
* `MG_CLOSE` is sent when the connection is closed. This event is used
|
|
||||||
to cleanup per-connection state stored in `connection_param`
|
|
||||||
if it was allocated.
|
|
||||||
|
|
||||||
Let's extend our minimal application example and
|
|
||||||
create an URI that will be served by user's C code. The app will handle
|
|
||||||
`/hello` URI by showing a hello message. So, when app is run,
|
|
||||||
http://127.0.0.1:8080/hello will say hello, and here's the code:
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include "mongoose.h"
|
|
||||||
|
|
||||||
static int event_handler(struct mg_connection *conn, enum mg_event ev) {
|
|
||||||
if (ev == MG_AUTH) {
|
|
||||||
return MG_TRUE; // Authorize all requests
|
|
||||||
} else if (ev == MG_REQUEST && !strcmp(conn->uri, "/hello")) {
|
|
||||||
mg_printf_data(conn, "%s", "Hello world");
|
|
||||||
return MG_TRUE; // Mark as processed
|
|
||||||
} else {
|
|
||||||
return MG_FALSE; // Rest of the events are not processed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
struct mg_server *server = mg_create_server(NULL, event_handler);
|
|
||||||
mg_set_option(server, "document_root", ".");
|
|
||||||
mg_set_option(server, "listening_port", "8080");
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
mg_poll_server(server, 1000); // Infinite loop, Ctrl-C to stop
|
|
||||||
}
|
|
||||||
mg_destroy_server(&server);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
## Example code
|
|
||||||
|
|
||||||
Mongoose source code contains number of examples, located in the
|
|
||||||
[examples](https://github.com/cesanta/mongoose/blob/master/examples/) directory.
|
|
||||||
To build any example, go to the respective directory and run `make`.
|
|
||||||
|
|
||||||
## Compilation flags
|
|
||||||
|
|
||||||
Below is the list of compilation flags that enable or disable certain
|
|
||||||
features. By default, some features are enabled, and could be disabled
|
|
||||||
by setting appropriate `NO_*` flag. Features that are disabled by default
|
|
||||||
could be enabled by setting appropriate `USE_*` flag. Bare bones Mongoose
|
|
||||||
is quite small, about 30 kilobytes of compiled x86 code. Each feature adds
|
|
||||||
a couple of kilobytes to the executable size, and also has some runtime penalty.
|
|
||||||
|
|
||||||
Note that some flags start with `NS_` prefix. This is because Mongoose uses
|
|
||||||
[Net Skeleton](http://github.com/cesanta/net_skeleton) as a low-level
|
|
||||||
networking engine. If user code has `#include <net_skeleton.h>`, then
|
|
||||||
all Net Skeleton functions will be available too.
|
|
||||||
|
|
||||||
|
|
||||||
-DMONGOOSE_NO_AUTH Disable MD5 authorization support
|
|
||||||
-DMONGOOSE_NO_CGI Disable CGI support
|
|
||||||
-DMONGOOSE_NO_DAV Disable WebDAV support
|
|
||||||
(PUT, DELETE, MKCOL, PROPFIND methods)
|
|
||||||
-DMONGOOSE_NO_DIRECTORY_LISTING Disable directory listing
|
|
||||||
-DMONGOOSE_NO_FILESYSTEM Disables all file IO, serving from memory only
|
|
||||||
-DMONGOOSE_NO_LOGGING Disable access/error logging
|
|
||||||
-DMONGOOSE_NO_THREADS
|
|
||||||
-DMONGOOSE_NO_WEBSOCKET Disable WebSocket support
|
|
||||||
|
|
||||||
-DMONGOOSE_USE_IDLE_TIMEOUT_SECONDS=X Idle connection timeout, default is 30
|
|
||||||
-DMONGOOSE_USE_LUA Enable Lua scripting
|
|
||||||
-DMONGOOSE_USE_LUA_SQLITE3 Enable sqlite3 binding for Lua
|
|
||||||
-DMONGOOSE_USE_POST_SIZE_LIMIT=X POST requests larger than X will be
|
|
||||||
rejected, not set by default
|
|
||||||
-DMONGOOSE_USE_EXTRA_HTTP_HEADERS=X Append X to the HTTP headers
|
|
||||||
for static files, empty by default
|
|
||||||
|
|
||||||
-DNS_ENABLE_DEBUG Enables debug messages on stdout, very noisy
|
|
||||||
-DNS_ENABLE_SSL Enable SSL
|
|
||||||
-DNS_ENABLE_IPV6 Enable IPv6 support
|
|
||||||
-DNS_ENABLE_HEXDUMP Enables hexdump of sent and received traffic
|
|
||||||
-DNS_STACK_SIZE=X Sets stack size to X for ns_start_thread()
|
|
||||||
-DNS_DISABLE_THREADS Disable threads support
|
|
||||||
-DNS_DISABLE_SOCKETPAIR For systems without loopback interface
|
|
||||||
-DMONGOOSE_SEND_NS_EVENTS Send Net Skeleton events to the event handler
|
|
||||||
in addition to the Mongoose events
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
# Mongoose FAQ
|
|
||||||
|
|
||||||
## My Antivirus Software reports Mongoose as a security threat
|
|
||||||
|
|
||||||
Mongoose doesn't contain any malicious logic. Antivirus reports a
|
|
||||||
[false positive](http://en.wikipedia.org/wiki/Type_I_and_type_II_errors#False_positive_error).
|
|
||||||
This is when certain byte sequence in Mongoose accidentally matches
|
|
||||||
virus signature in the Antivirus database.
|
|
||||||
|
|
||||||
## Download page doesn't work
|
|
||||||
|
|
||||||
Please make sure Javascript is enabled in your browser, and that the
|
|
||||||
antivirus software is not blocking the download.
|
|
||||||
|
|
||||||
|
|
||||||
## MacOS message: "Mongoose.app is damaged and can’t be opened. You should move it to the Trash"
|
|
||||||
|
|
||||||
This happens on newer MacOS systems. The reason for the message
|
|
||||||
is the fact Mongoose.app is not digitally signed.
|
|
||||||
Mongoose download procedure changes the app on the fly by injecting
|
|
||||||
user information in the binary, making any prior digital signature void.
|
|
||||||
Open "System Preferences" -> "Security" and set "Allow apps downloaded from"
|
|
||||||
to "Anywhere". Revert the settings once Mongoose is installed.
|
|
||||||
|
|
||||||
## PHP doesn't work: getting empty page, or 'File not found' error
|
|
||||||
|
|
||||||
The reason for that is wrong paths to the interpreter. Remember that with PHP,
|
|
||||||
correct interpreter is `php-cgi.exe` (`php-cgi` on UNIX). Solution: specify
|
|
||||||
full path to the PHP interpreter, e.g.:
|
|
||||||
|
|
||||||
mongoose -cgi_interpreter /full/path/to/php-cgi
|
|
||||||
|
|
||||||
## Mongoose fails to start
|
|
||||||
|
|
||||||
If Mongoose exits immediately when run, this
|
|
||||||
usually indicates a syntax error in the configuration file
|
|
||||||
(named `mongoose.conf` by default) or the command-line arguments.
|
|
||||||
Syntax checking is omitted from Mongoose to keep its size low. However,
|
|
||||||
the Manual should be of help. Note: the syntax changes from time to time,
|
|
||||||
so updating the config file might be necessary after executable update.
|
|
||||||
|
|
||||||
### Embedding with OpenSSL on Windows might fail because of calling convention
|
|
||||||
|
|
||||||
To force Mongoose to use `__stdcall` convention, add `/Gz` compilation
|
|
||||||
flag to the Visual Studio project settings.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
How To Share Files With Mongoose
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
## 1. Download Mongoose executable from http://cesanta.com/mongoose.shtml and copy the executable inside the directory you want to share:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 2. Double-click mongoose executable. A browser will start automatically, an icon will appear on a system tray in the bottom right corner of the desktop:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 3. Click on the mongoose icon
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## 4. Click on "Go to my address" to launch a browser locally. Or, to access a folder from another machine, launch a browser and type in the URL:
|
|
||||||
|
|
||||||

|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Mongoose Internals
|
|
||||||
|
|
||||||
Mongoose has single-threaded, event-driven, asynchronous, non-blocking core.
|
|
||||||
`mg_create_server()` creates a web server instance. An instance is a container
|
|
||||||
for the config options and list of active connections. To do the actual
|
|
||||||
serving, user must call `mg_poll_server()`, which iterates over all
|
|
||||||
active connections, performing `select()` syscall on all sockets with a
|
|
||||||
timeout of specified number of milliseconds. When `select()` returns, Mongoose
|
|
||||||
does an IO for each socket that has data to be sent or received. Application
|
|
||||||
code must call `mg_poll_server()` in a loop.
|
|
||||||
|
|
||||||
Mongoose server instance is designed to be used by a single thread.
|
|
||||||
It is an error to have more then
|
|
||||||
one thread calling `mg_poll_server()`, `mg_set_option()` or any other function
|
|
||||||
that take `struct mg_server *` parameter. Mongoose does not
|
|
||||||
mutex-protect `struct mg_server *`, therefore the best practice is
|
|
||||||
to call server management functions from the same thread (an IO thread).
|
|
||||||
On a multi-core systems, many server instances can be created, sharing the
|
|
||||||
same listening socket and managed by separate threads (see [multi_threaded.c](https://github.com/cesanta/mongoose/blob/master/examples/multi_threaded.c))
|
|
||||||
example.
|
|
||||||
|
|
||||||
It is an error to pass and store `struct mg_connection *` pointers for
|
|
||||||
later use to send data. The reason is that they can be invalidated by the
|
|
||||||
next `mg_poll_server()` call. For such a task,
|
|
||||||
there is `mg_iterate_over_connections()` API
|
|
||||||
exists, which sends a callback function to the IO thread, then IO thread
|
|
||||||
calls specified function for all active connection.
|
|
||||||
|
|
||||||
When mongoose buffers in HTTP request and successfully parses it, it calls
|
|
||||||
appropriate URI handler immediately for GET requests. For POST requests,
|
|
||||||
Mongoose delays the call until the whole POST request is buffered in memory.
|
|
||||||
POST data is available to the callback as `struct mg_connection::content`,
|
|
||||||
and POST data length is in `struct mg_connection::content_len`.
|
|
||||||
|
|
||||||
Note that websocket connections are treated the same way. Mongoose buffers
|
|
||||||
websocket frame in memory, and calls URI handler when frame is fully
|
|
||||||
buffered. Frame data is available `struct mg_connection::content`, and
|
|
||||||
data length is in `struct mg_connection::content_len`, i.e. very similar to
|
|
||||||
the POST request. `struct mg_connection::is_websocket` flag indicates
|
|
||||||
whether the request is websocket or not. Also, for websocket requests,
|
|
||||||
there is `struct mg_connection::wsbits` field which contains first byte
|
|
||||||
of the websocket frame which URI handler can examine. Note that to
|
|
||||||
reply to the websocket client, `mg_websocket_write()` should be used.
|
|
||||||
To reply to the plain HTTP client, `mg_write()` should be used.
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Mongoose Lua Server Pages
|
|
||||||
|
|
||||||
Pre-built Windows and Mac mongoose binaries support Lua Server Pages
|
|
||||||
functionality.
|
|
||||||
That means it is possible to write PHP-like scripts with mongoose
|
|
||||||
using Lua programming language instead of PHP. Lua is known
|
|
||||||
for it's speed and small size. Mongoose uses Lua version 5.2.3, the
|
|
||||||
documentation for it can be found at
|
|
||||||
[Lua 5.2 reference manual](http://www.lua.org/manual/5.2/).
|
|
||||||
|
|
||||||
To create a Lua Page, make a file that is called `ANY_NAME.lp`. For example,
|
|
||||||
`my_page.lp`. It is important to have a file
|
|
||||||
name that ends up with `.lp`, cause this is the way mongoose recognises
|
|
||||||
Lua Page file. The contents of the file, just like
|
|
||||||
with PHP, is HTML with embedded Lua code. Lua code must be enclosed within
|
|
||||||
`<? ?>` blocks, and can appear anywhere on the page.
|
|
||||||
|
|
||||||
Mongoose does not send HTTP headers for Lua pages. Therefore,
|
|
||||||
every Lua Page must begin with HTTP status line and headers, like this:
|
|
||||||
|
|
||||||
<? mg.write('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n') ?>
|
|
||||||
<html><body>
|
|
||||||
<span>Today is:</span> <? mg.write(os.date("%A")) ?>
|
|
||||||
</body></html>
|
|
||||||
|
|
||||||
Note that this example uses function `mg.write()`, which prints data to the
|
|
||||||
web page. Using function `mg.write()` is the way to generate web content from
|
|
||||||
inside Lua code. In addition to `mg.write()`, all standard library functions
|
|
||||||
are accessible from the Lua code (please check reference manual for details).
|
|
||||||
Information about the request is available via the `mg.request_info` object.
|
|
||||||
I contains request method, all headers, etcetera. Please refer to
|
|
||||||
`struct mg_request_info` definition in
|
|
||||||
[mongoose.h](https://github.com/cesanta/mongoose/blob/master/mongoose.h)
|
|
||||||
to see what is available via the `mg.request_info` object.
|
|
||||||
Check out [prime_numbers.lp](https://github.com/cesanta/mongoose/blob/master/examples/lua/prime_numbers.lp) for some example.
|
|
||||||
|
|
||||||
Mongoose exports the following to the Lua Server Page:
|
|
||||||
|
|
||||||
mg.write(str) -- writes string to the client
|
|
||||||
mg.onerror(msg) -- error handler, can be overridden
|
|
||||||
mg.request_info -- a table with request information
|
|
||||||
|
|
||||||
Using Lua scripting it is easy to emulate SSI functionality. For example,
|
|
||||||
to include the content of another file, one can write:
|
|
||||||
|
|
||||||
<? mg.write(io.open('MY_FILE.TXT'):read('*all')) ?>
|
|
||||||
|
|
||||||
To serve a Lua Page, mongoose creates Lua context. That context is used for
|
|
||||||
all Lua blocks within the page. That means, all Lua blocks on the same page
|
|
||||||
share the same context. If one block defines a variable, for example, that
|
|
||||||
variable is visible in all following blocks.
|
|
||||||
-154
@@ -1,154 +0,0 @@
|
|||||||
# Mongoose Configuration Options
|
|
||||||
|
|
||||||
### access\_control\_list
|
|
||||||
An Access Control List (ACL) allows restrictions to be put on the list of IP
|
|
||||||
addresses which have access to the web server. In the case of the Mongoose
|
|
||||||
web server, the ACL is a comma separated list of IP subnets, where each
|
|
||||||
subnet is prepended by either a `-` or a `+` sign. A plus sign means allow,
|
|
||||||
where a minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,
|
|
||||||
this means to deny only that single IP address.
|
|
||||||
|
|
||||||
Subnet masks may vary from 0 to 32, inclusive. The default setting is to allow
|
|
||||||
all accesses. On each request the full list is traversed, and
|
|
||||||
the last match wins. Example: `$ mongoose -access_control_list -0.0.0.0/0,+192.168/16` to deny all acccesses except those from `192.168/16` subnet. Note that if the option is set, then all accesses are forbidden
|
|
||||||
by default. Thus in a previous example, `-0.0.0.0` part is not necessary.
|
|
||||||
For example, `$mongoose access_control_list +10.0.0.0/8`
|
|
||||||
means disallow all, allow subnet 10/8 only.
|
|
||||||
|
|
||||||
To learn more about subnet masks, see the
|
|
||||||
[Wikipedia page on Subnetwork](http://en.wikipedia.org/wiki/Subnetwork)
|
|
||||||
|
|
||||||
Default: not set, all accesses are allowed.
|
|
||||||
|
|
||||||
### access\_log\_file
|
|
||||||
Path to a file for access logs. Either full path, or relative to the
|
|
||||||
mongoose executable. Default: not set, no query logging is done.
|
|
||||||
|
|
||||||
### auth_domain
|
|
||||||
Authorization realm used in `.htpasswd` authorization. Default: `mydomain.com`
|
|
||||||
|
|
||||||
### cgi_interpreter
|
|
||||||
Path to an executable to be used use as an interpreter for __all__ CGI scripts
|
|
||||||
regardless script extension. Default: not set, Mongoose looks at
|
|
||||||
[shebang line](http://en.wikipedia.org/wiki/Shebang_(Unix\).
|
|
||||||
|
|
||||||
For example, if both PHP and perl CGIs are used, then
|
|
||||||
`#!/path/to/php-cgi.exe` and `#!/path/to/perl.exe` must be first lines of the
|
|
||||||
respective CGI scripts. Note that paths should be either full file paths,
|
|
||||||
or file paths relative to the directory where mongoose executable is located.
|
|
||||||
|
|
||||||
If all CGIs use the same interpreter, for example they are all PHP, then
|
|
||||||
`cgi_interpreter` option can be set to the path to `php-cgi.exe` executable and
|
|
||||||
shebang line in the CGI scripts can be omitted.
|
|
||||||
**Note**: PHP scripts must use `php-cgi.exe`, not `php.exe`.
|
|
||||||
|
|
||||||
### cgi_pattern
|
|
||||||
All files that match `cgi_pattern` are treated as CGI files. Default pattern
|
|
||||||
allows CGI files be anywhere. To restrict CGIs to a certain directory,
|
|
||||||
use `/path/to/cgi-bin/**.cgi` as a pattern. Note that **full file path** is
|
|
||||||
matched against the pattern, not the URI.
|
|
||||||
|
|
||||||
When Mongoose starts CGI program, it creates new environment for it (in
|
|
||||||
contrast, usually child program inherits the environment from parent). Several
|
|
||||||
environment variables however are inherited from Mongoose's environment,
|
|
||||||
they are: `PATH`, `TMP`, `TEMP`, `TMPDIR`, `PERLLIB`, `MONGOOSE_CGI`. On UNIX
|
|
||||||
it is also `LD_LIBRARY_PATH`. On Windows it is also `COMSPEC`, `SYSTEMROOT`,
|
|
||||||
`SystemDrive`, `ProgramFiles`, `ProgramFiles(x86)`, `CommonProgramFiles(x86)`.
|
|
||||||
|
|
||||||
Default: `**.cgi$|**.pl$|**.php$`
|
|
||||||
|
|
||||||
### dav\_auth\_file
|
|
||||||
Authentication file for WebDAV mutation requests: `PUT`, `DELETE`, `MKCOL`.
|
|
||||||
The format of that file is the same as for the `.htpasswd` file
|
|
||||||
used for digest authentication. It can be created and managed by
|
|
||||||
`mongoose -A` command. Default: not set, WebDAV mutations are disallowed.
|
|
||||||
|
|
||||||
### document_root
|
|
||||||
A directory to serve. Default: current working directory.
|
|
||||||
|
|
||||||
### enable\_directory\_listing
|
|
||||||
Enable directory listing, either `yes` or `no`. Default: `yes`.
|
|
||||||
|
|
||||||
### enable\_proxy
|
|
||||||
Enable proxy functionality, either `yes` or `no`. If set to `yes`, then
|
|
||||||
browsers can be configured to use Mongoose as a proxy. Default: `no`.
|
|
||||||
|
|
||||||
|
|
||||||
### extra\_mime\_types
|
|
||||||
Extra mime types to recognize, in form `extension1=type1,extension2=type2,...`.
|
|
||||||
Extension must include dot. Example:
|
|
||||||
`mongoose -extra_mime_types .cpp=plain/text,.java=plain/text`. Default: not set.
|
|
||||||
|
|
||||||
|
|
||||||
### global\_auth\_file
|
|
||||||
Path to a global passwords file, either full path or relative to the mongoose
|
|
||||||
executable. If set, per-directory `.htpasswd` files are ignored,
|
|
||||||
and all requests are authorised against that file. Use `mongoose -A` to
|
|
||||||
manage passwords, or third party utilities like
|
|
||||||
[htpasswd-generator](http://www.askapache.com/online-tools/htpasswd-generator).
|
|
||||||
Default: not set, per-directory `.htpasswd` files are respected.
|
|
||||||
|
|
||||||
### hide\_files\_patterns
|
|
||||||
A pattern for the files to hide. Files that match the pattern will not
|
|
||||||
show up in directory listing and return `404 Not Found` if requested. Pattern
|
|
||||||
must be for a file name only, not including directory name, e.g.
|
|
||||||
`mongoose -hide_files_patterns secret.txt|even_more_secret.txt`. Default:
|
|
||||||
not set.
|
|
||||||
|
|
||||||
### index_files
|
|
||||||
Comma-separated list of files to be treated as directory index
|
|
||||||
files. Default: `index.html,index.htm,index.cgi,index.shtml,index.php`
|
|
||||||
|
|
||||||
### listening_port
|
|
||||||
Port to listen on. Port could be prepended by the specific IP address to bind
|
|
||||||
to, e.g. `mongoose -listening_port 127.0.0.1:8080`. Otherwise Mongoose
|
|
||||||
will bind to all addresses. To enable SSL, build Mongoose with
|
|
||||||
`-DNS_ENABLE_SSL` compilation option, and specify `listening_port` as
|
|
||||||
`ssl://PORT:SSL_CERTIFICATE.PEM`. Example SSL listener:
|
|
||||||
`mongoose -listening_port ssl://8043:ssl_cert.pem`. Note that PEM file should
|
|
||||||
be in PEM format, and must have both certificate and private key in it,
|
|
||||||
concatenated together. More than one listening port can be specified,
|
|
||||||
separated by comma,
|
|
||||||
for example `mongoose -listening_port 8080,8000`. Default: 8080.
|
|
||||||
|
|
||||||
### run\_as\_user
|
|
||||||
Switch to given user credentials after startup. UNIX-only. This option is
|
|
||||||
required when mongoose needs to bind on privileged port on UNIX, e.g.
|
|
||||||
|
|
||||||
$ sudo mongoose -listening_port 80 -run_as_user nobody
|
|
||||||
|
|
||||||
Default: not set.
|
|
||||||
|
|
||||||
### url\_rewrites
|
|
||||||
Comma-separated list of URL rewrites in the form of
|
|
||||||
`uri_pattern=file_or_directory_path`. When Mongoose receives the request,
|
|
||||||
it constructs the file name to show by combining `document_root` and the URI.
|
|
||||||
However, if the rewrite option is used and `uri_pattern` matches the
|
|
||||||
requested URI, then `document_root` is ignored. Instead,
|
|
||||||
`file_or_directory_path` is used, which should be a full path name or
|
|
||||||
a path relative to the web server's current working directory. Note that
|
|
||||||
`uri_pattern`, as all mongoose patterns, is a prefix pattern. If `uri_pattern`
|
|
||||||
is a number, then it is treated as HTTP error code, and `file_or_directory_path`
|
|
||||||
should be an URI to redirect to. Mongoose will issue `302` temporary redirect
|
|
||||||
to the specified URI with following parameters:
|
|
||||||
`?code=HTTP_ERROR_CODE&orig_uri=ORIGINAL_URI&query_string=QUERY_STRING`.
|
|
||||||
|
|
||||||
If `uri_pattern` starts with `@` symbol, then Mongoose compares
|
|
||||||
it with the `HOST` header of the request. If they are equal, Mongoose sets
|
|
||||||
document root to `file_or_directory_path`, implementing virtual hosts support.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
# Redirect all accesses to `.doc` files to a special script
|
|
||||||
mongoose -url_rewrites **.doc$=/path/to/cgi-bin/handle_doc.cgi
|
|
||||||
|
|
||||||
# Implement user home directories support
|
|
||||||
mongoose -url_rewrites /~joe/=/home/joe/,/~bill=/home/bill/
|
|
||||||
|
|
||||||
# Redirect 404 errors to a specific error page
|
|
||||||
mongoose -url_rewrites 404=/cgi-bin/error.cgi
|
|
||||||
|
|
||||||
# Virtual hosts example: serve foo.com domain from different directory
|
|
||||||
mongoose -url_rewrites @foo.com=/var/www/foo.com
|
|
||||||
|
|
||||||
Default: not set.
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
How To Create A PHP Website With Mongoose
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
## 1. Create a directory which will contain your website files. For example, on drive `C:\`, create a directory called `my_website`:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 2. Inside `my_website` directory, create a new file called "index". This will be the default web page shown when the website is visited.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 3. Open index file with your favorite editor (for example, Notepad) and enter some HTML / PHP code:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 4. Save this file as `index.php`:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## 5. Download Mongoose executable from http://cesanta.com/mongoose.shtml and copy the executable inside `my_website` directory:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 6. Double-click mongoose executable. An icon will appear on a system tray in the bottom right corner of the desktop:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 7. Download PHP 5.3 zip (do NOT download PHP 5.5 cause you might have missing DLLs problem) from http://windows.php.net/download and extract it to `C:\php5` directory:
|
|
||||||

|
|
||||||
|
|
||||||
## 8. Click on the mongoose icon and choose "Edit Settings" menu.:
|
|
||||||

|
|
||||||
|
|
||||||
## 9. A settings dialog will appear. Click on `cgi_interpreter` button:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 10. Choose `C:\php5\php-cgi.exe` and click "Save Settings":
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 11. Click on the mongoose icon and choose "Go to my address" menu:
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## 12. A browser will popup displaying `index.php`.
|
|
||||||
|
|
||||||

|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
# Mongoose Release Notes
|
|
||||||
|
|
||||||
## Release 5.5, October 28 2014
|
|
||||||
|
|
||||||
Changes in Libmongoose library:
|
|
||||||
|
|
||||||
- Added new API function: `mg_forward()` for proxying functionality
|
|
||||||
- Added new API function: `mg_send_file_data()` for sending file data
|
|
||||||
- Added new utility API functions: `mg_mmap() and mg_munpap()`
|
|
||||||
- Changed the way SSL settings are handled: removed `ssl_certificate` and
|
|
||||||
`ssl_ca_certificate` options, and instead made `listening_port` accept
|
|
||||||
`ssl://PORT:SSL_CERT:CA_CERT` notation
|
|
||||||
- Added ability to listen on multiple ports, see `listening_port` documentation
|
|
||||||
- Added `enable_proxy` option
|
|
||||||
- Added [cookie_authentication](https://github.com/cesanta/mongoose/tree/master/examples/cookie_authentication) example
|
|
||||||
- Added [websocket\_ssl\_proxy](https://github.com/cesanta/mongoose/tree/master/examples/websocket_ssl_proxy) example
|
|
||||||
- Added [http_client](https://github.com/cesanta/mongoose/tree/master/examples/http_client) example
|
|
||||||
- Increased default 'idle connection' timeout from 30 to 300 seconds
|
|
||||||
- Fixed MinGW build
|
|
||||||
- Refactored all examples, put each in it's own directory with dedicated build
|
|
||||||
- Many smaller bugfixed, including SSL, CGI, API, proxy, etc
|
|
||||||
|
|
||||||
Changes in pre-compiled binaries:
|
|
||||||
|
|
||||||
- Support for multiple listening ports
|
|
||||||
- Fixed CGI handling for scripts that specify interpreter in the hashbang line
|
|
||||||
|
|
||||||
## Release 5.4, July 28 2014
|
|
||||||
|
|
||||||
Changes in Libmongoose library:
|
|
||||||
|
|
||||||
- Added `hexdump_file` option for low-level request/reply debugging
|
|
||||||
- Added `mg_template()` API function for generating HTML pages from
|
|
||||||
templates with expansions
|
|
||||||
- Fixed `struct mg_connection::local_ip` handling, `mg_set_option()`
|
|
||||||
behavior with NULL values
|
|
||||||
- Added `mg_send_file()` call to send arbitrary file to the client
|
|
||||||
- Added `mg_terminate_ssl()` for SSL termination functionality
|
|
||||||
- Added HTTP proxy support, `enable_proxy` config option
|
|
||||||
- Added `mg_next()` for iterating over existing active connections
|
|
||||||
- Added client-side SSL auth, `ssl_ca_certificate` option
|
|
||||||
- Added `mg_wakeup_server_ex()` for pushing messages to existing connections
|
|
||||||
- Added `MG_WS_HANDSHAKE` and `MG_WS_CONNECT` events that are sent on
|
|
||||||
Websocket handshake is connection establishment, respectively
|
|
||||||
- Removed server-side Lua support
|
|
||||||
- Filesystem access, reading from socket/SSL performance improvements
|
|
||||||
- DAV PROPFIND memory leak fixed
|
|
||||||
- Added `big_upload.c` and enhanced `upload.c` example
|
|
||||||
- Added `proxy.c` example that demonstrates proxy functionality and SSE pushes
|
|
||||||
- Added `websocket2.c` example that shows simple web chat implementation
|
|
||||||
over websockets
|
|
||||||
- Various minor fixes
|
|
||||||
|
|
||||||
|
|
||||||
Changes in pre-compiled binaries:
|
|
||||||
|
|
||||||
- Created HTML administration console
|
|
||||||
- When server is started, browser is started automatically
|
|
||||||
- Fixed directory listing bug when directory contains `#` character
|
|
||||||
- Removed built-in Lua Server Pages in the binary, and instead
|
|
||||||
added Mongoose + Lua developer bundle which has Lua Server Pages support.
|
|
||||||
That also solves external Lua modules loading problem.
|
|
||||||
|
|
||||||
|
|
||||||
## Release 5.3, March 10 2014
|
|
||||||
|
|
||||||
Changes in Libmongoose library:
|
|
||||||
|
|
||||||
* Moved to the evented API. Updated API documentation is at
|
|
||||||
http://cesanta.com/docs/Embed.shtml
|
|
||||||
http://cesanta.com/docs/API.shtml
|
|
||||||
* Added `MG_LUA` event for exporting custom variables to the Lua environment
|
|
||||||
* Added virtual hosts capability, see `url_rewrites` option description at
|
|
||||||
http://cesanta.com/docs/Options.shtml
|
|
||||||
* Added mjpg serving example
|
|
||||||
* Cleaned up and documented HTTP client API, with unit tests
|
|
||||||
* Added `mg_wakeup_server()` to awaken `mg_poll_server()`
|
|
||||||
from another thread
|
|
||||||
* Moved Mongoose IO core to [https://github.com/cesanta/net_skeleton](Net Skeleton)
|
|
||||||
* Added connection hexdump functionality for developers
|
|
||||||
* Bug fixes
|
|
||||||
|
|
||||||
Changes in pre-compiled binaries:
|
|
||||||
|
|
||||||
* New awesome Mongoose logos buy our designer Katrin - thanks Katrin!
|
|
||||||
Check them out at http://cesanta.com/products.shtml
|
|
||||||
* Added Lua Server Pages support to the free version, quick intro is at
|
|
||||||
http://cesanta.com/docs/Lua.shtml
|
|
||||||
* Added quick "Set shared directory" menu item to set `document_root`
|
|
||||||
* Added SSI support to the Pro version
|
|
||||||
* Removed SSL support from the Pro version
|
|
||||||
|
|
||||||
## Release 5.2, Feb 1 2014
|
|
||||||
|
|
||||||
* Windows binary made fully UNICODE aware. In previous versions,
|
|
||||||
the presence of non-ASCII chars in document root, CGI script name,
|
|
||||||
or directory name might have broken Mongoose as stand-alone
|
|
||||||
or as Windows service. Now Mongoose works with non-ASCII paths properly.
|
|
||||||
Internally, Mongoose uses UTF8 encoding. When making WinAPI calls,
|
|
||||||
mongoose converts UTF8 strings to wide chars and calls UNICODE API.
|
|
||||||
* Enhanced authorization API by providing `mg_set_auth_handler()` and
|
|
||||||
`mg_authorize_digest()`
|
|
||||||
* Removed `mg_add_uri_handler()`, added `mg_set_request_handler()`.
|
|
||||||
There is only oneURI handler that handles all requests, just like in 4.x.
|
|
||||||
The reason for this change is to provide an ability to catch all URIs,
|
|
||||||
and at the same time signal Mongoose to continue handling specific URIs.
|
|
||||||
* Added `mg_parse_multipart()` API for file uploads.
|
|
||||||
Note that the restriction on uploading huge files still exists,
|
|
||||||
and will be eliminated in the next release.
|
|
||||||
* Allowing mongoose to bind to port 0, in which case it'll bind to any
|
|
||||||
random unused port.
|
|
||||||
* Moved `idle_timeout_ms` run-time option to compile-time flag
|
|
||||||
* Added asynchronous HTTP client, not documented yet. Documentation and
|
|
||||||
examples are coming in the next couple of weeks. Async Websocket client
|
|
||||||
is scheduled for the next release. See usage examples at `unit_test.c`
|
|
||||||
* Windows and MacOS pre-built binaries are now split to free and paid ones,
|
|
||||||
paid binaries include CGI, SSL, Lua, Sqlite, support and updates.
|
|
||||||
Linux pre-built binary includes all functionality and is free, and will
|
|
||||||
continue to be free. Source code for Windows and MacOS GUI is closed.
|
|
||||||
Disclaimer: source code for the command line stand-alone server,
|
|
||||||
as well as Mongoose library itself, will never be closed.
|
|
||||||
* Multiple bug fixes and minor enhancements
|
|
||||||
|
|
||||||
## Release 5.1, Jan 10 2014
|
|
||||||
|
|
||||||
* CGI-related bugs where fixed, primarily for Windows platform
|
|
||||||
* Bugs on Windows related to UNICODE support were fixed
|
|
||||||
* Added a feature to support "error pages" through redirect.
|
|
||||||
Done using `-url_redirects` option, details are on
|
|
||||||
http://cesanta.com/docs/Options.shtml
|
|
||||||
|
|
||||||
## Release 5.0, Jan 6 2014
|
|
||||||
|
|
||||||
* Internal core has been changed from blocking, thread-per-connection to
|
|
||||||
non-blocking, asynchronous, one thread for all.
|
|
||||||
* API modification for server creation and response creation. That allowed
|
|
||||||
keep-alive support for dynamic requests, boosting the embedded performance
|
|
||||||
to 100+ thousands requests per second on a single core
|
|
||||||
(as measured on my development MacBook laptop)
|
|
||||||
* Unified handling of POST requests and Websocket requests by putting a
|
|
||||||
payload into `conn->content`, `conn->content_len` attributes.
|
|
||||||
That simplified user code and eliminated the need of `mg_read()`,
|
|
||||||
since mongoose buffers all data prior to calling the callback
|
|
||||||
* keep-alive support is the default
|
|
||||||
* Dropped SSI support and throttling support
|
|
||||||
* Several configuraition parameters are gone:
|
|
||||||
* `cgi_environment` (replaced with MONGOOSE_CGI),
|
|
||||||
* `protect_uri` (not useful)
|
|
||||||
* `ssi_pattern` (SSI support is gone)
|
|
||||||
* `throttle` (throttling support is gone)
|
|
||||||
* `error_log_file` (not used)
|
|
||||||
* `enable_keep_alive` (enabled by default)
|
|
||||||
* `listening_ports` (renamed to listening_port)
|
|
||||||
* `num_threads` (core has changed to single thread)
|
|
||||||
* `put_delete_auth_file` (renamed to dav_auth_file)
|
|
||||||
* `authentication_domain` (renamed to auth_domain)
|
|
||||||
* Due to the async, non-blocking nature of the core, few restrictions
|
|
||||||
are now in place:
|
|
||||||
* user callbacks must not block
|
|
||||||
* POST and Websocket data are now buffered, and cannot be huge
|
|
||||||
* mongoose is now capable on listening on only one port
|
|
||||||
|
|
||||||
## Release 4.1, Oct 2013
|
|
||||||
## Release 4.0, Oct 2013
|
|
||||||
## Release 3.8, Sep 2013
|
|
||||||
|
|
||||||
## Release 3.7, Feb 2 2013
|
|
||||||
|
|
||||||
* Added "redirect to SSL port" functionality, e.g. if you specify
|
|
||||||
`-listening_ports 8080r,8043s`
|
|
||||||
then all requests to HTTP port 8080 will be redirected to HTTPS port 8043
|
|
||||||
* Added `mg_download()` API, an HTTP client interface!
|
|
||||||
* Lua server pages now must output HTTP headers -- full control for Lua
|
|
||||||
* Added pre-built binary for MacOS, with initial GUI support
|
|
||||||
* API change: got rid of events, moved to struct `mg_callbacks`
|
|
||||||
* Bugfixes, thanks to contributors
|
|
||||||
|
|
||||||
|
|
||||||
## Release 3.7, Jan 18 2013
|
|
||||||
* Fixed source code archive (main.c was missing)
|
|
||||||
* Extended Windows GUI functionality:
|
|
||||||
* Added "Start browser" systray popup menu item
|
|
||||||
* Enhanced configuration editor
|
|
||||||
* Renamed config options:
|
|
||||||
* `put_delete_passwords_file` -> `put_delete_auth_file`
|
|
||||||
* `global_passwords_file` -> `global_auth_file`
|
|
||||||
* `select()` changed to `poll()`, to avoid big file descriptor
|
|
||||||
`FD_SET` problem on UNIX
|
|
||||||
* Couple of bugfixes, thanks to contributors
|
|
||||||
|
|
||||||
|
|
||||||
Eearlier release notes could be found by searching
|
|
||||||
[Mongoose mailing list](https://groups.google.com/forum/#!forum/mongoose-users)
|
|
||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
# Mongoose SSL guide
|
|
||||||
|
|
||||||
SSL is a protocol that makes web communication secure. To enable SSL
|
|
||||||
in mongoose, 3 steps are required:
|
|
||||||
|
|
||||||
1. Valid certificate file must be created
|
|
||||||
2. `ssl_certificate` options must be set to contain path to the
|
|
||||||
certificate file.
|
|
||||||
3. `listening_ports` option must contain a port number with letter `s`
|
|
||||||
appended to it, which instructs Mongoose to use SSL for all connections
|
|
||||||
made to that port.
|
|
||||||
|
|
||||||
Below is the `mongoose.conf` file snippet for typical SSL setup:
|
|
||||||
|
|
||||||
document_root www_root # Serve files in www_root directory
|
|
||||||
listening_ports 80r,443s # Redirect all HTTP requests to HTTPS
|
|
||||||
ssl_certificate ssl_cert.pem # Location of certificate file
|
|
||||||
|
|
||||||
## How to create SSL certificate file
|
|
||||||
|
|
||||||
SSL certificate file is a text file that must contain at least two
|
|
||||||
sections:
|
|
||||||
|
|
||||||
1. A private key
|
|
||||||
2. A certificate
|
|
||||||
|
|
||||||
Both sections should be chunks of text in PEM format. When PEM file is
|
|
||||||
opened in a text editor, it looks like this:
|
|
||||||
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
|
|
||||||
hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
|
|
||||||
SEGI4JSxV56lYg==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
||||||
Two aforementioned sections are clearly seen. Typically, those section
|
|
||||||
are bigger then in the example shown. The text between the `BEGIN` and
|
|
||||||
`END` is the text representation of binary data, a private key and a
|
|
||||||
certificate. Therefore, in order to create a certificate file,
|
|
||||||
|
|
||||||
* private key must be converted to PEM format
|
|
||||||
* certificate must be converted to PEM format
|
|
||||||
* those two should be concatenated into a single file
|
|
||||||
|
|
||||||
If the certificate chain in used, a chain file also needs to be
|
|
||||||
converted into PEM format and appended to the certificate file.
|
|
||||||
|
|
||||||
## How SSL works
|
|
||||||
|
|
||||||
SSL is a protocol that can encrypt communication between two parties. If third
|
|
||||||
party observes all messages passed by, it would be very
|
|
||||||
hard for the third party (though not impossible) to decrypt the communication.
|
|
||||||
|
|
||||||
The idea is based on so-called public key encryption. Communicating parties
|
|
||||||
have two keys: a public key and a private key. A public key is advertised
|
|
||||||
to everybody, and it is contained in a certificate. A private key is kept
|
|
||||||
secret. Security algorithm works in a way that anybody can encrypt
|
|
||||||
a message using public key, and only private key can decrypt it.
|
|
||||||
|
|
||||||
This is why web server needs both private key and certificate: private key
|
|
||||||
is used to decrypt incoming messages, and certificate is used to tell the
|
|
||||||
public key to the other party. When communication starts, parties exchange
|
|
||||||
their public keys, and keep private keys to themselves. Man-in-the-middle
|
|
||||||
who observes the communication is unable to decrypt the messages cause
|
|
||||||
private keys are required for decryption.
|
|
||||||
|
|
||||||
Encryption algorithms are built on top of hard mathematical problem, which
|
|
||||||
makes it very expensive for man-in-the-middle to compute private keys.
|
|
||||||
For example, RSA algorithm is based on a mathematical problem of factorization.
|
|
||||||
It is easy to generate two very large prime numbers `P` and `Q` and make
|
|
||||||
a product `P * Q`. But given a product, it is very hard to recover these
|
|
||||||
two prime numbers - this is called factorization.
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
# Mongoose User Guide
|
|
||||||
|
|
||||||
Mongoose is small and easy to use web server built on top of
|
|
||||||
mongoose library. It is designed with maximum simplicity in mind. For example,
|
|
||||||
to share any directory, just drop mongoose executable in that directory,
|
|
||||||
double-click it (on UNIX, run it from shell) and launch a browser at
|
|
||||||
[http://localhost:8080](http://localhost:8080) Note that 'localhost' should
|
|
||||||
be changed to a machine's name if a folder is accessed from other computer.
|
|
||||||
|
|
||||||
On Windows and Mac, Mongoose iconifies itself to the system tray when started.
|
|
||||||
Right-click on the icon to pop up a menu, where it is possible to stop
|
|
||||||
mongoose, or configure it.
|
|
||||||
|
|
||||||
On UNIX, `mongoose` is a command line utility. Running `mongoose` in
|
|
||||||
terminal, optionally followed by configuration parameters
|
|
||||||
(`mongoose [OPTIONS]`) or configuration file name
|
|
||||||
(`mongoose [config_file_name]`) starts the
|
|
||||||
web server:
|
|
||||||
|
|
||||||
$ mongoose -document_root /var/www # Running mongoose with cmdline options
|
|
||||||
$ mongoose /etc/my_config.txt # Running mongoose with config file
|
|
||||||
$ mongoose # Running with no parameters. This will
|
|
||||||
# serve current directory on port 8080
|
|
||||||
|
|
||||||
Mongoose does not detach from terminal. Pressing `Ctrl-C` keys
|
|
||||||
stops the server.
|
|
||||||
|
|
||||||
When started, mongoose first searches for the configuration file.
|
|
||||||
If configuration file is specified explicitly in the command line, then
|
|
||||||
specified configuration file is used.
|
|
||||||
Otherwise, mongoose would search for file `mongoose.conf` in the same directory
|
|
||||||
where binary is located, and use it. Configuration file can be absent.
|
|
||||||
|
|
||||||
Configuration file is a sequence of lines, each line containing
|
|
||||||
command line argument name and it's value. Empty lines and lines beginning
|
|
||||||
with `#` are ignored. Here is the example of `mongoose.conf` file:
|
|
||||||
|
|
||||||
# This is a comment
|
|
||||||
document_root C:\www
|
|
||||||
listening_port 80
|
|
||||||
ssl_certificate C:\mongoose\ssl_cert.pem
|
|
||||||
|
|
||||||
Command line arguments are highest priority and can override
|
|
||||||
configuration file settings. For example, if `mongoose.conf` has line
|
|
||||||
`document_root /var/www`, and mongoose has been started as
|
|
||||||
`mongoose -document_root /etc`, then `/etc` directory will be used as
|
|
||||||
document root.
|
|
||||||
|
|
||||||
Note that configuration options on the command line must start with `-`,
|
|
||||||
and their names are the same as in the config file. Exampli gratia,
|
|
||||||
the following two setups are equivalent:
|
|
||||||
|
|
||||||
$ mongoose -listening_port 1234 -document_root /var/www
|
|
||||||
|
|
||||||
$ cat > mongoose.conf
|
|
||||||
listening_ports 1234
|
|
||||||
document_root /var/www
|
|
||||||
^D
|
|
||||||
$ mongoose
|
|
||||||
|
|
||||||
Mongoose can also be used to modify `.htpasswd` passwords file:
|
|
||||||
|
|
||||||
$ mongoose -A .htpasswd mydomain.com user_name user_password
|
|
||||||
|
|
||||||
Unlike other web servers, mongoose does not require CGI scripts be located in
|
|
||||||
a special directory. CGI scripts can be anywhere. CGI (and SSI) files are
|
|
||||||
recognized by the file name pattern. Mongoose uses shell-like glob
|
|
||||||
patterns. Pattern match starts at the beginning of the string, so essentially
|
|
||||||
patterns are prefix patterns. Syntax is as follows:
|
|
||||||
|
|
||||||
** Matches everything
|
|
||||||
* Matches everything but slash character, '/'
|
|
||||||
? Matches any character
|
|
||||||
$ Matches the end of the string
|
|
||||||
| Matches if pattern on the left side or the right side matches.
|
|
||||||
|
|
||||||
All other characters in the pattern match themselves. Examples:
|
|
||||||
|
|
||||||
# Pattern Meaning
|
|
||||||
**.cgi$ Any string that ends with .cgi
|
|
||||||
/foo Any string that begins with /foo
|
|
||||||
**a$|**b$ Any string that ends with a or b
|
|
||||||
|
|
||||||
To restrict CGI files only to `/cgi-bin/` directory, use this setting:
|
|
||||||
|
|
||||||
$ mongoose -cgi_pattern /cgi-bin/*.cgi # Emulate /cgi-bin/ restriction
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*.exe
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2014 Cesanta Software
|
# Copyright (c) 2014 Cesanta Software
|
||||||
# All rights reserved
|
# All rights reserved
|
||||||
|
|
||||||
SUBDIRS = $(sort $(filter-out csharp/, $(dir $(wildcard */))))
|
SUBDIRS = $(sort $(dir $(wildcard */)))
|
||||||
X = $(SUBDIRS)
|
X = $(SUBDIRS)
|
||||||
|
|
||||||
.PHONY: $(SUBDIRS)
|
.PHONY: $(SUBDIRS)
|
||||||
@@ -12,4 +12,4 @@ $(SUBDIRS):
|
|||||||
@$(MAKE) -C $@
|
@$(MAKE) -C $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
for d in $(SUBDIRS) ; do $(MAKE) -C $$d clean ; done
|
for d in $(SUBDIRS) ; do $(MAKE) -C $$d clean ; done
|
||||||
@@ -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)
|
||||||
@@ -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 = MG_STR("GET");
|
||||||
|
static const struct mg_str s_put_method = MG_STR("PUT");
|
||||||
|
static const struct mg_str s_delele_method = MG_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 = MG_STR("/api/v1");
|
||||||
|
struct http_message *hm = (struct http_message *) ev_data;
|
||||||
|
struct mg_str key;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_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;
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||||
+17
-12
@@ -2,32 +2,37 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>WebSocket Test</title>
|
<title>RESTful API demo</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
|
||||||
background-color: #cde; margin: 0;
|
|
||||||
padding: 0; font: 14px Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
* { outline: none; }
|
* { 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 {
|
div.content {
|
||||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||||
background-color: #fff; border-radius: 1em;
|
background-color: #fff; border-radius: 1em;
|
||||||
}
|
}
|
||||||
label { display: inline-block; min-width: 7em; }
|
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; }
|
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
body { background-color: #fff; }
|
body { background-color: #fff; }
|
||||||
div.content {
|
div.content { width: auto; margin: 0 auto; padding: 1em; }
|
||||||
width: auto; margin: 0 auto; border-radius: 0; padding: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>Mongoose Cookie Base Authentication</h1>
|
<h1>API server</h1>
|
||||||
<p>This is an index page. Authentication succeeded.</p>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# This "makefile" is only intended to prevent errors during main makefile execution
|
||||||
|
all:
|
||||||
@@ -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)
|
||||||
@@ -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 |= MG_F_SEND_AND_CLOSE;
|
||||||
|
} else {
|
||||||
|
nc->flags |= MG_F_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# This "makefile" is only intended to prevent errors during main makefile execution
|
||||||
|
all:
|
||||||
@@ -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.
|
||||||
@@ -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 |= MG_F_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,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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
PROG = captive_dns_server
|
||||||
|
MODULE_CFLAGS=-DMG_ENABLE_DNS_SERVER
|
||||||
|
|
||||||
|
include ../rules.mk
|
||||||
@@ -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 MG_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 == MG_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 MG_DNS_A_RECORD query type.
|
||||||
|
* This indicates that we have (syntetic) answers for MG_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
PROG = coap_client
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA) -DMG_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)
|
||||||
@@ -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 MG_EV_CONNECT: {
|
||||||
|
struct mg_coap_message cm;
|
||||||
|
uint32_t res;
|
||||||
|
|
||||||
|
memset(&cm, 0, sizeof(cm));
|
||||||
|
cm.msg_id = 1;
|
||||||
|
cm.msg_type = MG_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 MG_EV_COAP_ACK:
|
||||||
|
case MG_EV_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
PROG = coap_server
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA) -DMG_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)
|
||||||
@@ -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 MG_EV_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 MG_EV_COAP_NOC:
|
||||||
|
case MG_EV_COAP_ACK:
|
||||||
|
case MG_EV_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,96 +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"
|
|
||||||
"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,59 +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, ofs = 0;
|
|
||||||
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=\"file\" /> <br/>"
|
|
||||||
"<input type=\"submit\" value=\"Upload\" />"
|
|
||||||
"</form>");
|
|
||||||
|
|
||||||
while ((ofs = mg_parse_multipart(conn->content + ofs, conn->content_len - ofs,
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
PROG = http_client
|
||||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
|
||||||
SOURCES = $(PROG).c ../../mongoose.c
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. -DMG_ENABLE_SSL -lssl -lcrypto -pthread $(CFLAGS_EXTRA)
|
||||||
|
|
||||||
unix: $(SOURCES)
|
all: $(PROG)
|
||||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
|
||||||
|
$(PROG): $(SOURCES)
|
||||||
|
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG).exe: $(SOURCES)
|
||||||
|
cl $(SOURCES) /I../.. /MD /Fe$@
|
||||||
|
|
||||||
clean:
|
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
|
* Copyright (c) 2014 Cesanta Software Limited
|
||||||
//
|
* All rights reserved
|
||||||
// This example demostrates how to connect to the remote Web server,
|
*
|
||||||
// download data, process it and send back a reply.
|
* This program fetches HTTP URLs.
|
||||||
|
*/
|
||||||
#include <signal.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "mongoose.h"
|
#include "mongoose.h"
|
||||||
|
|
||||||
static int s_received_signal = 0;
|
static int s_exit_flag = 0;
|
||||||
static struct mg_server *s_server = NULL;
|
static int s_show_headers = 0;
|
||||||
static const char *s_remote_addr = "glosbe.com:80";
|
static const char *s_show_headers_opt = "--show-headers";
|
||||||
|
|
||||||
static void signal_handler(int sig_num) {
|
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||||
signal(sig_num, signal_handler);
|
struct http_message *hm = (struct http_message *) ev_data;
|
||||||
s_received_signal = sig_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
|
||||||
struct mg_connection *client, *orig;
|
|
||||||
|
|
||||||
switch (ev) {
|
switch (ev) {
|
||||||
case MG_AUTH:
|
case MG_EV_CONNECT:
|
||||||
return MG_TRUE;
|
if (* (int *) ev_data != 0) {
|
||||||
|
fprintf(stderr, "connect() failed: %s\n", strerror(* (int *) ev_data));
|
||||||
case MG_CONNECT:
|
s_exit_flag = 1;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case MG_EV_HTTP_REPLY:
|
||||||
|
nc->flags |= MG_F_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:
|
default:
|
||||||
return MG_FALSE;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
s_server = mg_create_server(NULL, ev_handler);
|
struct mg_mgr mgr;
|
||||||
|
int i;
|
||||||
|
|
||||||
mg_set_option(s_server, "listening_port", "8080");
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
|
||||||
// Setup signal handlers
|
/* Process command line arguments */
|
||||||
signal(SIGTERM, signal_handler);
|
for (i = 1; i < argc; i++) {
|
||||||
signal(SIGINT, signal_handler);
|
if (strcmp(argv[i], s_show_headers_opt) == 0) {
|
||||||
|
s_show_headers = 1;
|
||||||
printf("Listening on port %s\n", mg_get_option(s_server, "listening_port"));
|
} else if (strcmp(argv[i], "--hexdump") == 0 && i + 1 < argc) {
|
||||||
while (s_received_signal == 0) {
|
mgr.hexdump_file = argv[++i];
|
||||||
mg_poll_server(s_server, 1000);
|
} 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;
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -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 MG_EV_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 |= MG_F_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;
|
||||||
|
}
|
||||||
@@ -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 -DMG_ENABLE_SSL -lssl -lcrypto
|
||||||
|
EXPOSE 8000
|
||||||
|
VOLUME ["/mongoose/certs"]
|
||||||
|
ENTRYPOINT ["/mongoose/load_balancer"]
|
||||||
@@ -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 += -DMG_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 += -DMG_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$@ /DMG_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)
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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 MG_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 |= MG_F_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 < MG_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 MG_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 MG_EV_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 |= MG_F_CLOSE_IMMEDIATELY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MG_EV_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 = MG_F_CLOSE_IMMEDIATELY;
|
||||||
|
#ifdef DEBUG
|
||||||
|
write_log("bec=%p evicted\n", bec);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bec->idle_deadline = 0;
|
||||||
|
bec->nc->flags |= MG_F_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 == MG_EV_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 |= MG_F_CLOSE_IMMEDIATELY;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev != MG_EV_POLL) conn->last_activity = now;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_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 |= MG_F_SEND_AND_CLOSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(conn, hm, &conn->client, &conn->backend);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MG_EV_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 MG_EV_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 |= MG_F_SEND_AND_CLOSE;
|
||||||
|
} else {
|
||||||
|
#ifdef DEBUG
|
||||||
|
write_log("conn=%p remains open\n", conn);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MG_EV_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 |= MG_F_SEND_AND_CLOSE;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MG_EV_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 |= MG_F_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 MG_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 MG_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 MG_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;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
PROG = mqtt_broker
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. -pthread -DMG_ENABLE_SSL -DMG_ENABLE_MQTT_BROKER -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
|
||||||
|
$(PROG): $(SOURCES)
|
||||||
|
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG).exe: $(SOURCES)
|
||||||
|
cl $(SOURCES) /I../.. /DMG_ENABLE_SSL /DMG_ENABLE_MQTT_BROKER /MD /Fe$@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
PROG = mqtt_client
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. -pthread -DMG_ENABLE_SSL -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
|
||||||
|
$(PROG): $(SOURCES)
|
||||||
|
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG).exe: $(SOURCES)
|
||||||
|
cl $(SOURCES) /I../.. /DMG_ENABLE_SSL /MD /Fe$@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
||||||
@@ -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 != MG_EV_POLL)
|
||||||
|
printf("USER HANDLER GOT %d\n", ev);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_CONNECT:
|
||||||
|
mg_set_protocol_mqtt(nc);
|
||||||
|
mg_send_mqtt_handshake(nc, "dummy");
|
||||||
|
break;
|
||||||
|
case MG_EV_MQTT_CONNACK:
|
||||||
|
if (msg->connack_ret_code != MG_EV_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 MG_EV_MQTT_PUBACK:
|
||||||
|
printf("Message publishing acknowledged (msg_id: %d)\n", msg->message_id);
|
||||||
|
break;
|
||||||
|
case MG_EV_MQTT_SUBACK:
|
||||||
|
printf("Subscription acknowledged, forwarding to '/test'\n");
|
||||||
|
break;
|
||||||
|
case MG_EV_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, MG_MQTT_QOS(0), msg->payload.p, msg->payload.len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MG_EV_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 $(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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
PROG = multithreaded_restful_server
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
APP_FLAGS = -DMG_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 == MG_EV_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
PROG = nc
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. -pthread -DMG_ENABLE_SSL -DMG_ENABLE_THREADS -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
|
||||||
|
$(PROG): $(SOURCES)
|
||||||
|
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG).exe: $(SOURCES)
|
||||||
|
cl $(SOURCES) /I../.. /DMG_ENABLE_SSL /MD /Fe$@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
||||||
@@ -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 |= MG_F_SEND_AND_CLOSE;
|
||||||
|
if (nc->send_mbuf.len <= 0) {
|
||||||
|
nc->flags |= MG_F_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 MG_EV_ACCEPT:
|
||||||
|
case MG_EV_CONNECT:
|
||||||
|
mg_start_thread(stdio_thread_func, nc->mgr);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MG_EV_CLOSE:
|
||||||
|
s_received_signal = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MG_EV_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 $(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,212 +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 *cert = NULL, *ca_cert = NULL, *port = "2014";
|
|
||||||
const char *mitm = NULL, *dump = NULL, *root = "proxy_web_root";
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Parse command line options
|
|
||||||
for (i = 1; i < argc; i++) {
|
|
||||||
if (strcmp(argv[i], "-ca_cert") == 0 && i + 1 < argc) {
|
|
||||||
ca_cert = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) {
|
|
||||||
cert = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-port") == 0 && i + 1 < argc) {
|
|
||||||
port = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-mitm") == 0 && i + 1 < argc) {
|
|
||||||
mitm = 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, "ssl_certificate", cert);
|
|
||||||
setopt(s_server, "ssl_ca_certificate", ca_cert);
|
|
||||||
setopt(s_server, "hexdump_file", dump);
|
|
||||||
setopt(s_server, "ssl_mitm_certs", mitm);
|
|
||||||
|
|
||||||
// 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-----
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
PROG = publish_subscribe
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. -DMG_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)
|
||||||
@@ -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 == MG_EV_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 == MG_EV_CONNECT) {
|
||||||
|
if (conn->flags & MG_F_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 == MG_EV_RECV) {
|
||||||
|
if (conn->flags & MG_F_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 == MG_EV_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 |= MG_F_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
NS=../../mongoose.c
|
||||||
|
FLAGS = ../../mongoose.c -I../..
|
||||||
|
CFLAGS=-W -Wall -DMG_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)
|
||||||
@@ -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
|
||||||
|
----
|
||||||
@@ -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 MG_F_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 & MG_F_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 & MG_F_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 MG_EV_HTTP_REQUEST:
|
||||||
|
if (mg_vcmp(&hm->uri, "/mjpg") == 0) {
|
||||||
|
nc->flags |= MG_F_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 MG_EV_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;
|
||||||
|
}
|
||||||
@@ -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 MG_EV_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 MG_EV_CLOSE:
|
||||||
|
printf("Connection %p closed\n", nc);
|
||||||
|
client = NULL;
|
||||||
|
break;
|
||||||
|
case MG_EV_POLL:
|
||||||
|
send_mjpg_frame(nc, s_mjpg_file);
|
||||||
|
break;
|
||||||
|
case MG_EV_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;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../docs/arch.png
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Framework7 0.9.6
|
||||||
|
* Full Featured HTML Framework For Building iOS 7 Apps
|
||||||
|
*
|
||||||
|
* http://www.idangero.us/framework7
|
||||||
|
*
|
||||||
|
* Copyright 2014, Vladimir Kharlampidi
|
||||||
|
* The iDangero.us
|
||||||
|
* http://www.idangero.us/
|
||||||
|
*
|
||||||
|
* Licensed under MIT
|
||||||
|
*
|
||||||
|
* Released on: September 13, 2014
|
||||||
|
*/
|
||||||
|
html,body{position:relative;height:100%;width:100%;overflow-x:hidden}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;margin:0;padding:0;color:#000;font-size:14px;line-height:1.4;width:100%;-webkit-text-size-adjust:100%;background:#fff;overflow:hidden}@media all and (width:1024px) and (height:691px) and (orientation:landscape){html,body{height:671px}}@media all and (width:1024px) and (height:692px) and (orientation:landscape){html,body{height:672px}}*{-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-touch-callout:none}a,input,textarea,select{outline:0}a{text-decoration:none;color:#007aff}p{margin:1em 0}.row{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;-webkit-box-lines:multiple;-moz-box-lines:multiple;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:start;-ms-flex-align:start;-webkit-align-items:flex-start;align-items:flex-start}.row>[class*=col-]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row .col-100{width:100%;width:-webkit-calc((100% - 15px*0) / 1);width:calc((100% - 15px*0) / 1)}.row.no-gutter .col-100{width:100%}.row .col-95{width:95%;width:-webkit-calc((100% - 15px*0.05263157894736836) / 1.0526315789473684);width:calc((100% - 15px*0.05263157894736836) / 1.0526315789473684)}.row.no-gutter .col-95{width:95%}.row .col-90{width:90%;width:-webkit-calc((100% - 15px*0.11111111111111116) / 1.1111111111111112);width:calc((100% - 15px*0.11111111111111116) / 1.1111111111111112)}.row.no-gutter .col-90{width:90%}.row .col-85{width:85%;width:-webkit-calc((100% - 15px*0.17647058823529416) / 1.1764705882352942);width:calc((100% - 15px*0.17647058823529416) / 1.1764705882352942)}.row.no-gutter .col-85{width:85%}.row .col-80{width:80%;width:-webkit-calc((100% - 15px*0.25) / 1.25);width:calc((100% - 15px*0.25) / 1.25)}.row.no-gutter .col-80{width:80%}.row .col-75{width:75%;width:-webkit-calc((100% - 15px*0.33333333333333326) / 1.3333333333333333);width:calc((100% - 15px*0.33333333333333326) / 1.3333333333333333)}.row.no-gutter .col-75{width:75%}.row .col-66{width:66.66666666666666%;width:-webkit-calc((100% - 15px*0.5000000000000002) / 1.5000000000000002);width:calc((100% - 15px*0.5000000000000002) / 1.5000000000000002)}.row.no-gutter .col-66{width:66.66666666666666%}.row .col-60{width:60%;width:-webkit-calc((100% - 15px*0.6666666666666667) / 1.6666666666666667);width:calc((100% - 15px*0.6666666666666667) / 1.6666666666666667)}.row.no-gutter .col-60{width:60%}.row .col-50{width:50%;width:-webkit-calc((100% - 15px*1) / 2);width:calc((100% - 15px*1) / 2)}.row.no-gutter .col-50{width:50%}.row .col-40{width:40%;width:-webkit-calc((100% - 15px*1.5) / 2.5);width:calc((100% - 15px*1.5) / 2.5)}.row.no-gutter .col-40{width:40%}.row .col-33{width:33.333333333333336%;width:-webkit-calc((100% - 15px*2) / 3);width:calc((100% - 15px*2) / 3)}.row.no-gutter .col-33{width:33.333333333333336%}.row .col-25{width:25%;width:-webkit-calc((100% - 15px*3) / 4);width:calc((100% - 15px*3) / 4)}.row.no-gutter .col-25{width:25%}.row .col-20{width:20%;width:-webkit-calc((100% - 15px*4) / 5);width:calc((100% - 15px*4) / 5)}.row.no-gutter .col-20{width:20%}.row .col-15{width:15%;width:-webkit-calc((100% - 15px*5.666666666666667) / 6.666666666666667);width:calc((100% - 15px*5.666666666666667) / 6.666666666666667)}.row.no-gutter .col-15{width:15%}.row .col-10{width:10%;width:-webkit-calc((100% - 15px*9) / 10);width:calc((100% - 15px*9) / 10)}.row.no-gutter .col-10{width:10%}.row .col-5{width:5%;width:-webkit-calc((100% - 15px*19) / 20);width:calc((100% - 15px*19) / 20)}.row.no-gutter .col-5{width:5%}@media all and (min-width:768px){.row .tablet-100{width:100%;width:-webkit-calc((100% - 15px*0) / 1);width:calc((100% - 15px*0) / 1)}.row.no-gutter .tablet-100{width:100%}.row .tablet-95{width:95%;width:-webkit-calc((100% - 15px*0.05263157894736836) / 1.0526315789473684);width:calc((100% - 15px*0.05263157894736836) / 1.0526315789473684)}.row.no-gutter .tablet-95{width:95%}.row .tablet-90{width:90%;width:-webkit-calc((100% - 15px*0.11111111111111116) / 1.1111111111111112);width:calc((100% - 15px*0.11111111111111116) / 1.1111111111111112)}.row.no-gutter .tablet-90{width:90%}.row .tablet-85{width:85%;width:-webkit-calc((100% - 15px*0.17647058823529416) / 1.1764705882352942);width:calc((100% - 15px*0.17647058823529416) / 1.1764705882352942)}.row.no-gutter .tablet-85{width:85%}.row .tablet-80{width:80%;width:-webkit-calc((100% - 15px*0.25) / 1.25);width:calc((100% - 15px*0.25) / 1.25)}.row.no-gutter .tablet-80{width:80%}.row .tablet-75{width:75%;width:-webkit-calc((100% - 15px*0.33333333333333326) / 1.3333333333333333);width:calc((100% - 15px*0.33333333333333326) / 1.3333333333333333)}.row.no-gutter .tablet-75{width:75%}.row .tablet-66{width:66.66666666666666%;width:-webkit-calc((100% - 15px*0.5000000000000002) / 1.5000000000000002);width:calc((100Line truncated
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Framework7 0.9.6
|
||||||
|
* Full Featured HTML Framework For Building iOS 7 Apps
|
||||||
|
*
|
||||||
|
* http://www.idangero.us/framework7
|
||||||
|
*
|
||||||
|
* Copyright 2014, Vladimir Kharlampidi
|
||||||
|
* The iDangero.us
|
||||||
|
* http://www.idangero.us/
|
||||||
|
*
|
||||||
|
* Licensed under MIT
|
||||||
|
*
|
||||||
|
* Released on: September 13, 2014
|
||||||
|
*/
|
||||||
|
!function(){"use strict";window.Framework7=function(b){function c(a){var b=a.replace(/^./,function(a){return a.toUpperCase()});l["onPage"+b]=function(b,c){return l.onPage(a,b,c)}}function d(a,b,c){b=n(b);var d=b.find(a);return d.length>1&&("string"==typeof c.selector&&(d=b.find(c.selector+" "+a)),d.length>1&&(d=b.find("."+l.params.viewMainClass+" "+a))),1===d.length?d:void 0}function e(a,b,c){"to-left"===c&&(a.removeClass("page-on-center").addClass("page-from-center-to-left"),b.addClass("page-from-right-to-center")),"to-right"===c&&(a.removeClass("page-on-left").addClass("page-from-left-to-center"),b.removeClass("page-on-center").addClass("page-from-center-to-right"))}function f(a,b,c){"to-left"===c&&(b.removeClass("navbar-on-right").addClass("navbar-from-right-to-center"),b.find(".sliding").each(function(){var a=n(this);a.transform("translate3d(0px,0,0)"),l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d(0px,0,0)")}),a.removeClass("navbar-on-center").addClass("navbar-from-center-to-left"),a.find(".sliding").each(function(){var a=n(this);l.params.animateNavBackIcon&&(a.hasClass("center")&&b.find(".sliding.left .back .icon").length>0&&(this.f7NavbarLeftOffset+=b.find(".sliding.left .back span")[0].offsetLeft),a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d("+-this.f7NavbarLeftOffset+"px,0,0)")),a.transform("translate3d("+this.f7NavbarLeftOffset+"px,0,0)")})),"to-right"===c&&(a.removeClass("navbar-on-left").addClass("navbar-from-left-to-center"),a.find(".sliding").each(function(){var a=n(this);a.transform("translate3d(0px,0,0)"),l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d(0px,0,0)")}),b.removeClass("navbar-on-center").addClass("navbar-from-center-to-right"),b.find(".sliding").each(function(){var a=n(this);l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d("+-this.f7NavbarRightOffset+"px,0,0)"),a.transform("translate3d("+this.f7NavbarRightOffset+"px,0,0)")}))}function g(a,b,c,e){var f,g,h,i,j,k,m,o,p=n(a.container),q=n(a.pagesContainer);if(l.pluginHook("loadPage",a,b,c),l._tempDomElement.innerHTML="",b||"string"==typeof c)l._tempDomElement.innerHTML=c;else if("length"in c&&c.length>1)for(var r=0;r<c.length;r++)n(l._tempDomElement).append(c[r]);else n(l._tempDomElement).append(c);if(o=e.reloadPrevious?"left":"center",f=d(".page",l._tempDomElement,a),!f)return void(a.allowPageChange=!0);if(f.addClass("page-on-"+o),h=q.children(".page:not(.cached)"),e.reloadPrevious&&1===h.length)return void(a.allowPageChange=!0);g=h.eq(h.length-1),a.params.dynamicNavbar&&(m=!0,j=d(".navbar-inner",l._tempDomElement,a),j||(m=!1),k=p.find(".navbar"),i=k.find(".navbar-inner:last-child")),m&&j.addClass("navbar-on-"+o),b||(b="#content-"+a.history.length,a.params.domCache||(a.contentCache[b]=c)),a.url=b;var s=a.history[a.history.length-(e.reloadPrevious?2:1)];0===s.indexOf("#content")&&s in a.contentCache&&(a.contentCache[s]=null,delete a.contentCache[s]),a.history[a.history.length-(e.reloadPrevious?2:1)]=b,e.reloadPrevious?(g=g.prev(".page"),f.insertBefore(g),m&&(i=i.prev(".navbar-inner"),j.insertAfter(i))):(q.append(f[0]),m&&k.append(j[0])),l.pageRemoveCallback(a,g[0],o),g.remove(),m&&i.remove(),l.pageInitCallback(a,f[0],b,o,m?j[0]:void 0),m&&l.navbarInitCallback(a,f[0],k[0],j[0],b,o),a.allowPageChange=!0}function h(a,b,c,g){function h(){a.allowPageChange=!0,i.removeClass("page-from-right-to-center page-on-right").addClass("page-on-center"),j.removeClass("page-from-center-to-left page-on-center").addClass("page-on-left"),r&&(p.removeClass("navbar-from-right-to-center navbar-on-right").addClass("navbar-on-center"),o.removeClass("navbar-from-center-to-left navbar-on-center").addClass("navbar-on-left")),l.pageAnimCallbacks("after",a,{pageContainer:i[0],url:b,position:"right",oldPage:j,newPage:i}),l.params.pushState&&l.pushStateClearQueue(),a.params.swipeBackPage||a.params.preloadPreviousPage||(a.params.domCache?(j.addClass("cached"),o.addClass("cached")):(j.remove(),o.remove())),a.params.uniqueHistory&&w&&a.refreshPreviousPage()}var i,j,k,m,o,p,q,r,s=n(a.container),t=n(a.pagesContainer),u=g.animatePages;if("undefined"==typeof u&&(u=a.params.animatePages),l.pluginHook("loadPage",a,b,c),l._tempDomElement.innerHTML="",b||"string"==typeof c)l._tempDomElement.innerHTML=c;else if("length"in c&&c.length>1)for(var v=0;v<c.length;v++)n(l._tempDomElement).append(c[v]);else n(l._tempDomElement).append(c);if(i=d(".page",l._tempDomElement,a),!i)return void(a.allowPageChange=!0);if(i.addClass("page-on-right"),k=t.children(".page:not(.cached)"),k.length>1){for(m=0;m<k.length-2;m++)a.params.domCache?n(k[m]).addClass("cached"):(l.pageRemoveCallback(a,k[m],"left"),n(k[m]).remove());a.params.domCache?n(k[m]).addClass("cached"):(l.pageRemoveCallback(a,k[m],"left"),n(k[m]).removeLine truncated
|
||||||
|
var e=a.title?'<div class="modal-title">'+a.title+"</div>":"",f=a.title?'<div class="modal-text">'+a.text+"</div>":"",g=a.afterText?a.afterText:"",h=a.buttons&&0!==a.buttons.length?"":"modal-no-buttons";b='<div class="modal '+h+'"><div class="modal-inner">'+(e+f+g)+'</div><div class="modal-buttons">'+c+"</div></div>"}s.innerHTML=b;var i=n(s).children();return n("body").append(i[0]),i.find(".modal-button").each(function(b,c){n(c).on("click",function(c){a.buttons[b].close!==!1&&l.closeModal(i),a.buttons[b].onClick&&a.buttons[b].onClick(i,c),a.onClick&&a.onClick(i,b)})}),l.openModal(i),i[0]},l.alert=function(a,b,c){return"function"==typeof b&&(c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,buttons:[{text:l.params.modalButtonOk,bold:!0,onClick:c}]})},l.confirm=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,buttons:[{text:l.params.modalButtonCancel,onClick:d},{text:l.params.modalButtonOk,bold:!0,onClick:c}]})},l.prompt=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="text" class="modal-text-input">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){0===b&&d&&d(n(a).find(".modal-text-input").val()),1===b&&c&&c(n(a).find(".modal-text-input").val())}})},l.modalLogin=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="text" name="modal-username" placeholder="'+l.params.modalUsernamePlaceholder+'" class="modal-text-input modal-text-input-double"><input type="password" name="modal-password" placeholder="'+l.params.modalPasswordPlaceholder+'" class="modal-text-input modal-text-input-double">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){var e=n(a).find('.modal-text-input[name="modal-username"]').val(),f=n(a).find('.modal-text-input[name="modal-password"]').val();0===b&&d&&d(e,f),1===b&&c&&c(e,f)}})},l.modalPassword=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="password" name="modal-password" placeholder="'+l.params.modalPasswordPlaceholder+'" class="modal-text-input">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){var e=n(a).find('.modal-text-input[name="modal-password"]').val();0===b&&d&&d(e),1===b&&c&&c(e)}})},l.showPreloader=function(a){return l.modal({title:a||l.params.modalPreloaderTitle,text:'<div class="preloader"></div>'})},l.hidePreloader=function(){l.closeModal(".modal.modal-in")},l.showIndicator=function(){n("body").append('<div class="preloader-indicator-overlay"></div><div class="preloader-indicator-modal"><span class="preloader preloader-white"></span></div>')},l.hideIndicator=function(){n(".preloader-indicator-overlay, .preloader-indicator-modal").remove()},l.actions=function(a,b){var c,d,e,f=!1;1===arguments.length?b=a:l.device.ios?l.device.ipad&&(f=!0):n(window).width()>=768&&(f=!0),b=b||[],b.length>0&&!n.isArray(b[0])&&(b=[b]);var g;if(f){var h='<div class="popover actions-popover"><div class="popover-inner">{{#each this}}<div class="list-block"><ul>{{#each this}}{{#if label}}<li class="actions-popover-label {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}">{{text}}</li>{{else}}<li><a href="#" class="item-link list-button {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}">{{text}}</a></li>{{/if}}{{/each}}</ul></div>{{/each}}</div></div>';l._compiledTemplates.actionsPopover||(l._compiledTemplates.actionsPopover=o.compile(h));var i=l._compiledTemplates.actionsPopover(b);c=n(l.popover(i,a,!0)),d=".list-block ul",e=".list-button"}else{if(l.params.modalActionsTemplate)l._compiledTemplates.actions||(l._compiledTemplates.actions=o.compile(l.params.modalActionsTemplate)),g=l._compiledTemplates.actions(b);else{for(var j="",k=0;k<b.length;k++)for(var m=0;m<b[k].length;m++){0===m&&(j+='<div class="actions-modal-group">');var p=b[k][m],q=p.label?"actions-modal-label":"actions-modal-button";p.bold&&(q+=" actions-modal-button-bold"),p.color&&(q+=" color-"+p.color),j+='<span class="'+q+'">'+p.text+"</span>",m===b[k].length-1&&(j+="</div>")}g='<div class="actions-modal">'+j+"</div>"}s.innerHTML=g,c=n(s).children(),n("body").append(c[0]),d=".actions-modal-group",e=".actions-modal-button"}var r=c.find(d);return r.each(function(a,d){var g=a;n(d).children().each(function(a,d){var h,i=a,j=b[g][i];!f&&n(d).is(e)&&(h=n(d)),f&&n(d).find(e).length>0&&(h=n(d).find(e)),h&&h.on("click",function(a){j.close!==!1&&l.closeModal(c),j.onClick&&j.onClick(c,a)})})}),f||l.oLine truncated
|
||||||
|
}function b(){var a=B.parents(".page-content, .panel");return 0===a.length?!1:("yes"!==a.prop("scrollHandlerSet")&&(a.on("scroll",function(){clearTimeout(C)}),a.prop("scrollHandlerSet","yes")),!0)}function c(){B.addClass("active-state")}function d(){B.removeClass("active-state")}function e(a){var b="button checkbox file image radio submit input textarea".split(" ");return document.activeElement&&a!==document.activeElement&&document.activeElement!==document.body?b.indexOf(a.nodeName.toLowerCase())>=0?!1:!0:!1}function f(a){return"input"===a.nodeName.toLowerCase()&&"file"===a.type?!1:a.className.indexOf("no-fastclick")>=0?!1:!0}function g(a){if(document.activeElement===a)return!1;var b=a.nodeName.toLowerCase(),c="button checkbox file image radio submit".split(" ");return a.disabled||a.readOnly?!1:"textarea"===b?!0:"select"===b?"android"===l.device.os?!1:!0:"input"===b&&c.indexOf(a.type)<0?!0:void 0}function h(a){if(a=n(a),a.is("label")||a.parents("label").length>0){if("android"===l.device.os){var b=l.device.osVersion.split(".");return 1*b[0]>4||1*b[0]===4&&1*b[1]>=4?!1:!0}return!1}return!0}function i(b){a(b).addClass("active-state"),"which"in b&&3===b.which&&setTimeout(function(){n(".active-state").removeClass("active-state")},0)}function j(){n(".active-state").removeClass("active-state")}function k(){n(".active-state").removeClass("active-state")}function m(d){if(A=!1,d.targetTouches.length>1)return!0;if(D=f(d.target),!D)return w=!1,!0;if("ios"===l.device.os){var g=window.getSelection();if(g.rangeCount&&(!g.isCollapsed||document.activeElement===g.focusNode))return x=!0,!0;x=!1}"android"===l.device.os&&e(d.target)&&document.activeElement.blur(),w=!0,v=d.target,u=(new Date).getTime(),s=d.targetTouches[0].pageX,t=d.targetTouches[0].pageY,"ios"===l.device.os&&(y=void 0,n(v).parents().each(function(){var a=this;a.scrollHeight>a.offsetHeight&&!y&&(y=a,y.f7ScrollTop=y.scrollTop)})),d.timeStamp-z<200&&d.preventDefault(),l.params.activeState&&(B=a(d),b(d)?C=setTimeout(c,80):c())}function o(a){if(w){var b=!1,c=l.params.fastClicksDistanceThreshold;if(c){var e=a.targetTouches[0].pageX,f=a.targetTouches[0].pageY;(Math.abs(e-s)>c||Math.abs(f-t)>c)&&(b=!0)}else b=!0;b&&(w=!1,v=null,A=!0),l.params.activeState&&(clearTimeout(C),d())}}function p(a){if(clearTimeout(C),!w)return!x&&D&&a.preventDefault(),!0;if(document.activeElement===a.target)return!0;if(x||a.preventDefault(),a.timeStamp-z<200)return!0;if(z=a.timeStamp,u=0,w=!1,"ios"===l.device.os&&y&&y.scrollTop!==y.f7ScrollTop)return!1;l.params.activeState&&(c(),setTimeout(d,0)),g(v)&&v.focus(),a.preventDefault();var b=a.changedTouches[0],e=document.createEvent("MouseEvents"),f="click";return"android"===l.device.os&&"select"===v.nodeName.toLowerCase()&&(f="mousedown"),e.initMouseEvent(f,!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null),e.forwardedTouchEvent=!0,v.dispatchEvent(e),!1}function q(){w=!1,v=null}function r(a){var b=!1;return w?(v=null,w=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(v||(b=!0),document.activeElement===v&&(b=!0),a.forwardedTouchEvent&&(b=!0),a.cancelable||(b=!0),b||(a.stopImmediatePropagation(),a.stopPropagation(),v?(h(v)||A)&&a.preventDefault():a.preventDefault(),v=null),b)}l.params.activeState&&n("html").addClass("watch-active-state");var s,t,u,v,w,x,y,z,A,B,C,D;l.support.touch?(document.addEventListener("click",r,!0),l.addGlobalEventListener("touchstart",m),l.addGlobalEventListener("touchmove",o),l.addGlobalEventListener("touchend",p),l.addGlobalEventListener("touchcancel",q)):l.params.activeState&&(l.addGlobalEventListener("mousedown",i),l.addGlobalEventListener("mousemove",j),l.addGlobalEventListener("mouseup",k))},l.initClickEvents=function(){function a(a){function b(a){return"false"===a?!1:"true"===a?!0:void 0}var c=n(this),d=c.attr("href"),e="a"===c[0].nodeName.toLowerCase();if(e)for(var f=0;f<l.params.externalLinks.length;f++){if(c.hasClass(l.params.externalLinks[f]))return;if(c[0].rel===l.params.externalLinks[f])return}if(c.hasClass("smart-select")&&l.smartSelectOpen&&l.smartSelectOpen(c),c.hasClass("open-panel")&&l.openPanel(1===n(".panel").length?n(".panel").hasClass("panel-left")?"left":"right":"right"===c.attr("data-panel")?"right":"left"),c.hasClass("close-panel")&&l.closePanel(),c.hasClass("panel-overlay")&&l.params.panelsCloseByOutside&&l.closePanel(),c.hasClass("open-popover")){var g;g=c.attr("data-popover")?c.attr("data-popover"):".popover",l.popover(g,c)}c.hasClass("close-popover")&&l.closeModal(".popover.modal-in");var h;c.hasClass("open-popup")&&(h=c.attr("data-popup")?c.attr("data-popup"):".popup",l.popup(h)),c.hasClass("close-popup")&&(h=c.attr("data-popup")?c.attr("data-popup"):".popup.modal-in",l.closeModal(h));var i;if(c.hasClass("open-login-screen")&&(i=c.attr("data-login-screen")?c.attr("data-login-screen"):".login-screen",l.loginScreen(i)),c.hasClass("close-login-screen")&&l.closeModal(".login-screen.modal-in"),c.hasClass("modal-overlay")&&(n(".modal.modal-in").length>0&&l.params.modalCloseByLine truncated
|
||||||
|
l.addNotification=function(a){if(a){"undefined"==typeof a.media&&(a.media=l.params.notificationMedia),"undefined"==typeof a.title&&(a.title=l.params.notificationTitle),"undefined"==typeof a.subtitle&&(a.subtitle=l.params.notificationSubtitle),"undefined"==typeof a.closeIcon&&(a.closeIcon=l.params.notificationCloseIcon),"undefined"==typeof a.hold&&(a.hold=l.params.notificationHold),"undefined"==typeof a.closeOnClick&&(a.closeOnClick=l.params.notificationCloseOnClick),v||(v=document.createElement("div"));var b=n(".notifications");0===b.length&&(n("body").append('<div class="notifications list-block media-list"><ul></ul></div>'),b=n(".notifications"));var c,d=b.children("ul");c=a.custom?"<li>"+a.custom+"</li>":'<li class="notification-item notification-hidden"><div class="item-content">'+(a.media?'<div class="item-media">'+a.media+"</div>":"")+'<div class="item-inner"><div class="item-title-row">'+(a.title?'<div class="item-title">'+a.title+"</div>":"")+(a.closeIcon?'<div class="item-after"><a href="#" class="close-notification"><span></span></a></div>':"")+"</div>"+(a.subtitle?'<div class="item-subtitle">'+a.subtitle+"</div>":"")+(a.message?'<div class="item-text">'+a.message+"</div>":"")+"</div></div></li>",v.innerHTML=c;var e=n(v).children();e.on("click",function(b){var c=!1;n(b.target).is(".close-notification")||n(b.target).parents(".close-notification").length>0?c=!0:(a.onClick&&a.onClick(b,e[0]),a.closeOnClick&&(c=!0)),c&&l.closeNotification(e[0])}),a.onClose&&e.data("f7NotificationOnClose",function(){a.onClose(e[0])}),a.additionalClass&&e.addClass(a.additionalClass),a.hold&&setTimeout(function(){e.length>0&&l.closeNotification(e[0])},a.hold),d.prepend(e[0]),b.show();var f=e.height();e.css("marginTop",-f+"px"),e.transition(0);{e[0].clientLeft}return e.transition(""),e.css("marginTop","0px"),b.transform("translate3d(0, 0,0)"),e.removeClass("notification-hidden"),e[0]}},l.closeNotification=function(a){if(a=n(a),0!==a.length&&!a.hasClass("notification-item-removing")){var b=n(".notifications"),c=a.height();a.css("height",c+"px").transition(0);{a[0].clientLeft}a.css("height","0px").transition("").addClass("notification-item-removing"),a.data("f7NotificationOnClose")&&a.data("f7NotificationOnClose")(),0===b.find(".notification-item:not(.notification-item-removing)").length&&b.transform(""),a.addClass("notification-hidden").transitionEnd(function(){a.remove(),0===b.find(".notification-item").length&&b.hide()})}};var w=[];return l.initPlugins=function(){for(var a in l.plugins){var b=l.plugins[a](l,l.params[a]);b&&w.push(b)}},l.pluginHook=function(a){for(var b=0;b<w.length;b++)w[b].hooks&&a in w[b].hooks&&w[b].hooks[a](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])},l.pluginPrevent=function(a){for(var b=!1,c=0;c<w.length;c++)w[c].prevents&&a in w[c].prevents&&w[c].prevents[a](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])&&(b=!0);return b},l.pluginProcess=function(a,b){for(var c=b,d=0;d<w.length;d++)w[d].preprocess&&process in w[d].preprocess&&(c=w[d].preprocess[process](b,arguments[2],arguments[3],arguments[4],arguments[5],arguments[6]));return c},l.init=function(){l.initPlugins&&l.initPlugins(),l.getDeviceInfo&&l.getDeviceInfo(),l.initFastClicks&&l.params.fastClicks&&l.initFastClicks(),l.initClickEvents&&l.initClickEvents(),n(".page").each(function(){var a=n(this),b=a.parents("."+l.params.viewClass);if(0!==b.length){var c=b[0].f7View||!1,d=c&&c.url?c.url:!1;b&&b.attr("data-page",a.attr("data-page")||void 0),l.pageInitCallback(c,this,d,"center")}}),l.initResize&&l.initResize(),l.initPushState&&l.params.pushState&&l.initPushState(),l.initSwipeout&&l.params.swipeout&&l.initSwipeout(),l.initSortable&&l.params.sortable&&l.initSortable(),l.initSwipePanels&&l.params.swipePanel&&l.initSwipePanels(),l.params.onAppInit&&l.params.onAppInit(),l.pluginHook("appInit")},l.params.init&&l.init(),l};var a=function(){var a=function(a){var b=this,c=0;for(c=0;c<a.length;c++)b[c]=a[c];return b.length=a.length,this},b=function(b,c){var d=[],e=0;if(b&&!c&&b instanceof a)return b;if(b)if("string"==typeof b){var f=(c||document).querySelectorAll(b);for(e=0;e<f.length;e++)d.push(f[e])}else if(b.nodeType||b===window||b===document)d.push(b);else if(b.length>0&&b[0].nodeType)for(e=0;e<b.length;e++)d.push(b[e]);return new a(d)};a.prototype={addClass:function(a){if("undefined"==typeof a)return this;for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.add(b[c]);return this},removeClass:function(a){for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.remove(b[c]);return this},hasClass:function(a){return this[0]?this[0].classList.contains(a):!1},toggleClass:function(a){for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.toggle(b[c]);return this},attr:function(a,b){if("undefined"==typeof b)return this[0]?this[0].getAttribute(a):void 0;for(var c=0;c<this.length;c++)this[c].setAttribute(a,b);return tLine truncated
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
@@ -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 MG_EV_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 MG_EV_HTTP_REPLY:
|
||||||
|
printf("Got reply:\n%.*s\n", (int) hm->body.len, hm->body.p);
|
||||||
|
nc->flags |= MG_F_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
PROG = restful_server
|
||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -W -Wall -I../.. $(CFLAGS_EXTRA)
|
||||||
|
|
||||||
|
ifeq ($(SSL), openssl)
|
||||||
|
CFLAGS += -DMG_ENABLE_SSL -lssl -lcrypto -lcrypto
|
||||||
|
else ifeq ($(SSL), krypton)
|
||||||
|
CFLAGS += -DMG_ENABLE_SSL ../../../krypton/krypton.c
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
|
||||||
|
$(PROG): $(SOURCES)
|
||||||
|
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG).exe: $(SOURCES)
|
||||||
|
cl $(SOURCES) /I../.. /MD /DMG_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>
|
<title>RESTful API demo</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
* { outline: none; font: 16px/1.4 Helvetica, Arial, sans-serif; }
|
* { outline: none; }
|
||||||
body {
|
body {
|
||||||
background-color: #cde; margin: 0;
|
background-color: #789; margin: 0;
|
||||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||||
|
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
div.content {
|
div.content {
|
||||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||||
@@ -23,13 +24,13 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</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">
|
<script language="javascript" type="text/javascript">
|
||||||
jQuery(function() {
|
jQuery(function() {
|
||||||
|
|
||||||
$(document).on('keyup', '#n1, #n2', function() {
|
$(document).on('keyup', '#n1, #n2', function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/sum',
|
url: '/api/v1/sum',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: { n1: $('#n1').val(), n2: $('#n2').val() },
|
data: { n1: $('#n1').val(), n2: $('#n2').val() },
|
||||||
@@ -47,10 +48,10 @@
|
|||||||
<h1>RESTful API demo.</h1>
|
<h1>RESTful API demo.</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This page demonstrates how Mongoose web server could be used to implement
|
This page demonstrates how Mongoose could be used to implement
|
||||||
RESTful APIs. Enter numbers below, and press Submit. Browser will send
|
RESTful APIs. Start typing numbers into the text fields below.
|
||||||
two numbers to <tt>/api/sum</tt> URI, Mongoose calclulates the sum of
|
Browser sends two numbers to <tt>/api/v1/sum</tt> URI.
|
||||||
two and returns the result.
|
Web server calclulates the sum and returns the result.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* 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 MG_EV_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;
|
||||||
|
#ifdef MG_ENABLE_SSL
|
||||||
|
const char *ssl_cert = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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 MG_DISABLE_CGI
|
||||||
|
} else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
|
||||||
|
s_http_server_opts.cgi_interpreter = argv[++i];
|
||||||
|
#endif
|
||||||
|
#ifdef MG_ENABLE_SSL
|
||||||
|
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
|
||||||
|
ssl_cert = argv[++i];
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MG_ENABLE_SSL
|
||||||
|
if (ssl_cert != NULL) {
|
||||||
|
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
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user