mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63be1ebd38 | ||
|
|
796dc18cfb | ||
|
|
14e116553f | ||
|
|
c82ccd6cf7 | ||
|
|
0adb080319 | ||
|
|
dbb38fffae | ||
|
|
672a68219a | ||
|
|
22cb0efd00 | ||
|
|
a68d3d63b2 | ||
|
|
cf48c7280a | ||
|
|
1295973028 | ||
|
|
d5b9eda0f2 | ||
|
|
41c078d25c | ||
|
|
5614224e7d | ||
|
|
9ba44cb0d5 | ||
|
|
c18828af7b | ||
|
|
8a5f8439b4 | ||
|
|
8f0374569e | ||
|
|
09dde636d2 | ||
|
|
d347247bf3 | ||
|
|
a6c66fb669 | ||
|
|
b4dbc825b1 | ||
|
|
4a0cc8223e | ||
|
|
7db10857b3 | ||
|
|
7e927b43ad | ||
|
|
9eea072f2a | ||
|
|
aa2ff674db | ||
|
|
de24bfdf2f | ||
|
|
2a084c3d98 | ||
|
|
db8df41396 | ||
|
|
bda05d9372 | ||
|
|
59bc2af4d6 | ||
|
|
00a23ee330 | ||
|
|
51ad50a641 | ||
|
|
148e1926e8 | ||
|
|
96c023df41 | ||
|
|
44f37f49a1 | ||
|
|
a8a30f6d3f | ||
|
|
ba80b1cd55 | ||
|
|
b56e985ccc | ||
|
|
67521fd9f6 | ||
|
|
8c394f44b8 | ||
|
|
77b4fff268 | ||
|
|
f56a0c6075 | ||
|
|
9ce8486059 | ||
|
|
04a321150a | ||
|
|
d333c3c09e | ||
|
|
81d977c720 | ||
|
|
f477898c7c | ||
|
|
566193a1eb | ||
|
|
5d6eeb5d1d | ||
|
|
d9b9d71b2d | ||
|
|
643ee1df14 | ||
|
|
6c4d37ee68 | ||
|
|
97b79cd04a | ||
|
|
56c6229dd6 | ||
|
|
0fe2dd9b48 | ||
|
|
910aee7f7b | ||
|
|
97063937ff | ||
|
|
7e68b097d1 | ||
|
|
4d41dff4fd | ||
|
|
05082cec88 | ||
|
|
f05733da69 | ||
|
|
3c53909e82 | ||
|
|
1e53ec47a9 | ||
|
|
17802735d7 | ||
|
|
d2eb67a1ae | ||
|
|
3f254dd3fc | ||
|
|
e139bde6ae | ||
|
|
5f62951143 | ||
|
|
2207e87c8f | ||
|
|
5bd59c7b70 | ||
|
|
02f6867a2f | ||
|
|
b93fa3144a | ||
|
|
a68e0d9a3f | ||
|
|
cfe80ba9b7 | ||
|
|
35abfd44dd | ||
|
|
e98b5a691e | ||
|
|
ed24afd172 | ||
|
|
714556de65 | ||
|
|
547ec85b58 | ||
|
|
845d751e87 | ||
|
|
518a7f8a71 | ||
|
|
31323b01bc | ||
|
|
3a138ecc83 | ||
|
|
7294674a8b | ||
|
|
423b978395 | ||
|
|
ecf20ec7b8 | ||
|
|
c948a509fb | ||
|
|
f5ad19d76b | ||
|
|
d665d9e90e | ||
|
|
a5e10450dd | ||
|
|
e9d79b130d | ||
|
|
7951dac9d5 | ||
|
|
6c77351c58 | ||
|
|
7c4cda622f | ||
|
|
d8f8c1cb99 | ||
|
|
d2b9ad570f | ||
|
|
5b36f32996 | ||
|
|
3298030b8e | ||
|
|
6cfc696439 | ||
|
|
a93b3a06dd | ||
|
|
79e8d77ea0 | ||
|
|
d68fab9ee5 | ||
|
|
bda470245e | ||
|
|
0c95290f93 | ||
|
|
8989739d32 | ||
|
|
1112981e6e | ||
|
|
0cf82744ce | ||
|
|
5cdf838325 | ||
|
|
6418cad39f | ||
|
|
46529fd51e | ||
|
|
c09ba40a48 | ||
|
|
47d52896a1 | ||
|
|
d60c99b01c | ||
|
|
b8aeba06a2 | ||
|
|
8a0bc2d614 | ||
|
|
e34747d1ad | ||
|
|
1a6bc7c514 | ||
|
|
ffa0ddfdbf | ||
|
|
ca55bff1cf | ||
|
|
cfee5c4fdb | ||
|
|
e46185068b | ||
|
|
6ef2fea45e | ||
|
|
e244dd84a9 | ||
|
|
5034937a15 | ||
|
|
060045be65 | ||
|
|
9adfe8d484 | ||
|
|
799cc369e0 | ||
|
|
b97d3fea4b | ||
|
|
2a4ca9d5c3 | ||
|
|
747e40e74e | ||
|
|
cf279e7885 | ||
|
|
cbf968735a | ||
|
|
57b35e6007 | ||
|
|
1eaabdd8e5 | ||
|
|
dcd4e660a3 | ||
|
|
96409e74e5 | ||
|
|
e28ee097a1 | ||
|
|
2b4e544867 | ||
|
|
ee042a3481 | ||
|
|
63075f6846 | ||
|
|
7f2e42a06d | ||
|
|
3fbfa33b9a | ||
|
|
179aa75047 | ||
|
|
4658194d08 | ||
|
|
b35657814c | ||
|
|
a2322c77c6 | ||
|
|
b50c183c19 | ||
|
|
a941687db0 | ||
|
|
c2eef52b65 | ||
|
|
3e6cbbde04 | ||
|
|
6400536282 | ||
|
|
776103068f | ||
|
|
93beff3b65 | ||
|
|
22f8d86b05 | ||
|
|
3a9a9c7ad0 | ||
|
|
13238b1ad2 | ||
|
|
56f661e9c6 | ||
|
|
5351f46efb | ||
|
|
0cd3335b78 | ||
|
|
0611627448 | ||
|
|
f014918435 | ||
|
|
51e016089e | ||
|
|
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 | ||
|
|
7cf28a5623 | ||
|
|
6668c456ac | ||
|
|
98f2476751 |
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
People who have agreed to the
|
||||||
|
[Cesanta CLA](https://docs.cesanta.com/contributors_la.shtml)
|
||||||
|
can make contributions. Note that the CLA isn't a copyright
|
||||||
|
_assigment_ but rather a copyright _license_.
|
||||||
|
You retain the copyright on your contributions.
|
||||||
|
|
||||||
|
We follow the Google C/C++ style guide: https://google.github.io/styleguide/cppguide.html
|
||||||
|
We'd appreciate if your contribution follows the same style guide.
|
||||||
@@ -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-2015 Cesanta Software Limited
|
Copyright (c) 2013-2016 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,76 +1,78 @@
|
|||||||
# <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 / Embedded Networking Library
|
||||||
|
|
||||||
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.
|

|
||||||
|
|
||||||
Mongoose is built on top of Libmongoose embedded library, which can turn
|
Mongoose is ideal for embedded environments. It has been designed
|
||||||
anything into a web server in 5 minutes worth of effort and few lines of code.
|
for connecting devices and bringing them online.
|
||||||
Libmongoose is used to serve Web GUI on embedded devices, implement RESTful
|
[Mongoose](https://www.cesanta.com/products)
|
||||||
services, RPC frameworks (e.g. JSON-RPC), handle telemetry data exchange, and
|
has been on the market since 2004 and attracted
|
||||||
perform many other tasks in various different industries including aerospace,
|
over 1 million cumulative downloads.
|
||||||
manufacturing, finance, research, automotive, gaming, IT.
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
* [Mailing list](http://groups.google.com/group/mongoose-users)
|
- [Download Mongoose Source Code here](http://hubs.ly/H01bWvx0)
|
||||||
* [Downloads](http://cesanta.com/products.shtml)
|
- [Check our latest releases] (https://github.com/cesanta/mongoose/releases)
|
||||||
* [Documentation](http://cesanta.com/docs.shtml)
|
- [Read User Guide and API reference](https://docs.cesanta.com/mongoose)
|
||||||
|
- [Study mongoose example code](https://github.com/cesanta/mongoose/tree/master/examples)
|
||||||
|
|
||||||
Check out Fossa - our [embedded multi-protocol library](https://github.com/cesanta/fossa) with TCP,UDP,HTTP,Websocket,MQTT,DNS support, designed for Internet Of Things!
|
> Nothing overdone. Nothing less. So unbelievably easy to use.
|
||||||
|
> Just how good software should be! -- Pritin Tyagaraj, SAP
|
||||||
|
|
||||||
|
If you are looking for a complete IoT platform with firmware and cloud compotent, check out [Smart.js.] (https://github.com/cesanta/smart.js)
|
||||||
|
|
||||||
|
# Support
|
||||||
|
- [Support Forum - Ask your technical questions here] (http://forum.cesanta.com/index.php?p=/categories/smart-js)
|
||||||
|
- [Chat - Visit www.cesanta.com for our live chat] (http://www.cesanta.com)
|
||||||
|
- [Commercial one-to-one support - Available to all commercial license holders] (https://www.cesanta.com/services-support)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Works on Windows, Mac, UNIX/Linux, iPhone, Android eCos, QNX
|
* Cross-platform: works on Linux/UNIX, MacOS, QNX, eCos, Windows, Android,
|
||||||
and many other platforms
|
iPhone, FreeRTOS (TI CC3200, ESP8266), etc
|
||||||
- CGI, SSI, SSL, Digest auth, Websocket, WEbDAV, Resumed download,
|
* Single-threaded, asynchronous, non-blocking core with simple event-based API
|
||||||
URL rewrite, file blacklist
|
* Native support for [PicoTCP embedded TCP/IP stack](http://www.picotcp.com),
|
||||||
- Custom error pages, Virtual hosts, IP-based ACL, Windows service,
|
[LWIP embedded TCP/IP stack](https://en.wikipedia.org/wiki/LwIP)
|
||||||
HTTP/HTTPS client
|
* Builtin protocols:
|
||||||
- Simple and clean
|
- plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
|
||||||
[embedding API](https://github.com/cesanta/mongoose/blob/master/mongoose.h).
|
- HTTP client, HTTP server
|
||||||
The source is in single
|
- WebSocket client, WebSocket server
|
||||||
[mongoose.c](https://github.com/cesanta/mongoose/blob/master/mongoose.c) file
|
- JSON-RPC client, JSON-RPC server
|
||||||
to make embedding easy
|
- MQTT client, MQTT broker
|
||||||
- Extremely lightweight, has a core of under 40kB and tiny runtime footprint
|
- CoAP client, CoAP server
|
||||||
- Asynchronous, non-blocking core supporting single- or multi-threaded usage
|
- DNS client, DNS server, async DNS resolver
|
||||||
- On the market since 2004 with over 1 million cumulative downloads
|
* Tiny static and run-time footprint
|
||||||
- Stable, mature and tested, has several man-years invested
|
* Source code is both ISO C and ISO C++ compliant
|
||||||
in continuous improvement and refinement
|
* Very easy to integrate: just copy
|
||||||
|
[mongoose.c](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c) and
|
||||||
# Screenshots
|
[mongoose.h](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.h)
|
||||||
|
files to your build tree
|
||||||
Download, double-click to start, run browser -- that's all!
|
|
||||||
|
|
||||||

|
|
||||||

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

|
|
||||||

|
|
||||||
|
|
||||||
# Contributions
|
|
||||||
|
|
||||||
People who have agreed to the
|
|
||||||
[Cesanta CLA](http://cesanta.com/contributors_la.html)
|
|
||||||
can make contributions. Note that the CLA isn't a copyright
|
|
||||||
_assigment_ but rather a copyright _license_.
|
|
||||||
You retain the copyright on your contributions.
|
|
||||||
|
|
||||||
# Licensing
|
# Licensing
|
||||||
|
|
||||||
Mongoose is released under commercial and
|
Mongoose is released under commercial and [GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) open source licenses.
|
||||||
[GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) open
|
|
||||||
source licenses. The GPLv2 open source License does not generally permit
|
|
||||||
incorporating this software into non-open source programs.
|
|
||||||
For those customers who do not wish to comply with the GPLv2 open
|
|
||||||
source license requirements,
|
|
||||||
[Cesanta](http://cesanta.com) offers a full,
|
|
||||||
royalty-free commercial license and professional support
|
|
||||||
without any of the GPL restrictions.
|
|
||||||
|
|
||||||
# Other products by Cesanta
|
Commercial Projects:
|
||||||
|
Once your project becomes commercialised GPLv2 licensing dictates that you need to either open your source fully or purchase a commercial license. Cesanta offer full, royalty-free commercial licenses without any GPL restrictions. If your needs require a custom license, we’d be happy to work on a solution with you. [Contact us for pricing.] (https://www.cesanta.com/contact)
|
||||||
|
|
||||||
- [Fossa](http://github.com/cesanta/fossa) - Multi-protocol networking library
|
Prototyping:
|
||||||
- [SSL Wrapper](https://github.com/cesanta/ssl_wrapper) - application to
|
While your project is still in prototyping stage and not for sale, you can use Mongoose’s open source code without license restrictions.
|
||||||
secure network communications
|
|
||||||
- [Frozen](https://github.com/cesanta/frozen) - JSON parser and generator
|
# Dashboard Example
|
||||||
- [SLRE](https://github.com/cesanta/slre) - Super Light Regular Expression
|
|
||||||
library
|
Mongoose is often used to implement device dashboards and real-time
|
||||||
- [V7](https://github.com/cesanta/v7) - Embedded JavaScript engine
|
data exchange over Websocket. Here is a dashboard example that illustrates
|
||||||
|
the functionality:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[Developing a new product? Contact us today to discuss how Mongoose can help
|
||||||
|
](https://www.cesanta.com/contact)
|
||||||
|
|
||||||
|
# Contributions
|
||||||
|
|
||||||
|
People who have agreed to the [Cesanta CLA] (https://docs.cesanta.com/contributors_la.shtml) can make contributions. Note that the CLA isn't a copyright assigment but rather a copyright license. You retain the copyright on your contributions.
|
||||||
|
|
||||||
|
[](https://github.com/cesanta/mongoose)
|
||||||
-249
@@ -1,249 +0,0 @@
|
|||||||
# Mongoose API Reference
|
|
||||||
|
|
||||||
struct mg_server *mg_create_server(void *server_param, mg_handler_t handler);
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
size_t mg_websocket_write(struct mg_connection* conn, int opcode,
|
|
||||||
const char *data, size_t data_len);
|
|
||||||
size_t mg_websocket_printf(struct mg_connection* conn, int opcode,
|
|
||||||
const char *fmt, ...);
|
|
||||||
|
|
||||||
|
|
||||||
Similar to `mg_write()` and `mg_printf()`, 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`
|
|
||||||
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.
|
|
||||||
|
|
||||||

|
|
||||||
-173
@@ -1,173 +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_ENABLE_THREADS Enable mg_start_thread() function
|
|
||||||
-DMONGOOSE_NO_WEBSOCKET Disable WebSocket support
|
|
||||||
-DMONGOOSE_NO_USER No concept of a user on used platform.
|
|
||||||
(Platform does not provide getpwnam, setgid or setuid)
|
|
||||||
|
|
||||||
-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.
|
|
||||||
-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,218 +0,0 @@
|
|||||||
# Mongoose Release Notes
|
|
||||||
|
|
||||||
## Release 5.6, 2015-03-17
|
|
||||||
|
|
||||||
Changes in Libmongoose library:
|
|
||||||
|
|
||||||
- Added `-dav_root` configuration option that gives an ability to mount
|
|
||||||
a different root directory (not document_root)
|
|
||||||
- Fixes for build under Win23 and MinGW
|
|
||||||
- Bugfix: Double dots removal
|
|
||||||
- Bugfix: final chunked response double-send
|
|
||||||
- Fixed compilation in 64-bit environments
|
|
||||||
- Added OS/2 compatibility
|
|
||||||
- Added `getaddrinfo()` call and `NS_ENABLE_GETADDRINFO`
|
|
||||||
- Various SSL-related fixes
|
|
||||||
- Added integer overflow protection in `iobuf_append()` and `deliver_websocket_frame()`
|
|
||||||
- Fixed NetBSD build
|
|
||||||
- Enabled `NS_ENABLE_IPV6` build for Visual Studio 2008+
|
|
||||||
- Enhanced comma detection in `parse_header()`
|
|
||||||
- Fixed unchanged memory accesses on ARM
|
|
||||||
- Added ability to use custom memory allocator through NS_MALLOC, NS_FREE, NS_REALLOC
|
|
||||||
|
|
||||||
Changes in Mongoose binary:
|
|
||||||
|
|
||||||
- Added `-start_browser` option to disable automatic browser launch
|
|
||||||
- Added experimental SSL support. To listen on HTTPS port, use `ssl://PORT:SSL_CERT` format. For example, to listen on HTTP port 8080 and HTTPS port 8043, use `-listening_port 8080,ssl://8043:ssl_cert.pem`
|
|
||||||
|
|
||||||
## 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_munmap()`
|
|
||||||
- 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 by 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)
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
# Mongoose SSL guide
|
|
||||||
|
|
||||||
SSL is a protocol that makes web communication secure. To enable SSL
|
|
||||||
in mongoose, 2 steps are required:
|
|
||||||
|
|
||||||
1. Create valid SSL certificate file
|
|
||||||
2. Append SSL certificate file path to the `listening_ports` option
|
|
||||||
|
|
||||||
Below is the `mongoose.conf` file snippet for typical SSL setup:
|
|
||||||
|
|
||||||
document_root www_root # Serve files in www_root directory
|
|
||||||
listening_ports 80,443:cert.pem # Listen on ports 80 and 443
|
|
||||||
|
|
||||||
## 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,118 @@
|
|||||||
|
#############################################################
|
||||||
|
# Required variables for each makefile
|
||||||
|
# Discard this section from all parent makefiles
|
||||||
|
# Expected variables (with automatic defaults):
|
||||||
|
# CSRCS (all "C" files in the dir)
|
||||||
|
# SUBDIRS (all subdirs with a Makefile)
|
||||||
|
# GEN_LIBS - list of libs to be generated ()
|
||||||
|
# GEN_IMAGES - list of object file images to be generated ()
|
||||||
|
# GEN_BINS - list of binaries to be generated ()
|
||||||
|
# COMPONENTS_xxx - a list of libs/objs in the form
|
||||||
|
# subdir/lib to be extracted and rolled up into
|
||||||
|
# a generated lib/image xxx.a ()
|
||||||
|
#
|
||||||
|
TARGET = eagle
|
||||||
|
#FLAVOR = release
|
||||||
|
FLAVOR = debug
|
||||||
|
|
||||||
|
EXTRA_CCFLAGS += -Wall -Werror
|
||||||
|
|
||||||
|
ifndef PDIR # {
|
||||||
|
GEN_IMAGES= eagle.app.v6.out
|
||||||
|
GEN_BINS= eagle.app.v6.bin
|
||||||
|
SPECIAL_MKTARGETS=$(APP_MKTARGETS)
|
||||||
|
SUBDIRS=user
|
||||||
|
|
||||||
|
endif # } PDIR
|
||||||
|
|
||||||
|
LDDIR = $(SDK_PATH)/ld
|
||||||
|
|
||||||
|
CCFLAGS += -Os
|
||||||
|
|
||||||
|
TARGET_LDFLAGS = \
|
||||||
|
-nostdlib \
|
||||||
|
-Wl,-EL \
|
||||||
|
--longcalls \
|
||||||
|
--text-section-literals
|
||||||
|
|
||||||
|
ifeq ($(FLAVOR),debug)
|
||||||
|
TARGET_LDFLAGS += -g -O2
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(FLAVOR),release)
|
||||||
|
TARGET_LDFLAGS += -g -O0
|
||||||
|
endif
|
||||||
|
|
||||||
|
COMPONENTS_eagle.app.v6 = \
|
||||||
|
user/libuser.a
|
||||||
|
|
||||||
|
LINKFLAGS_eagle.app.v6 = \
|
||||||
|
-L$(SDK_PATH)/lib \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-nostdlib \
|
||||||
|
-T$(LD_FILE) \
|
||||||
|
-Wl,--no-check-sections \
|
||||||
|
-u call_user_start \
|
||||||
|
-Wl,-static \
|
||||||
|
-Wl,--start-group \
|
||||||
|
-lc \
|
||||||
|
-lgcc \
|
||||||
|
-lhal \
|
||||||
|
-lphy \
|
||||||
|
-lpp \
|
||||||
|
-lnet80211 \
|
||||||
|
-lwpa \
|
||||||
|
-lcrypto \
|
||||||
|
-lmain \
|
||||||
|
-lfreertos \
|
||||||
|
-llwip \
|
||||||
|
$(DEP_LIBS_eagle.app.v6) \
|
||||||
|
-Wl,--end-group
|
||||||
|
|
||||||
|
DEPENDS_eagle.app.v6 = \
|
||||||
|
$(LD_FILE) \
|
||||||
|
$(LDDIR)/eagle.rom.addr.v6.ld
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# Configuration i.e. compile options etc.
|
||||||
|
# Target specific stuff (defines etc.) goes in here!
|
||||||
|
# Generally values applying to a tree are captured in the
|
||||||
|
# makefile at its root level - these are then overridden
|
||||||
|
# for a subtree within the makefile rooted therein
|
||||||
|
#
|
||||||
|
|
||||||
|
#UNIVERSAL_TARGET_DEFINES = \
|
||||||
|
|
||||||
|
# Other potential configuration flags include:
|
||||||
|
# -DTXRX_TXBUF_DEBUG
|
||||||
|
# -DTXRX_RXBUF_DEBUG
|
||||||
|
# -DWLAN_CONFIG_CCX
|
||||||
|
CONFIGURATION_DEFINES = -DICACHE_FLASH
|
||||||
|
|
||||||
|
DEFINES += \
|
||||||
|
$(UNIVERSAL_TARGET_DEFINES) \
|
||||||
|
$(CONFIGURATION_DEFINES)
|
||||||
|
|
||||||
|
DDEFINES += \
|
||||||
|
$(UNIVERSAL_TARGET_DEFINES) \
|
||||||
|
$(CONFIGURATION_DEFINES)
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# Recursion Magic - Don't touch this!!
|
||||||
|
#
|
||||||
|
# Each subtree potentially has an include directory
|
||||||
|
# corresponding to the common APIs applicable to modules
|
||||||
|
# rooted at that subtree. Accordingly, the INCLUDE PATH
|
||||||
|
# of a module can only contain the include directories up
|
||||||
|
# its parent path, and not its siblings
|
||||||
|
#
|
||||||
|
# Required for each makefile to inherit from the parent
|
||||||
|
#
|
||||||
|
|
||||||
|
INCLUDES := $(INCLUDES) -I $(PDIR)include
|
||||||
|
sinclude $(SDK_PATH)/Makefile
|
||||||
|
|
||||||
|
.PHONY: FORCE
|
||||||
|
FORCE:
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
This is a Mongoose "Hello, world" that can be compiled under ESP8266 RTOS SDK.
|
||||||
|
|
||||||
|
It sets up an AP (SSID `Mongoose`) and serves a "hello world" page on http://192.168.4.1/
|
||||||
|
|
||||||
|
Most of the the boilerplate comes from [project_template](https://github.com/espressif/ESP8266_RTOS_SDK/tree/master/examples/project_template) (@ [3ca6af5](https://github.com/espressif/ESP8266_RTOS_SDK/tree/3ca6af5da68678d809b34c7cd98bee71e0235039/examples/project_template)) with minimal changes.
|
||||||
|
|
||||||
|
To build with no changes to the SDK, you will need a module with 1MB (8Mb) flash or more.
|
||||||
|
|
||||||
|
Compile (for NodeMCU 1.0):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ export SDK_PATH=/path/to/ESP8266_RTOS_SDK
|
||||||
|
$ export BIN_PATH=./bin
|
||||||
|
$ make clean; make BOOT=new APP=1 SPI_SPEED=40 SPI_MODE=dio SPI_SIZE_MAP=6
|
||||||
|
```
|
||||||
|
|
||||||
|
Flash (using [esptool](https://github.com/themadinventor/esptool)):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ esptool.py --port /dev/ttyUSB0 --baud 230400 \
|
||||||
|
write_flash --flash_mode=dio --flash_size=32m \
|
||||||
|
0x00000 ${SDK_PATH}/bin/boot_v1.4\(b1\).bin \
|
||||||
|
0x01000 ${BIN_PATH}/upgrade/user1.4096.new.6.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The output can be made to fit in 512KB (4Mb), but stock linker scripts do not reserve enough space for code.
|
||||||
|
Custom linker script is provided for that, so you can use it for smaller devices like so (example that will work with ESP-01):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ make clean; make BOOT=none APP=0 SPI_SPEED=40 SPI_MODE=qio SPI_SIZE_MAP=0 LD_FILE=ld/eagle.app.v6.512.compact.ld
|
||||||
|
$ esptool.py --port /dev/ttyUSB0 --baud 230400 \
|
||||||
|
write_flash --flash_mode=qio --flash_size=4m \
|
||||||
|
0x00000 ${BIN_PATH}/eagle.flash.bin \
|
||||||
|
0x10000 ${BIN_PATH}/eagle.irom0text.bin
|
||||||
|
```
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
:<<!
|
||||||
|
******NOTICE******
|
||||||
|
MUST set SDK_PATH & BIN_PATH firstly!!!
|
||||||
|
example:
|
||||||
|
export SDK_PATH=~/ESP8266_RTOS_SDK
|
||||||
|
export BIN_PATH=~/esp8266_bin
|
||||||
|
!
|
||||||
|
|
||||||
|
echo "gen_misc.sh version 20150911"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $SDK_PATH ]; then
|
||||||
|
echo "SDK_PATH:"
|
||||||
|
echo "$SDK_PATH"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "ERROR: Please export SDK_PATH in gen_misc.sh firstly, exit!!!"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $BIN_PATH ]; then
|
||||||
|
echo "BIN_PATH:"
|
||||||
|
echo "$BIN_PATH"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "ERROR: Please export BIN_PATH in gen_misc.sh firstly, exit!!!"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Please check SDK_PATH & BIN_PATH, enter (Y/y) to continue:"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [[ $input != Y ]] && [[ $input != y ]]; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Please follow below steps(1-5) to generate specific bin(s):"
|
||||||
|
echo "STEP 1: use boot_v1.2+ by default"
|
||||||
|
boot=new
|
||||||
|
|
||||||
|
echo "boot mode: $boot"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "STEP 2: choose bin generate(0=eagle.flash.bin+eagle.irom0text.bin, 1=user1.bin, 2=user2.bin)"
|
||||||
|
echo "enter (0/1/2, default 0):"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [ -z "$input" ]; then
|
||||||
|
if [ $boot != none ]; then
|
||||||
|
boot=none
|
||||||
|
echo "ignore boot"
|
||||||
|
fi
|
||||||
|
app=0
|
||||||
|
echo "generate bin: eagle.flash.bin+eagle.irom0text.bin"
|
||||||
|
elif [ $input == 1 ]; then
|
||||||
|
if [ $boot == none ]; then
|
||||||
|
app=0
|
||||||
|
echo "choose no boot before"
|
||||||
|
echo "generate bin: eagle.flash.bin+eagle.irom0text.bin"
|
||||||
|
else
|
||||||
|
app=1
|
||||||
|
echo "generate bin: user1.bin"
|
||||||
|
fi
|
||||||
|
elif [ $input == 2 ]; then
|
||||||
|
if [ $boot == none ]; then
|
||||||
|
app=0
|
||||||
|
echo "choose no boot before"
|
||||||
|
echo "generate bin: eagle.flash.bin+eagle.irom0text.bin"
|
||||||
|
else
|
||||||
|
app=2
|
||||||
|
echo "generate bin: user2.bin"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ $boot != none ]; then
|
||||||
|
boot=none
|
||||||
|
echo "ignore boot"
|
||||||
|
fi
|
||||||
|
app=0
|
||||||
|
echo "generate bin: eagle.flash.bin+eagle.irom0text.bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "STEP 3: choose spi speed(0=20MHz, 1=26.7MHz, 2=40MHz, 3=80MHz)"
|
||||||
|
echo "enter (0/1/2/3, default 2):"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [ -z "$input" ]; then
|
||||||
|
spi_speed=40
|
||||||
|
elif [ $input == 0 ]; then
|
||||||
|
spi_speed=20
|
||||||
|
elif [ $input == 1 ]; then
|
||||||
|
spi_speed=26.7
|
||||||
|
elif [ $input == 3 ]; then
|
||||||
|
spi_speed=80
|
||||||
|
else
|
||||||
|
spi_speed=40
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "spi speed: $spi_speed MHz"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "STEP 4: choose spi mode(0=QIO, 1=QOUT, 2=DIO, 3=DOUT)"
|
||||||
|
echo "enter (0/1/2/3, default 0):"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [ -z "$input" ]; then
|
||||||
|
spi_mode=QIO
|
||||||
|
elif [ $input == 1 ]; then
|
||||||
|
spi_mode=QOUT
|
||||||
|
elif [ $input == 2 ]; then
|
||||||
|
spi_mode=DIO
|
||||||
|
elif [ $input == 3 ]; then
|
||||||
|
spi_mode=DOUT
|
||||||
|
else
|
||||||
|
spi_mode=QIO
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "spi mode: $spi_mode"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "STEP 5: choose spi size and map"
|
||||||
|
echo " 0= 512KB( 256KB+ 256KB)"
|
||||||
|
echo " 2=1024KB( 512KB+ 512KB)"
|
||||||
|
echo " 3=2048KB( 512KB+ 512KB)"
|
||||||
|
echo " 4=4096KB( 512KB+ 512KB)"
|
||||||
|
echo " 5=2048KB(1024KB+1024KB)"
|
||||||
|
echo " 6=4096KB(1024KB+1024KB)"
|
||||||
|
echo "enter (0/2/3/4/5/6, default 0):"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [ -z "$input" ]; then
|
||||||
|
spi_size_map=0
|
||||||
|
echo "spi size: 512KB"
|
||||||
|
echo "spi ota map: 256KB + 256KB"
|
||||||
|
elif [ $input == 2 ]; then
|
||||||
|
spi_size_map=2
|
||||||
|
echo "spi size: 1024KB"
|
||||||
|
echo "spi ota map: 512KB + 512KB"
|
||||||
|
elif [ $input == 3 ]; then
|
||||||
|
spi_size_map=3
|
||||||
|
echo "spi size: 2048KB"
|
||||||
|
echo "spi ota map: 512KB + 512KB"
|
||||||
|
elif [ $input == 4 ]; then
|
||||||
|
spi_size_map=4
|
||||||
|
echo "spi size: 4096KB"
|
||||||
|
echo "spi ota map: 512KB + 512KB"
|
||||||
|
elif [ $input == 5 ]; then
|
||||||
|
spi_size_map=5
|
||||||
|
echo "spi size: 2048KB"
|
||||||
|
echo "spi ota map: 1024KB + 1024KB"
|
||||||
|
elif [ $input == 6 ]; then
|
||||||
|
spi_size_map=6
|
||||||
|
echo "spi size: 4096KB"
|
||||||
|
echo "spi ota map: 1024KB + 1024KB"
|
||||||
|
else
|
||||||
|
spi_size_map=0
|
||||||
|
echo "spi size: 512KB"
|
||||||
|
echo "spi ota map: 256KB + 256KB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "start..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
make clean
|
||||||
|
|
||||||
|
make BOOT=$boot APP=$app SPI_SPEED=$spi_speed SPI_MODE=$spi_mode SPI_SIZE_MAP=$spi_size_map
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/* eagle.flash.bin @ 0x00000 */
|
||||||
|
/* eagle.irom0text.bin @ 0x10000 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flash map for 512KB flash.
|
||||||
|
*
|
||||||
|
* There is 0x8000 (32KB) available between 0x8000 and 0x10000.
|
||||||
|
*
|
||||||
|
* Note: IROM images starts at 0x10000 (ignore Makefile output which says 0x40000.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
dport0_0_seg : org = 0x3FF00000, len = 0x10
|
||||||
|
dram0_0_seg : org = 0x3FFE8000, len = 0x18000
|
||||||
|
iram1_0_seg : org = 0x40100000, len = 0x8000
|
||||||
|
irom0_0_seg : org = 0x40210000, len = 0x6C000
|
||||||
|
}
|
||||||
|
|
||||||
|
INCLUDE "../ld/eagle.app.v6.common.ld"
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
This is a simple project template.
|
||||||
|
|
||||||
|
sample_lib is an example for multi-level folder Makefile, notice the folder structure and each Makefile, you can get the clue.
|
||||||
|
|
||||||
|
|
||||||
|
HOWTO:
|
||||||
|
1. Copy this folder to anywhere.
|
||||||
|
Example:
|
||||||
|
Copy to ~/workspace/project_template
|
||||||
|
You can rename this folder as you like.
|
||||||
|
|
||||||
|
2. Export SDK_PATH and BIN_PATH.
|
||||||
|
Example:
|
||||||
|
Your SDK path is ~/esp_iot_rtos_sdk, and want generate bin at ~/esp8266_bin.
|
||||||
|
Do follow steps:
|
||||||
|
1>. export SDK_PATH=~/esp_iot_rtos_sdk
|
||||||
|
2>. export BIN_PATH=~/esp8266_bin
|
||||||
|
SDK and project are seperate, you can update SDK without change your project.
|
||||||
|
|
||||||
|
3. Enter project_template folder, run ./gen_misc.sh, and follow the tips and steps.
|
||||||
|
|
||||||
|
|
||||||
|
Compile Options:
|
||||||
|
(1) COMPILE
|
||||||
|
Possible value: xcc
|
||||||
|
Default value:
|
||||||
|
If not set, use gcc by default.
|
||||||
|
|
||||||
|
(2) BOOT
|
||||||
|
Possible value: none/old/new
|
||||||
|
none: no need boot
|
||||||
|
old: use boot_v1.1
|
||||||
|
new: use boot_v1.2
|
||||||
|
Default value: new
|
||||||
|
|
||||||
|
(3) APP
|
||||||
|
Possible value: 0/1/2
|
||||||
|
0: original mode, generate eagle.app.v6.flash.bin and eagle.app.v6.irom0text.bin
|
||||||
|
1: generate user1
|
||||||
|
2: generate user2
|
||||||
|
Default value: 0
|
||||||
|
|
||||||
|
(3) SPI_SPEED
|
||||||
|
Possible value: 20/26.7/40/80
|
||||||
|
Default value: 40
|
||||||
|
|
||||||
|
(4) SPI_MODE
|
||||||
|
Possible value: QIO/QOUT/DIO/DOUT
|
||||||
|
Default value: QIO
|
||||||
|
|
||||||
|
(4) SPI_SIZE_MAP
|
||||||
|
Possible value: 0/2/3/4/5/6
|
||||||
|
Default value: 0
|
||||||
|
|
||||||
|
For example:
|
||||||
|
make COMPILE=gcc BOOT=new APP=1 SPI_SPEED=40 SPI_MODE=QIO SPI_SIZE_MAP=0
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
#############################################################
|
||||||
|
# Required variables for each makefile
|
||||||
|
# Discard this section from all parent makefiles
|
||||||
|
# Expected variables (with automatic defaults):
|
||||||
|
# CSRCS (all "C" files in the dir)
|
||||||
|
# SUBDIRS (all subdirs with a Makefile)
|
||||||
|
# GEN_LIBS - list of libs to be generated ()
|
||||||
|
# GEN_IMAGES - list of images to be generated ()
|
||||||
|
# COMPONENTS_xxx - a list of libs/objs in the form
|
||||||
|
# subdir/lib to be extracted and rolled up into
|
||||||
|
# a generated lib/image xxx.a ()
|
||||||
|
#
|
||||||
|
ifndef PDIR
|
||||||
|
GEN_LIBS = libuser.a
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# Configuration i.e. compile options etc.
|
||||||
|
# Target specific stuff (defines etc.) goes in here!
|
||||||
|
# Generally values applying to a tree are captured in the
|
||||||
|
# makefile at its root level - these are then overridden
|
||||||
|
# for a subtree within the makefile rooted therein
|
||||||
|
#
|
||||||
|
DEFINES += -DMG_LOCALS \
|
||||||
|
-DMG_NO_BSD_SOCKETS \
|
||||||
|
-DMG_DISABLE_FILESYSTEM \
|
||||||
|
-DMG_DISABLE_STDIO \
|
||||||
|
-DMG_MAX_HTTP_HEADERS=20 -DMG_MAX_HTTP_REQUEST_SIZE=1024 \
|
||||||
|
-DMG_MAX_PATH=40 -DMG_MAX_HTTP_SEND_MBUF=1024 \
|
||||||
|
-DRTOS_SDK -DMG_LWIP -DLWIP_TIMEVAL_PRIVATE=0 \
|
||||||
|
-DMG_INTERNAL=
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# Recursion Magic - Don't touch this!!
|
||||||
|
#
|
||||||
|
# Each subtree potentially has an include directory
|
||||||
|
# corresponding to the common APIs applicable to modules
|
||||||
|
# rooted at that subtree. Accordingly, the INCLUDE PATH
|
||||||
|
# of a module can only contain the include directories up
|
||||||
|
# its parent path, and not its siblings
|
||||||
|
#
|
||||||
|
# Required for each makefile to inherit from the parent
|
||||||
|
#
|
||||||
|
|
||||||
|
INCLUDES += -I ./
|
||||||
|
PDIR := ../$(PDIR)
|
||||||
|
sinclude $(PDIR)Makefile
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Cesanta Software Limited
|
||||||
|
* All rights reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_common.h"
|
||||||
|
|
||||||
|
/* Makes fprintf(stdout) and stderr work. */
|
||||||
|
/*
|
||||||
|
_ssize_t _write_r(struct _reent *r, int fd, void *buf, size_t len) {
|
||||||
|
if (fd == 1 || fd == 2) {
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
os_putc(((char *) buf)[i]);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You'll need to implement _open_r and friends if you want file operations. See
|
||||||
|
* https://github.com/cesanta/smart.js/blob/master/platforms/esp8266/user/esp_fs.c
|
||||||
|
* for SPIFFS-based implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void abort(void) __attribute__((no_instrument_function));
|
||||||
|
void abort(void) {
|
||||||
|
/* cause an unaligned access exception, that will drop you into gdb */
|
||||||
|
*(int *) 1 = 1;
|
||||||
|
while (1)
|
||||||
|
; /* avoid gcc warning because stdlib abort() has noreturn attribute */
|
||||||
|
}
|
||||||
|
|
||||||
|
void _exit(int status) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) {
|
||||||
|
uint32_t time = system_get_time();
|
||||||
|
tp->tv_sec = time / 1000000;
|
||||||
|
tp->tv_usec = time % 1000000;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long int random(void) {
|
||||||
|
return os_random();
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../mongoose.c
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../mongoose.h
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Cesanta Software Limited
|
||||||
|
* All rights reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "esp_common.h"
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
#define AP_SSID "Mongoose"
|
||||||
|
#define AP_PASS "Mongoose"
|
||||||
|
#define AP_CHAN 9
|
||||||
|
#define MG_LISTEN_ADDR "80"
|
||||||
|
|
||||||
|
#define MG_TASK_STACK_SIZE 2048
|
||||||
|
#define MG_TASK_PRIORITY 1
|
||||||
|
|
||||||
|
void uart_div_modify(int uart_no, unsigned int freq);
|
||||||
|
|
||||||
|
void ev_handler(struct mg_connection *nc, int ev, void *p) {
|
||||||
|
static const char *reply_fmt =
|
||||||
|
"HTTP/1.0 200 OK\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Content-Type: text/plain\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"Hello %s\n";
|
||||||
|
LOG(LL_DEBUG, ("conn %p ev %d", nc, ev));
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_ACCEPT: {
|
||||||
|
char addr[32];
|
||||||
|
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
|
||||||
|
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
|
||||||
|
LOG(LL_INFO, ("Connection %p from %s", nc, addr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MG_EV_HTTP_REQUEST: {
|
||||||
|
char addr[32];
|
||||||
|
struct http_message *hm = (struct http_message *) p;
|
||||||
|
(void) hm;
|
||||||
|
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
|
||||||
|
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
|
||||||
|
LOG(LL_INFO,
|
||||||
|
("HTTP request from %s: %.*s %.*s", addr, (int) hm->method.len,
|
||||||
|
hm->method.p, (int) hm->uri.len, hm->uri.p));
|
||||||
|
mg_printf(nc, reply_fmt, addr);
|
||||||
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MG_EV_CLOSE: {
|
||||||
|
LOG(LL_INFO, ("Connection %p closed", nc));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup_ap() {
|
||||||
|
struct softap_config cfg;
|
||||||
|
|
||||||
|
wifi_set_opmode_current(SOFTAP_MODE);
|
||||||
|
memset(&cfg, 0, sizeof(cfg));
|
||||||
|
strcpy((char *) cfg.ssid, AP_SSID);
|
||||||
|
strcpy((char *) cfg.password, AP_PASS);
|
||||||
|
cfg.ssid_len = strlen((const char *) cfg.ssid);
|
||||||
|
cfg.authmode =
|
||||||
|
strlen((const char *) cfg.password) ? AUTH_WPA2_PSK : AUTH_OPEN;
|
||||||
|
cfg.channel = AP_CHAN;
|
||||||
|
cfg.ssid_hidden = 0;
|
||||||
|
cfg.max_connection = 10;
|
||||||
|
cfg.beacon_interval = 100; /* ms */
|
||||||
|
|
||||||
|
LOG(LL_INFO, ("Setting up AP '%s' on channel %d", cfg.ssid, cfg.channel));
|
||||||
|
wifi_softap_set_config_current(&cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mg_task(void *arg) {
|
||||||
|
struct mg_mgr mgr;
|
||||||
|
struct mg_connection *nc;
|
||||||
|
|
||||||
|
cs_log_set_level(LL_INFO);
|
||||||
|
|
||||||
|
LOG(LL_INFO, ("SDK version: %s", system_get_sdk_version()));
|
||||||
|
setup_ap();
|
||||||
|
|
||||||
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
|
||||||
|
nc = mg_bind(&mgr, MG_LISTEN_ADDR, ev_handler);
|
||||||
|
if (nc == NULL) {
|
||||||
|
LOG(LL_ERROR, ("Error setting up listener!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mg_set_protocol_http_websocket(nc);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
mg_mgr_poll(&mgr, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xTaskHandle s_mg_task_handle;
|
||||||
|
|
||||||
|
void user_init(void) {
|
||||||
|
uart_div_modify(0, UART_CLK_FREQ / 115200);
|
||||||
|
|
||||||
|
// setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
|
// setvbuf(stderr, NULL, _IONBF, 0);
|
||||||
|
|
||||||
|
xTaskCreate(mg_task, (const signed char *) "mongoose", MG_TASK_STACK_SIZE,
|
||||||
|
NULL, MG_TASK_PRIORITY, &s_mg_task_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void call_user_start(void) {
|
||||||
|
user_init();
|
||||||
|
}
|
||||||
+2
-7
@@ -1,20 +1,15 @@
|
|||||||
# 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)
|
||||||
ifdef WINDIR
|
|
||||||
# appending the Winsock2 library at the end of the compiler
|
|
||||||
# invocation
|
|
||||||
CFLAGS_EXTRA += -lws2_32
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: $(SUBDIRS)
|
.PHONY: $(SUBDIRS)
|
||||||
|
|
||||||
all: $(SUBDIRS)
|
all: $(SUBDIRS)
|
||||||
|
|
||||||
$(SUBDIRS):
|
$(SUBDIRS):
|
||||||
@$(MAKE) CFLAGS_EXTRA="$(CFLAGS_EXTRA)" -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_MK_STR("GET");
|
||||||
|
static const struct mg_str s_put_method = MG_MK_STR("PUT");
|
||||||
|
static const struct mg_str s_delele_method = MG_MK_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_MK_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
|
||||||
+16
-11
@@ -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 dns_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(dns_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 dns_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(dns_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 +1,3 @@
|
|||||||
# Copyright (c) 2014 Cesanta Software
|
|
||||||
# All rights reserved
|
|
||||||
|
|
||||||
PROG = big_upload
|
PROG = big_upload
|
||||||
CFLAGS = -W -Wall -pthread -I../.. -g -O0 $(CFLAGS_EXTRA)
|
MODULE_CFLAGS = -DMG_ENABLE_HTTP_STREAMING_MULTIPART
|
||||||
SOURCES = $(PROG).c ../../mongoose.c
|
include ../examples.mk
|
||||||
|
|
||||||
$(PROG): $(SOURCES)
|
|
||||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
|
||||||
@@ -1,84 +1,112 @@
|
|||||||
|
// Copyright (c) 2015 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// This example demonstrates how to handle very large requests without keeping
|
||||||
|
// them in memory.
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "mongoose.h"
|
#include "mongoose.h"
|
||||||
|
|
||||||
static int handle_request(struct mg_connection *conn) {
|
static const char *s_http_port = "8000";
|
||||||
if (strcmp(conn->uri, "/upload") == 0) {
|
|
||||||
FILE *fp = (FILE *) conn->connection_param;
|
struct file_writer_data {
|
||||||
if (fp != NULL) {
|
FILE *fp;
|
||||||
fwrite(conn->content, 1, conn->content_len, fp); // Write last bits
|
size_t bytes_written;
|
||||||
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
};
|
||||||
|
|
||||||
|
static void handle_request(struct mg_connection *nc) {
|
||||||
|
// This handler gets for all endpoints but /upload
|
||||||
|
mg_printf(nc, "%s",
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"Content-Type: text/html\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"<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>");
|
||||||
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_upload(struct mg_connection *nc, int ev, void *p) {
|
||||||
|
struct file_writer_data *data = (struct file_writer_data *) nc->user_data;
|
||||||
|
struct mg_http_multipart_part *mp = (struct mg_http_multipart_part*)p;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_HTTP_PART_BEGIN: {
|
||||||
|
if (data == NULL) {
|
||||||
|
data = calloc(1, sizeof(struct file_writer_data));
|
||||||
|
data->fp = tmpfile();
|
||||||
|
data->bytes_written = 0;
|
||||||
|
|
||||||
|
if (data->fp == NULL) {
|
||||||
|
mg_printf(nc, "%s",
|
||||||
|
"HTTP/1.1 500 Failed to open a file\r\n"
|
||||||
|
"Content-Length: 0\r\n\r\n");
|
||||||
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nc->user_data = (void *) data;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MG_EV_HTTP_PART_DATA: {
|
||||||
|
if (fwrite(mp->data.p, 1, mp->data.len, data->fp) != mp->data.len) {
|
||||||
|
mg_printf(nc, "%s",
|
||||||
|
"HTTP/1.1 500 Failed to write to a file\r\n"
|
||||||
|
"Content-Length: 0\r\n\r\n");
|
||||||
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data->bytes_written += mp->data.len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MG_EV_HTTP_PART_END: {
|
||||||
|
mg_printf(nc,
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
"Content-Type: text/plain\r\n"
|
"Content-Type: text/plain\r\n"
|
||||||
"Connection: close\r\n\r\n"
|
"Connection: close\r\n\r\n"
|
||||||
"Written %ld of POST data to a temp file:\n\n",
|
"Written %ld of POST data to a temp file\n\n",
|
||||||
(long) ftell(fp));
|
(long) ftell(data->fp));
|
||||||
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||||
// Temp file will be destroyed after fclose(), do something with the
|
fclose(data->fp);
|
||||||
// data here -- for example, parse it and extract uploaded files.
|
free(data);
|
||||||
// As an example, we just echo the whole POST buffer back to the client.
|
nc->user_data = NULL;
|
||||||
rewind(fp);
|
break;
|
||||||
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.
|
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||||
// When last POST chunk is received, Mongoose sends MG_REQUEST, then MG_CLOSE.
|
(void) ev_data;
|
||||||
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) {
|
switch (ev) {
|
||||||
case MG_AUTH: return MG_TRUE;
|
case MG_EV_HTTP_REQUEST:
|
||||||
case MG_REQUEST: return handle_request(conn);
|
// Invoked when the full HTTP request is in the buffer (including body).
|
||||||
case MG_RECV: return handle_recv(conn);
|
handle_request(nc);
|
||||||
case MG_CLOSE: return handle_close(conn);
|
break;
|
||||||
default: return MG_FALSE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
struct mg_mgr mgr;
|
||||||
mg_set_option(server, "listening_port", "8080");
|
struct mg_connection *nc;
|
||||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
|
||||||
|
|
||||||
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||||
|
|
||||||
|
mg_register_http_endpoint(nc, "/upload", handle_upload);
|
||||||
|
// Set up HTTP server parameters
|
||||||
|
mg_set_protocol_http_websocket(nc);
|
||||||
|
|
||||||
|
printf("Starting web server on port %s\n", s_http_port);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mg_poll_server(server, 1000);
|
mg_mgr_poll(&mgr, 1000);
|
||||||
}
|
}
|
||||||
|
mg_mgr_free(&mgr);
|
||||||
|
|
||||||
mg_destroy_server(&server);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
PROG = captive_dns_server
|
||||||
|
MODULE_CFLAGS=-DMG_ENABLE_DNS_SERVER
|
||||||
|
|
||||||
|
include ../rules.mk
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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_dns_message *msg;
|
||||||
|
struct mg_dns_resource_record *rr;
|
||||||
|
struct mg_dns_reply reply;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_DNS_MESSAGE:
|
||||||
|
msg = (struct mg_dns_message *) ev_data;
|
||||||
|
reply = mg_dns_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_dns_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_dns_send_reply(nc, &reply);
|
||||||
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||||
|
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,71 @@
|
|||||||
|
/*
|
||||||
|
* 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, 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,72 @@
|
|||||||
|
/*
|
||||||
|
* 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, 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Exiting on signal %d\n", s_sig_received);
|
||||||
|
|
||||||
|
mg_mgr_free(&mgr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
PROG = server
|
||||||
|
include ../examples.mk
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2015 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 ev_handler(struct mg_connection *nc, int ev, void *p) {
|
||||||
|
if (ev == MG_EV_HTTP_REQUEST) {
|
||||||
|
mg_serve_http(nc, p, s_http_server_opts); // Serves static content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct mg_mgr mgr;
|
||||||
|
struct mg_connection *nc;
|
||||||
|
cs_stat_t st;
|
||||||
|
|
||||||
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||||
|
if (nc == NULL) {
|
||||||
|
fprintf(stderr, "Cannot bind to %s\n", s_http_port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up HTTP server parameters
|
||||||
|
mg_set_protocol_http_websocket(nc);
|
||||||
|
s_http_server_opts.document_root = "web_root"; // Set up web root directory
|
||||||
|
|
||||||
|
if (mg_stat(s_http_server_opts.document_root, &st) != 0) {
|
||||||
|
fprintf(stderr, "%s", "Cannot find web_root directory, exiting\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Starting web server on port %s\n", s_http_port);
|
||||||
|
for (;;) {
|
||||||
|
mg_mgr_poll(&mgr, 1000);
|
||||||
|
}
|
||||||
|
mg_mgr_free(&mgr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Connected device demo</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<img src="logo.png" style="float:right; height: 50px; border-radius: 3px;">
|
||||||
|
<h1>Connected device demo - static content only</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This page shows initial steps on how to make a device connected.
|
||||||
|
Mongoose serves static content from the <code>web_root</code>
|
||||||
|
directory. Images, CSS, HTML, JavaScript files could be dropped into the
|
||||||
|
<code>web_root</code> directory - and they'll be served happily
|
||||||
|
by Mongoose.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Below is a placeholder for the
|
||||||
|
settings section, and a real-time dashboard, which will be
|
||||||
|
implemented in the following examples.
|
||||||
|
Both settings section, and a dashboard section, could be organised
|
||||||
|
differently on this UI - for example, in different tabs. But, to
|
||||||
|
make the demonstration clearer, we show everything on one page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>Settings section</h1>
|
||||||
|
<p>Nothing here yet</p>
|
||||||
|
|
||||||
|
<h1>Dashboard section</h1>
|
||||||
|
<p>Nothing here yet</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
* {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #36c;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content {
|
||||||
|
width: 800px;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 2em;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
color: #396;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #69c;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
PROG = server
|
||||||
|
include ../examples.mk
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2015 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
struct device_settings {
|
||||||
|
char setting1[100];
|
||||||
|
char setting2[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *s_http_port = "8000";
|
||||||
|
static struct mg_serve_http_opts s_http_server_opts;
|
||||||
|
static struct device_settings s_settings = { "value1", "value2" };
|
||||||
|
|
||||||
|
static void handle_save(struct mg_connection *nc, struct http_message *hm) {
|
||||||
|
// Get form variables and store settings values
|
||||||
|
mg_get_http_var(&hm->body, "setting1", s_settings.setting1,
|
||||||
|
sizeof(s_settings.setting1));
|
||||||
|
mg_get_http_var(&hm->body, "setting2", s_settings.setting2,
|
||||||
|
sizeof(s_settings.setting2));
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
mg_printf(nc, "%s", "HTTP/1.1 302 OK\r\nLocation: /\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_ssi_call(struct mg_connection *nc, const char *param) {
|
||||||
|
if (strcmp(param, "setting1") == 0) {
|
||||||
|
mg_printf_html_escape(nc, "%s", s_settings.setting1);
|
||||||
|
} else if (strcmp(param, "setting2") == 0) {
|
||||||
|
mg_printf_html_escape(nc, "%s", s_settings.setting2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||||
|
struct http_message *hm = (struct http_message *) ev_data;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_HTTP_REQUEST:
|
||||||
|
if (mg_vcmp(&hm->uri, "/save") == 0) {
|
||||||
|
handle_save(nc, hm);
|
||||||
|
} else {
|
||||||
|
mg_serve_http(nc, hm, s_http_server_opts); // Serve static content
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MG_EV_SSI_CALL:
|
||||||
|
handle_ssi_call(nc, ev_data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct mg_mgr mgr;
|
||||||
|
struct mg_connection *nc;
|
||||||
|
cs_stat_t st;
|
||||||
|
|
||||||
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||||
|
if (nc == NULL) {
|
||||||
|
fprintf(stderr, "Cannot bind to %s\n", s_http_port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up HTTP server parameters
|
||||||
|
mg_set_protocol_http_websocket(nc);
|
||||||
|
s_http_server_opts.document_root = "web_root"; // Set up web root directory
|
||||||
|
|
||||||
|
if (mg_stat(s_http_server_opts.document_root, &st) != 0) {
|
||||||
|
fprintf(stderr, "%s", "Cannot find web_root directory, exiting\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Starting web server on port %s\n", s_http_port);
|
||||||
|
for (;;) {
|
||||||
|
mg_mgr_poll(&mgr, 1000);
|
||||||
|
}
|
||||||
|
mg_mgr_free(&mgr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Connected device demo</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<img src="logo.png" style="float:right; height: 50px; border-radius: 3px;">
|
||||||
|
<h1>Connected device demo - settings section</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This page is one of the series of examples that show how to make
|
||||||
|
devices connected with Mongoose. Here, we create a settings section
|
||||||
|
that expose some internal device variables, and allow user to change
|
||||||
|
them. They could be some stored in a database, in a file, or in
|
||||||
|
firmware RAM. In this example, we store them in RAM, and use two
|
||||||
|
variables, one integer and one string. We use SSI
|
||||||
|
<code><!--#call parameter_name --></code> directive to fetch
|
||||||
|
variable values from Mongoose.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>Settings section</h1>
|
||||||
|
<form method="POST" action="/save">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Device settings</legend>
|
||||||
|
<label>Setting 1:</label> <input type="text"
|
||||||
|
name="setting1" value="<!--#call setting1 -->" >
|
||||||
|
<label>Setting 2:</label> <input type="text"
|
||||||
|
name="setting2" value="<!--#call setting2 -->" >
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit">Save Settings</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h1>Dashboard section</h1>
|
||||||
|
<p>Nothing here yet</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,67 @@
|
|||||||
|
* {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #36c;
|
||||||
|
color: #555;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content {
|
||||||
|
width: 800px;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 2em;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset legend {
|
||||||
|
font-style: italic;
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset label {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
font-size: 100%;
|
||||||
|
padding: 0.2em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #666;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 0.2em 2em;
|
||||||
|
font-size: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
color: #396;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #69c;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
PROG = server
|
||||||
|
include ../examples.mk
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) 2015 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
struct device_settings {
|
||||||
|
char setting1[100];
|
||||||
|
char setting2[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *s_http_port = "8000";
|
||||||
|
static struct mg_serve_http_opts s_http_server_opts;
|
||||||
|
static struct device_settings s_settings = { "value1", "value2" };
|
||||||
|
|
||||||
|
static void handle_save(struct mg_connection *nc, struct http_message *hm) {
|
||||||
|
// Get form variables and store settings values
|
||||||
|
mg_get_http_var(&hm->body, "setting1", s_settings.setting1,
|
||||||
|
sizeof(s_settings.setting1));
|
||||||
|
mg_get_http_var(&hm->body, "setting2", s_settings.setting2,
|
||||||
|
sizeof(s_settings.setting2));
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
mg_printf(nc, "%s", "HTTP/1.1 302 OK\r\nLocation: /\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_get_cpu_usage(struct mg_connection *nc) {
|
||||||
|
// Generate random value, as an example of changing CPU usage
|
||||||
|
// Getting real CPU usage depends on the OS.
|
||||||
|
int cpu_usage = (double) rand() / RAND_MAX * 100.0;
|
||||||
|
|
||||||
|
// Use chunked encoding in order to avoid calculating Content-Length
|
||||||
|
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||||
|
|
||||||
|
// Output JSON object which holds CPU usage data
|
||||||
|
mg_printf_http_chunk(nc, "{ \"result\": %d }", cpu_usage);
|
||||||
|
|
||||||
|
// Send empty chunk, the end of response
|
||||||
|
mg_send_http_chunk(nc, "", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_ssi_call(struct mg_connection *nc, const char *param) {
|
||||||
|
if (strcmp(param, "setting1") == 0) {
|
||||||
|
mg_printf_html_escape(nc, "%s", s_settings.setting1);
|
||||||
|
} else if (strcmp(param, "setting2") == 0) {
|
||||||
|
mg_printf_html_escape(nc, "%s", s_settings.setting2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||||
|
struct http_message *hm = (struct http_message *) ev_data;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_HTTP_REQUEST:
|
||||||
|
if (mg_vcmp(&hm->uri, "/save") == 0) {
|
||||||
|
handle_save(nc, hm);
|
||||||
|
} else if (mg_vcmp(&hm->uri, "/get_cpu_usage") == 0) {
|
||||||
|
handle_get_cpu_usage(nc);
|
||||||
|
} else {
|
||||||
|
mg_serve_http(nc, hm, s_http_server_opts); // Serve static content
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MG_EV_SSI_CALL:
|
||||||
|
handle_ssi_call(nc, ev_data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct mg_mgr mgr;
|
||||||
|
struct mg_connection *nc;
|
||||||
|
cs_stat_t st;
|
||||||
|
|
||||||
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||||
|
if (nc == NULL) {
|
||||||
|
fprintf(stderr, "Cannot bind to %s\n", s_http_port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up HTTP server parameters
|
||||||
|
mg_set_protocol_http_websocket(nc);
|
||||||
|
s_http_server_opts.document_root = "web_root"; // Set up web root directory
|
||||||
|
|
||||||
|
if (mg_stat(s_http_server_opts.document_root, &st) != 0) {
|
||||||
|
fprintf(stderr, "%s", "Cannot find web_root directory, exiting\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Starting web server on port %s\n", s_http_port);
|
||||||
|
for (;;) {
|
||||||
|
mg_mgr_poll(&mgr, 1000);
|
||||||
|
}
|
||||||
|
mg_mgr_free(&mgr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Connected device demo</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
<script type="text/javascript" src="jquery-1.11.3.min.js"></script>
|
||||||
|
<script type="text/javascript" src="main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<img src="logo.png" style="float:right; height: 50px; border-radius: 3px;">
|
||||||
|
<h1>Connected device demo - RESTful call</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This page is one of the series of examples that show how to make
|
||||||
|
devices connected with Mongoose. Here we add a RESTful call
|
||||||
|
to the Mongoose to show CPU usage stats on a dashboard.
|
||||||
|
RESTful call is done using Jquery's <code>ajax</code> call.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>Settings section</h1>
|
||||||
|
<form method="POST" action="/save">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Device settings</legend>
|
||||||
|
<label>Setting 1:</label> <input type="text"
|
||||||
|
name="setting1" value="<!--#call setting1 -->" >
|
||||||
|
<label>Setting 2:</label> <input type="text"
|
||||||
|
name="setting2" value="<!--#call setting2 -->" >
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit">Save Settings</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h1>Dashboard section</h1>
|
||||||
|
<label>CPU usage:</label> <span id="cpu_usage"> </span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
|
||||||
|
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^Line truncated
|
||||||
|
|
||||||
|
return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var
Line truncated
|
||||||
|
return new Za.prototype.init(a,b,c,d,e)}m.Tween=Za,Za.prototype={constructor:Za,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Za.propHooks[this.prop];return a&&a.get?a.get(this):Za.propHooks._default.get(this)},run:function(a){var b,c=Za.propHooks[this.prop];return this.options.duration?this.pos=b=m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Za.propHooks._default.set(this),this}},Za.prototype.init.prototype=Za.prototype,Za.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Za.propHooks.scrollTop=Za.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Za.prototype.init,m.fx.step={};var $a,_a,ab=/^(?:toggle|show|hide)$/,bb=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cb=/queueHooks$/,db=[ib],eb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bb.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bb.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fb(){return setTimeout(function(){$a=void 0}),$a=m.now()}function gb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hb(a,b,c){for(var d,e=(eb[b]||[]).concat(eb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fa(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fa(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ab.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fa(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hb(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=db.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$a||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$a||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);g>f;f++)if(d=db[f].call(j,a,k,j.opts))return d;return m.map(k,hb,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kb,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],eb[cLine truncated
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
// Start 1-second timer to call RESTful endpoint
|
||||||
|
setInterval(function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/get_cpu_usage',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(json) {
|
||||||
|
$('#cpu_usage').text(json.result + '% ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
* {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #36c;
|
||||||
|
color: #555;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content {
|
||||||
|
width: 800px;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 2em;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset legend {
|
||||||
|
font-style: italic;
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset label {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
font-size: 100%;
|
||||||
|
padding: 0.2em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #666;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 0.2em 2em;
|
||||||
|
font-size: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
color: #396;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #69c;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
PROG = server
|
||||||
|
include ../examples.mk
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) 2015 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
struct device_settings {
|
||||||
|
char setting1[100];
|
||||||
|
char setting2[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *s_http_port = "8000";
|
||||||
|
static struct mg_serve_http_opts s_http_server_opts;
|
||||||
|
static struct device_settings s_settings = { "value1", "value2" };
|
||||||
|
|
||||||
|
static void handle_save(struct mg_connection *nc, struct http_message *hm) {
|
||||||
|
// Get form variables and store settings values
|
||||||
|
mg_get_http_var(&hm->body, "setting1", s_settings.setting1,
|
||||||
|
sizeof(s_settings.setting1));
|
||||||
|
mg_get_http_var(&hm->body, "setting2", s_settings.setting2,
|
||||||
|
sizeof(s_settings.setting2));
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
mg_printf(nc, "%s", "HTTP/1.1 302 OK\r\nLocation: /\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_get_cpu_usage(struct mg_connection *nc) {
|
||||||
|
// Generate random value, as an example of changing CPU usage
|
||||||
|
// Getting real CPU usage depends on the OS.
|
||||||
|
int cpu_usage = (double) rand() / RAND_MAX * 100.0;
|
||||||
|
|
||||||
|
// Use chunked encoding in order to avoid calculating Content-Length
|
||||||
|
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||||
|
|
||||||
|
// Output JSON object which holds CPU usage data
|
||||||
|
mg_printf_http_chunk(nc, "{ \"result\": %d }", cpu_usage);
|
||||||
|
|
||||||
|
// Send empty chunk, the end of response
|
||||||
|
mg_send_http_chunk(nc, "", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_ssi_call(struct mg_connection *nc, const char *param) {
|
||||||
|
if (strcmp(param, "setting1") == 0) {
|
||||||
|
mg_printf_html_escape(nc, "%s", s_settings.setting1);
|
||||||
|
} else if (strcmp(param, "setting2") == 0) {
|
||||||
|
mg_printf_html_escape(nc, "%s", s_settings.setting2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||||
|
struct http_message *hm = (struct http_message *) ev_data;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case MG_EV_HTTP_REQUEST:
|
||||||
|
if (mg_vcmp(&hm->uri, "/save") == 0) {
|
||||||
|
handle_save(nc, hm);
|
||||||
|
} else if (mg_vcmp(&hm->uri, "/get_cpu_usage") == 0) {
|
||||||
|
handle_get_cpu_usage(nc);
|
||||||
|
} else {
|
||||||
|
mg_serve_http(nc, hm, s_http_server_opts); // Serve static content
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MG_EV_SSI_CALL:
|
||||||
|
handle_ssi_call(nc, ev_data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void push_data_to_all_websocket_connections(struct mg_mgr *m) {
|
||||||
|
struct mg_connection *c;
|
||||||
|
int memory_usage = (double) rand() / RAND_MAX * 100.0;
|
||||||
|
|
||||||
|
for (c = mg_next(m, NULL); c != NULL; c = mg_next(m, c)) {
|
||||||
|
if (c->flags & MG_F_IS_WEBSOCKET) {
|
||||||
|
mg_printf_websocket_frame(c, WEBSOCKET_OP_TEXT, "%d", memory_usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct mg_mgr mgr;
|
||||||
|
struct mg_connection *nc;
|
||||||
|
cs_stat_t st;
|
||||||
|
|
||||||
|
mg_mgr_init(&mgr, NULL);
|
||||||
|
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||||
|
if (nc == NULL) {
|
||||||
|
fprintf(stderr, "Cannot bind to %s\n", s_http_port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up HTTP server parameters
|
||||||
|
mg_set_protocol_http_websocket(nc);
|
||||||
|
s_http_server_opts.document_root = "web_root"; // Set up web root directory
|
||||||
|
|
||||||
|
if (mg_stat(s_http_server_opts.document_root, &st) != 0) {
|
||||||
|
fprintf(stderr, "%s", "Cannot find web_root directory, exiting\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Starting web server on port %s\n", s_http_port);
|
||||||
|
for (;;) {
|
||||||
|
static time_t last_time;
|
||||||
|
time_t now = time(NULL);
|
||||||
|
mg_mgr_poll(&mgr, 1000);
|
||||||
|
if (now - last_time > 0) {
|
||||||
|
push_data_to_all_websocket_connections(&mgr);
|
||||||
|
last_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mg_mgr_free(&mgr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Connected device demo</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
<script type="text/javascript" src="jquery-1.11.3.min.js"></script>
|
||||||
|
<script src="jquery.flot.min.js"></script>
|
||||||
|
<script src="jquery.flot.time.js"></script>
|
||||||
|
<script type="text/javascript" src="main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<img src="logo.png" style="float:right; height: 50px; border-radius: 3px;">
|
||||||
|
<h1>Connected device demo - Websocket push</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This page is one of the series of examples that show how to make
|
||||||
|
devices connected with Mongoose. Here we add a real-time Websocket
|
||||||
|
server push, and update dashboard graph as data arrives.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>Settings section</h1>
|
||||||
|
<form method="POST" action="/save">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Device settings</legend>
|
||||||
|
<label>Setting 1:</label> <input type="text"
|
||||||
|
name="setting1" value="<!--#call setting1 -->" >
|
||||||
|
<label>Setting 2:</label> <input type="text"
|
||||||
|
name="setting2" value="<!--#call setting2 -->" >
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit">Save Settings</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h1>Dashboard section</h1>
|
||||||
|
<label>CPU usage:</label> <span id="cpu_usage"> </span>
|
||||||
|
<div id="graphs"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
|
||||||
|
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^Line truncated
|
||||||
|
|
||||||
|
return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var
Line truncated
|
||||||
|
return new Za.prototype.init(a,b,c,d,e)}m.Tween=Za,Za.prototype={constructor:Za,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Za.propHooks[this.prop];return a&&a.get?a.get(this):Za.propHooks._default.get(this)},run:function(a){var b,c=Za.propHooks[this.prop];return this.options.duration?this.pos=b=m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Za.propHooks._default.set(this),this}},Za.prototype.init.prototype=Za.prototype,Za.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Za.propHooks.scrollTop=Za.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Za.prototype.init,m.fx.step={};var $a,_a,ab=/^(?:toggle|show|hide)$/,bb=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cb=/queueHooks$/,db=[ib],eb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bb.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bb.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fb(){return setTimeout(function(){$a=void 0}),$a=m.now()}function gb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hb(a,b,c){for(var d,e=(eb[b]||[]).concat(eb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fa(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fa(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ab.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fa(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hb(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=db.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$a||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$a||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);g>f;f++)if(d=db[f].call(j,a,k,j.opts))return d;return m.map(k,hb,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kb,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],eb[cLine truncated
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* Javascript plotting library for jQuery, version 0.8.3.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
*/
|
||||||
|
(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;if(!$.fn.detach){$.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){vaLine truncated
|
||||||
|
if(yrange.from==null)yrange.from=yrange.axis.min;if(yrange.to==null)yrange.to=yrange.axis.max;if(xrange.to<xrange.axis.min||xrange.from>xrange.axis.max||yrange.to<yrange.axis.min||yrange.from>yrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);var xequal=xrange.from===xrange.to,yequal=yrange.from===yrange.to;if(xequal&&yequal){continue}xrange.from=Math.floor(xrange.axis.p2c(xrange.from));xrange.to=Math.floor(xrange.axis.p2c(xrange.to));yrange.from=Math.floor(yrange.axis.p2c(yrange.from));yrange.to=Math.floor(yrange.axis.p2c(yrange.to));if(xequal||yequal){var lineWidth=m.lineWidth||options.grid.markingsLineWidth,subPixel=lineWidth%2?.5:0;ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=lineWidth;if(xequal){ctx.moveTo(xrange.to+subPixel,yrange.from);ctx.lineTo(xrange.to+subPixel,yrange.to)}else{ctx.moveTo(xrange.from,yrange.to+subPixel);ctx.lineTo(xrange.to,yrange.to+subPixel)}ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;j<axes.length;++j){var axis=axes[j],box=axis.box,t=axis.tickLength,x,y,xoff,yoff;if(!axis.show||axis.ticks.length==0)continue;ctx.lineWidth=1;if(axis.direction=="x"){x=0;if(t=="full")y=axis.position=="top"?0:plotHeight;else y=box.top-plotOffset.top+(axis.position=="top"?box.height:0)}else{y=0;if(t=="full")x=axis.position=="left"?0:plotWidth;else x=box.left-plotOffset.left+(axis.position=="left"?box.width:0)}if(!axis.innermost){ctx.strokeStyle=axis.options.color;ctx.beginPath();xoff=yoff=0;if(axis.direction=="x")xoff=plotWidth+1;else yoff=plotHeight+1;if(ctx.lineWidth==1){if(axis.direction=="x"){y=Math.floor(y)+.5}else{x=Math.floor(x)+.5}}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff);ctx.stroke()}ctx.strokeStyle=axis.options.tickColor;ctx.beginPath();for(i=0;i<axis.ticks.length;++i){var v=axis.ticks[i].v;xoff=yoff=0;if(isNaN(v)||v<axis.min||v>axis.max||t=="full"&&(typeof bw=="object"&&bw[axis.position]>0||bw>0)&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0)return;for(var i=0;i<axis.ticks.length;++i){tick=axis.ticks[i];if(!tick.label||tick.v<axis.min||tick.v>axis.max)continue;if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i<points.length;i+=ps){var x1=points[i-ps],y1=points[i-ps+1],x2=points[i],y2=points[i+1];if(x1==null||x2==null)continue;if(y1<=y2&&y1<axisy.min){if(y2<axLine truncated
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
/* Pretty handling of time axes.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
Set axis.mode to "time" to enable. See the section "Time series data" in
|
||||||
|
API.txt for details.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
xaxis: {
|
||||||
|
timezone: null, // "browser" for local to the client or timezone for timezone-js
|
||||||
|
timeformat: null, // format string to use
|
||||||
|
twelveHourClock: false, // 12 or 24 time in time mode
|
||||||
|
monthNames: null // list of names of months
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// round to nearby lower multiple of base
|
||||||
|
|
||||||
|
function floorInBase(n, base) {
|
||||||
|
return base * Math.floor(n / base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string with the date d formatted according to fmt.
|
||||||
|
// A subset of the Open Group's strftime format is supported.
|
||||||
|
|
||||||
|
function formatDate(d, fmt, monthNames, dayNames) {
|
||||||
|
|
||||||
|
if (typeof d.strftime == "function") {
|
||||||
|
return d.strftime(fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftPad = function(n, pad) {
|
||||||
|
n = "" + n;
|
||||||
|
pad = "" + (pad == null ? "0" : pad);
|
||||||
|
return n.length == 1 ? pad + n : n;
|
||||||
|
};
|
||||||
|
|
||||||
|
var r = [];
|
||||||
|
var escape = false;
|
||||||
|
var hours = d.getHours();
|
||||||
|
var isAM = hours < 12;
|
||||||
|
|
||||||
|
if (monthNames == null) {
|
||||||
|
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayNames == null) {
|
||||||
|
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var hours12;
|
||||||
|
|
||||||
|
if (hours > 12) {
|
||||||
|
hours12 = hours - 12;
|
||||||
|
} else if (hours == 0) {
|
||||||
|
hours12 = 12;
|
||||||
|
} else {
|
||||||
|
hours12 = hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < fmt.length; ++i) {
|
||||||
|
|
||||||
|
var c = fmt.charAt(i);
|
||||||
|
|
||||||
|
if (escape) {
|
||||||
|
switch (c) {
|
||||||
|
case 'a': c = "" + dayNames[d.getDay()]; break;
|
||||||
|
case 'b': c = "" + monthNames[d.getMonth()]; break;
|
||||||
|
case 'd': c = leftPad(d.getDate()); break;
|
||||||
|
case 'e': c = leftPad(d.getDate(), " "); break;
|
||||||
|
case 'h': // For back-compat with 0.7; remove in 1.0
|
||||||
|
case 'H': c = leftPad(hours); break;
|
||||||
|
case 'I': c = leftPad(hours12); break;
|
||||||
|
case 'l': c = leftPad(hours12, " "); break;
|
||||||
|
case 'm': c = leftPad(d.getMonth() + 1); break;
|
||||||
|
case 'M': c = leftPad(d.getMinutes()); break;
|
||||||
|
// quarters not in Open Group's strftime specification
|
||||||
|
case 'q':
|
||||||
|
c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
|
||||||
|
case 'S': c = leftPad(d.getSeconds()); break;
|
||||||
|
case 'y': c = leftPad(d.getFullYear() % 100); break;
|
||||||
|
case 'Y': c = "" + d.getFullYear(); break;
|
||||||
|
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
|
||||||
|
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
|
||||||
|
case 'w': c = "" + d.getDay(); break;
|
||||||
|
}
|
||||||
|
r.push(c);
|
||||||
|
escape = false;
|
||||||
|
} else {
|
||||||
|
if (c == "%") {
|
||||||
|
escape = true;
|
||||||
|
} else {
|
||||||
|
r.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// To have a consistent view of time-based data independent of which time
|
||||||
|
// zone the client happens to be in we need a date-like object independent
|
||||||
|
// of time zones. This is done through a wrapper that only calls the UTC
|
||||||
|
// versions of the accessor methods.
|
||||||
|
|
||||||
|
function makeUtcWrapper(d) {
|
||||||
|
|
||||||
|
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
|
||||||
|
sourceObj[sourceMethod] = function() {
|
||||||
|
return targetObj[targetMethod].apply(targetObj, arguments);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var utc = {
|
||||||
|
date: d
|
||||||
|
};
|
||||||
|
|
||||||
|
// support strftime, if found
|
||||||
|
|
||||||
|
if (d.strftime != undefined) {
|
||||||
|
addProxyMethod(utc, "strftime", d, "strftime");
|
||||||
|
}
|
||||||
|
|
||||||
|
addProxyMethod(utc, "getTime", d, "getTime");
|
||||||
|
addProxyMethod(utc, "setTime", d, "setTime");
|
||||||
|
|
||||||
|
var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
|
||||||
|
|
||||||
|
for (var p = 0; p < props.length; p++) {
|
||||||
|
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
|
||||||
|
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return utc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// select time zone strategy. This returns a date-like object tied to the
|
||||||
|
// desired timezone
|
||||||
|
|
||||||
|
function dateGenerator(ts, opts) {
|
||||||
|
if (opts.timezone == "browser") {
|
||||||
|
return new Date(ts);
|
||||||
|
} else if (!opts.timezone || opts.timezone == "utc") {
|
||||||
|
return makeUtcWrapper(new Date(ts));
|
||||||
|
} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
|
||||||
|
var d = new timezoneJS.Date();
|
||||||
|
// timezone-js is fickle, so be sure to set the time zone before
|
||||||
|
// setting the time.
|
||||||
|
d.setTimezone(opts.timezone);
|
||||||
|
d.setTime(ts);
|
||||||
|
return d;
|
||||||
|
} else {
|
||||||
|
return makeUtcWrapper(new Date(ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map of app. size of time units in milliseconds
|
||||||
|
|
||||||
|
var timeUnitSize = {
|
||||||
|
"second": 1000,
|
||||||
|
"minute": 60 * 1000,
|
||||||
|
"hour": 60 * 60 * 1000,
|
||||||
|
"day": 24 * 60 * 60 * 1000,
|
||||||
|
"month": 30 * 24 * 60 * 60 * 1000,
|
||||||
|
"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
"year": 365.2425 * 24 * 60 * 60 * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
// the allowed tick sizes, after 1 year we use
|
||||||
|
// an integer algorithm
|
||||||
|
|
||||||
|
var baseSpec = [
|
||||||
|
[1, "second"], [2, "second"], [5, "second"], [10, "second"],
|
||||||
|
[30, "second"],
|
||||||
|
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
|
||||||
|
[30, "minute"],
|
||||||
|
[1, "hour"], [2, "hour"], [4, "hour"],
|
||||||
|
[8, "hour"], [12, "hour"],
|
||||||
|
[1, "day"], [2, "day"], [3, "day"],
|
||||||
|
[0.25, "month"], [0.5, "month"], [1, "month"],
|
||||||
|
[2, "month"]
|
||||||
|
];
|
||||||
|
|
||||||
|
// we don't know which variant(s) we'll need yet, but generating both is
|
||||||
|
// cheap
|
||||||
|
|
||||||
|
var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
|
||||||
|
[1, "year"]]);
|
||||||
|
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
|
||||||
|
[1, "year"]]);
|
||||||
|
|
||||||
|
function init(plot) {
|
||||||
|
plot.hooks.processOptions.push(function (plot, options) {
|
||||||
|
$.each(plot.getAxes(), function(axisName, axis) {
|
||||||
|
|
||||||
|
var opts = axis.options;
|
||||||
|
|
||||||
|
if (opts.mode == "time") {
|
||||||
|
axis.tickGenerator = function(axis) {
|
||||||
|
|
||||||
|
var ticks = [];
|
||||||
|
var d = dateGenerator(axis.min, opts);
|
||||||
|
var minSize = 0;
|
||||||
|
|
||||||
|
// make quarter use a possibility if quarters are
|
||||||
|
// mentioned in either of these options
|
||||||
|
|
||||||
|
var spec = (opts.tickSize && opts.tickSize[1] ===
|
||||||
|
"quarter") ||
|
||||||
|
(opts.minTickSize && opts.minTickSize[1] ===
|
||||||
|
"quarter") ? specQuarters : specMonths;
|
||||||
|
|
||||||
|
if (opts.minTickSize != null) {
|
||||||
|
if (typeof opts.tickSize == "number") {
|
||||||
|
minSize = opts.tickSize;
|
||||||
|
} else {
|
||||||
|
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < spec.length - 1; ++i) {
|
||||||
|
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
|
||||||
|
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
|
||||||
|
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = spec[i][0];
|
||||||
|
var unit = spec[i][1];
|
||||||
|
|
||||||
|
// special-case the possibility of several years
|
||||||
|
|
||||||
|
if (unit == "year") {
|
||||||
|
|
||||||
|
// if given a minTickSize in years, just use it,
|
||||||
|
// ensuring that it's an integer
|
||||||
|
|
||||||
|
if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
|
||||||
|
size = Math.floor(opts.minTickSize[0]);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
|
||||||
|
var norm = (axis.delta / timeUnitSize.year) / magn;
|
||||||
|
|
||||||
|
if (norm < 1.5) {
|
||||||
|
size = 1;
|
||||||
|
} else if (norm < 3) {
|
||||||
|
size = 2;
|
||||||
|
} else if (norm < 7.5) {
|
||||||
|
size = 5;
|
||||||
|
} else {
|
||||||
|
size = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
size *= magn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimum size for years is 1
|
||||||
|
|
||||||
|
if (size < 1) {
|
||||||
|
size = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axis.tickSize = opts.tickSize || [size, unit];
|
||||||
|
var tickSize = axis.tickSize[0];
|
||||||
|
unit = axis.tickSize[1];
|
||||||
|
|
||||||
|
var step = tickSize * timeUnitSize[unit];
|
||||||
|
|
||||||
|
if (unit == "second") {
|
||||||
|
d.setSeconds(floorInBase(d.getSeconds(), tickSize));
|
||||||
|
} else if (unit == "minute") {
|
||||||
|
d.setMinutes(floorInBase(d.getMinutes(), tickSize));
|
||||||
|
} else if (unit == "hour") {
|
||||||
|
d.setHours(floorInBase(d.getHours(), tickSize));
|
||||||
|
} else if (unit == "month") {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), tickSize));
|
||||||
|
} else if (unit == "quarter") {
|
||||||
|
d.setMonth(3 * floorInBase(d.getMonth() / 3,
|
||||||
|
tickSize));
|
||||||
|
} else if (unit == "year") {
|
||||||
|
d.setFullYear(floorInBase(d.getFullYear(), tickSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset smaller components
|
||||||
|
|
||||||
|
d.setMilliseconds(0);
|
||||||
|
|
||||||
|
if (step >= timeUnitSize.minute) {
|
||||||
|
d.setSeconds(0);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.hour) {
|
||||||
|
d.setMinutes(0);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.day) {
|
||||||
|
d.setHours(0);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.day * 4) {
|
||||||
|
d.setDate(1);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.month * 2) {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), 3));
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.quarter * 2) {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), 6));
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.year) {
|
||||||
|
d.setMonth(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var carry = 0;
|
||||||
|
var v = Number.NaN;
|
||||||
|
var prev;
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
prev = v;
|
||||||
|
v = d.getTime();
|
||||||
|
ticks.push(v);
|
||||||
|
|
||||||
|
if (unit == "month" || unit == "quarter") {
|
||||||
|
if (tickSize < 1) {
|
||||||
|
|
||||||
|
// a bit complicated - we'll divide the
|
||||||
|
// month/quarter up but we need to take
|
||||||
|
// care of fractions so we don't end up in
|
||||||
|
// the middle of a day
|
||||||
|
|
||||||
|
d.setDate(1);
|
||||||
|
var start = d.getTime();
|
||||||
|
d.setMonth(d.getMonth() +
|
||||||
|
(unit == "quarter" ? 3 : 1));
|
||||||
|
var end = d.getTime();
|
||||||
|
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
|
||||||
|
carry = d.getHours();
|
||||||
|
d.setHours(0);
|
||||||
|
} else {
|
||||||
|
d.setMonth(d.getMonth() +
|
||||||
|
tickSize * (unit == "quarter" ? 3 : 1));
|
||||||
|
}
|
||||||
|
} else if (unit == "year") {
|
||||||
|
d.setFullYear(d.getFullYear() + tickSize);
|
||||||
|
} else {
|
||||||
|
d.setTime(v + step);
|
||||||
|
}
|
||||||
|
} while (v < axis.max && v != prev);
|
||||||
|
|
||||||
|
return ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
axis.tickFormatter = function (v, axis) {
|
||||||
|
|
||||||
|
var d = dateGenerator(v, axis.options);
|
||||||
|
|
||||||
|
// first check global format
|
||||||
|
|
||||||
|
if (opts.timeformat != null) {
|
||||||
|
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
// possibly use quarters if quarters are mentioned in
|
||||||
|
// any of these places
|
||||||
|
|
||||||
|
var useQuarters = (axis.options.tickSize &&
|
||||||
|
axis.options.tickSize[1] == "quarter") ||
|
||||||
|
(axis.options.minTickSize &&
|
||||||
|
axis.options.minTickSize[1] == "quarter");
|
||||||
|
|
||||||
|
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
|
||||||
|
var span = axis.max - axis.min;
|
||||||
|
var suffix = (opts.twelveHourClock) ? " %p" : "";
|
||||||
|
var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
|
||||||
|
var fmt;
|
||||||
|
|
||||||
|
if (t < timeUnitSize.minute) {
|
||||||
|
fmt = hourCode + ":%M:%S" + suffix;
|
||||||
|
} else if (t < timeUnitSize.day) {
|
||||||
|
if (span < 2 * timeUnitSize.day) {
|
||||||
|
fmt = hourCode + ":%M" + suffix;
|
||||||
|
} else {
|
||||||
|
fmt = "%b %d " + hourCode + ":%M" + suffix;
|
||||||
|
}
|
||||||
|
} else if (t < timeUnitSize.month) {
|
||||||
|
fmt = "%b %d";
|
||||||
|
} else if ((useQuarters && t < timeUnitSize.quarter) ||
|
||||||
|
(!useQuarters && t < timeUnitSize.year)) {
|
||||||
|
if (span < timeUnitSize.year) {
|
||||||
|
fmt = "%b";
|
||||||
|
} else {
|
||||||
|
fmt = "%b %Y";
|
||||||
|
}
|
||||||
|
} else if (useQuarters && t < timeUnitSize.year) {
|
||||||
|
if (span < timeUnitSize.year) {
|
||||||
|
fmt = "Q%q";
|
||||||
|
} else {
|
||||||
|
fmt = "Q%q %Y";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt = "%Y";
|
||||||
|
}
|
||||||
|
|
||||||
|
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
|
||||||
|
|
||||||
|
return rt;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$.plot.plugins.push({
|
||||||
|
init: init,
|
||||||
|
options: options,
|
||||||
|
name: 'time',
|
||||||
|
version: '1.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Time-axis support used to be in Flot core, which exposed the
|
||||||
|
// formatDate function on the plot object. Various plugins depend
|
||||||
|
// on the function, so we need to re-expose it here.
|
||||||
|
|
||||||
|
$.plot.formatDate = formatDate;
|
||||||
|
$.plot.dateGenerator = dateGenerator;
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,75 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
// Start 1-second timer to call RESTful endpoint
|
||||||
|
setInterval(function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/get_cpu_usage',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(json) {
|
||||||
|
$('#cpu_usage').text(json.result + '% ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Initialize graph
|
||||||
|
var $c = $('<div class="graph"/>').appendTo('#graphs');
|
||||||
|
var dur = 120;
|
||||||
|
var past = Date.now() - dur * 1000;
|
||||||
|
var options = {
|
||||||
|
lines: {
|
||||||
|
fill: true,
|
||||||
|
lineWidth: 0,
|
||||||
|
fillColor: {colors: [ { opacity: 0 }, { opacity: 1 } ] }
|
||||||
|
},
|
||||||
|
grid: { borderWidth: 1, borderColor: '#ccc'},
|
||||||
|
xaxis: { mode: 'time', ticks: 5 },
|
||||||
|
legend: { labelBoxBorderColor: '#fff' },
|
||||||
|
colors: [ '#fec', '#396', '#e39', '9e2' ],
|
||||||
|
hooks: {
|
||||||
|
draw: [function(plot, canvas) {
|
||||||
|
canvas.font = '13px sans-serif';
|
||||||
|
canvas.fillStyle = '#aaa';
|
||||||
|
canvas.fillText('Resource Usage', 35, 25);
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var plot = $.plot($c, [], options);
|
||||||
|
|
||||||
|
var updateGraph = function(counter) {
|
||||||
|
var data = plot.getData();
|
||||||
|
var now = Date.now();
|
||||||
|
var oldest = now - dur * 1000;
|
||||||
|
data[0] = {
|
||||||
|
show: false,
|
||||||
|
data: [[oldest, null], [now, null]]
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// Remove old points
|
||||||
|
$.each(data, function(di, d) {
|
||||||
|
while (d.data.length > 0 && d.data[0][0] < oldest) {
|
||||||
|
d.data.shift();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new points
|
||||||
|
var new_datapoint = [now, counter];
|
||||||
|
if (data[1]) {
|
||||||
|
data[1].label = 'memory';
|
||||||
|
data[1].data.push(new_datapoint);
|
||||||
|
} else {
|
||||||
|
data[1] = [{ data: [new_datapoint] }];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw the graph
|
||||||
|
plot.setData(data);
|
||||||
|
plot.setupGrid();
|
||||||
|
plot.draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Websocket connection. For simplicity, no reconnect logic is here.
|
||||||
|
var ws = new WebSocket('ws://' + location.host);
|
||||||
|
ws.onmessage = function(ev) {
|
||||||
|
updateGraph(ev.data);
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
* {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #36c;
|
||||||
|
color: #555;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content {
|
||||||
|
width: 800px;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 2em;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset legend {
|
||||||
|
font-style: italic;
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset label {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
font-size: 100%;
|
||||||
|
padding: 0.2em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #666;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 0.2em 2em;
|
||||||
|
font-size: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
color: #396;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #69c;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#graphs {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph {
|
||||||
|
min-height: 12em;
|
||||||
|
border: 0px solid #ccc;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 1em 1em 0;
|
||||||
|
width: 36em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph_title {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em; left: 1em;
|
||||||
|
color: #ccc;
|
||||||
|
font-weight: bold;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
SOURCES = $(PROG).c ../../mongoose.c
|
||||||
|
CFLAGS = -g -W -Wall -I../.. -Wno-unused-function $(CFLAGS_EXTRA) $(MODULE_CFLAGS)
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -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,163 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\..\mongoose.c">
|
||||||
|
<WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">TurnOffAllWarnings</WarningLevel>
|
||||||
|
<WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">TurnOffAllWarnings</WarningLevel>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="http_client.c" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="..\..\mongoose.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>{BDF3FC6F-2658-4A27-846D-6A45FB53C602}</ProjectGuid>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<RootNamespace>http_client</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v140</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<WarningLevel>Level4</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;MG_ENABLE_THREADS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<AdditionalIncludeDirectories>./../../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;MG_ENABLE_THREADS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<AdditionalIncludeDirectories>./../../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
@@ -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,645 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/* Don't pass chunked transfer encoding to the client */
|
||||||
|
if (mg_vcasecmp(&hn, "Transfer-encoding") == 0 &&
|
||||||
|
mg_vcasecmp(&hv, "chunked") == 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 ? be->host_port : "not defined");
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if (ev != MG_EV_POLL) {
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
Loaded 100 of 179 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user