Commit e5659774 authored by Marko Mikulicic's avatar Marko Mikulicic

Merge pull request #568 from cesanta/fossamerge

Merge dev branch code named Fossa as next stable Mongoose
parents 28eb251c 8927c9d2
...@@ -2,15 +2,15 @@ Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> ...@@ -2,15 +2,15 @@ Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
Copyright (c) 2013-2015 Cesanta Software Limited Copyright (c) 2013-2015 Cesanta Software Limited
All rights reserved All rights reserved
This code is dual-licensed: you can redistribute it and/or modify This software is dual-licensed: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation. For the terms of this published by the Free Software Foundation. For the terms of this
license, see <http://www.gnu.org/licenses>. license, see <http://www.gnu.org/licenses/>.
You are free to use this code under the terms of the GNU General You are free to use this software under the terms of the GNU General
Public License, but WITHOUT ANY WARRANTY; without even the implied Public License, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details. See the GNU General Public License for more details.
Alternatively, you can license this code under a commercial Alternatively, you can license this software under a commercial
license, as set out in <http://cesanta.com/>. license, as set out in <https://www.cesanta.com/license>.
...@@ -2,20 +2,23 @@ ...@@ -2,20 +2,23 @@
[![Join the chat at https://gitter.im/cesanta/mongoose](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cesanta/mongoose?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/cesanta/mongoose](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cesanta/mongoose?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Mongoose is an embedded HTTP and WebSocket library that can turn anything ![](https://img.shields.io/badge/license-GPL_2-green.svg "License")
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 [Mongoose](https://www.cesanta.com/mongoose) is a
GUI on embedded devices, implement RESTful services, RPC frameworks (e.g. multi-protocol networking library written in C.
JSON-RPC), handle telemetry data exchange and perform many other tasks. You'll find It provides easy to use event-driven interface that allows to implement
it used across various industries including aerospace, manufacturing, finance, network protocols or scalable network applications with little effort.
research, automotive, gaming, IT and many more. 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 Mongoose has built-in support for several protocols, like
> software should be!" - Pritin Tyagaraj, SAP 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) [Download Mongoose Source Code here](http://hubs.ly/H0150FK0)
...@@ -23,21 +26,31 @@ Are you an embedded developer? Working on an embedded task? ...@@ -23,21 +26,31 @@ Are you an embedded developer? Working on an embedded task?
Check out our [embedded development products](http://hubs.ly/H0150sY0) Check out our [embedded development products](http://hubs.ly/H0150sY0)
to make the right choice for your project. to make the right choice for your project.
# Technical Specification # Features
- Works on Windows, Mac, UNIX/Linux, iPhone, Android eCos, QNX * Cross-platform: works on Linux/UNIX, QNX, eCos, Windows, Android, iPhone, etc
and many other platforms * Single-threaded, asynchronous, non-blocking core with simple event-based API
- CGI, SSI, SSL, Digest auth, Websocket, WEbDAV, Resumed download, * Builtin protocols:
URL rewrite, file blacklist - plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
- Custom error pages, Virtual hosts, IP-based ACL, Windows service, - HTTP client, HTTP server
HTTP/HTTPS client - Websocket client, Websocket server
- Simple and clean - JSON-RPC client, JSON-RPC server
[embedding API](https://github.com/cesanta/mongoose/blob/master/mongoose.h). - MQTT client, MQTT broker
The source is in single - CoAP client, CoAP server
[mongoose.c](https://github.com/cesanta/mongoose/blob/master/mongoose.c) file - DNS client, DNS server, async DNS resolver
to make embedding easy * Tiny static and run-time footprint
- Extremely lightweight, has a core of under 40kB and tiny runtime footprint * Source code is both ISO C and ISO C++ compliant
- Asynchronous, non-blocking core supporting single- or multi-threaded usage * Very easy to integrate: just copy
[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 # Dashboard Example
...@@ -47,43 +60,17 @@ and many other platforms ...@@ -47,43 +60,17 @@ and many other platforms
](https://www.cesanta.com/contact) ](https://www.cesanta.com/contact)
# Contributions
# Licensing People who have agreed to the
[Cesanta CLA](https://docs.cesanta.com/contributors_la.shtml)
Cesanta made Mongoose open source under GPLv2 for a reason. We are all can make contributions. Note that the CLA isn't a copyright
developers here and appreciate easy access to code and therefore seamless _assigment_ but rather a copyright _license_.
integration. It's great to be able to play around with the software before You retain the copyright on your contributions.
committing to it.
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 # License
- [Smart.js](https://github.com/cesanta/smart.js) - Generic, hardware independent, full-stack IoT software platform Mongoose is released under
- [Fossa](http://github.com/cesanta/fossa) - Multi-protocol networking library [GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
- [V7](https://github.com/cesanta/v7) - Embedded JavaScript engine Businesses have an option to get non-restrictive, royalty-free commercial
- [Frozen](https://github.com/cesanta/frozen) - JSON parser and generator license and professional support from [Cesanta](https://www.cesanta.com).
- [SLRE](https://github.com/cesanta/slre) - Super Light Regular Expression
library
# Copyright (c) 2014 Cesanta Software # Copyright (c) 2014 Cesanta Software
# All rights reserved # All rights reserved
SUBDIRS = $(sort $(filter-out csharp/, $(dir $(wildcard */)))) SUBDIRS = $(sort $(dir $(wildcard */)))
X = $(SUBDIRS) X = $(SUBDIRS)
ifdef WINDIR
# appending the Winsock2 library at the end of the compiler
# invocation
CFLAGS_EXTRA += -lws2_32
endif
.PHONY: $(SUBDIRS) .PHONY: $(SUBDIRS)
all: $(SUBDIRS) all: $(SUBDIRS)
$(SUBDIRS): $(SUBDIRS):
@$(MAKE) CFLAGS_EXTRA="$(CFLAGS_EXTRA)" -C $@ @$(MAKE) -C $@
clean: clean:
for d in $(SUBDIRS) ; do $(MAKE) -C $$d clean ; done for d in $(SUBDIRS) ; do $(MAKE) -C $$d clean ; done
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)
/*
* 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;
}
/*
* 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 */
/*
* 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;
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
#!/bin/sh
PROG=$1
PORT=${2:-8000} # If second param is given, this is load balancer port
DB_FILE=/tmp/_$$.db
URL=http://127.0.0.1:$PORT/api/v1
cleanup() {
rm -rf $DB_FILE
kill -9 $PID >/dev/null 2>&1
}
#set -x
trap cleanup EXIT
cleanup
$PROG -f $DB_FILE &
PID=$!
#sleep 1
curl -s -X PUT -d 'value=123' $URL/foo
curl -s -X PUT -d 'value=success' $URL/bar/baz
# Fetch existing key
RESULT=$(curl -s $URL/bar/baz)
test "$RESULT" = "success" || exit 1
# Delete it
curl -s -X DELETE $URL/bar/baz
# Make sure it's deleted - GET must result in 404
RESULT=$(curl -s -i $URL/bar/baz | head -1 | tr -d '\r')
test "$RESULT" = "HTTP/1.1 404 Not Found" || exit 1
exit 0
...@@ -2,32 +2,37 @@ ...@@ -2,32 +2,37 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>WebSocket Test</title> <title>RESTful API demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css"> <style type="text/css">
* { outline: none; }
body { body {
background-color: #cde; margin: 0; background-color: #789; margin: 0;
padding: 0; font: 14px Helvetica, Arial, sans-serif; padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
font: 16px/1.4 Helvetica, Arial, sans-serif;
} }
* { outline: none; }
div.content { div.content {
width: 800px; margin: 2em auto; padding: 20px 50px; width: 800px; margin: 2em auto; padding: 20px 50px;
background-color: #fff; border-radius: 1em; background-color: #fff; border-radius: 1em;
} }
label { display: inline-block; min-width: 7em; } label { display: inline-block; min-width: 7em; }
input { border: 1px solid #ccc; padding: 0.4em; margin: 0 0 10px 0; } input { border: 1px solid #ccc; padding: 0.2em; }
a:link, a:visited { color: #69c; text-decoration: none; } a:link, a:visited { color: #69c; text-decoration: none; }
@media (max-width: 700px) { @media (max-width: 700px) {
body { background-color: #fff; } body { background-color: #fff; }
div.content { div.content { width: auto; margin: 0 auto; padding: 1em; }
width: auto; margin: 0 auto; border-radius: 0; padding: 1em;
}
} }
</style> </style>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script language="javascript" type="text/javascript">
jQuery(function() {
});
</script>
</head>
<body> <body>
<div class="content"> <div class="content">
<h1>Mongoose Cookie Base Authentication</h1> <h1>API server</h1>
<p>This is an index page. Authentication succeeded.</p> </div>
</body> </body>
</html> </html>
# This "makefile" is only intended to prevent errors during main makefile execution
all:
# 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)
/*
* 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);
}
}
# This "makefile" is only intended to prevent errors during main makefile execution
all:
# 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.
/*
* 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);
}
# 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*
// 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;
}
# 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
#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;
}
PROG = captive_dns_server
MODULE_CFLAGS=-DNS_ENABLE_DNS_SERVER
include ../rules.mk
/*
* 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;
}
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)
/*
* 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;
}
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)
/*
* 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;
}
# 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
// 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;
}
<!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>
\ No newline at end of file
// 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);
}
}
}
// 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()
}
# 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
#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;
}
# 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*
// 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;
}
# 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
#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;
}
# 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*
// 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;
}
# Copyright (c) 2014 Cesanta Software
# All rights reserved
PROG = http_client PROG = http_client
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
SOURCES = $(PROG).c ../../mongoose.c SOURCES = $(PROG).c ../../mongoose.c
CFLAGS = -W -Wall -I../.. -DNS_ENABLE_SSL -lssl -lcrypto -pthread $(CFLAGS_EXTRA)
all: $(PROG)
$(PROG): $(SOURCES)
$(CC) $(SOURCES) -o $@ $(CFLAGS)
unix: $(SOURCES) $(PROG).exe: $(SOURCES)
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS) cl $(SOURCES) /I../.. /MD /Fe$@
clean: clean:
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp *.o *.lib rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROG)
// Copyright (c) 2014 Cesanta Software /*
// All rights reserved * Copyright (c) 2014 Cesanta Software Limited
// * All rights reserved
// This example demostrates how to connect to the remote Web server, *
// download data, process it and send back a reply. * This program fetches HTTP URLs.
*/
#include <signal.h>
#include <stdlib.h>
#include "mongoose.h" #include "mongoose.h"
static int s_received_signal = 0; static int s_exit_flag = 0;
static struct mg_server *s_server = NULL; static int s_show_headers = 0;
static const char *s_remote_addr = "glosbe.com:80"; static const char *s_show_headers_opt = "--show-headers";
static void signal_handler(int sig_num) {
signal(sig_num, signal_handler);
s_received_signal = sig_num;
}
static int ev_handler(struct mg_connection *conn, enum mg_event ev) { static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct mg_connection *client, *orig; struct http_message *hm = (struct http_message *) ev_data;
switch (ev) { switch (ev) {
case MG_AUTH: case NS_CONNECT:
return MG_TRUE; if (* (int *) ev_data != 0) {
fprintf(stderr, "connect() failed: %s\n", strerror(* (int *) ev_data));
case MG_CONNECT: s_exit_flag = 1;
// Send request to the remote host. }
// TODO(lsm): handle connect error here. break;
mg_printf(conn, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", case NS_HTTP_REPLY:
"/gapi/translate?from=eng&dest=fra&format=json&phrase=cat", nc->flags |= NSF_CLOSE_IMMEDIATELY;
s_remote_addr); if (s_show_headers) {
return MG_TRUE; fwrite(hm->message.p, 1, hm->message.len, stdout);
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 { } else {
mg_printf_data(conn, "%s", "cannot send API request"); fwrite(hm->body.p, 1, hm->body.len, stdout);
return MG_TRUE;
} }
putchar('\n');
s_exit_flag = 1;
break;
default: default:
return MG_FALSE; break;
} }
} }
int main(void) { int main(int argc, char *argv[]) {
s_server = mg_create_server(NULL, ev_handler); struct mg_mgr mgr;
int i;
mg_mgr_init(&mgr, NULL);
mg_set_option(s_server, "listening_port", "8080"); /* 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;
}
}
if (i + 1 != argc) {
fprintf(stderr, "Usage: %s [%s] [--hexdump <file>] <URL>\n",
argv[0], s_show_headers_opt);
exit(EXIT_FAILURE);
}
// Setup signal handlers mg_connect_http(&mgr, ev_handler, argv[i], NULL, NULL);
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
printf("Listening on port %s\n", mg_get_option(s_server, "listening_port")); while (s_exit_flag == 0) {
while (s_received_signal == 0) { mg_mgr_poll(&mgr, 1000);
mg_poll_server(s_server, 1000);
} }
mg_destroy_server(&s_server); mg_mgr_free(&mgr);
printf("Existing on signal %d\n", s_received_signal);
return EXIT_SUCCESS; return 0;
} }
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)
/*
* 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;
}
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"]
# 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)
# 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.
/*
* 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;
}
#!/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 $?
# 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
#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;
}
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)
/*
* 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);
}
}
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)
/*
* 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);
}
}
# 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
#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;
}
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)
// 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;
}
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)
...@@ -12,43 +12,80 @@ ...@@ -12,43 +12,80 @@
// See the GNU General Public License for more details. // See the GNU General Public License for more details.
// //
// Alternatively, you can license this software under a commercial // Alternatively, you can license this software under a commercial
// license, as set out in <http://cesanta.com/products.html>. // license, as set out in <https://www.cesanta.com/license>.
// //
// $Date$ // $Date: 2014-09-28 05:04:41 UTC $
#include "net_skeleton.h" // This file implements "netcat" utility with SSL and traffic hexdump.
#include "ssl_wrapper.h"
static void ev_handler(struct ns_connection *nc, int ev, void *p) { #include "mongoose.h"
const char *target_addr = (const char *) nc->mgr->user_data;
struct ns_connection *pc = (struct ns_connection *) nc->user_data;
struct iobuf *io = &nc->recv_iobuf;
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; (void) p;
switch (ev) { switch (ev) {
case NS_ACCEPT: case NS_ACCEPT:
// Create a connection to the target, and interlink both connections case NS_CONNECT:
nc->user_data = ns_connect(nc->mgr, target_addr, ev_handler, nc); mg_start_thread(stdio_thread_func, nc->mgr);
if (nc->user_data == NULL) {
nc->flags |= NSF_CLOSE_IMMEDIATELY;
}
break; break;
case NS_CLOSE: case NS_CLOSE:
// If either connection closes, unlink them and shedule closing s_received_signal = 1;
if (pc != NULL) {
pc->flags |= NSF_FINISHED_SENDING_DATA;
pc->user_data = NULL;
}
nc->user_data = NULL;
break; break;
case NS_RECV: case NS_RECV:
// Forward arrived data to the other connection, and discard from buffer fwrite(nc->recv_mbuf.buf, 1, nc->recv_mbuf.len, stdout);
if (pc != NULL) { mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len);
ns_send(pc, io->buf, io->len);
iobuf_remove(io, io->len);
}
break; break;
default: default:
...@@ -56,68 +93,48 @@ static void ev_handler(struct ns_connection *nc, int ev, void *p) { ...@@ -56,68 +93,48 @@ static void ev_handler(struct ns_connection *nc, int ev, void *p) {
} }
} }
void *ssl_wrapper_init(const char *local_addr, const char *target_addr, int main(int argc, char *argv[]) {
const char **err_msg) { struct mg_mgr mgr;
struct ns_mgr *mgr = (struct ns_mgr *) calloc(1, sizeof(mgr[0])); int i, is_listening = 0;
*err_msg = NULL; const char *address = NULL;
if (mgr == NULL) { mg_mgr_init(&mgr, NULL);
*err_msg = "malloc failed";
// 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 { } else {
ns_mgr_init(mgr, (void *) target_addr); show_usage_and_exit(argv[0]);
if (ns_bind(mgr, local_addr, ev_handler, NULL) == NULL) {
*err_msg = "ns_bind() failed: bad listening_port";
ns_mgr_free(mgr);
free(mgr);
mgr = NULL;
} }
} }
return mgr; if (i + 1 == argc) {
} address = argv[i];
} else {
void ssl_wrapper_serve(void *param, volatile int *quit) {
struct ns_mgr *mgr = (struct ns_mgr *) param;
while (*quit == 0) {
ns_mgr_poll(mgr, 1000);
}
ns_mgr_free(mgr);
free(mgr);
}
#ifndef SSL_WRAPPER_USE_AS_LIBRARY
static int 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) {
fprintf(stderr, "Usage: %s <listening_address> <target_address>\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
void *wrapper;
const char *err_msg;
if (argc != 3) {
show_usage_and_exit(argv[0]); show_usage_and_exit(argv[0]);
} }
// Setup signal handlers
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
if ((wrapper = ssl_wrapper_init(argv[1], argv[2], &err_msg)) == NULL) { if (is_listening) {
fprintf(stderr, "Error: %s\n", err_msg); 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); exit(EXIT_FAILURE);
} }
ssl_wrapper_serve(wrapper, &s_received_signal);
while (s_received_signal == 0) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
#endif // SSL_WRAPPER_USE_AS_LIBRARY
# 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
// 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;
}
<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>
<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>
<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>
-----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-----
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)
// 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;
}
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)
= 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
----
/*
* 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;
}
/*
* 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;
}
<!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&#8217;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 &amp;&amp; ./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 &gt;/sys/class/gpio/export
echo out &gt;/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 &amp;&amp; ./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>
\ No newline at end of file
../../docs/arch.png
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<!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>
# 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
#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;
}
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)
/*
* 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;
}
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 @@ ...@@ -5,10 +5,11 @@
<title>RESTful API demo</title> <title>RESTful API demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css"> <style type="text/css">
* { outline: none; font: 16px/1.4 Helvetica, Arial, sans-serif; } * { outline: none; }
body { body {
background-color: #cde; margin: 0; background-color: #789; margin: 0;
padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif; padding: 0; font: 16px/1.4 Helvetica, Arial, sans-serif;
font: 16px/1.4 Helvetica, Arial, sans-serif;
} }
div.content { div.content {
width: 800px; margin: 2em auto; padding: 20px 50px; width: 800px; margin: 2em auto; padding: 20px 50px;
...@@ -23,13 +24,13 @@ ...@@ -23,13 +24,13 @@
} }
</style> </style>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script language="javascript" type="text/javascript"> <script language="javascript" type="text/javascript">
jQuery(function() { jQuery(function() {
$(document).on('keyup', '#n1, #n2', function() { $(document).on('keyup', '#n1, #n2', function() {
$.ajax({ $.ajax({
url: '/api/sum', url: '/api/v1/sum',
method: 'POST', method: 'POST',
dataType: 'json', dataType: 'json',
data: { n1: $('#n1').val(), n2: $('#n2').val() }, data: { n1: $('#n1').val(), n2: $('#n2').val() },
...@@ -47,10 +48,10 @@ ...@@ -47,10 +48,10 @@
<h1>RESTful API demo.</h1> <h1>RESTful API demo.</h1>
<p> <p>
This page demonstrates how Mongoose web server could be used to implement This page demonstrates how Mongoose could be used to implement
RESTful APIs. Enter numbers below, and press Submit. Browser will send RESTful APIs. Start typing numbers into the text fields below.
two numbers to <tt>/api/sum</tt> URI, Mongoose calclulates the sum of Browser sends two numbers to <tt>/api/v1/sum</tt> URI.
two and returns the result. Web server calclulates the sum and returns the result.
</p> </p>
<div> <div>
......
/*
* 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;
}
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)
RESTful server with Amazon S3 upload example
============================================
This example demonstrates how Mongoose could be used to implement a RESTful
service that uses another RESTful service to handle it's own API call.
This example takes form data and uploads it as a file to Amazon S3.
## Prerequisites
- Amazon S3 account security credentials: Access Key ID and Secret Access
Key ID. Get them from the Amazon IAM console.
- Amazon S3 bucket.
## Building and running the example
$ git clone https://github.com/cesanta/mongoose.git
$ cd mongoose/examples/restful_server_s3
$ make
$ ./restful_server_s3 -a ACCESS_KEY_ID -s SECRET_ACCESS_KEY_ID
Starting RESTful server on port 8000
Then, open a browser on `http://localhost:8000`
Note: If you're getting a *Temporary Redirect* error, look what is the
Endpoint value is. It's likely that you have something like
`BUCKET_NAME.S3_ZONE.amazonaws.com`.
Change the *Host* field to `S3_ZONE.amazonaws.com and retry`.
## Screenshot
![](https://docs.cesanta.com/images/mongoose_s3_example.png)
<!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">&nbsp;</pre>
</div>
</div>
</div>
</body>
</html>
/*
* 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;
}
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)
# 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*
// 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;
}
# 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
/*
* 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;
}
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)
/*
* 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;
}
<!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>&lt;!--#call parameter_name --&gt;</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>&lt;!--#call parameter_name --&gt;</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">&nbsp;</div>
</form>
</div>
</body>
</html>
/*! 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+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;
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 f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==ca()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===ca()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?aa:ba):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=aa,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=aa,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=aa,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=ba;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=ba),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function da(a){var b=ea.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var ea="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fa=/ jQuery\d+="(?:null|\d+)"/g,ga=new RegExp("<(?:"+ea+")[\\s/>]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/<tbody/i,la=/<|&#?\w+;/,ma=/<(?:script|style|link)/i,na=/checked\s*(?:[^=]|=\s*.checked.)/i,oa=/^$|\/(?:java|ecma)script/i,pa=/^true\/(.*)/,qa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ra={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?"<table>"!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Ca[0].contentWindow||Ca[0].contentDocument).document,b.write(),b.close(),c=Ea(a,b),Ca.detach()),Da[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Ga=/^margin/,Ha=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ia,Ja,Ka=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ia=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Ha.test(g)&&Ga.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ia=function(a){return a.currentStyle},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Ha.test(g)&&!Ka.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function La(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Ma=/alpha\([^)]*\)/i,Na=/opacity\s*=\s*([^)]*)/,Oa=/^(none|table(?!-c[ea]).+)/,Pa=new RegExp("^("+S+")(.*)$","i"),Qa=new RegExp("^([+-])=("+S+")","i"),Ra={position:"absolute",visibility:"hidden",display:"block"},Sa={letterSpacing:"0",fontWeight:"400"},Ta=["Webkit","O","Moz","ms"];function Ua(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Ta.length;while(e--)if(b=Ta[e]+c,b in a)return b;return d}function Va(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fa(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wa(a,b,c){var d=Pa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Ya(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ia(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Ja(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ha.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xa(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Ja(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ua(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qa.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ua(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Ja(a,b,d)),"normal"===f&&b in Sa&&(f=Sa[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Oa.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Ra,function(){return Ya(a,b,d)}):Ya(a,b,d):void 0},set:function(a,c,d){var e=d&&Ia(a);return Wa(a,c,d?Xa(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Na.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Ma,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Ma.test(f)?f.replace(Ma,e):f+" "+e)}}),m.cssHooks.marginRight=La(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Ja,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Ga.test(a)||(m.cssHooks[a+b].set=Wa)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ia(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Va(this,!0)},hide:function(){return Va(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Za(a,b,c,d,e){
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[c]=eb[c]||[],eb[c].unshift(b)},prefilter:function(a,b){b?db.unshift(a):db.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kb(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),m.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($a=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$a=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_a||(_a=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_a),_a=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lb=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lb,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mb,nb,ob=m.expr.attrHandle,pb=/^(?:checked|selected)$/i,qb=k.getSetAttribute,rb=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nb:mb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rb&&qb||!pb.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qb?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nb={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rb&&qb||!pb.test(c)?a.setAttribute(!qb&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ob[b]||m.find.attr;ob[b]=rb&&qb||!pb.test(b)?function(a,b,d){var e,f;return d||(f=ob[b],ob[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,ob[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rb&&qb||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mb&&mb.set(a,b,c)}}),qb||(mb={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},ob.id=ob.name=ob.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mb.set},m.attrHooks.contenteditable={set:function(a,b,c){mb.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sb=/^(?:input|select|textarea|button|object)$/i,tb=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sb.test(a.nodeName)||tb.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var ub=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ub," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vb=m.now(),wb=/\?/,xb=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xb,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yb,zb,Ab=/#.*$/,Bb=/([?&])_=[^&]*/,Cb=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Db=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Eb=/^(?:GET|HEAD)$/,Fb=/^\/\//,Gb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hb={},Ib={},Jb="*/".concat("*");try{zb=location.href}catch(Kb){zb=y.createElement("a"),zb.href="",zb=zb.href}yb=Gb.exec(zb.toLowerCase())||[];function Lb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mb(a,b,c,d){var e={},f=a===Ib;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nb(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Ob(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zb,type:"GET",isLocal:Db.test(yb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nb(Nb(a,m.ajaxSettings),b):Nb(m.ajaxSettings,a)},ajaxPrefilter:Lb(Hb),ajaxTransport:Lb(Ib),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cb.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zb)+"").replace(Ab,"").replace(Fb,yb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gb.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yb[1]&&c[2]===yb[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yb[3]||("http:"===yb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mb(Hb,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Eb.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wb.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bb.test(e)?e.replace(Bb,"$1_="+vb++):e+(wb.test(e)?"&":"?")+"_="+vb++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jb+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mb(Ib,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Ob(k,v,c)),u=Pb(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qb=/%20/g,Rb=/\[\]$/,Sb=/\r?\n/g,Tb=/^(?:submit|button|image|reset|file)$/i,Ub=/^(?:input|select|textarea|keygen)/i;function Vb(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rb.test(a)?d(a,e):Vb(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vb(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vb(c,a[c],b,e);return d.join("&").replace(Qb,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Ub.test(this.nodeName)&&!Tb.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sb,"\r\n")}}):{name:b.name,value:c.replace(Sb,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zb()||$b()}:Zb;var Wb=0,Xb={},Yb=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xb)Xb[a](void 0,!0)}),k.cors=!!Yb&&"withCredentials"in Yb,Yb=k.ajax=!!Yb,Yb&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xb[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xb[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zb(){try{return new a.XMLHttpRequest}catch(b){}}function $b(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _b=[],ac=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_b.pop()||m.expando+"_"+vb++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ac.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ac.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ac,"$1"+e):b.jsonp!==!1&&(b.url+=(wb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_b.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bc=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bc)return bc.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cc=a.document.documentElement;function dc(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dc(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cc;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cc})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dc(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=La(k.pixelPosition,function(a,c){return c?(c=Ja(a,b),Ha.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ec=a.jQuery,fc=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fc),b&&a.jQuery===m&&(a.jQuery=ec),m},typeof b===K&&(a.jQuery=a.$=m),m});
PROG = simplest_web_server
SOURCES = $(PROG).c ../../mongoose.c
CFLAGS = -W -Wall -I../.. $(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)
// Copyright (c) 2015 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
static const char *s_http_port = "8000";
static struct mg_serve_http_opts s_http_server_opts;
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
if (ev == NS_HTTP_REQUEST) {
mg_serve_http(nc, p, s_http_server_opts);
}
}
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);
// Set up HTTP server parameters
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = "."; // Serve current directory
s_http_server_opts.enable_directory_listing = "yes";
printf("Starting web server on port %s\n", s_http_port);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
PROG = echo_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)
...@@ -12,23 +12,39 @@ ...@@ -12,23 +12,39 @@
// See the GNU General Public License for more details. // See the GNU General Public License for more details.
// //
// Alternatively, you can license this software under a commercial // Alternatively, you can license this software under a commercial
// license, as set out in <http://cesanta.com/products.html>. // license, as set out in <https://www.cesanta.com/license>.
// //
// $Date$ // $Date: 2014-09-28 05:04:41 UTC $
#ifndef SSL_WRAPPER_HEADER_INCLUDED #include "mongoose.h"
#define SSL_WRAPPER_HEADER_INCLUDED
#ifdef __cplusplus static void ev_handler(struct mg_connection *nc, int ev, void *p) {
extern "C" { struct mbuf *io = &nc->recv_mbuf;
#endif // __cplusplus (void) p;
void *ssl_wrapper_init(const char *listen_addr, const char *target_addr, switch (ev) {
const char **err_msg); case NS_RECV:
void ssl_wrapper_serve(void *, volatile int *stop_marker); mg_send(nc, io->buf, io->len); // Echo message back
mbuf_remove(io, io->len); // Discard message from recv buffer
#ifdef __cplusplus break;
default:
break;
}
} }
#endif // __cplusplus
#endif // SSL_WRAPPER_HEADER_INCLUDED int main(void) {
struct mg_mgr mgr;
const char *port1 = "1234", *port2 = "127.0.0.1:17000";
mg_mgr_init(&mgr, NULL);
mg_bind(&mgr, port1, ev_handler);
mg_bind(&mgr, port2, ev_handler);
printf("Starting echo mgr on ports %s, %s\n", port1, port2);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css"> <style type="text/css">
body { body {
background-color: #cde; margin: 0; background-color: #789; margin: 0;
padding: 0; font: 14px Helvetica, Arial, sans-serif; padding: 0; font: 14px Helvetica, Arial, sans-serif;
} }
div.content { div.content {
...@@ -39,24 +39,22 @@ ...@@ -39,24 +39,22 @@
ws.onclose = function(ev) { console.log(ev); }; ws.onclose = function(ev) { console.log(ev); };
ws.onmessage = function(ev) { ws.onmessage = function(ev) {
console.log(ev); console.log(ev);
var m = (ev.data || '').match(/^(\S+) (.+)/);
if (m[1] == 'id') {
document.getElementById('my_id').innerText = m[2];
} else if (m[1] == 'msg') {
var div = document.createElement('div'); var div = document.createElement('div');
div.innerHTML = m[2]; div.innerHTML = ev.data;
document.getElementById('messages').appendChild(div); document.getElementById('messages').appendChild(div);
}
}; };
window.onload = function() { window.onload = function() {
document.getElementById('send_button').onclick = function(ev) { document.getElementById('send_button').onclick = function(ev) {
var msg = document.getElementById('send_input').value; var msg = document.getElementById('send_input').value;
ws.send('msg ' + msg); document.getElementById('send_input').value = '';
ws.send(msg);
}; };
document.getElementById('room_sel').onchange = function(ev) { document.getElementById('send_input').onkeypress = function(ev) {
var roomName = this.value || '?'; if (ev.keyCode == 13 || ev.which == 13) {
ws.send('join ' + roomName); document.getElementById('send_button').click();
}
}; };
}; };
</script> </script>
...@@ -66,24 +64,13 @@ ...@@ -66,24 +64,13 @@
<h1>Websocket PubSub Demonstration</h1> <h1>Websocket PubSub Demonstration</h1>
<p> <p>
This page demonstrates how Mongoose web server could be used to implement This page demonstrates how Mongoose could be used to implement
<a href="http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern"> <a href="http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">
publish–subscribe pattern</a>. Open this page in several browser publish–subscribe pattern</a>. Open this page in several browser
windows. Each window initiates persistent windows. Each window initiates persistent
<a href="http://en.wikipedia.org/wiki/WebSocket">WebSocket</a> <a href="http://en.wikipedia.org/wiki/WebSocket">WebSocket</a>
connection with Mongoose, making each browser window a websocket client. connection with the server, making each browser window a websocket client.
Join a room, send messages, and see messages sent by other clients. Send messages, and see messages sent by other clients.
</p>
<p>
My ID: <b><span id="my_id"></b></span>
</p>
<p>
Join room: <select id="room_sel">
<option value="">-- select room -- </option>
<option>A</option>
<option>B</option>
</select>
</p> </p>
<div id="messages"> <div id="messages">
......
// Copyright (c) 2013-2014 Cesanta Software Limited /*
// $Date: 2014-09-09 17:07:55 UTC $ * Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
#include <string.h>
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include "mongoose.h" #include "mongoose.h"
static int s_signal_received = 0; static sig_atomic_t s_signal_received = 0;
static struct mg_server *s_server = NULL; static const char *s_http_port = "8000";
static struct mg_serve_http_opts s_http_server_opts;
// Data associated with each websocket connection
struct conn_data {
int room;
};
static void signal_handler(int sig_num) { static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler signal(sig_num, signal_handler); // Reinstantiate signal handler
s_signal_received = sig_num; s_signal_received = sig_num;
} }
static void handle_websocket_message(struct mg_connection *conn) { static int is_websocket(const struct mg_connection *nc) {
struct conn_data *d = (struct conn_data *) conn->connection_param; return nc->flags & NSF_IS_WEBSOCKET;
}
static void broadcast(struct mg_connection *nc, const char *msg, size_t len) {
struct mg_connection *c; struct mg_connection *c;
char buf[500];
printf("[%.*s]\n", (int) conn->content_len, conn->content); snprintf(buf, sizeof(buf), "%p %.*s", nc, (int) len, msg);
if (conn->content_len > 5 && !memcmp(conn->content, "join ", 5)) { for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
// Client joined new room mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, buf, strlen(buf));
d->room = conn->content[5];
} else if (conn->content_len > 4 && !memcmp(conn->content, "msg ", 4) &&
d->room != 0 && d->room != '?') {
// Client has sent a message. Push this message to all clients
// that are subscribed to the same room as client
for (c = mg_next(s_server, NULL); c != NULL; c = mg_next(s_server, c)) {
struct conn_data *d2 = (struct conn_data *) c->connection_param;
if (!c->is_websocket || d2->room != d->room) continue;
mg_websocket_printf(c, WEBSOCKET_OPCODE_TEXT, "msg %c %p %.*s",
(char) d->room, conn,
conn->content_len - 4, conn->content + 4);
}
} }
} }
static int ev_handler(struct mg_connection *conn, enum mg_event ev) { static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct http_message *hm = (struct http_message *) ev_data;
struct websocket_message *wm = (struct websocket_message *) ev_data;
switch (ev) { switch (ev) {
case MG_REQUEST: case NS_HTTP_REQUEST:
if (conn->is_websocket) { /* Usual HTTP request - serve static files */
handle_websocket_message(conn); mg_serve_http(nc, hm, s_http_server_opts);
return MG_TRUE; nc->flags |= NSF_SEND_AND_CLOSE;
} else { break;
mg_send_file(conn, "index.html", NULL); // Return MG_MORE after! case NS_WEBSOCKET_HANDSHAKE_DONE:
return MG_MORE; /* New websocket connection. Tell everybody. */
broadcast(nc, "joined", 6);
break;
case NS_WEBSOCKET_FRAME:
/* New websocket message. Tell everybody. */
broadcast(nc, (char *) wm->data, wm->size);
break;
case NS_CLOSE:
/* Disconnect. Tell everybody. */
if (is_websocket(nc)) {
broadcast(nc, "left", 4);
} }
case MG_WS_CONNECT: break;
// New websocket connection. Send connection ID back to the client.
conn->connection_param = calloc(1, sizeof(struct conn_data));
mg_websocket_printf(conn, WEBSOCKET_OPCODE_TEXT, "id %p", conn);
return MG_FALSE;
case MG_CLOSE:
free(conn->connection_param);
return MG_TRUE;
case MG_AUTH:
return MG_TRUE;
default: default:
return MG_FALSE; break;
} }
} }
int main(void) { int main(void) {
s_server = mg_create_server(NULL, ev_handler); struct mg_mgr mgr;
mg_set_option(s_server, "listening_port", "8080"); struct mg_connection *nc;
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
printf("Started on port %s\n", mg_get_option(s_server, "listening_port")); mg_mgr_init(&mgr, NULL);
nc = mg_bind(&mgr, s_http_port, ev_handler);
s_http_server_opts.document_root = ".";
mg_set_protocol_http_websocket(nc);
printf("Started on port %s\n", s_http_port);
while (s_signal_received == 0) { while (s_signal_received == 0) {
mg_poll_server(s_server, 100); mg_mgr_poll(&mgr, 200);
} }
mg_destroy_server(&s_server); mg_mgr_free(&mgr);
return 0; return 0;
} }
# Copyright (c) 2014 Cesanta Software
# All rights reserved
PROG = websocket_echo_server
CFLAGS = -W -Wall -I../.. -pthread -g -O0 $(CFLAGS_EXTRA)
SOURCES = $(PROG).c ../../mongoose.c
$(PROG): $(SOURCES)
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS)
clean:
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var out = function(message) {
var div = document.createElement('div');
div.innerHTML = message;
document.getElementById('output').appendChild(div);
};
window.onload = function() {
var url = 'ws://' + location.host + '/ws';
var num_messages = 0;
websocket = new WebSocket(url);
websocket.onopen = function(ev) {
out('CONNECTED');
var msg = 'Не всё подчиняется разуму. Но всё подчиняется упорству. ';
out('SENT: ' + msg);
websocket.send(msg);
};
websocket.onclose = function(ev) {
out('DISCONNECTED');
};
websocket.onmessage = function(ev) {
if (!ev.data) {
out('<span style="color: blue;">PING... </span>');
} else {
out('<span style="color: blue;">RESPONSE: ' + ev.data + ' </span>');
num_messages++;
}
if (num_messages > 3) {
websocket.send('exit');
}
};
websocket.onerror = function(ev) {
out('<span style="color: red; ">ERROR: </span> ' + ev.data);
};
};
</script>
<style> div {font: small Verdana; } </style>
<h2>Mongoose WebSocket Test</h2>
<div id="output"></div>
</html>
// Copyright (c) 2013-2014 Cesanta Software Limited
// $Date: 2014-09-09 17:07:55 UTC $
#include <string.h>
#include <time.h>
#include "mongoose.h"
static void push_message(struct mg_server *server, time_t current_time) {
struct mg_connection *c;
char buf[20];
int len = sprintf(buf, "%lu", (unsigned long) current_time);
// Iterate over all connections, and push current time message to websocket ones.
for (c = mg_next(server, NULL); c != NULL; c = mg_next(server, c)) {
if (c->is_websocket) {
mg_websocket_write(c, 1, buf, len);
}
}
}
static int send_reply(struct mg_connection *conn) {
if (conn->is_websocket) {
// This handler is called for each incoming websocket frame, one or more
// times for connection lifetime.
// Echo websocket data back to the client.
mg_websocket_write(conn, 1, conn->content, conn->content_len);
return conn->content_len == 4 && !memcmp(conn->content, "exit", 4) ?
MG_FALSE : MG_TRUE;
} else {
mg_send_file(conn, "index.html", NULL);
return MG_MORE;
}
}
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_reply(conn);
default: return MG_FALSE;
}
}
int main(void) {
struct mg_server *server = mg_create_server(NULL, ev_handler);
time_t current_timer = 0, last_timer = time(NULL);
mg_set_option(server, "listening_port", "8080");
printf("Started on port %s\n", mg_get_option(server, "listening_port"));
for (;;) {
mg_poll_server(server, 100);
current_timer = time(NULL);
if (current_timer - last_timer > 0) {
last_timer = current_timer;
push_message(server, current_timer);
}
}
mg_destroy_server(&server);
return 0;
}
# Copyright (c) 2014 Cesanta Software
# All rights reserved
PROG = ws_ssl
CFLAGS = -W -Wall -I../.. -I. -pthread -g -O0 -DMONGOOSE_ENABLE_THREADS -DNS_ENABLE_SSL -DSSL_WRAPPER_USE_AS_LIBRARY $(CFLAGS_EXTRA)
LDFLAGS = -lssl
SOURCES = ws_ssl.c ../../mongoose.c ssl_wrapper.c
# PolarSSL paths and flags
POLARSSL_PATH = /usr/local
POLARSSLCOMPAT_PATH = ./../../../polar
SOURCES_POLAR = $(SOURCES) $(POLARSSLCOMPAT_PATH)/polarssl_compat.c
INCDIR_POLAR = -I$(POLARSSLCOMPAT_PATH) -I$(POLARSSL_PATH)/include
LDFLAGS_POLAR = -L$(POLARSSL_PATH)/lib -lmbedtls
CFLAGS_POLAR = $(CFLAGS) $(INCDIR_POLAR)
#
all: $(PROG)
$(PROG): $(SOURCES)
$(CC) -o $(PROG) $(SOURCES) $(LDFLAGS) $(CFLAGS)
polarssl: $(SOURCES_POLAR)
$(CC) -o $(PROG) $(SOURCES_POLAR) $(LDFLAGS_POLAR) $(CFLAGS_POLAR)
clean:
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwizPnrCx+/kPSdEeSJFLDXrBH+cSQsSLrCm99G1hCjzqSlIk
1BhkZMEHxBaiVLky4+M/nwhjwwRHI10h6U2Or3tbPLv7z94cPf+uCx1aF7TE3Fm3
6YnDk+CjrYVFN5GRPGOPPdFxGoc+vFvQJyAAimvchnS1ZoEQFvermwzOnKspA6gc
Px+7wnOeju9TyJuDr5ngtDXFnkcpkBNPxz3En4MJY4xJiaueafh9pIES2vSl7uP0
J/qot9v2rdiL7nt1H1vwseeEkZhQ+NLB5e2z4psyktJcwDX7wQ6j7JnKfHeP+ixO
TUORgV4foBMVOqo//Guo92Q5HoLNK77V0y4+ZQIDAQABAoIBAGEsx+LlDs3JQQty
KjOq8uKWElyC6bKcZkIMydGvg6b6AU6ceW3jnyqFJ/vMUAUSghNmQQq3yiVo2Kks
DLKTa9sKYwisE0NeJsgoUtOhJttCTlrwU4f+t/AjtgY68f7zTLnqIV+Ql4ftM0pU
sIFEFMExZbWsZrQb1w+Hd0wrRqNEbSOfSjHeigvuw1T3GH2tSBUTGTpcoewCzy7U
PKS5pkYyiKySQQNqZTac3NHPjxdK6xxzwURZp1irKdiPdt04KHLVLX8KXelt/J0k
AeYkVbpFIeQ9rNBerMEp6uRBt+nE5mvP+xx1XPqKRxuxbMyTnBXeOM2zS/a/dBiz
fwokwcECgYEA9RSsv9AQ/AR8tt+aPEQvjhJ5pn/YbCb1DA9IDXpaq3tzacGd8JHj
3kUtb79nosu85LvSkAYmtzgfJs6xZyUkscra6q+xlsJ12QRxLzqfxcp9Y0wsdqM4
AcOwuiPKrjkWxOQpyWPWRwbmAefLfRMekHt4Y/QY0CwhslpnsOsj3O0CgYEAytOE
8I4GBfSQfSjXwfrso++Oi75VSsl5ZeiMGihfEhYFTE8/3rEZf7nf9iFSkN3TT+7f
pFqQzddzPBZXlpVM6k1jcEjdpJizbeR8DmICpABFrZvKz1o8pQ2Yw+FYI86ih0x4
806snMNgg/RgcVijXKFrC5joJOI+DVgwWoQyMFkCgYBxt4MkiV2oIkjf7ca6GgVa
zbXGjOGV5Umkq96J6nDxyplVw/IN8xOhScX4aP6kahaep4vfKguCzjaeIh/stS5e
lLqZVKZ5Roe6B7ag7HnAI+GkVm73KWrOXse8xui/iFvJRfkhqgJ9+HR3A9/GjD2N
Ws0Uy+lLhn6oLAya6bA9TQKBgAVfZP4aRP6TY+Bs3Io+41XUWqpI+GlqvNR+PHfU
6e/ItYs37jEv78T6X3xdlZpQxfAwG6x22a8aLetBjEBo5Aiw1Bl9VKGvidE3ZDHd
VsSRXUckAVNMyJ52pb1KktMf/h4nYGzRgLEGW+Ai8QsPlgQ2ImfEPSH8/DfORjmf
ltTBAoGBAMxIZ52DrJvuxogSOfA1MoCD6a90trkXCquvi+A/fXojZ8BHmMQshvhK
rAO7SDIV1i1Nh3jQ/oFWE8KOprqrOLO6jNTyF65vh+zk7ztGsEME9FkDhHasUiXf
t5PE9KeTChHRvIa4FGCl9We9GftE5Ii77LWMOIq22pyxYbvHQFEf
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIJAIOoO+AapJ5WMA0GCSqGSIb3DQEBBQUAMDoxDDAKBgNV
BAMTA3dzMTEMMAoGA1UEChMDd3MxMQswCQYDVQQGEwJJRTEPMA0GA1UEBxMGRHVi
bGluMB4XDTE0MDgwMzA5MTU0NVoXDTI0MDczMTA5MTU0NVowOjEMMAoGA1UEAxMD
d3MxMQwwCgYDVQQKEwN3czExCzAJBgNVBAYTAklFMQ8wDQYDVQQHEwZEdWJsaW4w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCLM+esLH7+Q9J0R5IkUsN
esEf5xJCxIusKb30bWEKPOpKUiTUGGRkwQfEFqJUuTLj4z+fCGPDBEcjXSHpTY6v
e1s8u/vP3hw9/64LHVoXtMTcWbfpicOT4KOthUU3kZE8Y4890XEahz68W9AnIACK
a9yGdLVmgRAW96ubDM6cqykDqBw/H7vCc56O71PIm4OvmeC0NcWeRymQE0/HPcSf
gwljjEmJq55p+H2kgRLa9KXu4/Qn+qi32/at2Ivue3UfW/Cx54SRmFD40sHl7bPi
mzKS0lzANfvBDqPsmcp8d4/6LE5NQ5GBXh+gExU6qj/8a6j3ZDkegs0rvtXTLj5l
AgMBAAGjgZwwgZkwHQYDVR0OBBYEFL54xAgtJTW6US4Mbr4QG0yKzvaxMGoGA1Ud
IwRjMGGAFL54xAgtJTW6US4Mbr4QG0yKzvaxoT6kPDA6MQwwCgYDVQQDEwN3czEx
DDAKBgNVBAoTA3dzMTELMAkGA1UEBhMCSUUxDzANBgNVBAcTBkR1YmxpboIJAIOo
O+AapJ5WMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJz/RzMa9Wa2
eEXed7ijH1gcWtgVsVT1xZo0ksFl+QJ5Be1AJpOIe8nKdzYjxPWUkofIoaGHdMLL
Uc/udRzsXncup+0mD+Yos6Cqyo9yHq7L1HbXfKYZtBXIjWHdF2+RP8j9tHfITXYI
Pb2zsQ+A6PYpp5OLGZTDAnI2qffqsmwXFNhPfFhOANrGlOjsvy1P7JDzvymj/90m
NomlO3vjxLHOf6MvedTgCB0dRcAoUWPgbxPWifjBmGBjQjA4ukMQ58wbBQgvIoCW
obrXmLCNZIkpWTw4gMRYquY880IYK/OuFNJH/dawxx/WzuVr7IdLmbFY15zf5TUb
ZpIpwqRCysg=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAwOp1RS9RnE8L5TszDPIOmpf/1pqb+ki99l/sGhqB/KZBKCuq
uoCc2VPK3PCByBE15/xJ7t691FnJfForI9DO5p2R0FPD6o357hqRsh0dJNBm0VgG
iNtLQ8lyYoE72HJbkgCAUZW4N0OBibOmvp/s3Fmr7rEjW5LmZxOtX+iULKIUZ0w6
gJlM8G5QA1SuqndN0PbPx+RZKSXZCoJj+Nboqw03nyJUexzs+lynR9ITMziUaaVM
4rzG+P6joQAnUkDNydxo/d4tj4xaioZdKxWLYEbj2BUtOlJJydotWo2wcG85ONrT
Gw0ltR1vku1hidMm2QL30uGIL5SeqyE8TqWk4QIDAQABAoIBAQCxHtKauc45MA4g
4hCGAzvLTmETnRI2YlEfAoTYlpvf5pkOE8GFyI25r4gjACJ4GO0gWG9dBF7Pt7wZ
EwRmttEvxV3aIv5OvRnKNdSs7rQSV9D+xc4CGy1oSG1f6X2TxbMzQoiN32OqQa2O
S0Z94IFs8lu8JCDtc9tcqiFVXEmnC3RvJZOShWpsCsbmh5ue1Xed0MQQ47vt7Zt7
I0cutvwSFJMsZkZUJp5+KjVNYo9TEJxVD3m2NJNJxBfBoRVHXNii3hUEHcTIdIAz
omtRwBU8AKgJirGIRo1h2ZFyubI8ScGOJWIiWMQvQqTHKiOaz3yUar1NSG+kFn0U
cj7s3FhdAoGBAOQbx8Jwhtp4iOkP6aW1nVTgiaTj4LMlyJZioFwgPFRIcA3oRHt9
5SRetmgFZNvcuNw1udfeaObKmlzxwUruprwOpihgAQWJFTtOjQNrt2gsYuX4l3W6
T46dO2W1pV+mW4A5gt0aqhLv7pCS4lpreNAqyHSPqcQWcCeiTzmp/LfDAoGBANiB
FQOTyMElR9OzKwmcGfIvnhUfJQwi5qNE3R+xXiP5x36a9HQBey5ri2lnBle0Ksr/
G+RKoflmk1eFXVHN0w35yw0dVco//WE4vOknldNEnIT85k02ld8lDTa2Q/EJZtPH
un6zeU0Q2/4SZ/GXPssEZPlpPM7WtQzztlH3+sqLAoGBAKnhppvAgi4ippQsLa4j
29BiiSAsNiQ1d3XIbfUubL+4UvuIh7gQwp6biu1dVwgHEgWuXYHPOgDn0p51zaao
pbRYlJZtKVWeChnpHkv15NnIdL8grGwZHTbxElNlPIxHsM2GB1fzi8YeumUhf0In
2AnwUum8NIq8yzo5PxeK6ZNRAoGBAIEA2Q6ankJH/nZsCbbeJq+iI+Wd+ysyGI8s
Vz2tJ9Tz3iTYG9SLlWRhfF4/nw3fMqhmPa5Xsg+zSRQbSTGXHKz1LEISOq4aVtX5
QscCaUnLVh//uRJE9iRSJX92NyGGYpjKJ5ubQSnkY9EOEpVnc2jwo2HhjPQKBzNC
fF53Dh5lAoGALwTN5uxrBZLPu4DtZkOosKkv4l+kzFoOjLJR4vA7ONBx2CSe9G7F
tSsH7lZS3b0mxBWjO90WhaSvtMWWrfqq8vrqmoTE795fYxNoLfCLK13W31aTDUsI
pQRJIL30MPvASbcFHN2MD2dXz2nQzY8C9lvtvap/krYiDKDU2L7+iP8=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC7DCCAdQCBRQHBXNHMA0GCSqGSIb3DQEBBQUAMDoxDDAKBgNVBAMTA3dzMTEM
MAoGA1UEChMDd3MxMQswCQYDVQQGEwJJRTEPMA0GA1UEBxMGRHVibGluMB4XDTE0
MDgwMzA5MTU0NVoXDTI0MDczMTA5MTU0NVowOjEMMAoGA1UEAxMDd3MxMQwwCgYD
VQQKEwN3czExCzAJBgNVBAYTAklFMQ8wDQYDVQQHEwZHYWx3YXkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDA6nVFL1GcTwvlOzMM8g6al//Wmpv6SL32
X+waGoH8pkEoK6q6gJzZU8rc8IHIETXn/Enu3r3UWcl8Wisj0M7mnZHQU8Pqjfnu
GpGyHR0k0GbRWAaI20tDyXJigTvYcluSAIBRlbg3Q4GJs6a+n+zcWavusSNbkuZn
E61f6JQsohRnTDqAmUzwblADVK6qd03Q9s/H5FkpJdkKgmP41uirDTefIlR7HOz6
XKdH0hMzOJRppUzivMb4/qOhACdSQM3J3Gj93i2PjFqKhl0rFYtgRuPYFS06UknJ
2i1ajbBwbzk42tMbDSW1HW+S7WGJ0ybZAvfS4YgvlJ6rITxOpaThAgMBAAEwDQYJ
KoZIhvcNAQEFBQADggEBABPLmq6zKOMY0WRjtBoSymq6f+vXeEwtWCfVejdG6RlG
/PTdCKNvp3OL7FDnmQQ+r5rMs4+Os4fX/g315QFKXu01rqxmFb2XVNhhaECdUWtK
QP6ZoVZviUiDjhK6a+05aerPCJpkGy/lz0W6gmj4qhuAQbobxb6UbzqTRYY+ZwGk
+SI3TAVCdmXFlxN/M9b0DbmkseRG8GGFmyRYyRb84vbV6zemFI++5ROUT9zXT7ey
nYfFJvAAk5jJhY5UP2aMlVWYYa4jUZrrPLoiBLUuRrp67EKGebCH9mgCIf8ztNJF
fpuvcz++LUeRyTlAGDefe+FyHGIwFzIfZn39D7CaRvM=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyal0BZKRYd+Wpxv4MB8LjjgXv/MxSN5oSAKThlCZ/AWG0FEP
d4nrBACT2xUxwo+xbYl3joiwL/eCPAp6QNKRGhvXVOnSIFVSjKZWbdX+toqK9pDS
QMDTL4ZJvK6pLZXknyHjEr0PxZh22F7iS1+C8HxBPj0Xgg/u5/+jPhFPPZ1d5elv
4cm/z+xy6RjFlA80aIeK7dcWssOsOIPjUNFfmoYgR63ScZIlUZj6j8VX9oX7fJID
jumGajDxgD2nBWFbHcGKin6bz/wZ+OIhXOCDdY7oKuMW4JiBwbfBtedkQuQYS11s
PRFFYDLoZH59Ivcu0c4F2tomE86qM8THsI910wIDAQABAoIBAG55FAQRfO8/C0rU
eavy9eOdOvV+hltC66G3N5X3BcQYSvhHz89OkJ6KqnT0MWRCT5KQIhzFKK++SWwW
2U41jCPfaKEtzlzEIQrH/MUC3Byn3OSiBWxPteFtEWv5ytgcKzg52iljxQYcNc7m
e9WKpzKS/zLXSM+JZvlVA9p2pRA88kUZ/EE5H+FW3kHj5eGNqX+cxUVpL0VKTiLv
aXWjYotpmDJW/Rn9wRQethm6Gdx3bvo+LEVlJRELNq8NM5H/tZIVRzudJOgzsw5v
3OQGhfKB6Eg/vqSFoZxX6ApXDxmtaSO83B0kK550bDVv4sBnOExGjKCzybt04tet
KtLPPoECgYEA5WUD+mFL99sCX6pzIyUVlxp9eUhVy5nvhoF6u3mvhay2XsZUY0wy
+/qVqYSZTvuvJ6JSXc4iVIX8u/gj7914805AwujepIF/8E0AaXLBMndzDE4ze5S5
2RHI7Cy4/3AWOcQ9wFFmUdSs7/6oAkcQtvzP40hGg3J2jAEhIdCqmbMCgYEA4Q0G
BYP9XeTdh0C+BcP9B5VUEC0jerYS8VqVqriB+9JfT3InI7K08sOG2DiQQBhAHuzL
RhCECU2a9j0+u5F5JNeY5m3IhU73Lw+lOlUkMaAO6x7JJEzhXhonE7Kv8fWygr/0
OB7yzqz+YsWdQ2VOPZ88ntlAYE65vzcaVswZY2ECgYEAr7Gt2VA6Ei0A5Wq0Yr+d
iKz2WzUG2TkelqOG8B4kTDrbNz2qFp+fERV9GWgAz9i+75lIgqZF7vzsdL96LtYv
NBLEUURwegjhh5hCb4E/7bpFOLCQh9+CdHpFrHYYfzRHIZlnPmxZ9OTyS6J85bmu
WKjLRKXvs++wUkzvJmoesDcCgYEAkTOB6xUZ5/a+J4HSGI43NylVr4owFgBbgHVd
k2SwGPXGoM+aCSJINUmKOv9jsrbyyAEntfD5/7aegLlLPGHDs82WzTWP5tLoEOkb
ReOhEpOejHy0ckNYNQrSo5bqhkZsAogu3fa52jcrejbeHJnEPWX8CtFJA9pHZeP7
jnzo9IECgYBefHg0dymSj2xxN0XmC+S5cvQu2K/NYUpatoWvHnPiQ87wIM0AWz0O
D0ghEI+Ze57NbtSrrcTE7hY/LHrAqXGAB9XNIM5g9Pp/lM+XjzKVr1FMf4xpuHf1
VJJRHrOU14CvMvKbgbPrL6B0d5yrYmeex7GxNw0ZVvtjCa502Eck+w==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC7DCCAdQCBRQHBXNGMA0GCSqGSIb3DQEBBQUAMDoxDDAKBgNVBAMTA3dzMTEM
MAoGA1UEChMDd3MxMQswCQYDVQQGEwJJRTEPMA0GA1UEBxMGRHVibGluMB4XDTE0
MDgwMzA5MTU0NVoXDTI0MDczMTA5MTU0NVowOjEMMAoGA1UEAxMDd3MxMQwwCgYD
VQQKEwN3czExCzAJBgNVBAYTAklFMQ8wDQYDVQQHEwZHYWx3YXkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJqXQFkpFh35anG/gwHwuOOBe/8zFI3mhI
ApOGUJn8BYbQUQ93iesEAJPbFTHCj7FtiXeOiLAv94I8CnpA0pEaG9dU6dIgVVKM
plZt1f62ior2kNJAwNMvhkm8rqktleSfIeMSvQ/FmHbYXuJLX4LwfEE+PReCD+7n
/6M+EU89nV3l6W/hyb/P7HLpGMWUDzRoh4rt1xayw6w4g+NQ0V+ahiBHrdJxkiVR
mPqPxVf2hft8kgOO6YZqMPGAPacFYVsdwYqKfpvP/Bn44iFc4IN1jugq4xbgmIHB
t8G152RC5BhLXWw9EUVgMuhkfn0i9y7RzgXa2iYTzqozxMewj3XTAgMBAAEwDQYJ
KoZIhvcNAQEFBQADggEBAE20gAykuuaCoP49GnZ/Z6ZItFry4Fl6iCWBDdEsWI9R
wRNYumeaeejdFPXfSJdTT7UlrVK1WWGLQLq+ixHRDX+V9T67ou85F92H/OxbUoPr
iz/TZAEBTC1GvTJl49lsfPl1dTWH8T4Ej2hxCUvIJrkCkI2Ov4Wwef6A26USrwBt
S/CPInjCe6qkE5E8xfTDl8k5IIgMadTPhi5sbV2piBJoN4floJPqR0hdDKbgUymn
5WNSiRkuI6UIDZwQEp+A8TmFBHbSwfTGt2Sz5iI27P8J6pFvR5eRA1k57dRUWNXC
WAU1nqteP3QAjj9L3o8IO0T62scaiJX8x01gTmVVe2I=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwXIMpHuTNsro2pxe6y1mu27md2Olhvfx26rO3maO0/stIC2z
G/xQatFDLIWzfKFYOT0iSEj252ENFDCw6aDRKpiaUFtXcMAWkNkkKntEyoEgE45k
rTrvpay0v0B+ojwjA1Jz/9v35cgvDwTs3vNFno5HhI0m2YF4ocTmeHJ6u0xRL/qy
atfKsfuVq5s5CXOYCXp3Ux6kJ1c22J0EdZMvS75SVjAZgRkqQpqt9L3e2ZBCEgUr
w0KwlERvpeJF+sJJOshXjfrDzvwL8IpPnGZLJNINFbSJMk5MFGcMyq/28pSZLB9E
Dh8vk5D5gUnxM60ONUy2nYPcYr5p1PLDiC8hfQIDAQABAoIBAB0Twpi6xn8W8vdh
R9c75NRJsDTD8q6d+GnXe+7sJY3xlG/gzqpnO8NCn0FC+57BNdystsl8xjgzW17s
jrsfZDFt7MwlXrhg90NgkFIeY1G5JRQrdDChykHx+t1AmYhTV8P5EdykuNd+RqyQ
RfahRJa3tkJTYUKSdoqCaU4zjwU2CSxltuJx24V+WoZE12EwewJ8HPg2XTnbsGE7
Fnx5s29O4ItM70CC0536AY/OgfuPix5z573VQiilqqfOQkVkKa1fHd6tGpWU+3kH
X9FnhEBf9cN9tVgmaB0sCSVVrfgqSXg1EwKHqe/+FCumuesA68Q35+/K3b+QrNiR
ka2yliECgYEA+V/4pbgG/lPYvTwWhKxGXXdJmrSPgZC0mwE+fRuYkeptbIkS0pwt
/UDTXk9nttj1f1ZJ3NgQbT/1w8jpXfsCJ8VeGzL9+ADhRKWVFRlu/nyFCMXawLEV
rot7SEr8BW/m8moHgY5lYipM3dXJPk0F+KLrN60U/aNmFUtPGW802BkCgYEAxpWy
FGL2sEQ0QaRDTcqqF5faVqw+5rVGvN+EX5o26E0QWWnoo3L2c2/5X93iBl+Fqtnm
9jSIQUC3rYOawKnZ/HcIE2ergFit/p6JaV9NiLDRtDUmSzlffEGVCj0neYFsnWp5
zcmkUyZ6fr19EmKQWfdteNBlXue32TjVlFbfUQUCgYAfMbgi0sBdNBPaqBeRBRPQ
QUm9xnRlGrrc4Oz2LWuKZS7G8uad3deK5H8MPxaUMtOS2DJpI8X6RJPzp8A5d1qv
quq4sEpAqauEMMpTV1khEGZ70HQqwnwZ12zWgDrCW1siW80QkcVw4CW5YjLITk4+
6fJOhqInkDcG1uLQJa8QkQKBgQCfs8l4DbJ4RRGFbLXXvNGXkb68j18yqLxPrq3F
OL9JiJhKYBsAP7clVPrG9ykLmQxlP0I35D1jxMkymLD+mlo9Z/itqmTJHggnyZWW
kVdIQ3MSKuA2BNjek9tpVY8Gb2hLHFMChVRKrpo6jOclvvB5+bsnOukbLtyyq7tP
xaFohQKBgByCmlltjOBWZLFLeA1x8j3inm9zM/FAJuANbHUOZ1RwrRcNFbDv/FXm
rLPnPCaH5AwAWhVRJcNHo37Ee0s/xqe+Q4dG4xL943k+6KlopAw1SXhuXF6PnBfF
y+ArVlh9d2oWN5cBEzRddnWnKJuMi70kMzYf6dIW9s/dHbq/gFDy
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIJAJDtcXU2wiJIMA0GCSqGSIb3DQEBBQUAMDoxDDAKBgNV
BAMTA3dzMjEMMAoGA1UEChMDd3MyMQswCQYDVQQGEwJJRTEPMA0GA1UEBxMGRHVi
bGluMB4XDTE0MDgwMzA5MTU0OFoXDTI0MDczMTA5MTU0OFowOjEMMAoGA1UEAxMD
d3MyMQwwCgYDVQQKEwN3czIxCzAJBgNVBAYTAklFMQ8wDQYDVQQHEwZEdWJsaW4w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBcgyke5M2yujanF7rLWa7
buZ3Y6WG9/Hbqs7eZo7T+y0gLbMb/FBq0UMshbN8oVg5PSJISPbnYQ0UMLDpoNEq
mJpQW1dwwBaQ2SQqe0TKgSATjmStOu+lrLS/QH6iPCMDUnP/2/flyC8PBOze80We
jkeEjSbZgXihxOZ4cnq7TFEv+rJq18qx+5WrmzkJc5gJendTHqQnVzbYnQR1ky9L
vlJWMBmBGSpCmq30vd7ZkEISBSvDQrCURG+l4kX6wkk6yFeN+sPO/Avwik+cZksk
0g0VtIkyTkwUZwzKr/bylJksH0QOHy+TkPmBSfEzrQ41TLadg9xivmnU8sOILyF9
AgMBAAGjgZwwgZkwHQYDVR0OBBYEFLK4flD5QD/mRufsPx63xlEKM8pwMGoGA1Ud
IwRjMGGAFLK4flD5QD/mRufsPx63xlEKM8pwoT6kPDA6MQwwCgYDVQQDEwN3czIx
DDAKBgNVBAoTA3dzMjELMAkGA1UEBhMCSUUxDzANBgNVBAcTBkR1YmxpboIJAJDt
cXU2wiJIMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEYD+CReikYr
Rzvk+/Vdi/7IcaH9CFknIdtineSIw1y98nxnbnNJqxwfNaRblbYvg6OFdUI3POuI
+rdYLCFl8z3tWqRHLkGqHSJA9xcng3jLJxz0+ctiVcekJvXaB3O6eSZjhGbmmI/s
CQhdy2zpEIVOeUq50DrSJp9CknyGu/IkaGx5GOZtkiHMrpig50CRjX1lS6qrMNYp
vB8gfuqpjsL4Ar3vg+lgMSwNWXBNHrIRPHB5VEzBEdmLFZlvueR0ooEMCklpwX/a
lFImVc6JcY1pBEkHTiTLGMpGAHG3I1aVUaWb3L+V+2ym/KNRNL5C2+1eiqql5u8m
HUaOcNC90ew=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0ucemqFwBFziVOgTgx4mZII4WnGDpA/rWAGHvUZOqy2dQ3Pz
woGKxBaVPAl5kxEosROVGa7dTuq5yFZ4XIGvwCKxF30vCmdGCytqq6MMp904jG32
KikkmjCApIMGxMO4eoBHZZxiyVvKTbg9M2CRXErwnYWhFH/qGdPnuo0CEaHJA4VK
A9incT9dpeEhU30R6ajAe/je9rCj2OMLMFSMfd51L/VYfO60zDwUNY7YVIghQZgJ
e44EVGsp1pXaqaD6o3PvGY3ohw2aZjJzlJ7MJHbKV9lft98R3pklbpBzMH849cEy
Q/51L/rlfTUgCpTy7wEZpWHQNtHfu/1rhJjpNQIDAQABAoIBAQCUNIHXG/dBuZv7
GpMLotZL7w520Co30lAJqhmfMpb5x7YpvoPffXTsUwpQBECAzqAPv7kZMT6nxF8F
n245Y5EDrd1QqlGyN9yK4Nm2/39XPygL1wITopHsIIVmFgVdpEQxIZAKoZjx8yT4
9K1dO1Eq0CbCKzOE2lbCC51eBNUdWZIMxwC6O/j/KoIkZ/HwlG2hpUuXg8x/XawA
ZJDCoTcWHCjYP10FxKVN3vAyWM2IM44o9IbsAGEOswR4gUwRsgq6Ehc1U59XUHi+
x30oda55I1/8xD+SfP/zk2dDPHkv/hq5+258GU/THsw2+AAexocvSIS/g9EppTEg
biFaDKzJAoGBAPqey10JeyiOlHbBjoSSa7lJYUjocQANFQ4ayOAgfNX72iyabrKF
p9sVAeO/nM00+DPrm2wWws03ScsPeh+9BDJMPRBUHfSNp7+F+oyj7PWHBEFHbyO9
5HnYZP+1vhdG2dYPIY2gRSFXpiGn3j0M1D0w9c7Ilm8ee3krdR4E/mw3AoGBANdu
EfS1dK3+m7sEgc2+3U32z83GpuCHeUIKGPYMLI0fIb1CPpboHU9YjOFJZH9iIIdl
00JC963O3+pqLe0XbMOmBVt9QjZfhfB+AY+JHtbPgoQVLtq9X/WvW7h3xn6S971r
Crkhqay3Cs4BzsgYDYraQCTw3oq4twR9Nuy4etfzAoGAXOsG5wWe3diO/sCggFJx
Eg88vHVBgA1ZoxMXKtGgtw1bRHI1XIblRvqw6qmeDw72fvl5dEe0DbXT7C9ezemc
ZrGRaj5lpMfoS7/2trIIJrfaQgGkGRJMZUhvmcbeJW8lUJHnlMS5HLWMaKn+YZAi
GFXQrMv9ylD44mHUWD7tvV0CgYBNctPfvvCQsQ05ofgsiKa1Jbs1hmpuJCYy2MB6
jIvjvEJ78PnhdNc8tGAJikIoDZYWN0RI+RxkDxCvDLcwGpDOkbwxVQnd1F+pwxM6
kBhXL8kDRT5QA28hO4bk/aKN1LZeEcKMJg8C+ddXkozNoOAVgDs5TKMlCh057u41
EmmPgwKBgQDOlYi7fPYOCy0yjHMxSrp2SZOS06AMWGbbCoGkjRtvuP+FmKSNB+LZ
pOSEPJgzjsRutKjneww4LpV6dViAyTcP5JoeQpokHf7UVo7yq2QH/iwF3zJwsC/S
OuVLkqpZzWuye/QCH5NOTfw27ye8jG8VcQW2QPbcbkLXLM7zg2yX7g==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC7DCCAdQCBRQHBXNJMA0GCSqGSIb3DQEBBQUAMDoxDDAKBgNVBAMTA3dzMjEM
MAoGA1UEChMDd3MyMQswCQYDVQQGEwJJRTEPMA0GA1UEBxMGRHVibGluMB4XDTE0
MDgwMzA5MTU0OFoXDTI0MDczMTA5MTU0OFowOjEMMAoGA1UEAxMDd3MyMQwwCgYD
VQQKEwN3czIxCzAJBgNVBAYTAklFMQ8wDQYDVQQHEwZHYWx3YXkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDS5x6aoXAEXOJU6BODHiZkgjhacYOkD+tY
AYe9Rk6rLZ1Dc/PCgYrEFpU8CXmTESixE5UZrt1O6rnIVnhcga/AIrEXfS8KZ0YL
K2qrowyn3TiMbfYqKSSaMICkgwbEw7h6gEdlnGLJW8pNuD0zYJFcSvCdhaEUf+oZ
0+e6jQIRockDhUoD2KdxP12l4SFTfRHpqMB7+N72sKPY4wswVIx93nUv9Vh87rTM
PBQ1jthUiCFBmAl7jgRUaynWldqpoPqjc+8ZjeiHDZpmMnOUnswkdspX2V+33xHe
mSVukHMwfzj1wTJD/nUv+uV9NSAKlPLvARmlYdA20d+7/WuEmOk1AgMBAAEwDQYJ
KoZIhvcNAQEFBQADggEBACCCAJypO9DFU6GeOH+FwE0JCLTypHoIwERWxNL7xfjg
rwVqIxwAEo+fJjL+QY7JbAb/eqKaXIBYkAF2lFc4iEmecXX/A3Aqw95AYi78o7HD
MwRPqJha9mxLcCWwjX8XK8pT152BvYFPNhi+6jd++rDRxKDfmNvgdUQ2/YW6a5Wv
zEmLDPUWRIuMQIEmOa2/JhlllDviMExTw51nbqYgCghycRvDACyQAuu8re7P6gcg
bXObNlfxcU/8Ph6MFI+2S9ODtQ4BHyuKd4kRNsYn8vV42a0h3bCYSGPk3kSIgxd7
XijwHT/o8E9ddH2BvDv+6Nhno9C6/MbezEOIs4nlhRk=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA29iFnMf4pjty6knOt4wT0joPUlU2dGCFWxQ5Eg77Yx3Lou2a
FOzNGp7zLYH8gauOK+mgY+B9cBv8PvVtUQQKB0SKTTV8ZNKVyP3O5F2gRSJ+tHtT
FqaPfG0WPOn/f02YbOpAe6Rk+NnlJoeuBxG4uapUSq7r0mTGJQe+52y1LcMs23ID
ENBfDoUMVt5vCnF+cYK5ndeFVLyPO3JjohDH1zv3a9ECG28rtjKHLpNYFDsPJaD7
UPuyyk3hIvfPCZoJOUlEVfpLT/lM+9oCPnq9PwIp5NqYofkuc3eKooCo7N4r4IlP
Ajktaao6b0ScNwNQB3leOIdEUFSIYy8N1JszVwIDAQABAoIBAFlIvjrGG/2m9yyf
fQyeHw6p9b8CTHNHH+G1fNgQrZe7ahBpXsJQyZueIjTBLcOb4MmEwFbPvSHiu7b2
Bcd5VHlPJLvmlPZ9b8eJDJVCUOzC7aJu03fHfU6THwzuG42f/d9942JTiY5nL+FO
CSdl0xfUTRdnou53buFrG+TxCUPj13HP1HY6DAVzEIq1H4TZwIZo7KRRTIYpTB3P
6yvr9xsISLlnmfQ4tp2pApl5o+bHJEhr1VO6SAT/pSyShi79KmMMqYtyTmOMz7w6
VJkre5ybnXBDN6tfMHWqdobJ4gRWK9rqf+LIZig5XQnyzkue8k+I7aPgO4xNFh56
dkejQcECgYEA9MDCWViqpfvof+epiKzccqnIRnz1EfHdRQjiKsKGRe39+K+pyaqJ
FOOPFy3aOw6M4cFWwcdMYzKTItvzyLVhDqMzT5eup/NVqo5tPoy93XPf2qRYiTl4
2j5wvm0RVkYEONd3pk2lbfbUmn7XQXj0+AG60SvsqErF/UhIBGec/xsCgYEA5fLC
EdiiC98kr4sVaE8G854WI+aAkStqO5YXyrnsMzRsqk8KVVYE1yCC9rYyymDBYmlx
uEW+vbWqLc8PO3v4ty3o5ff303lPMWIrvKiUldjqMjS6ncWxsQjU0bygGVgOgHO7
c8rjiDH5M0JgWSREYUVFT5mW/5+Y1LVT8mYNlHUCgYEAhMSX6N23XGkFW3Twu2qB
/1Vohgw86OoaDNvfzDBPpFmQ3rlz0ijHSeSTd5BxBH5FICXACUgygNErjcphOSxj
JQyUxgVTQlo2y1mNm1O/nwS/lxx1xqK9ky4x/Kqvr+w1WBxSFI2kQr2V4OUTobma
sXpGvDcmnrhJJLd0EaefO6cCgYEA3Xw/S9tC8nZjqqYn34nHI16Q6tF54tpTf8Np
dT4x8Xw8cqqhRGMPVHsfSi1irKYXfwgbnienuqlBmtAHVv9pKF+TJfb7gXkmO2XY
xOYIAHGn2uYJHjCun9vmyYKLHv4/MaDH3Jd/I88mviXgEdyp9Js5UJua4utB1Rg3
HJMJ34UCgYEAr0PpHEBMbZXZBybNU96+jRTgkrNeJpzlnMy7et2IsRAtLjZ0mpbn
NaX8i8eO+ubweqFdhOvbh7Hd0zr7BzrYcUG1e3njhtxJE1MgWL5plnLVUbIyDAm3
iBpIHIBASNCN3sqeq+VqXvavRmeZh5O0vyLP46/kxZx0rzR/NCi9xxU=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC7DCCAdQCBRQHBXNIMA0GCSqGSIb3DQEBBQUAMDoxDDAKBgNVBAMTA3dzMjEM
MAoGA1UEChMDd3MyMQswCQYDVQQGEwJJRTEPMA0GA1UEBxMGRHVibGluMB4XDTE0
MDgwMzA5MTU0OFoXDTI0MDczMTA5MTU0OFowOjEMMAoGA1UEAxMDd3MyMQwwCgYD
VQQKEwN3czIxCzAJBgNVBAYTAklFMQ8wDQYDVQQHEwZHYWx3YXkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb2IWcx/imO3LqSc63jBPSOg9SVTZ0YIVb
FDkSDvtjHcui7ZoU7M0anvMtgfyBq44r6aBj4H1wG/w+9W1RBAoHRIpNNXxk0pXI
/c7kXaBFIn60e1MWpo98bRY86f9/TZhs6kB7pGT42eUmh64HEbi5qlRKruvSZMYl
B77nbLUtwyzbcgMQ0F8OhQxW3m8KcX5xgrmd14VUvI87cmOiEMfXO/dr0QIbbyu2
Mocuk1gUOw8loPtQ+7LKTeEi988Jmgk5SURV+ktP+Uz72gI+er0/Aink2pih+S5z
d4qigKjs3ivgiU8COS1pqjpvRJw3A1AHeV44h0RQVIhjLw3UmzNXAgMBAAEwDQYJ
KoZIhvcNAQEFBQADggEBALi/RmqeXGazT/WRj9+ZqdcnbcHwK5wwr2/YkpFPJ0Hf
ZDm+2vgjDdTT6cJS6fau0M5nliYdz89aQQo1j9RSRZnzlc/2YCFXyRLCOJYaINbj
1MEUAvNDGL7xTpepK9hVkXASRkbyNXERXRKFI1N+vpKu6UorT6/osEV/qM+MFJ3s
24xE8/J3J4MirVQVt6eY6Jb+tkliOPMIugr6YQlLsqJygEWATP8Qsr81XSfcZhVq
rXzVt7QV8dO0nStMjKK5omrtEAhVnASk7w1tFHkpBF1rqXGoo9ML40RnFZ+E5zqi
iZtzp+NzzLnEnWMNs+fJpPJ96P0kbq2bQzuSBcUynq0=
-----END CERTIFICATE-----
// 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 <http://cesanta.com/>.
//
// $Date: 2014-09-28 05:04:41 UTC $
#ifndef NS_SKELETON_HEADER_INCLUDED
#define NS_SKELETON_HEADER_INCLUDED
#define NS_SKELETON_VERSION "2.1.0"
#undef UNICODE // Use ANSI WinAPI functions
#undef _UNICODE // Use multibyte encoding on Windows
#define _MBCS // Use multibyte encoding on Windows
#define _INTEGRAL_MAX_BITS 64 // Enable _stati64() on Windows
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005+
#undef WIN32_LEAN_AND_MEAN // Let windows.h always include winsock2.h
#define _XOPEN_SOURCE 600 // For flockfile() on Linux
#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE // Enable fseeko() and ftello() functions
#endif
#define _FILE_OFFSET_BITS 64 // Enable 64-bit file offsets
#ifdef _MSC_VER
#pragma warning (disable : 4127) // FD_SET() emits warning, disable it
#pragma warning (disable : 4204) // missing c99 support
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#ifdef _WIN32
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib") // Linking with winsock library
#endif
#include <windows.h>
#include <process.h>
#ifndef EINPROGRESS
#define EINPROGRESS WSAEINPROGRESS
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif
#ifndef __func__
#define STRX(x) #x
#define STR(x) STRX(x)
#define __func__ __FILE__ ":" STR(__LINE__)
#endif
#ifndef va_copy
#define va_copy(x,y) x = y
#endif // MINGW #defines va_copy
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define sleep(x) Sleep((x) * 1000)
#define to64(x) _atoi64(x)
typedef int socklen_t;
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned __int64 uint64_t;
typedef __int64 int64_t;
typedef SOCKET sock_t;
typedef struct _stati64 ns_stat_t;
#ifndef S_ISDIR
#define S_ISDIR(x) ((x) & _S_IFDIR)
#endif
#else
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pthread.h>
#include <stdarg.h>
#include <unistd.h>
#include <arpa/inet.h> // For inet_pton() when NS_ENABLE_IPV6 is defined
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#define closesocket(x) close(x)
#define __cdecl
#define INVALID_SOCKET (-1)
#define to64(x) strtoll(x, NULL, 10)
typedef int sock_t;
typedef struct stat ns_stat_t;
#endif
#ifdef NS_ENABLE_DEBUG
#define DBG(x) do { printf("%-20s ", __func__); printf x; putchar('\n'); \
fflush(stdout); } while(0)
#else
#define DBG(x)
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#endif
#ifdef NS_ENABLE_SSL
#ifdef __APPLE__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#include <openssl/ssl.h>
#else
typedef void *SSL;
typedef void *SSL_CTX;
#endif
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
union socket_address {
struct sockaddr sa;
struct sockaddr_in sin;
#ifdef NS_ENABLE_IPV6
struct sockaddr_in6 sin6;
#else
struct sockaddr sin6;
#endif
};
// Describes chunk of memory
struct ns_str {
const char *p;
size_t len;
};
// IO buffers interface
struct iobuf {
char *buf;
size_t len;
size_t size;
};
void iobuf_init(struct iobuf *, size_t initial_size);
void iobuf_free(struct iobuf *);
size_t iobuf_append(struct iobuf *, const void *data, size_t data_size);
void iobuf_remove(struct iobuf *, size_t data_size);
void iobuf_resize(struct iobuf *, size_t new_size);
// Callback function (event handler) prototype, must be defined by user.
// Net skeleton will call event handler, passing events defined above.
struct ns_connection;
typedef void (*ns_callback_t)(struct ns_connection *, int event_num, void *evp);
// Events. Meaning of event parameter (evp) is given in the comment.
#define NS_POLL 0 // Sent to each connection on each call to ns_mgr_poll()
#define NS_ACCEPT 1 // New connection accept()-ed. union socket_address *addr
#define NS_CONNECT 2 // connect() succeeded or failed. int *success_status
#define NS_RECV 3 // Data has benn received. int *num_bytes
#define NS_SEND 4 // Data has been written to a socket. int *num_bytes
#define NS_CLOSE 5 // Connection is closed. NULL
struct ns_mgr {
struct ns_connection *active_connections;
const char *hexdump_file; // Debug hexdump file path
sock_t ctl[2]; // Socketpair for mg_wakeup()
void *user_data; // User data
};
struct ns_connection {
struct ns_connection *next, *prev; // ns_mgr::active_connections linkage
struct ns_connection *listener; // Set only for accept()-ed connections
struct ns_mgr *mgr;
sock_t sock; // Socket
union socket_address sa; // Peer address
struct iobuf recv_iobuf; // Received data
struct iobuf send_iobuf; // Data scheduled for sending
SSL *ssl;
SSL_CTX *ssl_ctx;
void *user_data; // User-specific data
void *proto_data; // Application protocol-specific data
time_t last_io_time; // Timestamp of the last socket IO
ns_callback_t callback; // Event handler function
unsigned int flags;
#define NSF_FINISHED_SENDING_DATA (1 << 0)
#define NSF_BUFFER_BUT_DONT_SEND (1 << 1)
#define NSF_SSL_HANDSHAKE_DONE (1 << 2)
#define NSF_CONNECTING (1 << 3)
#define NSF_CLOSE_IMMEDIATELY (1 << 4)
#define NSF_WANT_READ (1 << 5)
#define NSF_WANT_WRITE (1 << 6)
#define NSF_LISTENING (1 << 7)
#define NSF_UDP (1 << 8)
#define NSF_USER_1 (1 << 20)
#define NSF_USER_2 (1 << 21)
#define NSF_USER_3 (1 << 22)
#define NSF_USER_4 (1 << 23)
#define NSF_USER_5 (1 << 24)
#define NSF_USER_6 (1 << 25)
};
void ns_mgr_init(struct ns_mgr *, void *user_data);
void ns_mgr_free(struct ns_mgr *);
time_t ns_mgr_poll(struct ns_mgr *, int milli);
void ns_broadcast(struct ns_mgr *, ns_callback_t, void *, size_t);
struct ns_connection *ns_next(struct ns_mgr *, struct ns_connection *);
struct ns_connection *ns_add_sock(struct ns_mgr *, sock_t,
ns_callback_t, void *);
struct ns_connection *ns_bind(struct ns_mgr *, const char *,
ns_callback_t, void *);
struct ns_connection *ns_connect(struct ns_mgr *, const char *,
ns_callback_t, void *);
int ns_send(struct ns_connection *, const void *buf, int len);
int ns_printf(struct ns_connection *, const char *fmt, ...);
int ns_vprintf(struct ns_connection *, const char *fmt, va_list ap);
// Utility functions
void *ns_start_thread(void *(*f)(void *), void *p);
int ns_socketpair(sock_t [2]);
int ns_socketpair2(sock_t [2], int sock_type); // SOCK_STREAM or SOCK_DGRAM
void ns_set_close_on_exec(sock_t);
void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags);
int ns_hexdump(const void *buf, int len, char *dst, int dst_len);
int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
int ns_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // NS_SKELETON_HEADER_INCLUDED
// Copyright (c) 2014 Cesanta Software
// All rights reserved
//
// This example demostrates proxying of WebSocket traffic, regardless of the
// protocol (ws:// or wss://).
// To use this example:
// 1. configure your browser to use a proxy on port 2014
// 2. import certs/ws1_ca.pem and certs/ws2_ca.pem into the trusted
// certificates list on your browser
// 3. make && ./ws_ssl
// 4. Point your browser to http://ws_ssl.com
// A page with 4 sections should appear, showing websocket echoes
#include "net_skeleton.h"
#include "mongoose.h"
#include "ssl_wrapper.h"
#define S1_PEM "certs/ws1_server.pem"
#define C1_PEM "certs/ws1_client.pem"
#define CA1_PEM "certs/ws1_ca.pem"
#define S2_PEM "certs/ws2_server.pem"
#define C2_PEM "certs/ws2_client.pem"
#define CA2_PEM "certs/ws2_ca.pem"
struct config {
const char *uri;
const char *wrapper_server_addr;
const char *wrapper_client_addr;
const char *target_addr;
};
static struct config s_wrappers[] = {
{
"ws1:80",
"tcp://127.0.0.1:7001",
"tcp://127.0.0.1:7001",
"tcp://127.0.0.1:9001"
},
{
"ws1:443",
"ssl://127.0.0.1:7002:" S1_PEM,
"tcp://127.0.0.1:7002",
"tcp://127.0.0.1:9001"
},
{
"ws2:80",
"tcp://127.0.0.1:7003",
"tcp://127.0.0.1:7003",
"ssl://127.0.0.1:9002:" C2_PEM ":" CA2_PEM
},
{
"ws2:443",
"ssl://127.0.0.1:7004:" S2_PEM,
"tcp://127.0.0.1:7004",
"ssl://127.0.0.1:9002:" C2_PEM ":" CA2_PEM
},
};
static int s_received_signal = 0;
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) {
int i;
switch (ev) {
case MG_AUTH:
return MG_TRUE;
case MG_REQUEST:
printf("==> [%s] [%s]\n", conn->request_method, conn->uri);
if (strcmp(conn->request_method, "CONNECT") == 0) {
// Iterate over configured wrappers, see if we can use one of them
for (i = 0; i < (int) ARRAY_SIZE(s_wrappers); i++) {
if (strcmp(conn->uri, s_wrappers[i].uri) == 0) {
mg_forward(conn, s_wrappers[i].wrapper_client_addr);
return MG_MORE;
}
}
// No suitable wrappers found. Disallow that CONNECT request.
mg_send_status(conn, 405);
return MG_TRUE;
}
// Not a CONNECT request, serve HTML file.
mg_send_file(conn, "ws_ssl.html", NULL);
return MG_MORE;
default:
return MG_FALSE;
}
}
static int ws_handler(struct mg_connection *conn, enum mg_event ev) {
switch (ev) {
case MG_AUTH:
return MG_TRUE;
case MG_REQUEST:
if (conn->is_websocket) {
// Simple websocket echo server
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT,
conn->content, conn->content_len);
} else {
mg_printf_data(conn, "%s", "websocket connection expected");
}
return MG_TRUE;
default:
return MG_FALSE;
}
}
static void *serve_thread_func(void *param) {
struct mg_server *server = (struct mg_server *) param;
printf("Listening on port %s\n", mg_get_option(server, "listening_port"));
while (s_received_signal == 0) {
mg_poll_server(server, 1000);
}
mg_destroy_server(&server);
return NULL;
}
static void *wrapper_thread_func(void *param) {
struct config *c = (struct config *) param;
const char *err_msg;
void *wrapper;
wrapper = ssl_wrapper_init(c->wrapper_server_addr, c->target_addr, &err_msg);
if (wrapper == NULL) {
fprintf(stderr, "Error: %s\n", err_msg);
exit(EXIT_FAILURE);
}
//((struct ns_mgr *) wrapper)->hexdump_file = "/dev/stderr";
ssl_wrapper_serve(wrapper, &s_received_signal);
return NULL;
}
int main(void) {
struct mg_server *proxy_server = mg_create_server(NULL, ev_handler);
struct mg_server *ws1_server = mg_create_server(NULL, ws_handler);
struct mg_server *ws2_server = mg_create_server(NULL, ws_handler);
size_t i;
((struct ns_mgr *) proxy_server)->hexdump_file = "/dev/stderr";
// Configure proxy server to listen on port 2014
mg_set_option(proxy_server, "listening_port", "2014");
//mg_set_option(proxy_server, "enable_proxy", "yes");
// Configure two websocket echo servers:
// ws1 is WS, listening on 9001
// ws2 is WSS, listening on 9002
// Note that HTML page thinks that ws1 is WSS, and ws2 is WS,
// where in reality it is vice versa and proxy server makes the decision.
mg_set_option(ws1_server, "listening_port", "tcp://127.0.0.1:9001");
mg_set_option(ws2_server, "listening_port",
"ssl://127.0.0.1:9002:" S2_PEM ":" CA2_PEM);
// Setup signal handlers
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
// Start SSL wrappers, each in it's own thread
for (i = 0; i < ARRAY_SIZE(s_wrappers); i++) {
ns_start_thread(wrapper_thread_func, &s_wrappers[i]);
}
// Start websocket servers in separate threads
mg_start_thread(serve_thread_func, ws1_server);
mg_start_thread(serve_thread_func, ws2_server);
// Finally, start proxy server in this thread: this call blocks
serve_thread_func(proxy_server);
printf("Existing on signal %d\n", s_received_signal);
return EXIT_SUCCESS;
}
<html>
<head>
<title>Websocket Proxy SSL Test</title>
<meta charset="utf-8">
<script>
window.onload = function() {
var protocols = ['ws://', 'wss://'];
var websocketServers = ['ws1', 'ws2'];
//protocols = ['wss://'];
//websocketServers = ['ws1']
var createWebsocketConnection = function(proto, server) {
var conn = new WebSocket(proto + server);
var div = document.createElement('div');
var h2 = document.createElement('h2');
h2.innerHTML = 'Connection to ' + proto + server;
document.body.appendChild(h2);
document.body.appendChild(div);
conn.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 some string to the websocket connection periodically.
// websocket server much echo it back.
window.setInterval(function() { conn.send(Math.random()); }, 1000);
};
for (var i = 0; i < protocols.length; i++) {
for (var j = 0; j < websocketServers.length; j++) {
createWebsocketConnection(protocols[i], websocketServers[j]);
}
}
};
</script>
<style>
body > div {
border: 1px solid #ccc; background: #f0f0f0; padding: 0 1em;
margin: 0 2em; min-height: 4em; max-width: 40em;
}
</style>
</head>
<body>
</body>
</html>
\ No newline at end of file
...@@ -3,6 +3,6 @@ include $(CLEAR_VARS) ...@@ -3,6 +3,6 @@ include $(CLEAR_VARS)
LOCAL_CFLAGS := -std=c99 -O2 -W -Wall -pthread -pipe $(COPT) LOCAL_CFLAGS := -std=c99 -O2 -W -Wall -pthread -pipe $(COPT)
LOCAL_MODULE := mongoose LOCAL_MODULE := mongoose
LOCAL_SRC_FILES := examples/web_server/web_server.c mongoose.c LOCAL_SRC_FILES := examples/hello_world/hello_world.c mongoose.c
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)
This source diff could not be displayed because it is too large. You can view the blob instead.
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> #ifdef __AVR__
// Copyright (c) 2013-2014 Cesanta Software Limited #include "avrsupport.h"
// All rights reserved #endif
// /*
// This software is dual-licensed: you can redistribute it and/or modify * Copyright (c) 2014 Cesanta Software Limited
// it under the terms of the GNU General Public License version 2 as * All rights reserved
// published by the Free Software Foundation. For the terms of this * This software is dual-licensed: you can redistribute it and/or modify
// license, see <http://www.gnu.org/licenses/>. * it under the terms of the GNU General Public License version 2 as
// * published by the Free Software Foundation. For the terms of this
// You are free to use this software under the terms of the GNU General * license, see <http://www.gnu.org/licenses/>.
// Public License, but WITHOUT ANY WARRANTY; without even the implied *
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * You are free to use this software under the terms of the GNU General
// See the GNU General Public License for more details. * Public License, but WITHOUT ANY WARRANTY; without even the implied
// * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// Alternatively, you can license this software under a commercial * See the GNU General Public License for more details.
// 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>.
*/
#ifndef MONGOOSE_HEADER_INCLUDED #define NS_FOSSA_VERSION "2.0.0"
#define MONGOOSE_HEADER_INCLUDED /*
* Copyright (c) 2015 Cesanta Software Limited
* All rights reserved
*/
#define MONGOOSE_VERSION "5.6" #ifndef OSDEP_HEADER_INCLUDED
#define OSDEP_HEADER_INCLUDED
#include <stdio.h> // required for FILE #if !defined(NS_DISABLE_FILESYSTEM) && defined(AVR_NOFS)
#include <stddef.h> // required for size_t #define NS_DISABLE_FILESYSTEM
#include <sys/types.h> // required for time_t #endif
#undef UNICODE /* Use ANSI WinAPI functions */
#undef _UNICODE /* Use multibyte encoding on Windows */
#define _MBCS /* Use multibyte encoding on Windows */
#define _INTEGRAL_MAX_BITS 64 /* Enable _stati64() on Windows */
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */
#endif
#undef WIN32_LEAN_AND_MEAN /* Let windows.h always include winsock2.h */
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 /* For flockfile() on Linux */
#define __STDC_FORMAT_MACROS /* <inttypes.h> wants this for C++ */
#define __STDC_LIMIT_MACROS /* C++ wants that for INT64_MAX */
#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE /* Enable fseeko() and ftello() functions */
#endif
#define _FILE_OFFSET_BITS 64 /* Enable 64-bit file offsets */
#if !(defined(AVR_LIBC) || defined(PICOTCP))
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#endif
#ifndef BYTE_ORDER
#define LITTLE_ENDIAN 0x41424344
#define BIG_ENDIAN 0x44434241
#define PDP_ENDIAN 0x42414443
/* TODO(lsm): fix for big-endian machines. 'ABCD' is not portable */
/*#define BYTE_ORDER 'ABCD'*/
#define BYTE_ORDER LITTLE_ENDIAN
#endif
/*
* MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
* MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
* MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
* MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
* MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
* MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio 2003)
* MSVC++ 7.0 _MSC_VER == 1300
* MSVC++ 6.0 _MSC_VER == 1200
* MSVC++ 5.0 _MSC_VER == 1100
*/
#ifdef _MSC_VER
#pragma warning(disable : 4127) /* FD_SET() emits warning, disable it */
#pragma warning(disable : 4204) /* missing c99 support */
#endif
#ifdef PICOTCP
#define time(x) PICO_TIME()
#ifndef SOMAXCONN
#define SOMAXCONN (16)
#endif
#ifdef _POSIX_VERSION
#define signal(...)
#endif
#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef va_copy
#ifdef __va_copy
#define va_copy __va_copy
#else
#define va_copy(x, y) (x) = (y)
#endif
#endif
#ifdef _WIN32
#define random() rand()
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib") /* Linking with winsock library */
#endif
#include <windows.h>
#include <process.h>
#ifndef EINPROGRESS
#define EINPROGRESS WSAEINPROGRESS
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif
#ifndef __func__
#define STRX(x) #x
#define STR(x) STRX(x)
#define __func__ __FILE__ ":" STR(__LINE__)
#endif
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define sleep(x) Sleep((x) *1000)
#define to64(x) _atoi64(x)
#define popen(x, y) _popen((x), (y))
#define pclose(x) _pclose(x)
#if defined(_MSC_VER) && _MSC_VER >= 1400
#define fseeko(x, y, z) _fseeki64((x), (y), (z))
#else
#define fseeko(x, y, z) fseek((x), (y), (z))
#endif
#define random() rand()
typedef int socklen_t;
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned __int64 uint64_t;
typedef __int64 int64_t;
typedef SOCKET sock_t;
typedef uint32_t in_addr_t;
#ifndef UINT16_MAX
#define UINT16_MAX 65535
#endif
#ifndef UINT32_MAX
#define UINT32_MAX 4294967295
#endif
#ifndef pid_t
#define pid_t HANDLE
#endif
#define INT64_FMT "I64d"
#define SIZE_T_FMT "Iu"
#ifdef __MINGW32__
typedef struct stat cs_stat_t;
#else
typedef struct _stati64 cs_stat_t;
#endif
#ifndef S_ISDIR
#define S_ISDIR(x) ((x) &_S_IFDIR)
#endif
#define DIRSEP '\\'
/* POSIX opendir/closedir/readdir API for Windows. */
struct dirent {
char d_name[MAX_PATH];
};
typedef struct DIR {
HANDLE handle;
WIN32_FIND_DATAW info;
struct dirent result;
} DIR;
DIR *opendir(const char *name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);
#elif /* not _WIN32 */ defined(NS_CC3200)
#include <fcntl.h>
#include <unistd.h>
#include <cc3200_libc.h>
#include <cc3200_socket.h>
#elif /* not CC3200 */ defined(NS_ESP8266) && defined(RTOS_SDK)
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include <lwip/dns.h>
#include <esp_libc.h>
#define random() os_random()
/* TODO(alashkin): check if zero is OK */
#define SOMAXCONN 0
#include <stdlib.h>
#elif /* not ESP8266 RTOS */ !defined(NO_LIBC) && !defined(NO_BSD_SOCKETS)
#include <dirent.h>
#include <fcntl.h>
#include <netdb.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/inet.h> /* For inet_pton() when NS_ENABLE_IPV6 is defined */
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#endif
#ifndef _WIN32
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#ifndef AVR_LIBC
#ifndef NS_ESP8266
#define closesocket(x) close(x)
#endif
#ifndef __cdecl
#define __cdecl
#endif
#define INVALID_SOCKET (-1)
#define INT64_FMT PRId64
#define SIZE_T_FMT "zu"
#define to64(x) strtoll(x, NULL, 10)
typedef int sock_t;
typedef struct stat cs_stat_t;
#define DIRSEP '/'
#endif /* !AVR_LIBC */
#ifdef __APPLE__
int64_t strtoll(const char *str, char **endptr, int base);
#endif
#endif /* !_WIN32 */
#define __DBG(x) \
do { \
printf("%-20s ", __func__); \
printf x; \
putchar('\n'); \
fflush(stdout); \
} while (0)
#ifdef NS_ENABLE_DEBUG
#define DBG __DBG
#else
#define DBG(x)
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#endif
#endif /* OSDEP_HEADER_INCLUDED */
/*
* Copyright (c) 2015 Cesanta Software Limited
* All rights reserved
*/
/*
* === Memory Buffers
*
* Mbufs are mutable/growing memory buffers, like C++ strings.
* Mbuf can append data to the end of a buffer, or insert data into arbitrary
* position in the middle of a buffer. The buffer grows automatically when
* needed.
*/
#ifndef MBUF_H_INCLUDED
#define MBUF_H_INCLUDED
#if defined(__cplusplus)
extern "C" {
#endif
#include <stdlib.h>
#ifndef MBUF_SIZE_MULTIPLIER
#define MBUF_SIZE_MULTIPLIER 1.5
#endif
/* Memory buffer descriptor */
struct mbuf {
char *buf; /* Buffer pointer */
size_t len; /* Data length. Data is located between offset 0 and len. */
size_t size; /* Buffer size allocated by realloc(1). Must be >= len */
};
/*
* Initialize an Mbuf.
* `initial_capacity` specifies the initial capacity of the mbuf.
*/
void mbuf_init(struct mbuf *, size_t initial_capacity);
/* Free the space allocated for the mbuffer and resets the mbuf structure. */
void mbuf_free(struct mbuf *);
/*
* Appends data to the Mbuf.
*
* Return the number of bytes appended, or 0 if out of memory.
*/
size_t mbuf_append(struct mbuf *, const void *data, size_t data_size);
/*
* Insert data at a specified offset in the Mbuf.
*
* Existing data will be shifted forwards and the buffer will
* be grown if necessary.
* Return the number of bytes inserted.
*/
size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t);
/* Remove `data_size` bytes from the beginning of the buffer. */
void mbuf_remove(struct mbuf *, size_t data_size);
/*
* Resize an Mbuf.
*
* If `new_size` is smaller than buffer's `len`, the
* resize is not performed.
*/
void mbuf_resize(struct mbuf *, size_t new_size);
/* Shrink an Mbuf by resizing its `size` to `len`. */
void mbuf_trim(struct mbuf *);
#if defined(__cplusplus)
}
#endif /* __cplusplus */
#endif /* MBUF_H_INCLUDED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
#if !defined(NS_SHA1_HEADER_INCLUDED) && !defined(DISABLE_SHA1)
#define NS_SHA1_HEADER_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef struct {
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} cs_sha1_ctx;
void cs_sha1_init(cs_sha1_ctx *);
void cs_sha1_update(cs_sha1_ctx *, const unsigned char *data, uint32_t len);
void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *);
void hmac_sha1(const unsigned char *key, size_t key_len,
const unsigned char *text, size_t text_len,
unsigned char out[20]);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_SHA1_HEADER_INCLUDED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
#ifndef MD5_HEADER_DEFINED
#define MD5_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
void MD5_Init(MD5_CTX *c);
void MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len);
void MD5_Final(unsigned char *md, MD5_CTX *c);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
#if !defined(BASE64_H_INCLUDED) && !defined(DISABLE_BASE64)
#define BASE64_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
void cs_base64_encode(const unsigned char *src, int src_len, char *dst);
int cs_base64_decode(const unsigned char *s, int len, char *dst);
#ifdef __cplusplus
}
#endif
#endif
/*
* Copyright (c) 2015 Cesanta Software Limited
* All rights reserved
*/
#ifndef STR_UTIL_H
#define STR_UTIL_H
#include <stdarg.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
int c_snprintf(char *buf, size_t buf_size, const char *format, ...);
int c_vsnprintf(char *buf, size_t buf_size, const char *format, va_list ap);
#if !(_XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L) && \
!(__DARWIN_C_LEVEL >= 200809L) && !defined(RTOS_SDK) || \
defined(_WIN32)
int strnlen(const char *s, size_t maxlen);
#endif
#ifdef __cplusplus
}
#endif
#endif
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2013 Cesanta Software Limited
* All rights reserved
*
* This library 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 library 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 library under a commercial
* license, as set out in <https://www.cesanta.com/license>.
*/
#ifndef FROZEN_HEADER_INCLUDED
#define FROZEN_HEADER_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <stdarg.h>
enum json_type {
JSON_TYPE_EOF = 0, /* End of parsed tokens marker */
JSON_TYPE_STRING = 1,
JSON_TYPE_NUMBER = 2,
JSON_TYPE_OBJECT = 3,
JSON_TYPE_TRUE = 4,
JSON_TYPE_FALSE = 5,
JSON_TYPE_NULL = 6,
JSON_TYPE_ARRAY = 7
};
struct json_token {
const char *ptr; /* Points to the beginning of the token */
int len; /* Token length */
int num_desc; /* For arrays and object, total number of descendants */
enum json_type type; /* Type of the token, possible values above */
};
/* Error codes */
#define JSON_STRING_INVALID -1
#define JSON_STRING_INCOMPLETE -2
#define JSON_TOKEN_ARRAY_TOO_SMALL -3
int parse_json(const char *json_string, int json_string_length,
struct json_token *tokens_array, int size_of_tokens_array);
struct json_token *parse_json2(const char *json_string, int string_length);
struct json_token *find_json_token(struct json_token *toks, const char *path);
int json_emit_long(char *buf, int buf_len, long value);
int json_emit_double(char *buf, int buf_len, double value);
int json_emit_quoted_str(char *buf, int buf_len, const char *str, int len);
int json_emit_unquoted_str(char *buf, int buf_len, const char *str, int len);
int json_emit(char *buf, int buf_len, const char *fmt, ...);
int json_emit_va(char *buf, int buf_len, const char *fmt, va_list);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* FROZEN_HEADER_INCLUDED */
/*
* 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>.
*/
/*
* === Core: TCP/UDP/SSL
*
* NOTE: Mongoose manager is single threaded. It does not protect
* its data structures by mutexes, therefore all functions that are dealing
* with particular event manager should be called from the same thread,
* with exception of `mg_broadcast()` function. It is fine to have different
* event managers handled by different threads.
*/
#ifndef NS_NET_HEADER_INCLUDED
#define NS_NET_HEADER_INCLUDED
#ifdef NS_ENABLE_SSL
#ifdef __APPLE__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#include <openssl/ssl.h>
#else
typedef void *SSL;
typedef void *SSL_CTX;
#endif
#ifdef NS_USE_READ_WRITE
#define NS_RECV_FUNC(s, b, l, f) read(s, b, l)
#define NS_SEND_FUNC(s, b, l, f) write(s, b, l)
#else
#define NS_RECV_FUNC(s, b, l, f) recv(s, b, l, f)
#define NS_SEND_FUNC(s, b, l, f) send(s, b, l, f)
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif // __cplusplus #endif /* __cplusplus */
union socket_address {
struct sockaddr sa;
struct sockaddr_in sin;
#ifdef NS_ENABLE_IPV6
struct sockaddr_in6 sin6;
#else
struct sockaddr sin6;
#endif
};
/* Describes chunk of memory */
struct mg_str {
const char *p; /* Memory chunk pointer */
size_t len; /* Memory chunk length */
};
#define NS_STR(str_literal) \
{ str_literal, sizeof(str_literal) - 1 }
/*
* Callback function (event handler) prototype, must be defined by user.
* Mongoose calls event handler, passing events defined below.
*/
struct mg_connection;
typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, void *);
/* Events. Meaning of event parameter (evp) is given in the comment. */
#define NS_POLL 0 /* Sent to each connection on each mg_mgr_poll() call */
#define NS_ACCEPT 1 /* New connection accepted. union socket_address *addr */
#define NS_CONNECT 2 /* connect() succeeded or failed. int *success_status */
#define NS_RECV 3 /* Data has benn received. int *num_bytes */
#define NS_SEND 4 /* Data has been written to a socket. int *num_bytes */
#define NS_CLOSE 5 /* Connection is closed. NULL */
// This structure contains information about HTTP request. /*
* Mongoose event manager.
*/
struct mg_mgr {
struct mg_connection *active_connections;
const char *hexdump_file; /* Debug hexdump file path */
sock_t ctl[2]; /* Socketpair for mg_wakeup() */
void *user_data; /* User data */
void *mgr_data; /* Implementation-specific event manager's data. */
};
/*
* Mongoose connection.
*/
struct mg_connection { struct mg_connection {
const char *request_method; // "GET", "POST", etc struct mg_connection *next, *prev; /* mg_mgr::active_connections linkage */
const char *uri; // URL-decoded URI struct mg_connection *listener; /* Set only for accept()-ed connections */
const char *http_version; // E.g. "1.0", "1.1" struct mg_mgr *mgr; /* Pointer to containing manager */
const char *query_string; // URL part after '?', not including '?', or NULL
sock_t sock; /* Socket to the remote peer */
char remote_ip[48]; // Max IPv6 string length is 45 characters union socket_address sa; /* Remote peer address */
char local_ip[48]; // Local IP address size_t recv_mbuf_limit; /* Max size of recv buffer */
unsigned short remote_port; // Client's port struct mbuf recv_mbuf; /* Received data */
unsigned short local_port; // Local port number struct mbuf send_mbuf; /* Data scheduled for sending */
SSL *ssl;
int num_headers; // Number of HTTP headers SSL_CTX *ssl_ctx;
struct mg_header { time_t last_io_time; /* Timestamp of the last socket IO */
const char *name; // HTTP header name mg_event_handler_t proto_handler; /* Protocol-specific event handler */
const char *value; // HTTP header value void *proto_data; /* Protocol-specific data */
} http_headers[30]; mg_event_handler_t handler; /* Event handler function */
void *user_data; /* User-specific data */
char *content; // POST (or websocket message) data, or NULL void *priv_1; /* Used by mg_enable_multithreading() */
size_t content_len; // Data length void *priv_2; /* Used by mg_enable_multithreading() */
void *mgr_data; /* Implementation-specific event manager's data. */
int is_websocket; // Connection is a websocket connection
int status_code; // HTTP status code for HTTP error handler unsigned long flags;
int wsbits; // First byte of the websocket frame /* Flags set by Mongoose */
void *server_param; // Parameter passed to mg_create_server() #define NSF_LISTENING (1 << 0) /* This connection is listening */
void *connection_param; // Placeholder for connection-specific data #define NSF_UDP (1 << 1) /* This connection is UDP */
void *callback_param; #define NSF_RESOLVING (1 << 2) /* Waiting for async resolver */
}; #define NSF_CONNECTING (1 << 3) /* connect() call in progress */
#define NSF_SSL_HANDSHAKE_DONE (1 << 4) /* SSL specific */
struct mg_server; // Opaque structure describing server instance #define NSF_WANT_READ (1 << 5) /* SSL specific */
enum mg_result { MG_FALSE, MG_TRUE, MG_MORE }; #define NSF_WANT_WRITE (1 << 6) /* SSL specific */
enum mg_event { #define NSF_IS_WEBSOCKET (1 << 7) /* Websocket specific */
MG_POLL = 100, // If callback returns MG_TRUE connection closes
// after all of data is sent /* Flags that are settable by user */
MG_CONNECT, // If callback returns MG_FALSE, connect fails #define NSF_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */
MG_AUTH, // If callback returns MG_FALSE, authentication fails #define NSF_DONT_SEND (1 << 11) /* Do not send data to peer */
MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req #define NSF_CLOSE_IMMEDIATELY (1 << 12) /* Disconnect */
MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection #define NSF_WEBSOCKET_NO_DEFRAG (1 << 13) /* Websocket specific */
MG_RECV, // Mongoose has received POST data chunk. #define NSF_DELETE_CHUNK (1 << 14) /* HTTP specific */
// Callback should return a number of bytes to discard from
// the receive buffer, or -1 to close the connection. #define NSF_USER_1 (1 << 20) /* Flags left for application */
MG_CLOSE, // Connection is closed, callback return value is ignored #define NSF_USER_2 (1 << 21)
MG_WS_HANDSHAKE, // New websocket connection, handshake request #define NSF_USER_3 (1 << 22)
MG_WS_CONNECT, // New websocket connection established #define NSF_USER_4 (1 << 23)
MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err #define NSF_USER_5 (1 << 24)
}; #define NSF_USER_6 (1 << 25)
typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event); };
// Websocket opcodes, from http://tools.ietf.org/html/rfc6455 /*
enum { * Initialize Mongoose manager. Side effect: ignores SIGPIPE signal.
WEBSOCKET_OPCODE_CONTINUATION = 0x0, * `mgr->user_data` field will be initialized with `user_data` parameter.
WEBSOCKET_OPCODE_TEXT = 0x1, * That is an arbitrary pointer, where user code can associate some data
WEBSOCKET_OPCODE_BINARY = 0x2, * with the particular Mongoose manager. For example, a C++ wrapper class
WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8, * could be written, in which case `user_data` can hold a pointer to the
WEBSOCKET_OPCODE_PING = 0x9, * class instance.
WEBSOCKET_OPCODE_PONG = 0xa */
}; void mg_mgr_init(struct mg_mgr *mgr, void *user_data);
// Server management functions /*
struct mg_server *mg_create_server(void *server_param, mg_handler_t handler); * De-initializes Mongoose manager.
void mg_destroy_server(struct mg_server **); *
const char *mg_set_option(struct mg_server *, const char *opt, const char *val); * Close and deallocate all active connections.
time_t mg_poll_server(struct mg_server *, int milliseconds); */
const char **mg_get_valid_option_names(void); void mg_mgr_free(struct mg_mgr *);
const char *mg_get_option(const struct mg_server *server, const char *name);
void mg_copy_listeners(struct mg_server *from, struct mg_server *to); /*
struct mg_connection *mg_next(struct mg_server *, struct mg_connection *); * This function performs the actual IO, and must be called in a loop
void mg_wakeup_server(struct mg_server *); * (an event loop). Returns the current timestamp.
void mg_wakeup_server_ex(struct mg_server *, mg_handler_t, const char *, ...); * `milli` is the maximum number of milliseconds to sleep.
struct mg_connection *mg_connect(struct mg_server *, const char *); * `mg_mgr_poll()` checks all connection for IO readiness. If at least one
* of the connections is IO-ready, `mg_mgr_poll()` triggers respective
// Connection management functions * event handlers and returns.
void mg_send_status(struct mg_connection *, int status_code); */
void mg_send_header(struct mg_connection *, const char *name, const char *val); time_t mg_mgr_poll(struct mg_mgr *, int milli);
size_t mg_send_data(struct mg_connection *, const void *data, int data_len);
size_t mg_printf_data(struct mg_connection *, const char *format, ...); /*
size_t mg_vprintf_data(struct mg_connection *, const char *format, va_list ap); * Pass a message of a given length to all connections.
size_t mg_write(struct mg_connection *, const void *buf, size_t len); *
size_t mg_printf(struct mg_connection *conn, const char *fmt, ...); * Must be called from a thread that does NOT call `mg_mgr_poll()`.
size_t mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap); * Note that `mg_broadcast()` is the only function
* that can be, and must be, called from a different (non-IO) thread.
size_t mg_websocket_write(struct mg_connection *, int opcode, *
const char *data, size_t data_len); * `func` callback function will be called by the IO thread for each
size_t mg_websocket_printf(struct mg_connection* conn, int opcode, * connection. When called, event would be `NS_POLL`, and message will
* be passed as `ev_data` pointer. Maximum message size is capped
* by `NS_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes.
*/
void mg_broadcast(struct mg_mgr *, mg_event_handler_t func, void *, size_t);
/*
* Iterate over all active connections.
*
* Returns next connection from the list
* of active connections, or `NULL` if there is no more connections. Below
* is the iteration idiom:
*
* [source,c]
* ----
* for (c = mg_next(srv, NULL); c != NULL; c = mg_next(srv, c)) {
* // Do something with connection `c`
* }
* ----
*/
struct mg_connection *mg_next(struct mg_mgr *, struct mg_connection *);
/*
* Optional parameters to mg_add_sock_opt()
* `flags` is an initial `struct mg_connection::flags` bitmask to set,
* see `NSF_*` flags definitions.
*/
struct mg_add_sock_opts {
void *user_data; /* Initial value for connection's user_data */
unsigned int flags; /* Initial connection flags */
const char **error_string; /* Placeholder for the error string */
};
/*
* Create a connection, associate it with the given socket and event handler,
* and add it to the manager.
*
* For more options see the `mg_add_sock_opt` variant.
*/
struct mg_connection *mg_add_sock(struct mg_mgr *, sock_t, mg_event_handler_t);
/*
* Create a connection, associate it with the given socket and event handler,
* and add to the manager.
*
* See the `mg_add_sock_opts` structure for a description of the options.
*/
struct mg_connection *mg_add_sock_opt(struct mg_mgr *, sock_t,
mg_event_handler_t,
struct mg_add_sock_opts);
/*
* Optional parameters to mg_bind_opt()
* `flags` is an initial `struct mg_connection::flags` bitmask to set,
* see `NSF_*` flags definitions.
*/
struct mg_bind_opts {
void *user_data; /* Initial value for connection's user_data */
unsigned int flags; /* Extra connection flags */
const char **error_string; /* Placeholder for the error string */
};
/*
* Create listening connection.
*
* See `mg_bind_opt` for full documentation.
*/
struct mg_connection *mg_bind(struct mg_mgr *, const char *,
mg_event_handler_t);
/*
* Create listening connection.
*
* `address` parameter tells which address to bind to. It's format is the same
* as for the `mg_connect()` call, where `HOST` part is optional. `address`
* can be just a port number, e.g. `:8000`. To bind to a specific interface,
* an IP address can be specified, e.g. `1.2.3.4:8000`. By default, a TCP
* connection is created. To create UDP connection, prepend `udp://` prefix,
* e.g. `udp://:8000`. To summarize, `address` paramer has following format:
* `[PROTO://][IP_ADDRESS]:PORT`, where `PROTO` could be `tcp` or `udp`.
*
* See the `mg_bind_opts` structure for a description of the optional
* parameters.
*
* Return a new listening connection, or `NULL` on error.
* NOTE: Connection remains owned by the manager, do not free().
*/
struct mg_connection *mg_bind_opt(struct mg_mgr *, const char *,
mg_event_handler_t, struct mg_bind_opts);
/* Optional parameters to mg_connect_opt() */
struct mg_connect_opts {
void *user_data; /* Initial value for connection's user_data */
unsigned int flags; /* Extra connection flags */
const char **error_string; /* Placeholder for the error string */
};
/*
* Connect to a remote host.
*
* See `mg_connect_opt()` for full documentation.
*/
struct mg_connection *mg_connect(struct mg_mgr *, const char *,
mg_event_handler_t);
/*
* Connect to a remote host.
*
* `address` format is `[PROTO://]HOST:PORT`. `PROTO` could be `tcp` or `udp`.
* `HOST` could be an IP address,
* IPv6 address (if Mongoose is compiled with `-DNS_ENABLE_IPV6`), or a host
* name. If `HOST` is a name, Mongoose will resolve it asynchronously. Examples
* of valid addresses: `google.com:80`, `udp://1.2.3.4:53`, `10.0.0.1:443`,
* `[::1]:80`
*
* See the `mg_connect_opts` structure for a description of the optional
* parameters.
*
* Returns a new outbound connection, or `NULL` on error.
*
* NOTE: Connection remains owned by the manager, do not free().
*
* NOTE: To enable IPv6 addresses, `-DNS_ENABLE_IPV6` should be specified
* in the compilation flags.
*
* NOTE: New connection will receive `NS_CONNECT` as it's first event
* which will report connect success status.
* If asynchronous resolution fail, or `connect()` syscall fail for whatever
* reason (e.g. with `ECONNREFUSED` or `ENETUNREACH`), then `NS_CONNECT`
* event report failure. Code example below:
*
* [source,c]
* ----
* static void 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) {
* // Success
* } else {
* // Error
* printf("connect() error: %s\n", strerror(connect_status));
* }
* break;
* ...
* }
* }
*
* ...
* mg_connect(mgr, "my_site.com:80", ev_handler);
* ----
*/
struct mg_connection *mg_connect_opt(struct mg_mgr *, const char *,
mg_event_handler_t,
struct mg_connect_opts);
/*
* Enable SSL for a given connection.
* `cert` is a server certificate file name for a listening connection,
* or a client certificate file name for an outgoing connection.
* Certificate files must be in PEM format. Server certificate file
* must contain a certificate, concatenated with a private key, optionally
* concatenated with parameters.
* `ca_cert` is a CA certificate, or NULL if peer verification is not
* required.
* Return: NULL on success, or error message on error.
*/
const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
const char *ca_cert);
/*
* Send data to the connection.
*
* Return number of written bytes. Note that sending
* functions do not actually push data to the socket. They just append data
* to the output buffer. The exception is UDP connections. For UDP, data is
* sent immediately, and returned value indicates an actual number of bytes
* sent to the socket.
*/
int mg_send(struct mg_connection *, const void *buf, int len);
/*
* Send `printf`-style formatted data to the connection.
*
* See `mg_send` for more details on send semantics.
*/
int mg_printf(struct mg_connection *, const char *fmt, ...);
/* Same as `mg_printf()`, but takes `va_list ap` as an argument. */
int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
/*
* Create a socket pair.
* `sock_type` can be either `SOCK_STREAM` or `SOCK_DGRAM`.
* Return 0 on failure, 1 on success.
*/
int mg_socketpair(sock_t[2], int sock_type);
/*
* Convert domain name into IP address.
*
* This is a utility function. If compilation flags have
* `-DNS_ENABLE_GETADDRINFO`, then `getaddrinfo()` call is used for name
* resolution. Otherwise, `gethostbyname()` is used.
*
* CAUTION: this function can block.
* Return 1 on success, 0 on failure.
*/
int mg_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);
/*
* Verify given IP address against the ACL.
*
* `remote_ip` - an IPv4 address to check, in host byte order
* `acl` - a comma separated list of IP subnets: `x.x.x.x/x` or `x.x.x.x`.
* Each subnet is
* prepended by either a - or a + sign. A plus sign means allow, where a
* minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,
* this means to deny only that single IP address.
* Subnet masks may vary from 0 to 32, inclusive. The default setting
* is to allow all accesses. On each request the full list is traversed,
* and the last match wins. Example:
*
* `-0.0.0.0/0,+192.168/16` - deny all acccesses, only allow 192.168/16 subnet
*
* To learn more about subnet masks, see the
* link:https://en.wikipedia.org/wiki/Subnetwork[Wikipedia page on Subnetwork]
*
* Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
*/
int mg_check_ip_acl(const char *acl, uint32_t remote_ip);
/*
* Enable multi-threaded handling for the given listening connection `nc`.
* For each accepted connection, Mongoose will create a separate thread
* and run event handler in that thread. Thus, if an event hanler is doing
* a blocking call or some long computation, that will not slow down
* other connections.
*/
void mg_enable_multithreading(struct mg_connection *nc);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_NET_HEADER_INCLUDED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* === Utilities
*/
#ifndef NS_UTIL_HEADER_DEFINED
#define NS_UTIL_HEADER_DEFINED
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifndef MAX_PATH_SIZE
#define MAX_PATH_SIZE 500
#endif
/*
* Fetch substring from input string `s`, `end` into `v`.
* Skips initial delimiter characters. Records first non-delimiter character
* as the beginning of substring `v`. Then scans the rest of the string
* until a delimiter character or end-of-string is found.
* `delimiters` is a 0-terminated string containing delimiter characters.
* Either one of `delimiters` or `end_string` terminates the search.
* Return an `s` pointer, advanced forward where parsing stopped.
*/
const char *mg_skip(const char *s, const char *end_string,
const char *delimiters, struct mg_str *v);
/*
* Cross-platform version of `strncasecmp()`.
*/
int mg_ncasecmp(const char *s1, const char *s2, size_t len);
/*
* Cross-platform version of `strcasecmp()`.
*/
int mg_casecmp(const char *s1, const char *s2);
/*
* Cross-platform version of `strcmp()` where where first string is
* specified by `struct mg_str`.
*/
int mg_vcmp(const struct mg_str *str2, const char *str1);
/*
* Cross-platform version of `strncasecmp()` where first string is
* specified by `struct mg_str`.
*/
int mg_vcasecmp(const struct mg_str *str2, const char *str1);
/*
* Decode base64-encoded string `s`, `len` into the destination `dst`.
* Destination has to have enough space to hold decoded buffer.
* Decoding stops either when all string has been decoded, or invalid
* character appeared.
* Destination is '\0'-terminated.
* Return number of decoded characters. On success, that should be equal to
* `len`. On error (invalid character) the return value is smaller then `len`.
*/
int mg_base64_decode(const unsigned char *s, int len, char *dst);
/*
* Base64-encode chunk of memory `src`, `src_len` into the destination `dst`.
* Destination has to have enough space to hold encoded buffer.
* Destination is '\0'-terminated.
*/
void mg_base64_encode(const unsigned char *src, int src_len, char *dst);
#ifndef NS_DISABLE_FILESYSTEM
/*
* Perform a 64-bit `stat()` call against given file.
*
* `path` should be UTF8 encoded.
*
* Return value is the same as for `stat()` syscall.
*/
int mg_stat(const char *path, cs_stat_t *st);
/*
* Open the given file and return a file stream.
*
* `path` and `mode` should be UTF8 encoded.
*
* Return value is the same as for the `fopen()` call.
*/
FILE *mg_fopen(const char *path, const char *mode);
/*
* Open the given file and return a file stream.
*
* `path` should be UTF8 encoded.
*
* Return value is the same as for the `open()` syscall.
*/
int mg_open(const char *path, int flag, int mode);
#endif /* NS_DISABLE_FILESYSTEM */
#ifdef NS_ENABLE_THREADS
/*
* Start a new detached thread.
* Arguments and semantic is the same as pthead's `pthread_create()`.
* `thread_func` is a thread function, `thread_func_param` is a parameter
* that is passed to the thread function.
*/
void *mg_start_thread(void *(*thread_func)(void *), void *thread_func_param);
#endif
void mg_set_close_on_exec(sock_t);
#define NS_SOCK_STRINGIFY_IP 1
#define NS_SOCK_STRINGIFY_PORT 2
#define NS_SOCK_STRINGIFY_REMOTE 4
/*
* Convert socket's local or remote address into string.
*
* The `flags` parameter is a bit mask that controls the behavior,
* see `NS_SOCK_STRINGIFY_*` definitions.
*
* - NS_SOCK_STRINGIFY_IP - print IP address
* - NS_SOCK_STRINGIFY_PORT - print port number
* - NS_SOCK_STRINGIFY_REMOTE - print remote peer's IP/port, not local address
*
* If both port number and IP address are printed, they are separated by `:`.
* If compiled with `-DNS_ENABLE_IPV6`, IPv6 addresses are supported.
*/
void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags);
/*
* Convert socket's address into string.
*
* `flags` is NS_SOCK_STRINGIFY_IP and/or NS_SOCK_STRINGIFY_PORT.
*/
void mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
int flags);
/*
* Generates human-readable hexdump of memory chunk.
*
* Takes a memory buffer `buf` of length `len` and creates a hex dump of that
* buffer in `dst`. Generated output is a-la hexdump(1).
* Return length of generated string, excluding terminating `\0`. If returned
* length is bigger than `dst_len`, overflow bytes are discarded.
*/
int mg_hexdump(const void *buf, int len, char *dst, int dst_len);
/*
* Generates human-readable hexdump of the data sent or received by connection.
* `path` is a file name where hexdump should be written. `num_bytes` is
* a number of bytes sent/received. `ev` is one of the `NS_*` events sent to
* an event handler. This function is supposed to be called from the
* event handler.
*/
void mg_hexdump_connection(struct mg_connection *nc, const char *path,
int num_bytes, int ev);
/*
* Print message to buffer. If buffer is large enough to hold the message,
* return buffer. If buffer is to small, allocate large enough buffer on heap,
* and return allocated buffer.
* This is a supposed use case:
*
* char buf[5], *p = buf;
* p = mg_avprintf(&p, sizeof(buf), "%s", "hi there");
* use_p_somehow(p);
* if (p != buf) {
* free(p);
* }
*
* The purpose of this is to avoid malloc-ing if generated strings are small.
*/
int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
/*
* Return true if target platform is big endian.
*/
int mg_is_big_endian(void);
/*
* A helper function for traversing a comma separated list of values.
* It returns a list pointer shifted to the next value, or NULL if the end
* of the list found.
* Value is stored in val vector. If value has form "x=y", then eq_val
* vector is initialized to point to the "y" part, and val vector length
* is adjusted to point only to "x".
* If list is just a comma separated list of entries, like "aa,bb,cc" then
* `eq_val` will contain zero-length string.
*
* The purpose of this function is to parse comma separated string without
* any copying/memory allocation.
*/
const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
struct mg_str *eq_val);
/*
* Match 0-terminated string against a glob pattern.
* Match is case-insensitive. Return number of bytes matched, or -1 if no match.
*/
int mg_match_prefix(const char *pattern, int pattern_len, const char *str);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_UTIL_HEADER_DEFINED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* === HTTP + Websocket
*/
#ifndef NS_HTTP_HEADER_DEFINED
#define NS_HTTP_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifndef NS_MAX_HTTP_HEADERS
#define NS_MAX_HTTP_HEADERS 40
#endif
#ifndef NS_MAX_HTTP_REQUEST_SIZE
#define NS_MAX_HTTP_REQUEST_SIZE 8192
#endif
#ifndef NS_MAX_PATH
#define NS_MAX_PATH 1024
#endif
#ifndef NS_MAX_HTTP_SEND_IOBUF
#define NS_MAX_HTTP_SEND_IOBUF 4096
#endif
#ifndef NS_WEBSOCKET_PING_INTERVAL_SECONDS
#define NS_WEBSOCKET_PING_INTERVAL_SECONDS 5
#endif
#ifndef NS_CGI_ENVIRONMENT_SIZE
#define NS_CGI_ENVIRONMENT_SIZE 8192
#endif
#ifndef NS_MAX_CGI_ENVIR_VARS
#define NS_MAX_CGI_ENVIR_VARS 64
#endif
#ifndef NS_ENV_EXPORT_TO_CGI
#define NS_ENV_EXPORT_TO_CGI "FOSSA_CGI"
#endif
/* HTTP message */
struct http_message {
struct mg_str message; /* Whole message: request line + headers + body */
struct mg_str proto; /* "HTTP/1.1" -- for both request and response */
/* HTTP Request line (or HTTP response line) */
struct mg_str method; /* "GET" */
struct mg_str uri; /* "/my_file.html" */
/* For responses, code and response status message are set */
int resp_code;
struct mg_str resp_status_msg;
/*
* Query-string part of the URI. For example, for HTTP request
* GET /foo/bar?param1=val1&param2=val2
* | uri | query_string |
*
* Note that question mark character doesn't belong neither to the uri,
* nor to the query_string
*/
struct mg_str query_string;
/* Headers */
struct mg_str header_names[NS_MAX_HTTP_HEADERS];
struct mg_str header_values[NS_MAX_HTTP_HEADERS];
/* Message body */
struct mg_str body; /* Zero-length for requests with no body */
};
struct websocket_message {
unsigned char *data;
size_t size;
unsigned char flags;
};
/* HTTP and websocket events. void *ev_data is described in a comment. */
#define NS_HTTP_REQUEST 100 /* struct http_message * */
#define NS_HTTP_REPLY 101 /* struct http_message * */
#define NS_HTTP_CHUNK 102 /* struct http_message * */
#define NS_SSI_CALL 105 /* char * */
#define NS_WEBSOCKET_HANDSHAKE_REQUEST 111 /* NULL */
#define NS_WEBSOCKET_HANDSHAKE_DONE 112 /* NULL */
#define NS_WEBSOCKET_FRAME 113 /* struct websocket_message * */
#define NS_WEBSOCKET_CONTROL_FRAME 114 /* struct websocket_message * */
/*
* Attach built-in HTTP event handler to the given connection.
* User-defined event handler will receive following extra events:
*
* - NS_HTTP_REQUEST: HTTP request has arrived. Parsed HTTP request is passed as
* `struct http_message` through the handler's `void *ev_data` pointer.
* - NS_HTTP_REPLY: HTTP reply has arrived. Parsed HTTP reply is passed as
* `struct http_message` through the handler's `void *ev_data` pointer.
* - NS_HTTP_CHUNK: HTTP chunked-encoding chunk has arrived.
* Parsed HTTP reply is passed as `struct http_message` through the
* handler's `void *ev_data` pointer. `http_message::body` would contain
* incomplete, reassembled HTTP body.
* It will grow with every new chunk arrived, and
* potentially can consume a lot of memory. An event handler may process
* the body as chunks are coming, and signal Mongoose to delete processed
* body by setting `NSF_DELETE_CHUNK` in `mg_connection::flags`. When
* the last zero chunk is received, Mongoose sends `NS_HTTP_REPLY` event will
* full reassembled body (if handler did not signal to delete chunks) or
* with empty body (if handler did signal to delete chunks).
* - NS_WEBSOCKET_HANDSHAKE_REQUEST: server has received websocket handshake
* request. `ev_data` contains parsed HTTP request.
* - NS_WEBSOCKET_HANDSHAKE_DONE: server has completed Websocket handshake.
* `ev_data` is `NULL`.
* - NS_WEBSOCKET_FRAME: new websocket frame has arrived. `ev_data` is
* `struct websocket_message *`
*/
void mg_set_protocol_http_websocket(struct mg_connection *nc);
/*
* Send websocket handshake to the server.
*
* `nc` must be a valid connection, connected to a server. `uri` is an URI
* to fetch, extra_headers` is extra HTTP headers to send or `NULL`.
*
* This function is intended to be used by websocket client.
*/
void mg_send_websocket_handshake(struct mg_connection *nc, const char *uri,
const char *extra_headers);
/*
* Send websocket frame to the remote end.
*
* `op` specifies frame's type , one of:
*
* - WEBSOCKET_OP_CONTINUE
* - WEBSOCKET_OP_TEXT
* - WEBSOCKET_OP_BINARY
* - WEBSOCKET_OP_CLOSE
* - WEBSOCKET_OP_PING
* - WEBSOCKET_OP_PONG
* `data` and `data_len` contain frame data.
*/
void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data,
size_t data_len);
/*
* Send multiple websocket frames.
*
* Like `mg_send_websocket_frame()`, but composes a frame from multiple buffers.
*/
void mg_send_websocket_framev(struct mg_connection *nc, int op,
const struct mg_str *strings, int num_strings);
/*
* Send websocket frame to the remote end.
*
* Like `mg_send_websocket_frame()`, but allows to create formatted message
* with `printf()`-like semantics.
*/
void mg_printf_websocket_frame(struct mg_connection *nc, int op,
const char *fmt, ...); const char *fmt, ...);
void mg_send_file(struct mg_connection *, const char *path, const char *); /*
void mg_send_file_data(struct mg_connection *, int fd); * Send buffer `buf` of size `len` to the client using chunked HTTP encoding.
* This function first sends buffer size as hex number + newline, then
* buffer itself, then newline. For example,
* `mg_send_http_chunk(nc, "foo", 3)` whill append `3\r\nfoo\r\n` string to
* the `nc->send_mbuf` output IO buffer.
*
* NOTE: HTTP header "Transfer-Encoding: chunked" should be sent prior to
* using this function.
*
* NOTE: do not forget to send empty chunk at the end of the response,
* to tell the client that everything was sent. Example:
*
* ```
* mg_printf_http_chunk(nc, "%s", "my response!");
* mg_send_http_chunk(nc, "", 0); // Tell the client we're finished
* ```
*/
void mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len);
/*
* Send printf-formatted HTTP chunk.
* Functionality is similar to `mg_send_http_chunk()`.
*/
void mg_printf_http_chunk(struct mg_connection *, const char *, ...);
/*
* Send printf-formatted HTTP chunk, escaping HTML tags.
*/
void mg_printf_html_escape(struct mg_connection *, const char *, ...);
/* Websocket opcodes, from http://tools.ietf.org/html/rfc6455 */
#define WEBSOCKET_OP_CONTINUE 0
#define WEBSOCKET_OP_TEXT 1
#define WEBSOCKET_OP_BINARY 2
#define WEBSOCKET_OP_CLOSE 8
#define WEBSOCKET_OP_PING 9
#define WEBSOCKET_OP_PONG 10
/*
* Parse a HTTP message.
*
* `is_req` should be set to 1 if parsing request, 0 if reply.
*
* Return number of bytes parsed. If HTTP message is
* incomplete, `0` is returned. On parse error, negative number is returned.
*/
int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req);
/*
* Search and return header `name` in parsed HTTP message `hm`.
* If header is not found, NULL is returned. Example:
*
* struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
*/
struct mg_str *mg_get_http_header(struct http_message *hm, const char *name);
/*
* Parse HTTP header `hdr`. Find variable `var_name` and store it's value
* in the buffer `buf`, `buf_size`. Return 0 if variable not found, non-zero
* otherwise.
*
* This function is supposed to parse
* cookies, authentication headers, etcetera. Example (error handling omitted):
*
* char user[20];
* struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
* mg_http_parse_header(hdr, "username", user, sizeof(user));
*
* Return length of the variable's value. If buffer is not large enough,
* or variable not found, 0 is returned.
*/
int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
size_t buf_size);
/*
* Parse buffer `buf`, `buf_len` that contains multipart form data chunks.
* Store chunk name in a `var_name`, `var_name_len` buffer.
* If a chunk is an uploaded file, then `file_name`, `file_name_len` is
* filled with an uploaded file name. `chunk`, `chunk_len`
* points to the chunk data.
*
* Return: number of bytes to skip to the next chunk, or 0 if there are
* no more chunks.
*
* Usage example:
*
* static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
* switch(ev) {
* case NS_HTTP_REQUEST: {
* struct http_message *hm = (struct http_message *) ev_data;
* char var_name[100], file_name[100];
* const char *chunk;
* size_t chunk_len, n1, n2;
*
* n1 = n2 = 0;
* while ((n2 = mg_parse_multipart(hm->body.p + n1,
* hm->body.len - n1,
* var_name, sizeof(var_name),
* file_name, sizeof(file_name),
* &chunk, &chunk_len)) > 0) {
* printf("var: %s, file_name: %s, size: %d, chunk: [%.*s]\n",
* var_name, file_name, (int) chunk_len,
* (int) chunk_len, chunk);
* n1 += n2;
* }
* }
* break;
*
*/
size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
size_t var_name_len, char *file_name,
size_t file_name_len, const char **chunk,
size_t *chunk_len);
/*
* Fetch an HTTP form variable.
*
* Fetch a variable `name` from a `buf` into a buffer specified by
* `dst`, `dst_len`. Destination is always zero-terminated. Return length
* of a fetched variable. If not found, 0 is returned. `buf` must be
* valid url-encoded buffer. If destination is too small, `-1` is returned.
*/
int mg_get_http_var(const struct mg_str *, const char *, char *dst, size_t);
/* Create Digest authentication header for client request. */
int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
const char *method, const char *uri,
const char *auth_domain, const char *user,
const char *passwd);
/*
* Helper function that creates outbound HTTP connection.
*
* `url` is a URL to fetch. It must be properly URL-encoded, e.g. have
* no spaces, etc. By default, `mg_connect_http()` sends Connection and
* Host headers. `extra_headers` is an extra HTTP headers to send, e.g.
* `"User-Agent: my-app\r\n"`.
* If `post_data` is NULL, then GET request is created. Otherwise, POST request
* is created with the specified POST data. Examples:
*
* [source,c]
* ----
* nc1 = mg_connect_http(mgr, ev_handler_1, "http://www.google.com", NULL,
* NULL);
* nc2 = mg_connect_http(mgr, ev_handler_1, "https://github.com", NULL, NULL);
* nc3 = mg_connect_http(mgr, ev_handler_1, "my_server:8000/form_submit/",
* NULL, "var_1=value_1&var_2=value_2");
* ----
*/
struct mg_connection *mg_connect_http(struct mg_mgr *,
mg_event_handler_t event_handler,
const char *url,
const char *extra_headers,
const char *post_data);
/*
* This structure defines how `mg_serve_http()` works.
* Best practice is to set only required settings, and leave the rest as NULL.
*/
struct mg_serve_http_opts {
/* Path to web root directory */
const char *document_root;
/* List of index files. Default is "" */
const char *index_files;
/*
* Leave as NULL to disable authentication.
* To enable directory protection with authentication, set this to ".htpasswd"
* Then, creating ".htpasswd" file in any directory automatically protects
* it with digest authentication.
* Use `mongoose` web server binary, or `htdigest` Apache utility to
* create/manipulate passwords file.
* Make sure `auth_domain` is set to a valid domain name.
*/
const char *per_directory_auth_file;
/* Authorization domain (domain name of this web server) */
const char *auth_domain;
/*
* Leave as NULL to disable authentication.
* Normally, only selected directories in the document root are protected.
* If absolutely every access to the web server needs to be authenticated,
* regardless of the URI, set this option to the path to the passwords file.
* Format of that file is the same as ".htpasswd" file. Make sure that file
* is located outside document root to prevent people fetching it.
*/
const char *global_auth_file;
/* Set to "no" to disable directory listing. Enabled by default. */
const char *enable_directory_listing;
/* SSI files pattern. If not set, "**.shtml$|**.shtm$" is used. */
const char *ssi_pattern;
/* IP ACL. By default, NULL, meaning all IPs are allowed to connect */
const char *ip_acl;
/* URL rewrites.
*
* Comma-separated list of `uri_pattern=file_or_directory_path` rewrites.
* When HTTP request is received, Mongoose constructs a file name from the
* requested URI by combining `document_root` and the URI. However, if the
* rewrite option is used and `uri_pattern` matches requested URI, then
* `document_root` is ignored. Instead, `file_or_directory_path` is used,
* which should be a full path name or a path relative to the web server's
* current working directory. Note that `uri_pattern`, as all Mongoose
* patterns, is a prefix pattern.
*
* If uri_pattern starts with `@` symbol, then Mongoose compares it with the
* HOST header of the request. If they are equal, Mongoose sets document root
* to `file_or_directory_path`, implementing virtual hosts support.
*/
const char *url_rewrites;
/* DAV document root. If NULL, DAV requests are going to fail. */
const char *dav_document_root;
/* Glob pattern for the files to hide. */
const char *hidden_file_pattern;
/* Set to non-NULL to enable CGI, e.g. **.cgi$|**.php$" */
const char *cgi_file_pattern;
/* If not NULL, ignore CGI script hashbang and use this interpreter */
const char *cgi_interpreter;
/*
* Comma-separated list of Content-Type overrides for path suffixes, e.g.
* ".txt=text/plain; charset=utf-8,.c=text/plain"
*/
const char *custom_mime_types;
};
/*
* Serve given HTTP request according to the `options`.
*
* Example code snippet:
*
* [source,c]
* .web_server.c
* ----
* static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
* struct http_message *hm = (struct http_message *) ev_data;
* struct mg_serve_http_opts opts = { .document_root = "/var/www" }; // C99
*
* switch (ev) {
* case NS_HTTP_REQUEST:
* mg_serve_http(nc, hm, opts);
* break;
* default:
* break;
* }
* }
* ----
*/
void mg_serve_http(struct mg_connection *, struct http_message *,
struct mg_serve_http_opts);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_HTTP_HEADER_DEFINED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* === JSON-RPC
*/
#ifndef NS_JSON_RPC_HEADER_DEFINED
#define NS_JSON_RPC_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* JSON-RPC request */
struct mg_rpc_request {
struct json_token *message; /* Whole RPC message */
struct json_token *id; /* Message ID */
struct json_token *method; /* Method name */
struct json_token *params; /* Method params */
};
/* JSON-RPC response */
struct mg_rpc_reply {
struct json_token *message; /* Whole RPC message */
struct json_token *id; /* Message ID */
struct json_token *result; /* Remote call result */
};
/* JSON-RPC error */
struct mg_rpc_error {
struct json_token *message; /* Whole RPC message */
struct json_token *id; /* Message ID */
struct json_token *error_code; /* error.code */
struct json_token *error_message; /* error.message */
struct json_token *error_data; /* error.data, can be NULL */
};
/*
* Parse JSON-RPC reply contained in `buf`, `len` into JSON tokens array
* `toks`, `max_toks`. If buffer contains valid reply, `reply` structure is
* populated. The result of RPC call is located in `reply.result`. On error,
* `error` structure is populated. Returns: the result of calling
* `parse_json(buf, len, toks, max_toks)`:
*
* On success, an offset inside `json_string` is returned
* where parsing has finished. On failure, a negative number is
* returned, one of:
*
* - #define JSON_STRING_INVALID -1
* - #define JSON_STRING_INCOMPLETE -2
* - #define JSON_TOKEN_ARRAY_TOO_SMALL -3
*/
int mg_rpc_parse_reply(const char *buf, int len, struct json_token *toks,
int max_toks, struct mg_rpc_reply *,
struct mg_rpc_error *);
/*
* Create JSON-RPC request in a given buffer.
*
* Return length of the request, which
* can be larger then `len` that indicates an overflow.
* `params_fmt` format string should conform to `json_emit()` API,
* see https://github.com/cesanta/frozen
*/
int mg_rpc_create_request(char *buf, int len, const char *method,
const char *id, const char *params_fmt, ...);
/*
* Create JSON-RPC reply in a given buffer.
*
* Return length of the reply, which
* can be larger then `len` that indicates an overflow.
* `result_fmt` format string should conform to `json_emit()` API,
* see https://github.com/cesanta/frozen
*/
int mg_rpc_create_reply(char *buf, int len, const struct mg_rpc_request *req,
const char *result_fmt, ...);
/*
* Create JSON-RPC error reply in a given buffer.
*
* Return length of the error, which
* can be larger then `len` that indicates an overflow.
* `fmt` format string should conform to `json_emit()` API,
* see https://github.com/cesanta/frozen
*/
int mg_rpc_create_error(char *buf, int len, struct mg_rpc_request *req,
int code, const char *message, const char *fmt, ...);
/* JSON-RPC standard error codes */
#define JSON_RPC_PARSE_ERROR (-32700)
#define JSON_RPC_INVALID_REQUEST_ERROR (-32600)
#define JSON_RPC_METHOD_NOT_FOUND_ERROR (-32601)
#define JSON_RPC_INVALID_PARAMS_ERROR (-32602)
#define JSON_RPC_INTERNAL_ERROR (-32603)
#define JSON_RPC_SERVER_ERROR (-32000)
/*
* Create JSON-RPC error in a given buffer.
*
* Return length of the error, which
* can be larger then `len` that indicates an overflow. See
* JSON_RPC_*_ERROR definitions for standard error values:
*
* - #define JSON_RPC_PARSE_ERROR (-32700)
* - #define JSON_RPC_INVALID_REQUEST_ERROR (-32600)
* - #define JSON_RPC_METHOD_NOT_FOUND_ERROR (-32601)
* - #define JSON_RPC_INVALID_PARAMS_ERROR (-32602)
* - #define JSON_RPC_INTERNAL_ERROR (-32603)
* - #define JSON_RPC_SERVER_ERROR (-32000)
*/
int mg_rpc_create_std_error(char *, int, struct mg_rpc_request *, int code);
typedef int (*mg_rpc_handler_t)(char *buf, int len, struct mg_rpc_request *);
/*
* Dispatches a JSON-RPC request.
*
* Parses JSON-RPC request contained in `buf`, `len`.
* Then, dispatches the request to the correct handler method.
* Valid method names should be specified in NULL
* terminated array `methods`, and corresponding handlers in `handlers`.
* Result is put in `dst`, `dst_len`. Return: length of the result, which
* can be larger then `dst_len` that indicates an overflow.
* Overflown bytes are not written to the buffer.
* If method is not found, an error is automatically generated.
*/
int mg_rpc_dispatch(const char *buf, int, char *dst, int dst_len,
const char **methods, mg_rpc_handler_t *handlers);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_JSON_RPC_HEADER_DEFINED */
/*
* 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>.
*/
/*
* === MQTT
*/
#ifndef NS_MQTT_HEADER_INCLUDED
#define NS_MQTT_HEADER_INCLUDED
struct mg_mqtt_message {
int cmd;
struct mg_str payload;
int qos;
uint8_t connack_ret_code; /* connack */
uint16_t message_id; /* puback */
char *topic;
};
struct mg_mqtt_topic_expression {
const char *topic;
uint8_t qos;
};
struct mg_send_mqtt_handshake_opts {
unsigned char flags; /* connection flags */
uint16_t keep_alive;
const char *will_topic;
const char *will_message;
const char *user_name;
const char *password;
};
/* Message types */
#define NS_MQTT_CMD_CONNECT 1
#define NS_MQTT_CMD_CONNACK 2
#define NS_MQTT_CMD_PUBLISH 3
#define NS_MQTT_CMD_PUBACK 4
#define NS_MQTT_CMD_PUBREC 5
#define NS_MQTT_CMD_PUBREL 6
#define NS_MQTT_CMD_PUBCOMP 7
#define NS_MQTT_CMD_SUBSCRIBE 8
#define NS_MQTT_CMD_SUBACK 9
#define NS_MQTT_CMD_UNSUBSCRIBE 10
#define NS_MQTT_CMD_UNSUBACK 11
#define NS_MQTT_CMD_PINGREQ 12
#define NS_MQTT_CMD_PINGRESP 13
#define NS_MQTT_CMD_DISCONNECT 14
/* MQTT event types */
#define NS_MQTT_EVENT_BASE 200
#define NS_MQTT_CONNECT (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_CONNECT)
#define NS_MQTT_CONNACK (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_CONNACK)
#define NS_MQTT_PUBLISH (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PUBLISH)
#define NS_MQTT_PUBACK (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PUBACK)
#define NS_MQTT_PUBREC (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PUBREC)
#define NS_MQTT_PUBREL (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PUBREL)
#define NS_MQTT_PUBCOMP (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PUBCOMP)
#define NS_MQTT_SUBSCRIBE (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_SUBSCRIBE)
#define NS_MQTT_SUBACK (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_SUBACK)
#define NS_MQTT_UNSUBSCRIBE (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_UNSUBSCRIBE)
#define NS_MQTT_UNSUBACK (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_UNSUBACK)
#define NS_MQTT_PINGREQ (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PINGREQ)
#define NS_MQTT_PINGRESP (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_PINGRESP)
#define NS_MQTT_DISCONNECT (NS_MQTT_EVENT_BASE + NS_MQTT_CMD_DISCONNECT)
/* Message flags */
#define NS_MQTT_RETAIN 0x1
#define NS_MQTT_DUP 0x4
#define NS_MQTT_QOS(qos) ((qos) << 1)
#define NS_MQTT_GET_QOS(flags) (((flags) &0x6) >> 1)
#define NS_MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1)
/* Connection flags */
#define NS_MQTT_CLEAN_SESSION 0x02
#define NS_MQTT_HAS_WILL 0x04
#define NS_MQTT_WILL_RETAIN 0x20
#define NS_MQTT_HAS_PASSWORD 0x40
#define NS_MQTT_HAS_USER_NAME 0x80
#define NS_MQTT_GET_WILL_QOS(flags) (((flags) &0x18) >> 3)
#define NS_MQTT_SET_WILL_QOS(flags, qos) \
(flags) = ((flags) & ~0x18) | ((qos) << 3)
/* CONNACK return codes */
#define NS_MQTT_CONNACK_ACCEPTED 0
#define NS_MQTT_CONNACK_UNACCEPTABLE_VERSION 1
#define NS_MQTT_CONNACK_IDENTIFIER_REJECTED 2
#define NS_MQTT_CONNACK_SERVER_UNAVAILABLE 3
#define NS_MQTT_CONNACK_BAD_AUTH 4
#define NS_MQTT_CONNACK_NOT_AUTHORIZED 5
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*
* Attach built-in MQTT event handler to the given connection.
*
* The user-defined event handler will receive following extra events:
*
* - NS_MQTT_CONNACK
* - NS_MQTT_PUBLISH
* - NS_MQTT_PUBACK
* - NS_MQTT_PUBREC
* - NS_MQTT_PUBREL
* - NS_MQTT_PUBCOMP
* - NS_MQTT_SUBACK
*/
void mg_set_protocol_mqtt(struct mg_connection *);
/* Send MQTT handshake. */
void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id);
/* Send MQTT handshake with optional parameters. */
void mg_send_mqtt_handshake_opt(struct mg_connection *, const char *client_id,
struct mg_send_mqtt_handshake_opts);
/* Publish a message to a given topic. */
void mg_mqtt_publish(struct mg_connection *nc, const char *topic,
uint16_t message_id, int flags, const void *data,
size_t len);
/* Subscribe to a bunch of topics. */
void mg_mqtt_subscribe(struct mg_connection *nc,
const struct mg_mqtt_topic_expression *topics,
size_t topics_len, uint16_t message_id);
/* Unsubscribe from a bunch of topics. */
void mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,
size_t topics_len, uint16_t message_id);
/* Send a DISCONNECT command. */
void mg_mqtt_disconnect(struct mg_connection *nc);
/* Send a CONNACK command with a given `return_code`. */
void mg_mqtt_connack(struct mg_connection *, uint8_t);
/* Send a PUBACK command with a given `message_id`. */
void mg_mqtt_puback(struct mg_connection *, uint16_t);
/* Send a PUBREC command with a given `message_id`. */
void mg_mqtt_pubrec(struct mg_connection *, uint16_t);
/* Send a PUBREL command with a given `message_id`. */
void mg_mqtt_pubrel(struct mg_connection *, uint16_t);
/* Send a PUBCOMP command with a given `message_id`. */
void mg_mqtt_pubcomp(struct mg_connection *, uint16_t);
/*
* Send a SUBACK command with a given `message_id`
* and a sequence of granted QoSs.
*/
void mg_mqtt_suback(struct mg_connection *, uint8_t *, size_t, uint16_t);
/* Send a UNSUBACK command with a given `message_id`. */
void mg_mqtt_unsuback(struct mg_connection *, uint16_t);
/* Send a PINGREQ command. */
void mg_mqtt_ping(struct mg_connection *);
/* Send a PINGRESP command. */
void mg_mqtt_pong(struct mg_connection *);
/*
* Extract the next topic expression from a SUBSCRIBE command payload.
*
* Topic expression name will point to a string in the payload buffer.
* Return the pos of the next topic expression or -1 when the list
* of topics is exhausted.
*/
int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *, struct mg_str *,
uint8_t *, int);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_MQTT_HEADER_INCLUDED */
/*
* 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>.
*/
/*
* === MQTT Broker
*/
const char *mg_get_header(const struct mg_connection *, const char *name); #ifndef NS_MQTT_BROKER_HEADER_INCLUDED
const char *mg_get_mime_type(const char *name, const char *default_mime_type); #define NS_MQTT_BROKER_HEADER_INCLUDED
int mg_get_var(const struct mg_connection *conn, const char *var_name,
char *buf, size_t buf_len);
int mg_get_var_n(const struct mg_connection *conn, const char *var_name,
char *buf, size_t buf_len, int n);
int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t);
int mg_parse_multipart(const char *buf, int buf_len,
char *var_name, int var_name_len,
char *file_name, int file_name_len,
const char **data, int *data_len);
#ifdef NS_ENABLE_MQTT_BROKER
// Utility functions
void *mg_start_thread(void *(*func)(void *), void *param);
char *mg_md5(char buf[33], ...);
int mg_authorize_digest(struct mg_connection *c, FILE *fp);
size_t mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len);
int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, int);
int mg_terminate_ssl(struct mg_connection *c, const char *cert);
int mg_forward(struct mg_connection *c, const char *addr);
void *mg_mmap(FILE *fp, size_t size);
void mg_munmap(void *p, size_t size);
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
// Templates support #define NS_MQTT_MAX_SESSION_SUBSCRIPTIONS 512;
struct mg_expansion {
const char *keyword; struct mg_mqtt_broker;
void (*handler)(struct mg_connection *);
/* MQTT session (Broker side). */
struct mg_mqtt_session {
struct mg_mqtt_broker *brk; /* Broker */
struct mg_mqtt_session *next, *prev; /* mg_mqtt_broker::sessions linkage */
struct mg_connection *nc; /* Connection with the client */
size_t num_subscriptions; /* Size of `subscriptions` array */
struct mg_mqtt_topic_expression *subscriptions;
void *user_data; /* User data */
}; };
void mg_template(struct mg_connection *, const char *text,
struct mg_expansion *expansions); /* MQTT broker. */
struct mg_mqtt_broker {
struct mg_mqtt_session *sessions; /* Session list */
void *user_data; /* User data */
};
/* Initialize a MQTT broker. */
void mg_mqtt_broker_init(struct mg_mqtt_broker *, void *);
/*
* Process a MQTT broker message.
*
* Listening connection expects a pointer to an initialized `mg_mqtt_broker`
* structure in the `user_data` field.
*
* Basic usage:
*
* [source,c]
* -----
* mg_mqtt_broker_init(&brk, NULL);
*
* if ((nc = mg_bind(&mgr, address, mg_mqtt_broker)) == NULL) {
* // fail;
* }
* nc->user_data = &brk;
* -----
*
* New incoming connections will receive a `mg_mqtt_session` structure
* in the connection `user_data`. The original `user_data` will be stored
* in the `user_data` field of the session structure. This allows the user
* handler to store user data before `mg_mqtt_broker` creates the session.
*
* Since only the NS_ACCEPT message is processed by the listening socket,
* for most events the `user_data` will thus point to a `mg_mqtt_session`.
*/
void mg_mqtt_broker(struct mg_connection *, int, void *);
/*
* Iterate over all mqtt sessions connections. Example:
*
* struct mg_mqtt_session *s;
* for (s = mg_mqtt_next(brk, NULL); s != NULL; s = mg_mqtt_next(brk, s)) {
* // Do something
* }
*/
struct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *,
struct mg_mqtt_session *);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif // __cplusplus #endif /* __cplusplus */
#endif /* NS_ENABLE_MQTT_BROKER */
#endif /* NS_MQTT_HEADER_INCLUDED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* === DNS
*/
#ifndef NS_DNS_HEADER_DEFINED
#define NS_DNS_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define NS_DNS_A_RECORD 0x01 /* Lookup IP address */
#define NS_DNS_CNAME_RECORD 0x05 /* Lookup CNAME */
#define NS_DNS_AAAA_RECORD 0x1c /* Lookup IPv6 address */
#define NS_DNS_MX_RECORD 0x0f /* Lookup mail server for domain */
#define NS_MAX_DNS_QUESTIONS 32
#define NS_MAX_DNS_ANSWERS 32
#define NS_DNS_MESSAGE 100 /* High-level DNS message event */
enum mg_dmg_resource_record_kind {
NS_DNS_INVALID_RECORD = 0,
NS_DNS_QUESTION,
NS_DNS_ANSWER
};
/* DNS resource record. */
struct mg_dmg_resource_record {
struct mg_str name; /* buffer with compressed name */
int rtype;
int rclass;
int ttl;
enum mg_dmg_resource_record_kind kind;
struct mg_str rdata; /* protocol data (can be a compressed name) */
};
/* DNS message (request and response). */
struct mg_dmg_message {
struct mg_str pkt; /* packet body */
uint16_t flags;
uint16_t transaction_id;
int num_questions;
int num_answers;
struct mg_dmg_resource_record questions[NS_MAX_DNS_QUESTIONS];
struct mg_dmg_resource_record answers[NS_MAX_DNS_ANSWERS];
};
struct mg_dmg_resource_record *mg_dmg_next_record(
struct mg_dmg_message *, int, struct mg_dmg_resource_record *);
/*
* Parse the record data from a DNS resource record.
*
* - A: struct in_addr *ina
* - AAAA: struct in6_addr *ina
* - CNAME: char buffer
*
* Returns -1 on error.
*
* TODO(mkm): MX
*/
int mg_dmg_parse_record_data(struct mg_dmg_message *,
struct mg_dmg_resource_record *, void *, size_t);
/*
* Send a DNS query to the remote end.
*/
void mg_send_dmg_query(struct mg_connection *, const char *, int);
/*
* Insert a DNS header to an IO buffer.
*
* Return number of bytes inserted.
*/
int mg_dmg_insert_header(struct mbuf *, size_t, struct mg_dmg_message *);
/*
* Append already encoded body from an existing message.
*
* This is useful when generating a DNS reply message which includes
* all question records.
*
* Return number of appened bytes.
*/
int mg_dmg_copy_body(struct mbuf *, struct mg_dmg_message *);
/*
* Encode and append a DNS resource record to an IO buffer.
*
* The record metadata is taken from the `rr` parameter, while the name and data
* are taken from the parameters, encoded in the appropriate format depending on
* record type, and stored in the IO buffer. The encoded values might contain
* offsets within the IO buffer. It's thus important that the IO buffer doesn't
* get trimmed while a sequence of records are encoded while preparing a DNS
*reply.
*
* This function doesn't update the `name` and `rdata` pointers in the `rr`
*struct
* because they might be invalidated as soon as the IO buffer grows again.
*
* Return the number of bytes appened or -1 in case of error.
*/
int mg_dmg_encode_record(struct mbuf *, struct mg_dmg_resource_record *,
const char *, size_t, const void *, size_t);
/* Low-level: parses a DNS response. */
int mg_parse_dns(const char *, int, struct mg_dmg_message *);
/*
* Uncompress a DNS compressed name.
*
* The containing dns message is required because the compressed encoding
* and reference suffixes present elsewhere in the packet.
*
* If name is less than `dst_len` characters long, the remainder
* of `dst` is terminated with `\0' characters. Otherwise, `dst` is not
*terminated.
*
* If `dst_len` is 0 `dst` can be NULL.
* Return the uncompressed name length.
*/
size_t mg_dmg_uncompress_name(struct mg_dmg_message *, struct mg_str *, char *,
int);
/*
* Attach built-in DNS event handler to the given listening connection.
*
* DNS event handler parses incoming UDP packets, treating them as DNS
* requests. If incoming packet gets successfully parsed by the DNS event
* handler, a user event handler will receive `NS_DNS_REQUEST` event, with
* `ev_data` pointing to the parsed `struct mg_dmg_message`.
*
* See
* https://github.com/cesanta/mongoose/tree/master/examples/captive_dmg_server[captive_dmg_server]
* example on how to handle DNS request and send DNS reply.
*/
void mg_set_protocol_dns(struct mg_connection *);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_HTTP_HEADER_DEFINED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* === DNS server
*
* Disabled by default; enable with `-DNS_ENABLE_DNS_SERVER`.
*/
#ifndef NS_DNS_SERVER_HEADER_DEFINED
#define NS_DNS_SERVER_HEADER_DEFINED
#ifdef NS_ENABLE_DNS_SERVER
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define NS_DNS_SERVER_DEFAULT_TTL 3600
struct mg_dmg_reply {
struct mg_dmg_message *msg;
struct mbuf *io;
size_t start;
};
/*
* Create a DNS reply.
*
* The reply will be based on an existing query message `msg`.
* The query body will be appended to the output buffer.
* "reply + recursion allowed" will be added to the message flags and
* message's num_answers will be set to 0.
*
* Answer records can be appended with `mg_dmg_send_reply` or by lower
* level function defined in the DNS API.
*
* In order to send the reply use `mg_dmg_send_reply`.
* It's possible to use a connection's send buffer as reply buffers,
* and it will work for both UDP and TCP connections.
*
* Example:
*
* [source,c]
* -----
* 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, 3600, &dummy_ip_addr, 4);
* }
* }
* mg_dmg_send_reply(nc, &reply);
* -----
*/
struct mg_dmg_reply mg_dmg_create_reply(struct mbuf *, struct mg_dmg_message *);
/*
* Append a DNS reply record to the IO buffer and to the DNS message.
*
* The message num_answers field will be incremented. It's caller's duty
* to ensure num_answers is propertly initialized.
*
* Returns -1 on error.
*/
int mg_dmg_reply_record(struct mg_dmg_reply *, struct mg_dmg_resource_record *,
const char *, int, int, const void *, size_t);
/*
* Send a DNS reply through a connection.
*
* The DNS data is stored in an IO buffer pointed by reply structure in `r`.
* This function mutates the content of that buffer in order to ensure that
* the DNS header reflects size and flags of the mssage, that might have been
* updated either with `mg_dmg_reply_record` or by direct manipulation of
* `r->message`.
*
* Once sent, the IO buffer will be trimmed unless the reply IO buffer
* is the connection's send buffer and the connection is not in UDP mode.
*/
int mg_dmg_send_reply(struct mg_connection *, struct mg_dmg_reply *);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_ENABLE_DNS_SERVER */
#endif /* NS_HTTP_HEADER_DEFINED */
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* === Asynchronouns DNS resolver
*/
#ifndef NS_RESOLV_HEADER_DEFINED
#define NS_RESOLV_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef void (*mg_resolve_callback_t)(struct mg_dmg_message *, void *);
/* Options for `mg_resolve_async_opt`. */
struct mg_resolve_async_opts {
const char *nameserver_url;
int max_retries; /* defaults to 2 if zero */
int timeout; /* in seconds; defaults to 5 if zero */
int accept_literal; /* pseudo-resolve literal ipv4 and ipv6 addrs */
int only_literal; /* only resolves literal addrs; sync cb invocation */
};
/* See `mg_resolve_async_opt()` */
int mg_resolve_async(struct mg_mgr *, const char *, int, mg_resolve_callback_t,
void *data);
/*
* Resolved a DNS name asynchronously.
*
* Upon successful resolution, the user callback will be invoked
* with the full DNS response message and a pointer to the user's
* context `data`.
*
* In case of timeout while performing the resolution the callback
* will receive a NULL `msg`.
*
* The DNS answers can be extracted with `mg_next_record` and
* `mg_dmg_parse_record_data`:
*
* [source,c]
* ----
* struct in_addr ina;
* struct mg_dmg_resource_record *rr = mg_next_record(msg, NS_DNS_A_RECORD,
* NULL);
* mg_dmg_parse_record_data(msg, rr, &ina, sizeof(ina));
* ----
*/
int mg_resolve_async_opt(struct mg_mgr *, const char *, int,
mg_resolve_callback_t, void *data,
struct mg_resolve_async_opts opts);
/*
* Resolve a name from `/etc/hosts`.
*
* Returns 0 on success, -1 on failure.
*/
int mg_resolve_from_hosts_file(const char *host, union socket_address *usa);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_RESOLV_HEADER_DEFINED */
/*
* Copyright (c) 2015 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>.
*/
/*
* === CoAP
*
* CoAP message format:
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
* |Ver| T | TKL | Code | Message ID | Token (if any, TKL bytes) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
* | Options (if any) ... |1 1 1 1 1 1 1 1| Payload (if any) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
*/
#ifndef NS_COAP_HEADER_INCLUDED
#define NS_COAP_HEADER_INCLUDED
#ifdef NS_ENABLE_COAP
#define NS_COAP_MSG_TYPE_FIELD 0x2
#define NS_COAP_CODE_CLASS_FIELD 0x4
#define NS_COAP_CODE_DETAIL_FIELD 0x8
#define NS_COAP_MSG_ID_FIELD 0x10
#define NS_COAP_TOKEN_FIELD 0x20
#define NS_COAP_OPTIONS_FIELD 0x40
#define NS_COAP_PAYLOAD_FIELD 0x80
#define NS_COAP_ERROR 0x10000
#define NS_COAP_FORMAT_ERROR (NS_COAP_ERROR | 0x20000)
#define NS_COAP_IGNORE (NS_COAP_ERROR | 0x40000)
#define NS_COAP_NOT_ENOUGH_DATA (NS_COAP_ERROR | 0x80000)
#define NS_COAP_NETWORK_ERROR (NS_COAP_ERROR | 0x100000)
#define NS_COAP_MSG_CON 0
#define NS_COAP_MSG_NOC 1
#define NS_COAP_MSG_ACK 2
#define NS_COAP_MSG_RST 3
#define NS_COAP_MSG_MAX 3
#define NS_COAP_CODECLASS_REQUEST 0
#define NS_COAP_CODECLASS_RESP_OK 2
#define NS_COAP_CODECLASS_CLIENT_ERR 4
#define NS_COAP_CODECLASS_SRV_ERR 5
#define NS_COAP_EVENT_BASE 300
#define NS_COAP_CON (NS_COAP_EVENT_BASE + NS_COAP_MSG_CON)
#define NS_COAP_NOC (NS_COAP_EVENT_BASE + NS_COAP_MSG_NOC)
#define NS_COAP_ACK (NS_COAP_EVENT_BASE + NS_COAP_MSG_ACK)
#define NS_COAP_RST (NS_COAP_EVENT_BASE + NS_COAP_MSG_RST)
/*
* CoAP options.
* Use mg_coap_add_option and mg_coap_free_options
* for creation and destruction.
*/
struct mg_coap_option {
struct mg_coap_option *next;
uint32_t number;
struct mg_str value;
};
/* CoAP message. See RFC 7252 for details. */
struct mg_coap_message {
uint32_t flags;
uint8_t msg_type;
uint8_t code_class;
uint8_t code_detail;
uint16_t msg_id;
struct mg_str token;
struct mg_coap_option *options;
struct mg_str payload;
struct mg_coap_option *optiomg_tail;
};
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Set CoAP protocol handler - trigger CoAP specific events */
int mg_set_protocol_coap(struct mg_connection *nc);
/*
* Add new option to mg_coap_message structure.
* Returns pointer to the newly created option.
*/
struct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,
uint32_t number, char *value,
size_t len);
/*
* Free the memory allocated for options,
* if cm paramater doesn't contain any option does nothing.
*/
void mg_coap_free_options(struct mg_coap_message *cm);
/*
* Compose CoAP message from `mg_coap_message`
* and send it into `nc` connection.
* Return 0 on success. On error, it is a bitmask:
*
* - #define NS_COAP_ERROR 0x10000
* - #define NS_COAP_FORMAT_ERROR (NS_COAP_ERROR | 0x20000)
* - #define NS_COAP_IGNORE (NS_COAP_ERROR | 0x40000)
* - #define NS_COAP_NOT_ENOUGH_DATA (NS_COAP_ERROR | 0x80000)
* - #define NS_COAP_NETWORK_ERROR (NS_COAP_ERROR | 0x100000)
*/
uint32_t mg_coap_send_message(struct mg_connection *nc,
struct mg_coap_message *cm);
/*
* Compose CoAP acknowledgement from `mg_coap_message`
* and send it into `nc` connection.
* Return value: see `mg_coap_send_message()`
*/
uint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id);
/*
* Parse COAP message and fills mg_coap_message and returns cm->flags.
* This is a helper function.
*
* NOTE: usually CoAP work over UDP, so lack of data means format error,
* but in theory it is possible to use CoAP over TCP (according to RFC)
*
* The caller have to check results and treat COAP_NOT_ENOUGH_DATA according to
* underlying protocol:
*
* - in case of UDP COAP_NOT_ENOUGH_DATA means COAP_FORMAT_ERROR,
* - in case of TCP client can try to recieve more data
*
* Return value: see `mg_coap_send_message()`
*/
uint32_t mg_coap_parse(struct mbuf *io, struct mg_coap_message *cm);
/*
* Composes CoAP message from mg_coap_message structure.
* This is a helper function.
* Return value: see `mg_coap_send_message()`
*/
uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NS_ENABLE_COAP */
#endif // MONGOOSE_HEADER_INCLUDED #endif /* NS_COAP_HEADER_INCLUDED */
# Copyright (c) 2014 Cesanta Software
# All rights reserved
PROG = unit_test
PROF_FLAGS = -fprofile-arcs -ftest-coverage -g -O0 -DGUI
CFLAGS = -W -Wall -pthread -I.. $(PROF_FLAGS) $(CFLAGS_EXTRA)
SOURCES = $(PROG).c
all: $(PROG)
./$(PROG)
gcov -b $(PROG).c | egrep '^(File|Lines)'
$(PROG): $(SOURCES) Makefile ../mongoose.c clean
$(CC) -o $(PROG) $(SOURCES) $(CFLAGS) -ldl -lssl
win:
wine cl $(SOURCES) /MD /nologo /DNDEBUG /O1 /Fe$(PROG).exe
wine $(PROG).exe
clean:
rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib *.gc*
// Unit test for the mongoose web server.
// g++ -W -Wall -pedantic -g unit_test.c -lssl && ./a.out
// cl unit_test.c /MD
#ifndef _WIN32
#define NS_ENABLE_IPV6
#define NS_ENABLE_SSL
#endif
#define MONGOOSE_POST_SIZE_LIMIT 999
// USE_* definitions must be made before #include "mongoose.c" !
#include "../mongoose.c"
#define FAIL(str, line) do { \
printf("Fail on line %d: [%s]\n", line, str); \
return str; \
} while (0)
#define ASSERT(expr) do { \
static_num_tests++; \
if (!(expr)) FAIL(#expr, __LINE__); \
} while (0)
#define RUN_TEST(test) do { const char *msg = test(); \
if (msg) return msg; } while (0)
#define HTTP_PORT "45772"
#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
static int static_num_tests = 0;
#if 0
// Connects to host:port, and sends formatted request to it. Returns
// malloc-ed reply and reply length, or NULL on error. Reply contains
// everything including headers, not just the message body.
static char *wget(const char *host, int port, int *len, const char *fmt, ...) {
char buf[2000], *reply = NULL;
int request_len, reply_size = 0, n, sock = -1;
struct sockaddr_in sin;
struct hostent *he = NULL;
va_list ap;
if (host != NULL &&
(he = gethostbyname(host)) != NULL &&
(sock = socket(PF_INET, SOCK_STREAM, 0)) != -1) {
sin.sin_family = AF_INET;
sin.sin_port = htons((uint16_t) port);
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) == 0) {
// Format and send the request.
va_start(ap, fmt);
request_len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
while (request_len > 0 && (n = send(sock, buf, request_len, 0)) > 0) {
request_len -= n;
}
if (request_len == 0) {
*len = 0;
while ((n = recv(sock, buf, sizeof(buf), 0)) > 0) {
if (*len + n > reply_size) {
// Leak possible
reply = (char *) realloc(reply, reply_size + sizeof(buf));
reply_size += sizeof(buf);
}
if (reply != NULL) {
memcpy(reply + *len, buf, n);
*len += n;
}
}
}
closesocket(sock);
}
}
return reply;
}
#endif
static char *read_file(const char *path, int *size) {
FILE *fp;
struct stat st;
char *data = NULL;
if ((fp = fopen(path, "rb")) != NULL && !fstat(fileno(fp), &st)) {
*size = (int) st.st_size;
data = (char *) malloc(*size);
fread(data, 1, *size, fp);
fclose(fp);
}
return data;
}
static const char *test_parse_http_message() {
struct mg_connection ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n";
char req5[] = "GET / HTTP/1.1\r\n\r\n";
char req6[] = "G";
char req7[] = " blah ";
char req8[] = " HTTP/1.1 200 OK \n\n";
char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
ASSERT(get_request_len("\r\n", 3) == -1);
ASSERT(get_request_len("\r\n", 2) == 0);
ASSERT(get_request_len("GET", 3) == 0);
ASSERT(get_request_len("\n\n", 2) == 2);
ASSERT(get_request_len("\n\r\n", 3) == 3);
ASSERT(get_request_len("\xdd\xdd", 2) == 0);
ASSERT(get_request_len("\xdd\x03", 2) == -1);
ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0);
ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0);
ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0);
ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1);
ASSERT(ri.num_headers == 1);
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 0);
ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == (size_t) ~0);
ASSERT(parse_http_message(req6, 0, &ri) == (size_t) ~0);
ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
// TODO(lsm): Fix this. Header value may span multiple lines.
ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 3);
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
ASSERT(strcmp(ri.http_headers[1].name, "B") == 0);
ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0);
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
ASSERT(strcmp(ri.request_method, "GET") == 0);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
return NULL;
}
static const char *test_should_keep_alive(void) {
struct mg_connection conn;
char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "GET / HTTP/1.0\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
memset(&conn, 0, sizeof(conn));
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &conn) == sizeof(req1) - 1);
ASSERT(should_keep_alive(&conn) != 0);
parse_http_message(req2, sizeof(req2) - 1, &conn);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_message(req3, sizeof(req3) - 1, &conn);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_message(req4, sizeof(req4) - 1, &conn);
ASSERT(should_keep_alive(&conn) != 0);
return NULL;
}
static const char *test_match_prefix(void) {
ASSERT(mg_match_prefix("/api", 4, "/api") == 4);
ASSERT(mg_match_prefix("/a/", 3, "/a/b/c") == 3);
ASSERT(mg_match_prefix("/a/", 3, "/ab/c") == -1);
ASSERT(mg_match_prefix("/blog/", 6, "/") == -1);
ASSERT(mg_match_prefix("/*/", 3, "/ab/c") == 4);
ASSERT(mg_match_prefix("**", 2, "/a/b/c") == 6);
ASSERT(mg_match_prefix("/*", 2, "/a/b/c") == 2);
ASSERT(mg_match_prefix("*/*", 3, "/a/b/c") == 2);
ASSERT(mg_match_prefix("**/", 3, "/a/b/c") == 5);
ASSERT(mg_match_prefix("**.foo|**.bar", 13, "a.bar") == 5);
ASSERT(mg_match_prefix("a|b|cd", 6, "cdef") == 2);
ASSERT(mg_match_prefix("a|b|c?", 6, "cdef") == 2);
ASSERT(mg_match_prefix("a|?|cd", 6, "cdef") == 1);
ASSERT(mg_match_prefix("/a/**.cgi", 9, "/foo/bar/x.cgi") == -1);
ASSERT(mg_match_prefix("/a/**.cgi", 9, "/a/bar/x.cgi") == 12);
ASSERT(mg_match_prefix("**/", 3, "/a/b/c") == 5);
ASSERT(mg_match_prefix("**/$", 4, "/a/b/c") == -1);
ASSERT(mg_match_prefix("**/$", 4, "/a/b/") == 5);
ASSERT(mg_match_prefix("$", 1, "") == 0);
ASSERT(mg_match_prefix("$", 1, "x") == -1);
ASSERT(mg_match_prefix("*$", 2, "x") == 1);
ASSERT(mg_match_prefix("/$", 2, "/") == 1);
ASSERT(mg_match_prefix("**/$", 4, "/a/b/c") == -1);
ASSERT(mg_match_prefix("**/$", 4, "/a/b/") == 5);
ASSERT(mg_match_prefix("*", 1, "/hello/") == 0);
ASSERT(mg_match_prefix("**.a$|**.b$", 11, "/a/b.b/") == -1);
ASSERT(mg_match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
ASSERT(mg_match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6);
ASSERT(mg_match_prefix("**o$", 4, "HELLO") == 5);
return NULL;
}
static const char *test_remove_double_dots() {
struct { char before[30], after[30]; } data[] = {
{"////a", "/a"},
{"/.....", "/....."},
{"/......", "/......"},
{"...", "..."},
{"/...///", "/.../"},
{"/a...///", "/a.../"},
{"/.x", "/.x"},
{"/\\", "/"},
{"/a\\", "/a\\"},
{"/a\\\\...", "/a\\..."},
{"foo/x..y/././y/../../..", "foo/x..y/y/"},
{"foo/..x", "foo/..x"},
};
size_t i;
for (i = 0; i < ARRAY_SIZE(data); i++) {
remove_double_dots_and_double_slashes(data[i].before);
ASSERT(strcmp(data[i].before, data[i].after) == 0);
}
return NULL;
}
static const char *test_get_var(void) {
static const char *data = "a=1&&b=2&d&=&c=3%20&e=&k=aa&a=23";
static const char *data2 = "q=&st=2012%2F11%2F13+17%3A05&et=&team_id=";
char buf[20];
ASSERT(get_var(data, strlen(data), "a", buf, sizeof(buf), 0) == 1);
ASSERT(buf[0] == '1' && buf[1] == '\0');
ASSERT(get_var(data, strlen(data), "a", buf, sizeof(buf), 1) == 2);
ASSERT(strcmp(buf, "23") == 0);
ASSERT(get_var(data, strlen(data), "b", buf, sizeof(buf), 0) == 1);
ASSERT(buf[0] == '2' && buf[1] == '\0');
ASSERT(get_var(data, strlen(data), "c", buf, sizeof(buf), 0) == 2);
ASSERT(buf[0] == '3' && buf[1] == ' ' && buf[2] == '\0');
ASSERT(get_var(data, strlen(data), "e", buf, sizeof(buf), 0) == 0);
ASSERT(buf[0] == '\0');
ASSERT(get_var(data, strlen(data), "d", buf, sizeof(buf), 0) == -1);
ASSERT(get_var(data, strlen(data), "c", buf, 2, 0) == -2);
ASSERT(get_var(data, strlen(data), "x", NULL, 10, 0) == -2);
ASSERT(get_var(data, strlen(data), "x", buf, 0, 0) == -2);
ASSERT(get_var(data2, strlen(data2), "st", buf, 16, 0) == -2);
ASSERT(get_var(data2, strlen(data2), "st", buf, 17, 0) == 16);
return NULL;
}
static const char *test_url_decode(void) {
char buf[100];
ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); // No space for \0
ASSERT(mg_url_decode("foo", 3, buf, 4, 0) == 3);
ASSERT(strcmp(buf, "foo") == 0);
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 0) == 2);
ASSERT(strcmp(buf, "a+") == 0);
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 1) == 2);
ASSERT(strcmp(buf, "a ") == 0);
ASSERT(mg_url_decode("%61", 1, buf, sizeof(buf), 1) == 1);
printf("[%s]\n", buf);
ASSERT(strcmp(buf, "%") == 0);
ASSERT(mg_url_decode("%61", 2, buf, sizeof(buf), 1) == 2);
ASSERT(strcmp(buf, "%6") == 0);
ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1);
ASSERT(strcmp(buf, "a") == 0);
return NULL;
}
static const char *test_url_encode(void) {
char buf[100];
ASSERT(mg_url_encode("", 0, buf, sizeof(buf)) == 0);
ASSERT(buf[0] == '\0');
ASSERT(mg_url_encode("foo", 3, buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "foo") == 0);
ASSERT(mg_url_encode("f o", 3, buf, sizeof(buf)) == 5);
ASSERT(strcmp(buf, "f%20o") == 0);
return NULL;
}
static const char *test_to64(void) {
ASSERT(to64("0") == 0);
ASSERT(to64("") == 0);
ASSERT(to64("123") == 123);
ASSERT(to64("-34") == -34);
ASSERT(to64("3566626116") == 3566626116);
return NULL;
}
static const char *test_base64_encode(void) {
const char *in[] = {"a", "ab", "abc", "abcd", NULL};
const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
char buf[100];
int i;
for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
ASSERT(!strcmp(buf, out[i]));
}
return NULL;
}
static const char *test_mg_parse_header(void) {
const char *str = "xx=1 kl yy, ert=234 kl=123, uri=\"/?name=x,y\", "
"ii=\"12\\\"34\" zz='aa bb',tt=2,gf=\"xx d=1234";
char buf[20];
ASSERT(mg_parse_header(str, "yy", buf, sizeof(buf)) == 0);
ASSERT(mg_parse_header(str, "ert", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "234") == 0);
ASSERT(mg_parse_header(str, "ert", buf, 2) == 0);
ASSERT(mg_parse_header(str, "ert", buf, 3) == 0);
ASSERT(mg_parse_header(str, "ert", buf, 4) == 3);
ASSERT(mg_parse_header(str, "gf", buf, sizeof(buf)) == 0);
ASSERT(mg_parse_header(str, "zz", buf, sizeof(buf)) == 5);
ASSERT(strcmp(buf, "aa bb") == 0);
ASSERT(mg_parse_header(str, "d", buf, sizeof(buf)) == 4);
ASSERT(strcmp(buf, "1234") == 0);
buf[0] = 'x';
ASSERT(mg_parse_header(str, "MMM", buf, sizeof(buf)) == 0);
ASSERT(buf[0] == '\0');
ASSERT(mg_parse_header(str, "kl", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "123") == 0);
ASSERT(mg_parse_header(str, "xx", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "1") == 0);
ASSERT(mg_parse_header(str, "ii", buf, sizeof(buf)) == 5);
ASSERT(strcmp(buf, "12\"34") == 0);
ASSERT(mg_parse_header(str, "tt", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "2") == 0);
ASSERT(mg_parse_header(str, "uri", buf, sizeof(buf)) == 10);
return NULL;
}
static const char *test_next_option(void) {
const char *p, *list = "x/8,/y**=1;2k,z";
struct vec a, b;
int i;
ASSERT(next_option(NULL, &a, &b) == NULL);
for (i = 0, p = list; (p = next_option(p, &a, &b)) != NULL; i++) {
ASSERT(i != 0 || (a.ptr == list && a.len == 3 && b.len == 0));
ASSERT(i != 1 || (a.ptr == list + 4 && a.len == 4 && b.ptr == list + 9 &&
b.len == 4));
ASSERT(i != 2 || (a.ptr == list + 14 && a.len == 1 && b.len == 0));
}
return NULL;
}
static int evh1(struct mg_connection *conn, enum mg_event ev) {
char *buf = (char *) conn->connection_param;
int result = MG_FALSE;
switch (ev) {
case MG_CONNECT:
mg_printf(conn, "GET %s HTTP/1.0\r\n\r\n",
buf[0] == '1' ? "/cb1" : "/non_exist");
result = MG_TRUE;
break;
case MG_HTTP_ERROR:
mg_printf(conn, "HTTP/1.0 404 NF\r\n\r\nERR: %d", conn->status_code);
result = MG_TRUE;
break;
case MG_REQUEST:
if (!strcmp(conn->uri, "/cb1")) {
mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n%s %s %s",
(char *) conn->server_param,
buf == NULL ? "?" : "!", conn->remote_ip);
result = MG_TRUE;
}
break;
case MG_REPLY:
if (buf != NULL) {
sprintf(buf + 1, "%.*s", (int) conn->content_len, conn->content);
}
break;
case MG_AUTH:
result = MG_TRUE;
break;
default:
break;
}
return result;
}
static const char *test_server(void) {
char buf1[100] = "1", buf2[100] = "2";
struct mg_server *server = mg_create_server((void *) "foo", evh1);
struct mg_connection *conn;
ASSERT(server != NULL);
ASSERT(mg_set_option(server, "listening_port", LISTENING_ADDR) == NULL);
ASSERT(mg_set_option(server, "document_root", ".") == NULL);
ASSERT((conn = mg_connect(server, "127.0.0.1:" HTTP_PORT)) != NULL);
conn->connection_param = buf1;
ASSERT((conn = mg_connect(server, "127.0.0.1:" HTTP_PORT)) != NULL);
conn->connection_param = buf2;
{ int i; for (i = 0; i < 50; i++) mg_poll_server(server, 1); }
ASSERT(strcmp(buf1, "1foo ? 127.0.0.1") == 0);
ASSERT(strcmp(buf2, "2ERR: 404") == 0);
ASSERT(strcmp(static_config_options[URL_REWRITES * 2], "url_rewrites") == 0);
mg_destroy_server(&server);
ASSERT(server == NULL);
return NULL;
}
#define DISP "Content-Disposition: form/data; "
#define CRLF "\r\n"
#define BOUNDARY "--xyz"
static const char *test_parse_multipart(void) {
char a[100], b[100];
const char *p;
static const char f1[] = BOUNDARY CRLF DISP "name=f1" CRLF CRLF
"some_content" CRLF BOUNDARY CRLF
BOUNDARY CRLF DISP "name=f2; filename=\"foo bar.txt\"" CRLF CRLF
"another_content" CRLF BOUNDARY CRLF
"--" CRLF;
int n, n2, len, f1_len = sizeof(f1) - 1;
ASSERT(mg_parse_multipart("", 0, a, sizeof(a), b, sizeof(b), &p, &len) == 0);
ASSERT(mg_parse_multipart("a", 1, a, sizeof(a), b, sizeof(b), &p, &len) == 0);
ASSERT((n = mg_parse_multipart(f1, f1_len, a, sizeof(a),
b, sizeof(b), &p, &len)) > 0);
ASSERT(len == 12);
ASSERT(memcmp(p, "some_content", len) == 0);
ASSERT(strcmp(a, "f1") == 0);
ASSERT(b[0] == '\0');
ASSERT((n2 = mg_parse_multipart(f1 + n, f1_len - n, a, sizeof(a),
b, sizeof(b), &p, &len)) > 0);
ASSERT(len == 15);
ASSERT(memcmp(p, "another_content", len) == 0);
ASSERT(strcmp(a, "f2") == 0);
ASSERT(strcmp(b, "foo bar.txt") == 0);
ASSERT((n2 = mg_parse_multipart(f1 + n + n2, f1_len - (n + n2), a, sizeof(a),
b, sizeof(b), &p, &len)) == 0);
return NULL;
}
static int evh2(struct mg_connection *conn, enum mg_event ev) {
char *file_data, *cp = (char *) conn->connection_param;
int file_size, result = MG_FALSE;
switch (ev) {
case MG_AUTH:
result = MG_TRUE;
break;
case MG_CONNECT:
mg_printf(conn, "GET /%s HTTP/1.0\r\n\r\n", cp);
result = MG_TRUE;
break;
case MG_REQUEST:
break;
case MG_REPLY:
file_data = read_file("unit_test.c", &file_size);
sprintf(cp, "%d %s", (size_t) file_size == conn->content_len &&
memcmp(file_data, conn->content, file_size) == 0 ? 1 : 0,
conn->query_string == NULL ? "?" : conn->query_string);
free(file_data);
break;
default:
break;
}
return result;
}
static const char *test_mg_set_option(void) {
struct mg_server *server = mg_create_server(NULL, NULL);
ASSERT(mg_set_option(server, "listening_port", "0") == NULL);
ASSERT(mg_get_option(server, "listening_port")[0] != '\0');
mg_destroy_server(&server);
return NULL;
}
static const char *test_rewrites(void) {
char buf1[100] = "xx", addr[50];
struct mg_server *server = mg_create_server(NULL, evh2);
struct mg_connection *conn;
const char *port;
ASSERT(mg_set_option(server, "listening_port", "0") == NULL);
ASSERT(mg_set_option(server, "document_root", ".") == NULL);
ASSERT(mg_set_option(server, "url_rewrites", "/xx=unit_test.c") == NULL);
ASSERT((port = mg_get_option(server, "listening_port")) != NULL);
snprintf(addr, sizeof(addr), "127.0.0.1:%s", port);
ASSERT((conn = mg_connect(server, addr)) != NULL);
conn->connection_param = buf1;
{ int i; for (i = 0; i < 50; i++) mg_poll_server(server, 1); }
ASSERT(strcmp(buf1, "1 ?") == 0);
mg_destroy_server(&server);
return NULL;
}
static const char *run_all_tests(void) {
RUN_TEST(test_should_keep_alive);
RUN_TEST(test_match_prefix);
RUN_TEST(test_remove_double_dots);
RUN_TEST(test_parse_http_message);
RUN_TEST(test_to64);
RUN_TEST(test_url_decode);
RUN_TEST(test_url_encode);
RUN_TEST(test_base64_encode);
RUN_TEST(test_mg_parse_header);
RUN_TEST(test_get_var);
RUN_TEST(test_next_option);
RUN_TEST(test_parse_multipart);
RUN_TEST(test_mg_set_option);
RUN_TEST(test_server);
RUN_TEST(test_rewrites);
return NULL;
}
int __cdecl main(void) {
const char *fail_msg = run_all_tests();
printf("%s, tests run: %d\n", fail_msg ? "FAIL" : "PASS", static_num_tests);
return fail_msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment