mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-15 10:29:47 +00:00
Merge dev branch code named Fossa as next stable Mongoose
This commit is contained in:
1 parent
28eb251ccd
commit
8927c9d22b
126 files changed
+162049
-8502
No files matched your search
@@ -2,15 +2,15 @@ Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
Copyright (c) 2013-2015 Cesanta Software Limited
|
||||
All rights reserved
|
||||
|
||||
This code is dual-licensed: you can redistribute it and/or modify
|
||||
This software is dual-licensed: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation. For the terms of this
|
||||
license, see <http://www.gnu.org/licenses>.
|
||||
license, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
You are free to use this code under the terms of the GNU General
|
||||
You are free to use this software under the terms of the GNU General
|
||||
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
Alternatively, you can license this code under a commercial
|
||||
license, as set out in <http://cesanta.com/>.
|
||||
Alternatively, you can license this software under a commercial
|
||||
license, as set out in <https://www.cesanta.com/license>.
|
||||
@@ -2,20 +2,23 @@
|
||||
|
||||
[](https://gitter.im/cesanta/mongoose?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Mongoose is an embedded HTTP and WebSocket library that can turn anything
|
||||
into a web server in 5 minutes by adding a few lines of C/C++ code.
|
||||
On the market since 2004 with over 1 million cumulative downloads,
|
||||
it's simplicity and flexibility has made it the top choice for
|
||||
embedded software engineers.
|
||||

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

|
||||
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>RESTful API demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
* { outline: none; }
|
||||
body {
|
||||
background-color: #789; margin: 0;
|
||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
background-color: #fff; border-radius: 1em;
|
||||
}
|
||||
label { display: inline-block; min-width: 12em; }
|
||||
input { border: 1px solid #ccc; padding: 0.2em; min-width: 20em; }
|
||||
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||
#result {
|
||||
background: #ffc; min-height: 3em; border: 1px solid #ccc;
|
||||
white-space: pre-wrap; font-size: 85%;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body { background-color: #fff; }
|
||||
div.content { width: auto; margin: 0 auto; padding: 1em; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||
<script language="javascript" type="text/javascript">
|
||||
jQuery(function() {
|
||||
|
||||
$(document).on('click', '#upload', function() {
|
||||
var data = {};
|
||||
$('#upload_form [name]').each(function(index, el) {
|
||||
data[$(el).attr('name')] = $(el).val();
|
||||
});
|
||||
$('#result').text('');
|
||||
$.ajax({
|
||||
url: '/upload',
|
||||
method: 'POST',
|
||||
dataType: 'html',
|
||||
data: data,
|
||||
success: function(data) {
|
||||
$('#result').text(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>RESTful server with Amazon S3 integration demo.</h1>
|
||||
|
||||
<p>
|
||||
This page demonstrates how Mongoose could be used to implement
|
||||
a RESTful service that uses another RESTful service to handle
|
||||
it's own API call. This example takes form data and uploads it as a file
|
||||
to Amazon S3.
|
||||
Open S3 console and create a bucket for testing. Fill out correct
|
||||
bucket name in the fields below.
|
||||
</p>
|
||||
|
||||
<p>If you're getting a "Temporary Redirect" error, look what is the
|
||||
<b>Endpoint</b> value is. It's likely that you have something like
|
||||
<b>BUCKET_NAME.S3_ZONE.amazonaws.com</b>. Change
|
||||
the <b>Host</b> field to <b>S3_ZONE.amazonaws.com</b> and retry.
|
||||
</p>
|
||||
|
||||
<div id="upload_form">
|
||||
<div>
|
||||
<label>Bucket name:</label> <input type="text" name="bucket"
|
||||
value="my_uploads"/>
|
||||
</div><div>
|
||||
<label>File name:</label> <input type="text" name="file_name"
|
||||
value="myfile.txt"/>
|
||||
</div><div>
|
||||
<label>File content:</label> <input type="text" name="file_data"
|
||||
value=":-)"/>
|
||||
</div><div>
|
||||
<label>Host:</label> <input type="text" name="host"
|
||||
value="s3.amazonaws.com"/>
|
||||
</div><div>
|
||||
<label></label>
|
||||
<button id="upload">Upload</button>
|
||||
</div><div>
|
||||
<label>Result:</label> <pre id="result"> </pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
static const char *s_access_key_id = NULL;
|
||||
static const char *s_secret_access_key = NULL;
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
|
||||
static void send_error_result(struct mg_connection *nc, const char *msg) {
|
||||
mg_printf_http_chunk(nc, "Error: %s", msg);
|
||||
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
|
||||
}
|
||||
|
||||
static void link_conns(struct mg_connection *nc1, struct mg_connection *nc2) {
|
||||
nc1->user_data = nc2;
|
||||
nc2->user_data = nc1;
|
||||
}
|
||||
|
||||
static void unlink_conns(struct mg_connection *nc1) {
|
||||
struct mg_connection *nc2 = (struct mg_connection *) nc1->user_data;
|
||||
if (nc1->user_data != NULL) {
|
||||
nc1->user_data = NULL;
|
||||
nc2->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* S3 client handler */
|
||||
static void s3_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
struct mg_connection *nc2 = (struct mg_connection *) nc->user_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REPLY:
|
||||
if (nc2 != NULL) {
|
||||
mg_printf_http_chunk(nc2, "Error: %.*s", (int)hm->message.len,
|
||||
hm->message.p);
|
||||
mg_send_http_chunk(nc2, "", 0);
|
||||
}
|
||||
unlink_conns(nc);
|
||||
nc->flags |= NSF_SEND_AND_CLOSE;
|
||||
break;
|
||||
case NS_CLOSE:
|
||||
unlink_conns(nc);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void send_s3_request(struct mg_connection *nc, const char *file_name,
|
||||
const char *file_data, const char *host,
|
||||
const char *bucket) {
|
||||
char host_port[100];
|
||||
struct mg_connection *s3_conn;
|
||||
|
||||
snprintf(host_port, sizeof(host_port), "%s:80", host);
|
||||
s3_conn = mg_connect(nc->mgr, host_port, s3_handler);
|
||||
|
||||
if (s3_conn == NULL) {
|
||||
send_error_result(nc, "s3 connection failed");
|
||||
} else {
|
||||
const char *content_type = "text/plain", *method = "PUT";
|
||||
char date[100], to_sign[500], signature[100], sha1[20], req[1000];
|
||||
time_t now = time(NULL);
|
||||
|
||||
link_conns(nc, s3_conn);
|
||||
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
|
||||
mg_set_protocol_http_websocket(s3_conn);
|
||||
|
||||
/* Prepare S3 authorization header */
|
||||
snprintf(to_sign, sizeof(to_sign), "%s\n\n%s\n%s\n/%s/%s", method,
|
||||
content_type, date, bucket, file_name);
|
||||
hmac_sha1((unsigned char *)s_secret_access_key, strlen(s_secret_access_key),
|
||||
(unsigned char *)to_sign, strlen(to_sign), (unsigned char *)sha1);
|
||||
mg_base64_encode((unsigned char *) sha1, sizeof(sha1), signature);
|
||||
snprintf(req, sizeof(req),
|
||||
"%s /%s HTTP/1.1\r\n"
|
||||
"Host: %s.%s\r\n"
|
||||
"Date: %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %lu\r\n"
|
||||
"Authorization: AWS %s:%s\r\n"
|
||||
"\r\n",
|
||||
method, file_name, bucket, host, date, content_type,
|
||||
(unsigned long)strlen(file_data), s_access_key_id, signature);
|
||||
mg_printf(s3_conn, "%s%s", req, file_data);
|
||||
/* S3 request sent, wait for a reply */
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_api_call(struct mg_connection *nc, struct http_message *hm) {
|
||||
char file_name[100], file_data[100], host[100], bucket[100];
|
||||
|
||||
/* Get form variables */
|
||||
mg_get_http_var(&hm->body, "file_name", file_name, sizeof(file_name));
|
||||
mg_get_http_var(&hm->body, "file_data", file_data, sizeof(file_data));
|
||||
mg_get_http_var(&hm->body, "host", host, sizeof(host));
|
||||
mg_get_http_var(&hm->body, "bucket", bucket, sizeof(bucket));
|
||||
|
||||
/* Send headers */
|
||||
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
|
||||
/* Send body */
|
||||
if (file_name[0] == '\0' || file_data[0] == '\0' || bucket[0] == '\0') {
|
||||
send_error_result(nc, "bad input");
|
||||
} else {
|
||||
send_s3_request(nc, file_name, file_data, host, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
/* Server handler */
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/upload") == 0) {
|
||||
handle_api_call(nc, hm); /* Handle RESTful call */
|
||||
} else {
|
||||
mg_serve_http(nc, hm, s_http_server_opts); /* Serve static content */
|
||||
}
|
||||
break;
|
||||
case NS_CLOSE:
|
||||
unlink_conns(nc);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
int i;
|
||||
char *cp;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
s_http_server_opts.document_root = ".";
|
||||
s_http_server_opts.enable_directory_listing = "yes";
|
||||
|
||||
/* Use current binary directory as document root */
|
||||
if (argc > 0 && ((cp = strrchr(argv[0], '/')) != NULL ||
|
||||
(cp = strrchr(argv[0], '/')) != NULL)) {
|
||||
*cp = '\0';
|
||||
s_http_server_opts.document_root = argv[0];
|
||||
}
|
||||
|
||||
/* Process command line options to customize HTTP server */
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-D") == 0 && i + 1 < argc) {
|
||||
mgr.hexdump_file = argv[++i];
|
||||
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
|
||||
s_http_port = argv[++i];
|
||||
} else if (strcmp(argv[i], "-a") == 0 && i + 1 < argc) {
|
||||
s_access_key_id = argv[++i];
|
||||
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
|
||||
s_secret_access_key = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
if (s_access_key_id == NULL || s_secret_access_key == NULL) {
|
||||
fprintf(stderr, "Usage: %s -a access_key_id -s s_secret_access_key "
|
||||
"[-p port] [-D hexdump_file]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Starting RESTful server on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -pthread -DNS_ENABLE_SSL -DNS_ENABLE_IPV6 -DNS_ENABLE_THREADS -lssl -lcrypto $(CFLAGS_EXTRA) $(MODULE_CFLAGS)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
$(PROG).exe: $(SOURCES)
|
||||
cl $(SOURCES) /I../.. /DNS_ENABLE_SSL /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
||||
@@ -1,21 +0,0 @@
|
||||
# Copyright (c) 2014 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = send_file
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
run: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
$(PROG): $(SOURCES) Makefile
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
win:
|
||||
wine cl $(SOURCES) /MD /nologo /DNDEBUG /O1 /I../.. /Fe$(PROG).exe
|
||||
wine $(PROG).exe
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib *.gc*
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2014 Cesanta Software
|
||||
// All rights reserved
|
||||
//
|
||||
// This example demostrates how to send arbitrary files to the client.
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_REQUEST:
|
||||
mg_send_file(conn, "send_file.c", NULL); // Also could be a dir, or CGI
|
||||
return MG_MORE; // It is important to return MG_MORE after mg_send_file!
|
||||
case MG_AUTH: return MG_TRUE;
|
||||
default: return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_server *server = mg_create_server(NULL, ev_handler);
|
||||
mg_set_option(server, "listening_port", "8080");
|
||||
|
||||
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||
for (;;) mg_poll_server(server, 1000);
|
||||
mg_destroy_server(&server);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# Copyright (c) 2015 Cesanta Software
|
||||
# All rights reserved
|
||||
|
||||
PROG = server_data_push
|
||||
CFLAGS = -W -Wall -I../.. -pthread -g -O0 -DMONGOOSE_ENABLE_THREADS $(CFLAGS_EXTRA)
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
*
|
||||
* In this example we have a background data producer that periodically
|
||||
* produces a piece of data, which is then distributed to all connected
|
||||
* WebSocket clients.
|
||||
*
|
||||
* Data producing thread is outside of the event loop so synchronization
|
||||
* using a mutex is required.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static pthread_mutex_t s_data_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int s_data_version = 0;
|
||||
static char s_data[100];
|
||||
|
||||
void *data_producer(void *arg) {
|
||||
(void) arg;
|
||||
fprintf(stderr, "Data producer running\n");
|
||||
srand(time(NULL));
|
||||
while (1) {
|
||||
pthread_mutex_lock(&s_data_lock);
|
||||
snprintf(s_data, sizeof(s_data), "The lucky number is %d.", rand() % 100);
|
||||
s_data_version++;
|
||||
pthread_mutex_unlock(&s_data_lock);
|
||||
sleep(1 + rand() % 10);
|
||||
}
|
||||
}
|
||||
|
||||
struct conn_state {
|
||||
int data_version;
|
||||
/* More state goes here. */
|
||||
};
|
||||
|
||||
void maybe_send_data(struct mg_connection *conn) {
|
||||
struct conn_state *cs = (struct conn_state *) conn->connection_param;
|
||||
if (cs == NULL) return; /* Not connected yet. */
|
||||
pthread_mutex_lock(&s_data_lock);
|
||||
if (cs->data_version != s_data_version) {
|
||||
mg_websocket_printf(conn, WEBSOCKET_OPCODE_TEXT, "%s\n", s_data);
|
||||
cs->data_version = s_data_version;
|
||||
}
|
||||
pthread_mutex_unlock(&s_data_lock);
|
||||
}
|
||||
|
||||
static int ev_handler(struct mg_connection *conn, enum mg_event ev) {
|
||||
switch (ev) {
|
||||
case MG_AUTH:
|
||||
return MG_TRUE; /* Authenticated. */
|
||||
case MG_WS_HANDSHAKE:
|
||||
return MG_FALSE; /* Let Mongoose complete the handshake. */
|
||||
case MG_WS_CONNECT:
|
||||
fprintf(stderr, "%s:%u joined\n", conn->remote_ip, conn->remote_port);
|
||||
conn->connection_param = calloc(1, sizeof(struct conn_state));
|
||||
mg_websocket_printf(conn, WEBSOCKET_OPCODE_TEXT, "Hi %p!\n", conn);
|
||||
maybe_send_data(conn);
|
||||
return MG_FALSE; /* Keep the connection open. */
|
||||
case MG_POLL:
|
||||
maybe_send_data(conn);
|
||||
return MG_FALSE; /* Keep the connection open. */
|
||||
case MG_CLOSE:
|
||||
fprintf(stderr, "%s:%u went away\n", conn->remote_ip, conn->remote_port);
|
||||
free(conn->connection_param);
|
||||
conn->connection_param = NULL;
|
||||
return MG_TRUE;
|
||||
default:
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const char *listen_port = "8080";
|
||||
struct mg_server *server;
|
||||
const char *err;
|
||||
|
||||
server = mg_create_server(NULL, ev_handler);
|
||||
err = mg_set_option(server, "listening_port", listen_port);
|
||||
if (err != NULL) {
|
||||
fprintf(stderr, "Error setting up listener on %s: %s\n", listen_port, err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
mg_start_thread(data_producer, NULL);
|
||||
|
||||
printf("Listening on %s\n", listen_port);
|
||||
while (1) {
|
||||
mg_poll_server(server, 100);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
PROG = settings_panel
|
||||
SOURCES = $(PROG).c ../../mongoose.c
|
||||
CFLAGS = -W -Wall -I../.. -DNS_ENABLE_SSL -lssl -lcrypto $(CFLAGS_EXTRA)
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) -o $@ $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
struct device_settings {
|
||||
char setting1[100];
|
||||
char setting2[100];
|
||||
};
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
static struct device_settings s_settings = { "value1", "value2" };
|
||||
|
||||
static void handle_save(struct mg_connection *nc, struct http_message *hm) {
|
||||
/* Get form variables and store settings values */
|
||||
mg_get_http_var(&hm->body, "setting1", s_settings.setting1,
|
||||
sizeof(s_settings.setting1));
|
||||
mg_get_http_var(&hm->body, "setting2", s_settings.setting2,
|
||||
sizeof(s_settings.setting2));
|
||||
|
||||
/* Send response */
|
||||
mg_printf(nc, "HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n%.*s",
|
||||
(unsigned long) hm->body.len, (int) hm->body.len, hm->body.p);
|
||||
}
|
||||
|
||||
static void handle_ssi_call(struct mg_connection *nc, const char *param) {
|
||||
if (strcmp(param, "setting1") == 0) {
|
||||
mg_printf_html_escape(nc, "%s", s_settings.setting1);
|
||||
} else if (strcmp(param, "setting2") == 0) {
|
||||
mg_printf_html_escape(nc, "%s", s_settings.setting2);
|
||||
}
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case NS_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/save") == 0) {
|
||||
handle_save(nc, hm); /* Handle RESTful call */
|
||||
} else {
|
||||
mg_serve_http(nc, hm, s_http_server_opts); /* Serve static content */
|
||||
}
|
||||
break;
|
||||
case NS_SSI_CALL:
|
||||
handle_ssi_call(nc, ev_data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
char *p, path[512];
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
s_http_server_opts.document_root = "./web_root";
|
||||
s_http_server_opts.auth_domain = "example.com";
|
||||
//mgr.hexdump_file = "/dev/stdout";
|
||||
|
||||
/* If our current directory */
|
||||
if (argc > 0 && (p = strrchr(argv[0], '/'))) {
|
||||
snprintf(path, sizeof(path), "%.*s/web_root", (int)(p - argv[0]), argv[0]);
|
||||
s_http_server_opts.document_root = path;
|
||||
}
|
||||
|
||||
printf("Starting device configurator on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>RESTful API demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
* { outline: none; }
|
||||
body {
|
||||
background-color: #789; margin: 0;
|
||||
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
font: 16px/1.4 Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div.content {
|
||||
width: 800px; margin: 2em auto; padding: 20px 50px;
|
||||
background-color: #fff; border-radius: 1em;
|
||||
}
|
||||
code { background: #eee; padding: 0 0.3em; border-radius: 0.2em; }
|
||||
label { display: inline-block; min-width: 5em; }
|
||||
input { border: 1px solid #ccc; padding: 0.2em; margin-right: 2em; }
|
||||
a:link, a:visited { color: #69c; text-decoration: none; }
|
||||
@media (max-width: 900px) {
|
||||
div.content { width: auto; margin: 2em; padding: 1em; }
|
||||
}
|
||||
fieldset { border: 1px solid #ccc; padding: 1em; }
|
||||
#result {
|
||||
background: #cfc; border: 1px solid #ccc; padding: 2px 1em;
|
||||
white-space: pre-wrap; font-size: 85%; display: none; text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="jquery-1.11.3.min.js"></script>
|
||||
<script language="javascript" type="text/javascript">
|
||||
jQuery(function() {
|
||||
|
||||
$(document).on('submit', '#settings_form', function() {
|
||||
var data = {};
|
||||
$('#settings_form [name]').each(function(index, el) {
|
||||
data[$(el).attr('name')] = $(el).val();
|
||||
});
|
||||
$('#result').text('');
|
||||
$.ajax({
|
||||
url: '/save',
|
||||
method: 'POST',
|
||||
dataType: 'html',
|
||||
data: data,
|
||||
success: function(data) {
|
||||
$('#result').text('saved').show().fadeOut(2000);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<img src="mongoose.jpg" style="float:right; height: 50px; border-radius: 3px;">
|
||||
<h1>Device Configurator demo.</h1>
|
||||
|
||||
<p>
|
||||
This page demonstrates how Mongoose could be used to implement
|
||||
device settings page.</p>
|
||||
|
||||
<h3>How to show device parameters on the page</h3>
|
||||
<p>This page has embedded
|
||||
<code><!--#call parameter_name --></code> blocks. For each such
|
||||
block, mongoose triggers <code>NS_SSI_CALL</code> event, passing
|
||||
<code>parameter_name</code> string as an event parameter. A callback
|
||||
then can print some content, which will replace the
|
||||
<code><!--#call parameter_name --></code> block.
|
||||
Take a look at <code>handle_ssi_call()</code> to see how that is done.
|
||||
</p>
|
||||
|
||||
<h3>How to save updated values</h3>
|
||||
<p>When Save button is clicked, this page makes Ajax call and passes
|
||||
values to the backend in a POST request. Backend extracts values from
|
||||
the POST request and updates the configuration. Take a look at
|
||||
<code>handle_save()</code> to see how that is done.</p>
|
||||
|
||||
|
||||
<p>You can change values, click Save button, refresh this page - make sure
|
||||
settings values stay intact between refreshes. </p>
|
||||
|
||||
<form method="POST" id="settings_form">
|
||||
<fieldset>
|
||||
<legend>Device settings</legend>
|
||||
<label>Setting 1:</label> <input type="text"
|
||||
name="setting1" value="<!--#call setting1 -->" >
|
||||
<label>Setting 2:</label> <input type="text"
|
||||
name="setting2" value="<!--#call setting2 -->" >
|
||||
</fieldset>
|
||||
|
||||
<div style="margin: 1em 0;">
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
<div id="result"> </div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user