mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
Ditch JSON-RPC from mongoose
PUBLISHED_FROM=89b978c02be2f10eb930ff13673d45249fd67763
This commit is contained in:
1 parent
421b420a0c
commit
b7a0748312
30 files changed
+14
-2323
No files matched your search
@@ -36,7 +36,6 @@ If you are looking for a complete solution with firmware and cloud components, c
|
||||
- plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
|
||||
- HTTP client, HTTP server
|
||||
- WebSocket client, WebSocket server
|
||||
- JSON-RPC client, JSON-RPC server
|
||||
- MQTT client, MQTT broker
|
||||
- CoAP client, CoAP server
|
||||
- DNS client, DNS server, async DNS resolver
|
||||
|
||||
@@ -7,6 +7,5 @@ title: Disabling flags
|
||||
- `MG_DISABLE_MQTT` disable MQTT support
|
||||
- `MG_DISABLE_SHA1` disable SHA1 support (used by Websocket)
|
||||
- `MG_DISABLE_MD5` disable MD5 support (used by HTTP auth)
|
||||
- `MG_DISABLE_JSON_RPC` disable JSON-RPC support
|
||||
- `MG_DISABLE_SOCKETPAIR` disable `mg_broadcast()` API
|
||||
- `MG_DISABLE_HTTP_KEEP_ALIVE` useful for embedded systems to save resources
|
||||
@@ -14,9 +14,9 @@ flags. Also, some preprocessor flags can be used to tune internal Mongoose
|
||||
parameters.
|
||||
|
||||
To set a preprocessor flag during compile time, use `-D <PREPROCESSOR_FLAG>`
|
||||
compiler option. For example, to disable both MQTT and JSON-RPC,
|
||||
compiler option. For example, to disable both MQTT and COAP,
|
||||
compile the application `my_app.c` like this (assumed UNIX system):
|
||||
|
||||
```
|
||||
$ cc my_app.c mongoose.c -D MG_DISABLE_MQTT -D MG_DISABLE_JSON_RPC
|
||||
$ cc my_app.c mongoose.c -D MG_DISABLE_MQTT -D MG_DISABLE_COAP
|
||||
```
|
||||
@@ -3,7 +3,6 @@ items:
|
||||
- { type: dir, name: mbuf.h }
|
||||
- { type: dir, name: net.h }
|
||||
- { type: dir, name: http.h }
|
||||
- { type: dir, name: json-rpc.h }
|
||||
- { type: dir, name: dns.h }
|
||||
- { type: dir, name: dns-server.h }
|
||||
- { type: dir, name: mqtt.h }
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: "JSON-RPC"
|
||||
symbol_kind: "intro"
|
||||
decl_name: "json-rpc.h"
|
||||
items:
|
||||
- { type: file, name: mg_rpc_parse_reply.md }
|
||||
- { type: file, name: mg_rpc_create_request.md }
|
||||
- { type: file, name: mg_rpc_create_reply.md }
|
||||
- { type: file, name: mg_rpc_create_error.md }
|
||||
- { type: file, name: mg_rpc_create_std_error.md }
|
||||
- { type: file, name: mg_rpc_dispatch.md }
|
||||
- { type: file, name: struct_mg_rpc_request.md }
|
||||
- { type: file, name: struct_mg_rpc_reply.md }
|
||||
- { type: file, name: struct_mg_rpc_error.md }
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "mg_rpc_create_error()"
|
||||
decl_name: "mg_rpc_create_error"
|
||||
symbol_kind: "func"
|
||||
signature: |
|
||||
int mg_rpc_create_error(char *buf, int len, struct mg_rpc_request *req,
|
||||
int code, const char *message, const char *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
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "mg_rpc_create_reply()"
|
||||
decl_name: "mg_rpc_create_reply"
|
||||
symbol_kind: "func"
|
||||
signature: |
|
||||
int mg_rpc_create_reply(char *buf, int len, const struct mg_rpc_request *req,
|
||||
const char *result_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
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "mg_rpc_create_request()"
|
||||
decl_name: "mg_rpc_create_request"
|
||||
symbol_kind: "func"
|
||||
signature: |
|
||||
int mg_rpc_create_request(char *buf, int len, const char *method,
|
||||
const char *id, const char *params_fmt, ...);
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
title: "mg_rpc_create_std_error()"
|
||||
decl_name: "mg_rpc_create_std_error"
|
||||
symbol_kind: "func"
|
||||
signature: |
|
||||
int mg_rpc_create_std_error(char *buf, int len, struct mg_rpc_request *req,
|
||||
int code);
|
||||
---
|
||||
|
||||
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)`
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
title: "mg_rpc_dispatch()"
|
||||
decl_name: "mg_rpc_dispatch"
|
||||
symbol_kind: "func"
|
||||
signature: |
|
||||
int mg_rpc_dispatch(const char *buf, int, char *dst, int dst_len,
|
||||
const char **methods, mg_rpc_handler_t *handlers);
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "mg_rpc_parse_reply()"
|
||||
decl_name: "mg_rpc_parse_reply"
|
||||
symbol_kind: "func"
|
||||
signature: |
|
||||
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 *);
|
||||
---
|
||||
|
||||
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`
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "struct mg_rpc_error"
|
||||
decl_name: "struct mg_rpc_error"
|
||||
symbol_kind: "struct"
|
||||
signature: |
|
||||
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 */
|
||||
};
|
||||
---
|
||||
|
||||
JSON-RPC error
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
title: "struct mg_rpc_reply"
|
||||
decl_name: "struct mg_rpc_reply"
|
||||
symbol_kind: "struct"
|
||||
signature: |
|
||||
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 response
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "struct mg_rpc_request"
|
||||
decl_name: "struct mg_rpc_request"
|
||||
symbol_kind: "struct"
|
||||
signature: |
|
||||
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 request
|
||||
|
||||
+1
-2
@@ -4,7 +4,7 @@ title: Overview
|
||||
|
||||
Mongoose is a swiss army knife for embedded network programming.
|
||||
It implements event-driven non-blocking API for TCP, UDP, HTTP,
|
||||
WebSocket, CoAP, MQTT, JSON-RPC for both client and server mode.
|
||||
WebSocket, CoAP, MQTT for both client and server mode.
|
||||
Features include:
|
||||
|
||||
- Cross-platform: works on Linux/UNIX, MacOS, QNX, eCos, Windows, Android,
|
||||
@@ -16,7 +16,6 @@ Features include:
|
||||
- plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
|
||||
- HTTP client and server
|
||||
- WebSocket client and server
|
||||
- JSON-RPC client and server
|
||||
- MQTT client and server
|
||||
- CoAP client and server
|
||||
- DNS client and server
|
||||
|
||||
+11
-21
@@ -95,27 +95,19 @@ static double send_acc_data_since(struct mg_connection *nc,
|
||||
|
||||
static void process_command(struct mg_connection *nc, unsigned char *data,
|
||||
size_t len) {
|
||||
struct json_token *toks = parse_json2((const char *) data, len);
|
||||
if (toks == NULL) {
|
||||
// TODO(lsm): use proper JSON parser
|
||||
int cmd, n, val;
|
||||
double t;
|
||||
if (sscanf((char *) data, "{\t\": %d, \"ts\": %lf, %n", &cmd, &t, &n) != 2) {
|
||||
LOG(LL_ERROR, ("Invalid command: %.*s", (int) len, data));
|
||||
return;
|
||||
}
|
||||
struct json_token *t = find_json_token(toks, "t");
|
||||
if (t == NULL) {
|
||||
LOG(LL_ERROR, ("Missing type field: %.*s", (int) len, data));
|
||||
goto out_free;
|
||||
}
|
||||
if (t->len == 1 && *t->ptr == '1') {
|
||||
struct json_token *v = find_json_token(toks, "v");
|
||||
if (v == NULL) {
|
||||
if (t == 1) {
|
||||
if (sscanf((char *) data + n, "\"v\": %d", &val) != 1) {
|
||||
LOG(LL_ERROR, ("Missing value: %.*s", (int) len, data));
|
||||
goto out_free;
|
||||
return;
|
||||
}
|
||||
if (v->len != 1) {
|
||||
LOG(LL_ERROR, ("Invalid value: %.*s", (int) len, data));
|
||||
goto out_free;
|
||||
}
|
||||
switch (*v->ptr) {
|
||||
switch (val) {
|
||||
case '0': {
|
||||
GPIO_IF_LedOff(MCU_RED_LED_GPIO);
|
||||
break;
|
||||
@@ -130,15 +122,13 @@ static void process_command(struct mg_connection *nc, unsigned char *data,
|
||||
}
|
||||
default: {
|
||||
LOG(LL_ERROR, ("Invalid value: %.*s", (int) len, data));
|
||||
goto out_free;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG(LL_ERROR, ("Unknown command: %.*s", (int) t->len, t->ptr));
|
||||
goto out_free;
|
||||
LOG(LL_ERROR, ("Unknown command: %.*s", (int) len, data));
|
||||
return;
|
||||
}
|
||||
out_free:
|
||||
free(toks);
|
||||
}
|
||||
|
||||
void data_conn_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
PROG = json_rpc_server
|
||||
include ../examples.mk
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* To test this server, do
|
||||
* $ curl -d '{"id":1,method:"sum",params:[22,33]}' 127.0.0.1:8000
|
||||
*/
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_http_port = "8000";
|
||||
|
||||
static int rpc_sum(char *buf, int len, struct mg_rpc_request *req) {
|
||||
double sum = 0;
|
||||
int i;
|
||||
|
||||
if (req->params[0].type != JSON_TYPE_ARRAY) {
|
||||
return mg_rpc_create_std_error(buf, len, req,
|
||||
JSON_RPC_INVALID_PARAMS_ERROR);
|
||||
}
|
||||
|
||||
for (i = 0; i < req->params[0].num_desc; i++) {
|
||||
if (req->params[i + 1].type != JSON_TYPE_NUMBER) {
|
||||
return mg_rpc_create_std_error(buf, len, req,
|
||||
JSON_RPC_INVALID_PARAMS_ERROR);
|
||||
}
|
||||
sum += strtod(req->params[i + 1].ptr, NULL);
|
||||
}
|
||||
return mg_rpc_create_reply(buf, len, req, "f", sum);
|
||||
}
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
static const char *methods[] = {"sum", NULL};
|
||||
static mg_rpc_handler_t handlers[] = {rpc_sum, NULL};
|
||||
char buf[100];
|
||||
|
||||
switch (ev) {
|
||||
case MG_EV_HTTP_REQUEST:
|
||||
mg_rpc_dispatch(hm->body.p, hm->body.len, buf, sizeof(buf), methods,
|
||||
handlers);
|
||||
mg_printf(nc,
|
||||
"HTTP/1.0 200 OK\r\nContent-Length: %d\r\n"
|
||||
"Content-Type: application/json\r\n\r\n%s",
|
||||
(int) strlen(buf), buf);
|
||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler);
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
|
||||
printf("Starting JSON-RPC server on port %s\n", s_http_port);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
NS=../../mongoose.c
|
||||
FLAGS = ../../mongoose.c -I../..
|
||||
CFLAGS=-W -Wall -DMG_ENABLE_THREADS -pthread $(CFLAGS_EXTRA)
|
||||
PROGS = device_side cloud_side
|
||||
|
||||
all: $(PROGS)
|
||||
|
||||
device_side: Makefile device_side.c $(NS)
|
||||
$(CC) device_side.c $(FLAGS) -o $@ $(CFLAGS)
|
||||
|
||||
cloud_side: Makefile cloud_side.c $(NS)
|
||||
$(CC) cloud_side.c $(FLAGS) -o $@ $(CFLAGS)
|
||||
|
||||
device_side.exe: Makefile device_side.c $(NS)
|
||||
cl device_side.c $(FLAGS) /MD /Fe$@
|
||||
|
||||
clean:
|
||||
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROGS)
|
||||
@@ -1,72 +0,0 @@
|
||||
= 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 receive 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
|
||||
----
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the cloud endpoint of the Raspberry Pi camera/LED example
|
||||
* of the Mongoose networking library.
|
||||
* It is a simple web server, serving both static files, a REST API handler,
|
||||
* and a WebSocket handler.
|
||||
*/
|
||||
#include "mongoose.h"
|
||||
|
||||
static struct mg_serve_http_opts web_root_opts;
|
||||
|
||||
/*
|
||||
* Forwards the jpeg frame data to all open mjpeg connections.
|
||||
*
|
||||
* Incoming messages follow a very simple binary frame format:
|
||||
* 4 bytes: timestamp (in network byte order)
|
||||
* n bytes: jpeg payload
|
||||
*
|
||||
* The timestamp is used to compute a lag.
|
||||
* It's done in a quite stupid way as it requires the device clock
|
||||
* to be synchronized with the cloud endpoint.
|
||||
*/
|
||||
static void push_frame_to_clients(struct mg_mgr *mgr,
|
||||
const struct websocket_message *wm) {
|
||||
struct mg_connection *nc;
|
||||
/*
|
||||
* mjpeg connections are tagged with the MG_F_USER_2 flag so we can find them
|
||||
* my scanning the connection list provided by the mongoose manager.
|
||||
*/
|
||||
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
|
||||
if (!(nc->flags & MG_F_USER_2)) continue; // Ignore un-marked requests
|
||||
|
||||
mg_printf(nc,
|
||||
"--w00t\r\nContent-Type: image/jpeg\r\n"
|
||||
"Content-Length: %lu\r\n\r\n",
|
||||
(unsigned long) wm->size);
|
||||
mg_send(nc, wm->data, wm->size);
|
||||
mg_send(nc, "\r\n", 2);
|
||||
printf("Image pushed to %p\n", nc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Forwards API payload to the device, by scanning through
|
||||
* all the connections to find those that are tagged as WebSocket.
|
||||
*/
|
||||
static void send_command_to_the_device(struct mg_mgr *mgr,
|
||||
const struct mg_str *cmd) {
|
||||
struct mg_connection *nc;
|
||||
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
|
||||
if (!(nc->flags & MG_F_IS_WEBSOCKET))
|
||||
continue; // Ignore non-websocket requests
|
||||
|
||||
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, cmd->p, cmd->len);
|
||||
printf("Sent API command [%.*s] to %p\n", (int) cmd->len, cmd->p, nc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Main event handler. Receives data events and dispatches to
|
||||
* the appropriate handler function.
|
||||
*
|
||||
* 1. RESTful API requests are handled by send_command_to_the_device.
|
||||
* 2. requests to /mpeg are established and left open waiting for data to arrive
|
||||
* from WebSocket.
|
||||
* 3. WebSocket frames are handled by push_frame_to_clients.
|
||||
* 4. All other connections are passed to the mg_serve_http handler
|
||||
* which serves static files.
|
||||
*/
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct websocket_message *wm = (struct websocket_message *) ev_data;
|
||||
struct http_message *hm = (struct http_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case MG_EV_HTTP_REQUEST:
|
||||
if (mg_vcmp(&hm->uri, "/mjpg") == 0) {
|
||||
nc->flags |= MG_F_USER_2; /* Set a mark on image requests */
|
||||
mg_printf(nc, "%s",
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Pragma: no-cache\r\n"
|
||||
"Expires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Type: multipart/x-mixed-replace; "
|
||||
"boundary=--w00t\r\n\r\n");
|
||||
} else if (mg_vcmp(&hm->uri, "/api") == 0 && hm->body.len > 0) {
|
||||
/*
|
||||
* RESTful API call. HTTP message body should be a JSON message.
|
||||
* We should parse it and take appropriate action.
|
||||
* In our case, simply forward that call to the device.
|
||||
*/
|
||||
printf("API CALL: [%.*s] [%.*s]\n", (int) hm->method.len, hm->method.p,
|
||||
(int) hm->body.len, hm->body.p);
|
||||
send_command_to_the_device(nc->mgr, &hm->body);
|
||||
mg_printf(nc, "HTTP/1.0 200 OK\nContent-Length: 0\n\n");
|
||||
} else {
|
||||
/* Delegate to the static web server handler for all other paths. */
|
||||
mg_serve_http(nc, hm, web_root_opts);
|
||||
}
|
||||
break;
|
||||
case MG_EV_WEBSOCKET_FRAME:
|
||||
printf("Got websocket frame, size %lu\n", (unsigned long) wm->size);
|
||||
push_frame_to_clients(nc->mgr, wm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <listening_addr>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Listening on: [%s]\n", argv[1]);
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
/*
|
||||
* mg_bind() creates a listening connection on a given ip:port and
|
||||
* with an attached event handler.
|
||||
* The event handler will only trigger TCP events until the http
|
||||
* protocol handler is installed.
|
||||
*/
|
||||
if ((nc = mg_bind(&mgr, argv[1], ev_handler)) == NULL) {
|
||||
fprintf(stderr, "Error binding to %s\n", argv[1]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
web_root_opts.document_root = "./web_root";
|
||||
|
||||
/*
|
||||
* We explicitly hand over control to the Mongoose manager
|
||||
* in this event loop and we can easily multiplex other activities.
|
||||
*/
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the device endpoint of the Raspberry Pi camera/LED example
|
||||
* of the Mongoose networking library.
|
||||
* It is a simple websocket client, sending jpeg frames obtained from the
|
||||
* RPi camera and receiving JSON commands through the same WebSocket channel
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static int s_poll_interval_ms = 100;
|
||||
static int s_still_period = 100;
|
||||
static int s_vertical_flip = 0;
|
||||
static int s_width = 320;
|
||||
static int s_height = 180;
|
||||
static const char *s_mjpg_file = "/var/run/shm/cam.jpg";
|
||||
|
||||
static struct mg_connection *client;
|
||||
|
||||
/*
|
||||
* Check if there is a new image available and
|
||||
* send it to the cloud endpoint if the send buffer is not too full.
|
||||
* The image is moved in a new file by the jpeg optimizer function;
|
||||
* this ensures that we will detect a new frame when raspistill writes
|
||||
* it's output file.
|
||||
*/
|
||||
static void send_mjpg_frame(struct mg_connection *nc, const char *file_path) {
|
||||
static int skipped_frames = 0;
|
||||
struct stat st;
|
||||
FILE *fp;
|
||||
|
||||
/* Check file modification time. */
|
||||
if (stat(file_path, &st) == 0) {
|
||||
/* Skip the frame if there is too much unsent data. */
|
||||
if (nc->send_mbuf.len > 256) skipped_frames++;
|
||||
|
||||
/* Read new mjpg frame into a buffer */
|
||||
fp = fopen(file_path, "rb");
|
||||
char buf[st.st_size];
|
||||
fread(buf, 1, sizeof(buf), fp);
|
||||
fclose(fp);
|
||||
|
||||
/*
|
||||
* Delete the file so we can detect when raspistill creates a new one.
|
||||
* mtime granularity is only 1s.
|
||||
*/
|
||||
unlink(file_path);
|
||||
|
||||
/* Send those buffer through the websocket connection */
|
||||
mg_send_websocket_frame(nc, WEBSOCKET_OP_BINARY, buf, sizeof(buf));
|
||||
printf("Sent mjpg frame, %lu bytes after skippping %d frames\n",
|
||||
(unsigned long) sizeof(buf), skipped_frames);
|
||||
skipped_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn on or off the LED.
|
||||
* The LED in this example is an RGB led, so all the colors have to be set.
|
||||
*/
|
||||
static void set_led(int v) {
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"for i in 22 23 24; do"
|
||||
" echo %d >/sys/class/gpio/gpio$i/value; done",
|
||||
v);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse control JSON and perform command:
|
||||
* for now only LED on/off is supported.
|
||||
*/
|
||||
static void perform_control_command(const char *data, size_t len) {
|
||||
struct json_token toks[200], *onoff;
|
||||
parse_json(data, len, toks, sizeof(toks));
|
||||
onoff = find_json_token(toks, "onoff");
|
||||
set_led(strncmp("[\"on\"]", onoff->ptr, onoff->len) == 0);
|
||||
}
|
||||
|
||||
/* Main event handler. Sends websocket frames and receives control commands */
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
|
||||
struct websocket_message *wm = (struct websocket_message *) ev_data;
|
||||
|
||||
switch (ev) {
|
||||
case MG_EV_CONNECT:
|
||||
printf("Reconnect: %s\n", *(int *) ev_data == 0 ? "ok" : "failed");
|
||||
if (*(int *) ev_data == 0) {
|
||||
/*
|
||||
* Tune the tcp send buffer size, so that we can skip frames
|
||||
* when the connection is congested. This helps maintaining a
|
||||
* reasonable latency.
|
||||
*/
|
||||
int sndbuf_size = 512;
|
||||
if (setsockopt(nc->sock, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf_size,
|
||||
sizeof(int)) == -1) {
|
||||
perror("failed to tune TCP send buffer size\n");
|
||||
}
|
||||
|
||||
mg_send_websocket_handshake(nc, "/stream", NULL);
|
||||
}
|
||||
break;
|
||||
case MG_EV_CLOSE:
|
||||
printf("Connection %p closed\n", nc);
|
||||
client = NULL;
|
||||
break;
|
||||
case MG_EV_POLL:
|
||||
send_mjpg_frame(nc, s_mjpg_file);
|
||||
break;
|
||||
case MG_EV_WEBSOCKET_FRAME:
|
||||
printf("Got control command: [%.*s]\n", (int) wm->size, wm->data);
|
||||
perform_control_command((const char *) wm->data, wm->size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This thread regenerates s_mjpg_file every s_poll_interval_ms milliseconds.
|
||||
* It is Raspberry PI specific, change this function on other systems.
|
||||
*/
|
||||
static void *generate_mjpg_data_thread_func(void *param) {
|
||||
char cmd[400];
|
||||
(void) param;
|
||||
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"raspistill -w %d -h %d -n -q 100 -tl %d "
|
||||
"-t 999999999 -v %s -o %s >/dev/null 2>&1",
|
||||
s_width, s_height, s_still_period, s_vertical_flip ? "-vf" : "",
|
||||
s_mjpg_file);
|
||||
|
||||
for (;;) {
|
||||
int ret = system(cmd);
|
||||
if (WIFSIGNALED(ret)) exit(1);
|
||||
sleep(1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
char *addr = argv[1];
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_address>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Start separate thread that generates MJPG data */
|
||||
mg_start_thread(generate_mjpg_data_thread_func, NULL);
|
||||
|
||||
printf("Streaming [%s] to [%s]\n", s_mjpg_file, addr);
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, s_poll_interval_ms);
|
||||
|
||||
/* Reconnect if disconnected */
|
||||
if (!client) {
|
||||
sleep(1); /* limit reconnections frequency */
|
||||
printf("Reconnecting to %s...\n", addr);
|
||||
client = mg_connect(&mgr, addr, ev_handler);
|
||||
if (client) mg_set_protocol_http_websocket(client);
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
@@ -1,533 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="generator" content="Asciidoctor 1.5.1">
|
||||
<title>Raspberry Pi camera/LED demo</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400">
|
||||
<style>
|
||||
/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
|
||||
/* Remove the comments around the @import statement below when using this as a custom stylesheet */
|
||||
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400";*/
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
|
||||
audio,canvas,video{display:inline-block}
|
||||
audio:not([controls]){display:none;height:0}
|
||||
[hidden],template{display:none}
|
||||
script{display:none!important}
|
||||
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
|
||||
body{margin:0}
|
||||
a{background:transparent}
|
||||
a:focus{outline:thin dotted}
|
||||
a:active,a:hover{outline:0}
|
||||
h1{font-size:2em;margin:.67em 0}
|
||||
abbr[title]{border-bottom:1px dotted}
|
||||
b,strong{font-weight:bold}
|
||||
dfn{font-style:italic}
|
||||
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
|
||||
mark{background:#ff0;color:#000}
|
||||
code,kbd,pre,samp{font-family:monospace;font-size:1em}
|
||||
pre{white-space:pre-wrap}
|
||||
q{quotes:"\201C" "\201D" "\2018" "\2019"}
|
||||
small{font-size:80%}
|
||||
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sup{top:-.5em}
|
||||
sub{bottom:-.25em}
|
||||
img{border:0}
|
||||
svg:not(:root){overflow:hidden}
|
||||
figure{margin:0}
|
||||
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{border:0;padding:0}
|
||||
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
|
||||
button,input{line-height:normal}
|
||||
button,select{text-transform:none}
|
||||
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
|
||||
button[disabled],html input[disabled]{cursor:default}
|
||||
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
|
||||
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
|
||||
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
|
||||
textarea{overflow:auto;vertical-align:top}
|
||||
table{border-collapse:collapse;border-spacing:0}
|
||||
*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
|
||||
html,body{font-size:100%}
|
||||
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto}
|
||||
a:hover{cursor:pointer}
|
||||
img,object,embed{max-width:100%;height:auto}
|
||||
object,embed{height:100%}
|
||||
img{-ms-interpolation-mode:bicubic}
|
||||
#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none!important}
|
||||
.left{float:left!important}
|
||||
.right{float:right!important}
|
||||
.text-left{text-align:left!important}
|
||||
.text-right{text-align:right!important}
|
||||
.text-center{text-align:center!important}
|
||||
.text-justify{text-align:justify!important}
|
||||
.hide{display:none}
|
||||
.antialiased,body{-webkit-font-smoothing:antialiased}
|
||||
img{display:inline-block;vertical-align:middle}
|
||||
textarea{height:auto;min-height:50px}
|
||||
select{width:100%}
|
||||
p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}
|
||||
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
|
||||
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
|
||||
a{color:#2156a5;text-decoration:underline;line-height:inherit}
|
||||
a:hover,a:focus{color:#1d4b8f}
|
||||
a img{border:none}
|
||||
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
|
||||
p aside{font-size:.875em;line-height:1.35;font-style:italic}
|
||||
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
|
||||
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
|
||||
h1{font-size:2.125em}
|
||||
h2{font-size:1.6875em}
|
||||
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
|
||||
h4,h5{font-size:1.125em}
|
||||
h6{font-size:1em}
|
||||
hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
|
||||
em,i{font-style:italic;line-height:inherit}
|
||||
strong,b{font-weight:bold;line-height:inherit}
|
||||
small{font-size:60%;line-height:inherit}
|
||||
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
|
||||
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
|
||||
ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em}
|
||||
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
|
||||
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
|
||||
ul.square{list-style-type:square}
|
||||
ul.circle{list-style-type:circle}
|
||||
ul.disc{list-style-type:disc}
|
||||
ul.no-bullet{list-style:none}
|
||||
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
|
||||
dl dt{margin-bottom:.3125em;font-weight:bold}
|
||||
dl dd{margin-bottom:1.25em}
|
||||
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
|
||||
abbr{text-transform:none}
|
||||
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
|
||||
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
|
||||
blockquote cite:before{content:"\2014 \0020"}
|
||||
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
|
||||
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
|
||||
@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
|
||||
h1{font-size:2.75em}
|
||||
h2{font-size:2.3125em}
|
||||
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
|
||||
h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
|
||||
table thead,table tfoot{background:#f7f8f7;font-weight:bold}
|
||||
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
|
||||
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
|
||||
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
|
||||
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
|
||||
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
|
||||
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
|
||||
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
|
||||
.clearfix:after,.float-group:after{clear:both}
|
||||
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
|
||||
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
|
||||
.keyseq{color:rgba(51,51,51,.8)}
|
||||
kbd{display:inline-block;color:rgba(0,0,0,.8);font-size:.75em;line-height:1.4;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:-.15em .15em 0 .15em;padding:.2em .6em .2em .5em;vertical-align:middle;white-space:nowrap}
|
||||
.keyseq kbd:first-child{margin-left:0}
|
||||
.keyseq kbd:last-child{margin-right:0}
|
||||
.menuseq,.menu{color:rgba(0,0,0,.8)}
|
||||
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
|
||||
b.button:before{content:"[";padding:0 3px 0 2px}
|
||||
b.button:after{content:"]";padding:0 2px 0 3px}
|
||||
p a>code:hover{color:rgba(0,0,0,.9)}
|
||||
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
|
||||
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
|
||||
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
|
||||
#content{margin-top:1.25em}
|
||||
#content:before{content:none}
|
||||
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
|
||||
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
|
||||
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
|
||||
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
|
||||
#header .details span:first-child{margin-left:-.125em}
|
||||
#header .details span.email a{color:rgba(0,0,0,.85)}
|
||||
#header .details br{display:none}
|
||||
#header .details br+span:before{content:"\00a0\2013\00a0"}
|
||||
#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
|
||||
#header .details br+span#revremark:before{content:"\00a0|\00a0"}
|
||||
#header #revnumber{text-transform:capitalize}
|
||||
#header #revnumber:after{content:"\00a0"}
|
||||
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
|
||||
#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
|
||||
#toc>ul{margin-left:.125em}
|
||||
#toc ul.sectlevel0>li>a{font-style:italic}
|
||||
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
|
||||
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
|
||||
#toc a{text-decoration:none}
|
||||
#toc a:active{text-decoration:underline}
|
||||
#toctitle{color:#7a2518;font-size:1.2em}
|
||||
@media only screen and (min-width:768px){#toctitle{font-size:1.375em}
|
||||
body.toc2{padding-left:15em;padding-right:0}
|
||||
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
|
||||
#toc.toc2 #toctitle{margin-top:0;font-size:1.2em}
|
||||
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
|
||||
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
|
||||
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
|
||||
body.toc2.toc-right{padding-left:0;padding-right:15em}
|
||||
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
|
||||
#toc.toc2{width:20em}
|
||||
#toc.toc2 #toctitle{font-size:1.375em}
|
||||
#toc.toc2>ul{font-size:.95em}
|
||||
#toc.toc2 ul ul{padding-left:1.25em}
|
||||
body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
|
||||
#content #toc>:first-child{margin-top:0}
|
||||
#content #toc>:last-child{margin-bottom:0}
|
||||
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
|
||||
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
|
||||
.sect1{padding-bottom:.625em}
|
||||
@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed}
|
||||
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
|
||||
#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
|
||||
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
|
||||
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
|
||||
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
|
||||
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
|
||||
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
|
||||
table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}
|
||||
.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}
|
||||
table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}
|
||||
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
|
||||
.admonitionblock>table td.icon{text-align:center;width:80px}
|
||||
.admonitionblock>table td.icon img{max-width:none}
|
||||
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
|
||||
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
|
||||
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
|
||||
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
|
||||
.exampleblock>.content>:first-child{margin-top:0}
|
||||
.exampleblock>.content>:last-child{margin-bottom:0}
|
||||
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
|
||||
.sidebarblock>:first-child{margin-top:0}
|
||||
.sidebarblock>:last-child{margin-bottom:0}
|
||||
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
|
||||
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
|
||||
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
|
||||
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
|
||||
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
|
||||
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
|
||||
@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
|
||||
.listingblock pre.highlightjs{padding:0}
|
||||
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
|
||||
.listingblock pre.prettyprint{border-width:0}
|
||||
.listingblock>.content{position:relative}
|
||||
.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
|
||||
.listingblock:hover code[data-lang]:before{display:block}
|
||||
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
|
||||
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
|
||||
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
|
||||
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0}
|
||||
table.pyhltable td.code{padding-left:.75em;padding-right:0}
|
||||
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
|
||||
pre.pygments .lineno{display:inline-block;margin-right:.25em}
|
||||
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
|
||||
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
|
||||
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
|
||||
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
|
||||
.quoteblock blockquote{margin:0;padding:0;border:0}
|
||||
.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
|
||||
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
|
||||
.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
|
||||
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
|
||||
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
|
||||
.quoteblock .quoteblock blockquote:before{display:none}
|
||||
.verseblock{margin:0 1em 1.25em 1em}
|
||||
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
|
||||
.verseblock pre strong{font-weight:400}
|
||||
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
|
||||
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
|
||||
.quoteblock .attribution br,.verseblock .attribution br{display:none}
|
||||
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.05em;color:rgba(0,0,0,.6)}
|
||||
.quoteblock.abstract{margin:0 0 1.25em 0;display:block}
|
||||
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
|
||||
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
|
||||
table.tableblock{max-width:100%;border-collapse:separate}
|
||||
table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}
|
||||
table.spread{width:100%}
|
||||
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
|
||||
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
|
||||
table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0}
|
||||
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
|
||||
table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0}
|
||||
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0}
|
||||
table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0}
|
||||
table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0}
|
||||
table.frame-all{border-width:1px}
|
||||
table.frame-sides{border-width:0 1px}
|
||||
table.frame-topbot{border-width:1px 0}
|
||||
th.halign-left,td.halign-left{text-align:left}
|
||||
th.halign-right,td.halign-right{text-align:right}
|
||||
th.halign-center,td.halign-center{text-align:center}
|
||||
th.valign-top,td.valign-top{vertical-align:top}
|
||||
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
|
||||
th.valign-middle,td.valign-middle{vertical-align:middle}
|
||||
table thead th,table tfoot th{font-weight:bold}
|
||||
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
|
||||
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
|
||||
p.tableblock>code:only-child{background:none;padding:0}
|
||||
p.tableblock{font-size:1em}
|
||||
td>div.verse{white-space:pre}
|
||||
ol{margin-left:1.75em}
|
||||
ul li ol{margin-left:1.5em}
|
||||
dl dd{margin-left:1.125em}
|
||||
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
|
||||
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
|
||||
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
|
||||
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
|
||||
ul.checklist li>p:first-child>.fa-check-square-o:first-child,ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
|
||||
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{position:relative;top:1px}
|
||||
ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
|
||||
ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}
|
||||
ul.inline>li>*{display:block}
|
||||
.unstyled dl dt{font-weight:400;font-style:normal}
|
||||
ol.arabic{list-style-type:decimal}
|
||||
ol.decimal{list-style-type:decimal-leading-zero}
|
||||
ol.loweralpha{list-style-type:lower-alpha}
|
||||
ol.upperalpha{list-style-type:upper-alpha}
|
||||
ol.lowerroman{list-style-type:lower-roman}
|
||||
ol.upperroman{list-style-type:upper-roman}
|
||||
ol.lowergreek{list-style-type:lower-greek}
|
||||
.hdlist>table,.colist>table{border:0;background:none}
|
||||
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
|
||||
td.hdlist1{padding-right:.75em;font-weight:bold}
|
||||
td.hdlist1,td.hdlist2{vertical-align:top}
|
||||
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
|
||||
.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1}
|
||||
.colist>table tr>td:last-of-type{padding:.25em 0}
|
||||
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
|
||||
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
|
||||
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
|
||||
.imageblock>.title{margin-bottom:0}
|
||||
.imageblock.thumb,.imageblock.th{border-width:6px}
|
||||
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
|
||||
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
|
||||
.image.left{margin-right:.625em}
|
||||
.image.right{margin-left:.625em}
|
||||
a.image{text-decoration:none}
|
||||
span.footnote,span.footnoteref{vertical-align:super;font-size:.875em}
|
||||
span.footnote a,span.footnoteref a{text-decoration:none}
|
||||
span.footnote a:active,span.footnoteref a:active{text-decoration:underline}
|
||||
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
|
||||
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}
|
||||
#footnotes .footnote{padding:0 .375em;line-height:1.3;font-size:.875em;margin-left:1.2em;text-indent:-1.2em;margin-bottom:.2em}
|
||||
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}
|
||||
#footnotes .footnote:last-of-type{margin-bottom:0}
|
||||
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
|
||||
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
|
||||
.gist .file-data>table td.line-data{width:99%}
|
||||
div.unbreakable{page-break-inside:avoid}
|
||||
.big{font-size:larger}
|
||||
.small{font-size:smaller}
|
||||
.underline{text-decoration:underline}
|
||||
.overline{text-decoration:overline}
|
||||
.line-through{text-decoration:line-through}
|
||||
.aqua{color:#00bfbf}
|
||||
.aqua-background{background-color:#00fafa}
|
||||
.black{color:#000}
|
||||
.black-background{background-color:#000}
|
||||
.blue{color:#0000bf}
|
||||
.blue-background{background-color:#0000fa}
|
||||
.fuchsia{color:#bf00bf}
|
||||
.fuchsia-background{background-color:#fa00fa}
|
||||
.gray{color:#606060}
|
||||
.gray-background{background-color:#7d7d7d}
|
||||
.green{color:#006000}
|
||||
.green-background{background-color:#007d00}
|
||||
.lime{color:#00bf00}
|
||||
.lime-background{background-color:#00fa00}
|
||||
.maroon{color:#600000}
|
||||
.maroon-background{background-color:#7d0000}
|
||||
.navy{color:#000060}
|
||||
.navy-background{background-color:#00007d}
|
||||
.olive{color:#606000}
|
||||
.olive-background{background-color:#7d7d00}
|
||||
.purple{color:#600060}
|
||||
.purple-background{background-color:#7d007d}
|
||||
.red{color:#bf0000}
|
||||
.red-background{background-color:#fa0000}
|
||||
.silver{color:#909090}
|
||||
.silver-background{background-color:#bcbcbc}
|
||||
.teal{color:#006060}
|
||||
.teal-background{background-color:#007d7d}
|
||||
.white{color:#bfbfbf}
|
||||
.white-background{background-color:#fafafa}
|
||||
.yellow{color:#bfbf00}
|
||||
.yellow-background{background-color:#fafa00}
|
||||
span.icon>.fa{cursor:default}
|
||||
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
|
||||
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}
|
||||
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
|
||||
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}
|
||||
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
|
||||
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
|
||||
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
|
||||
.conum[data-value] *{color:#fff!important}
|
||||
.conum[data-value]+b{display:none}
|
||||
.conum[data-value]:after{content:attr(data-value)}
|
||||
pre .conum[data-value]{position:relative;top:-.125em}
|
||||
b.conum *{color:inherit!important}
|
||||
.conum:not([data-value]):empty{display:none}
|
||||
h1,h2{letter-spacing:-.01em}
|
||||
dt,th.tableblock,td.content{text-rendering:optimizeLegibility}
|
||||
p,td.content{letter-spacing:-.01em}
|
||||
p strong,td.content strong{letter-spacing:-.005em}
|
||||
p,blockquote,dt,td.content{font-size:1.0625rem}
|
||||
p{margin-bottom:1.25rem}
|
||||
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
|
||||
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
|
||||
.print-only{display:none!important}
|
||||
@media print{@page{margin:1.25cm .75cm}
|
||||
*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
|
||||
a{color:inherit!important;text-decoration:underline!important}
|
||||
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
|
||||
a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
|
||||
abbr[title]:after{content:" (" attr(title) ")"}
|
||||
pre,blockquote,tr,img{page-break-inside:avoid}
|
||||
thead{display:table-header-group}
|
||||
img{max-width:100%!important}
|
||||
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
|
||||
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
|
||||
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
|
||||
#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
|
||||
.sect1{padding-bottom:0!important}
|
||||
.sect1+.sect1{border:0!important}
|
||||
#header>h1:first-child{margin-top:1.25rem}
|
||||
body.book #header{text-align:center}
|
||||
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}
|
||||
body.book #header .details{border:0!important;display:block;padding:0!important}
|
||||
body.book #header .details span:first-child{margin-left:0!important}
|
||||
body.book #header .details br{display:block}
|
||||
body.book #header .details br+span:before{content:none!important}
|
||||
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
|
||||
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
|
||||
.listingblock code[data-lang]:before{display:block}
|
||||
#footer{background:none!important;padding:0 .9375em}
|
||||
#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}
|
||||
.hide-on-print{display:none!important}
|
||||
.print-only{display:block!important}
|
||||
.hide-for-print{display:none!important}
|
||||
.show-for-print{display:inherit!important}}
|
||||
</style>
|
||||
</head>
|
||||
<body class="article">
|
||||
<div id="header">
|
||||
<h1>Raspberry Pi camera/LED demo</h1>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div class="sect1">
|
||||
<h2 id="_overview">Overview</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>The <a href="/">demo</a> consists of web app providing access to a webcam and a LED attached to a RaspberryPi.
|
||||
The device is assumed to have a limited bandwidth towards the server hosting the web app.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_objective">Objective</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>The demo shows how to use websockets to communicate bidirectionally with an embedded device using standard protocols.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>It also shows that it’s possible to use Smart.c to develop also the cloud endpoint and expose WebSocket and RESTful APIs
|
||||
easy to integreate with modern web stacks.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_how_it_works">How it works</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="imageblock">
|
||||
<div class="content">
|
||||
<img src="docs/arch.png" alt="arch">
|
||||
</div>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>There are two components, once with runs on the device (<code>device_side</code>) and one that runs on a stronger machine
|
||||
and with more bandwidth (<code>cloud_side</code>).</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The device app connects to the cloud app via websocket and sends a new jpeg frame as fast as the underlying <code>raspistill</code> camera
|
||||
grabbing application can handle. The device automatically attempts reconnecting.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The cloud side serves the webapp static pages and serves an MPJEG image on <code>/mpjg</code>.
|
||||
The MPJEG image handler blocks all the clients until a JPEG frame arrives via websocket
|
||||
and then every client will receive a copy of the frame.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The web app can turn on and off the LED via a RESTful api accessible via the <code>/api</code> handler.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_installation">Installation</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="sect2">
|
||||
<h3 id="_server_side">Server side</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>git clone https://github.com/cesanta/mongoose
|
||||
cd mongoose/examples/web_demo
|
||||
make cloud_side && ./cloud_side 0.0.0.0:8080</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_raspberry_pi">Raspberry Pi</h3>
|
||||
<div class="paragraph">
|
||||
<p>The instructions provided here are tailored for the Raspbian distribution.</p>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
<h4 id="_dependencies">Dependencies</h4>
|
||||
<div class="dlist">
|
||||
<dl>
|
||||
<dt class="hdlist1">jpegoptim</dt>
|
||||
<dd>
|
||||
<p>apt-get install jpegoptim</p>
|
||||
</dd>
|
||||
<dt class="hdlist1">camera</dt>
|
||||
<dd>
|
||||
<p>run raspi-config and enable camera</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
<h4 id="_led">LED</h4>
|
||||
<div class="paragraph">
|
||||
<p>In order to access the led on your <a href="http://www.qdh.org.uk/wordpress/?page_id=15">HotPi</a>
|
||||
board you need to export the gpio pins:</p>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>for i in 22 23 24; do
|
||||
echo $i >/sys/class/gpio/export
|
||||
echo out >/sys/class/gpio/gpio$i/direction
|
||||
chgrp pi /sys/class/gpio/gpio$i/value
|
||||
done</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
<h4 id="_build_and_run">Build and run</h4>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>git clone https://github.com/cesanta/mongoose
|
||||
cd mongoose/examples/web_demo
|
||||
make device_side && ./device_side yourserver:8080</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2014-11-03 21:30:06 GMT
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
../../docs/arch.png
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Framework7 0.9.6
|
||||
* Full Featured HTML Framework For Building iOS 7 Apps
|
||||
*
|
||||
* http://www.idangero.us/framework7
|
||||
*
|
||||
* Copyright 2014, Vladimir Kharlampidi
|
||||
* The iDangero.us
|
||||
* http://www.idangero.us/
|
||||
*
|
||||
* Licensed under MIT
|
||||
*
|
||||
* Released on: September 13, 2014
|
||||
*/
|
||||
html,body{position:relative;height:100%;width:100%;overflow-x:hidden}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;margin:0;padding:0;color:#000;font-size:14px;line-height:1.4;width:100%;-webkit-text-size-adjust:100%;background:#fff;overflow:hidden}@media all and (width:1024px) and (height:691px) and (orientation:landscape){html,body{height:671px}}@media all and (width:1024px) and (height:692px) and (orientation:landscape){html,body{height:672px}}*{-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-touch-callout:none}a,input,textarea,select{outline:0}a{text-decoration:none;color:#007aff}p{margin:1em 0}.row{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;-webkit-box-lines:multiple;-moz-box-lines:multiple;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:start;-ms-flex-align:start;-webkit-align-items:flex-start;align-items:flex-start}.row>[class*=col-]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row .col-100{width:100%;width:-webkit-calc((100% - 15px*0) / 1);width:calc((100% - 15px*0) / 1)}.row.no-gutter .col-100{width:100%}.row .col-95{width:95%;width:-webkit-calc((100% - 15px*0.05263157894736836) / 1.0526315789473684);width:calc((100% - 15px*0.05263157894736836) / 1.0526315789473684)}.row.no-gutter .col-95{width:95%}.row .col-90{width:90%;width:-webkit-calc((100% - 15px*0.11111111111111116) / 1.1111111111111112);width:calc((100% - 15px*0.11111111111111116) / 1.1111111111111112)}.row.no-gutter .col-90{width:90%}.row .col-85{width:85%;width:-webkit-calc((100% - 15px*0.17647058823529416) / 1.1764705882352942);width:calc((100% - 15px*0.17647058823529416) / 1.1764705882352942)}.row.no-gutter .col-85{width:85%}.row .col-80{width:80%;width:-webkit-calc((100% - 15px*0.25) / 1.25);width:calc((100% - 15px*0.25) / 1.25)}.row.no-gutter .col-80{width:80%}.row .col-75{width:75%;width:-webkit-calc((100% - 15px*0.33333333333333326) / 1.3333333333333333);width:calc((100% - 15px*0.33333333333333326) / 1.3333333333333333)}.row.no-gutter .col-75{width:75%}.row .col-66{width:66.66666666666666%;width:-webkit-calc((100% - 15px*0.5000000000000002) / 1.5000000000000002);width:calc((100% - 15px*0.5000000000000002) / 1.5000000000000002)}.row.no-gutter .col-66{width:66.66666666666666%}.row .col-60{width:60%;width:-webkit-calc((100% - 15px*0.6666666666666667) / 1.6666666666666667);width:calc((100% - 15px*0.6666666666666667) / 1.6666666666666667)}.row.no-gutter .col-60{width:60%}.row .col-50{width:50%;width:-webkit-calc((100% - 15px*1) / 2);width:calc((100% - 15px*1) / 2)}.row.no-gutter .col-50{width:50%}.row .col-40{width:40%;width:-webkit-calc((100% - 15px*1.5) / 2.5);width:calc((100% - 15px*1.5) / 2.5)}.row.no-gutter .col-40{width:40%}.row .col-33{width:33.333333333333336%;width:-webkit-calc((100% - 15px*2) / 3);width:calc((100% - 15px*2) / 3)}.row.no-gutter .col-33{width:33.333333333333336%}.row .col-25{width:25%;width:-webkit-calc((100% - 15px*3) / 4);width:calc((100% - 15px*3) / 4)}.row.no-gutter .col-25{width:25%}.row .col-20{width:20%;width:-webkit-calc((100% - 15px*4) / 5);width:calc((100% - 15px*4) / 5)}.row.no-gutter .col-20{width:20%}.row .col-15{width:15%;width:-webkit-calc((100% - 15px*5.666666666666667) / 6.666666666666667);width:calc((100% - 15px*5.666666666666667) / 6.666666666666667)}.row.no-gutter .col-15{width:15%}.row .col-10{width:10%;width:-webkit-calc((100% - 15px*9) / 10);width:calc((100% - 15px*9) / 10)}.row.no-gutter .col-10{width:10%}.row .col-5{width:5%;width:-webkit-calc((100% - 15px*19) / 20);width:calc((100% - 15px*19) / 20)}.row.no-gutter .col-5{width:5%}@media all and (min-width:768px){.row .tablet-100{width:100%;width:-webkit-calc((100% - 15px*0) / 1);width:calc((100% - 15px*0) / 1)}.row.no-gutter .tablet-100{width:100%}.row .tablet-95{width:95%;width:-webkit-calc((100% - 15px*0.05263157894736836) / 1.0526315789473684);width:calc((100% - 15px*0.05263157894736836) / 1.0526315789473684)}.row.no-gutter .tablet-95{width:95%}.row .tablet-90{width:90%;width:-webkit-calc((100% - 15px*0.11111111111111116) / 1.1111111111111112);width:calc((100% - 15px*0.11111111111111116) / 1.1111111111111112)}.row.no-gutter .tablet-90{width:90%}.row .tablet-85{width:85%;width:-webkit-calc((100% - 15px*0.17647058823529416) / 1.1764705882352942);width:calc((100% - 15px*0.17647058823529416) / 1.1764705882352942)}.row.no-gutter .tablet-85{width:85%}.row .tablet-80{width:80%;width:-webkit-calc((100% - 15px*0.25) / 1.25);width:calc((100% - 15px*0.25) / 1.25)}.row.no-gutter .tablet-80{width:80%}.row .tablet-75{width:75%;width:-webkit-calc((100% - 15px*0.33333333333333326) / 1.3333333333333333);width:calc((100% - 15px*0.33333333333333326) / 1.3333333333333333)}.row.no-gutter .tablet-75{width:75%}.row .tablet-66{width:66.66666666666666%;width:-webkit-calc((100% - 15px*0.5000000000000002) / 1.5000000000000002);width:calc((100Line truncated
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Framework7 0.9.6
|
||||
* Full Featured HTML Framework For Building iOS 7 Apps
|
||||
*
|
||||
* http://www.idangero.us/framework7
|
||||
*
|
||||
* Copyright 2014, Vladimir Kharlampidi
|
||||
* The iDangero.us
|
||||
* http://www.idangero.us/
|
||||
*
|
||||
* Licensed under MIT
|
||||
*
|
||||
* Released on: September 13, 2014
|
||||
*/
|
||||
!function(){"use strict";window.Framework7=function(b){function c(a){var b=a.replace(/^./,function(a){return a.toUpperCase()});l["onPage"+b]=function(b,c){return l.onPage(a,b,c)}}function d(a,b,c){b=n(b);var d=b.find(a);return d.length>1&&("string"==typeof c.selector&&(d=b.find(c.selector+" "+a)),d.length>1&&(d=b.find("."+l.params.viewMainClass+" "+a))),1===d.length?d:void 0}function e(a,b,c){"to-left"===c&&(a.removeClass("page-on-center").addClass("page-from-center-to-left"),b.addClass("page-from-right-to-center")),"to-right"===c&&(a.removeClass("page-on-left").addClass("page-from-left-to-center"),b.removeClass("page-on-center").addClass("page-from-center-to-right"))}function f(a,b,c){"to-left"===c&&(b.removeClass("navbar-on-right").addClass("navbar-from-right-to-center"),b.find(".sliding").each(function(){var a=n(this);a.transform("translate3d(0px,0,0)"),l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d(0px,0,0)")}),a.removeClass("navbar-on-center").addClass("navbar-from-center-to-left"),a.find(".sliding").each(function(){var a=n(this);l.params.animateNavBackIcon&&(a.hasClass("center")&&b.find(".sliding.left .back .icon").length>0&&(this.f7NavbarLeftOffset+=b.find(".sliding.left .back span")[0].offsetLeft),a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d("+-this.f7NavbarLeftOffset+"px,0,0)")),a.transform("translate3d("+this.f7NavbarLeftOffset+"px,0,0)")})),"to-right"===c&&(a.removeClass("navbar-on-left").addClass("navbar-from-left-to-center"),a.find(".sliding").each(function(){var a=n(this);a.transform("translate3d(0px,0,0)"),l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d(0px,0,0)")}),b.removeClass("navbar-on-center").addClass("navbar-from-center-to-right"),b.find(".sliding").each(function(){var a=n(this);l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d("+-this.f7NavbarRightOffset+"px,0,0)"),a.transform("translate3d("+this.f7NavbarRightOffset+"px,0,0)")}))}function g(a,b,c,e){var f,g,h,i,j,k,m,o,p=n(a.container),q=n(a.pagesContainer);if(l.pluginHook("loadPage",a,b,c),l._tempDomElement.innerHTML="",b||"string"==typeof c)l._tempDomElement.innerHTML=c;else if("length"in c&&c.length>1)for(var r=0;r<c.length;r++)n(l._tempDomElement).append(c[r]);else n(l._tempDomElement).append(c);if(o=e.reloadPrevious?"left":"center",f=d(".page",l._tempDomElement,a),!f)return void(a.allowPageChange=!0);if(f.addClass("page-on-"+o),h=q.children(".page:not(.cached)"),e.reloadPrevious&&1===h.length)return void(a.allowPageChange=!0);g=h.eq(h.length-1),a.params.dynamicNavbar&&(m=!0,j=d(".navbar-inner",l._tempDomElement,a),j||(m=!1),k=p.find(".navbar"),i=k.find(".navbar-inner:last-child")),m&&j.addClass("navbar-on-"+o),b||(b="#content-"+a.history.length,a.params.domCache||(a.contentCache[b]=c)),a.url=b;var s=a.history[a.history.length-(e.reloadPrevious?2:1)];0===s.indexOf("#content")&&s in a.contentCache&&(a.contentCache[s]=null,delete a.contentCache[s]),a.history[a.history.length-(e.reloadPrevious?2:1)]=b,e.reloadPrevious?(g=g.prev(".page"),f.insertBefore(g),m&&(i=i.prev(".navbar-inner"),j.insertAfter(i))):(q.append(f[0]),m&&k.append(j[0])),l.pageRemoveCallback(a,g[0],o),g.remove(),m&&i.remove(),l.pageInitCallback(a,f[0],b,o,m?j[0]:void 0),m&&l.navbarInitCallback(a,f[0],k[0],j[0],b,o),a.allowPageChange=!0}function h(a,b,c,g){function h(){a.allowPageChange=!0,i.removeClass("page-from-right-to-center page-on-right").addClass("page-on-center"),j.removeClass("page-from-center-to-left page-on-center").addClass("page-on-left"),r&&(p.removeClass("navbar-from-right-to-center navbar-on-right").addClass("navbar-on-center"),o.removeClass("navbar-from-center-to-left navbar-on-center").addClass("navbar-on-left")),l.pageAnimCallbacks("after",a,{pageContainer:i[0],url:b,position:"right",oldPage:j,newPage:i}),l.params.pushState&&l.pushStateClearQueue(),a.params.swipeBackPage||a.params.preloadPreviousPage||(a.params.domCache?(j.addClass("cached"),o.addClass("cached")):(j.remove(),o.remove())),a.params.uniqueHistory&&w&&a.refreshPreviousPage()}var i,j,k,m,o,p,q,r,s=n(a.container),t=n(a.pagesContainer),u=g.animatePages;if("undefined"==typeof u&&(u=a.params.animatePages),l.pluginHook("loadPage",a,b,c),l._tempDomElement.innerHTML="",b||"string"==typeof c)l._tempDomElement.innerHTML=c;else if("length"in c&&c.length>1)for(var v=0;v<c.length;v++)n(l._tempDomElement).append(c[v]);else n(l._tempDomElement).append(c);if(i=d(".page",l._tempDomElement,a),!i)return void(a.allowPageChange=!0);if(i.addClass("page-on-right"),k=t.children(".page:not(.cached)"),k.length>1){for(m=0;m<k.length-2;m++)a.params.domCache?n(k[m]).addClass("cached"):(l.pageRemoveCallback(a,k[m],"left"),n(k[m]).remove());a.params.domCache?n(k[m]).addClass("cached"):(l.pageRemoveCallback(a,k[m],"left"),n(k[m]).removeLine truncated
|
||||
var e=a.title?'<div class="modal-title">'+a.title+"</div>":"",f=a.title?'<div class="modal-text">'+a.text+"</div>":"",g=a.afterText?a.afterText:"",h=a.buttons&&0!==a.buttons.length?"":"modal-no-buttons";b='<div class="modal '+h+'"><div class="modal-inner">'+(e+f+g)+'</div><div class="modal-buttons">'+c+"</div></div>"}s.innerHTML=b;var i=n(s).children();return n("body").append(i[0]),i.find(".modal-button").each(function(b,c){n(c).on("click",function(c){a.buttons[b].close!==!1&&l.closeModal(i),a.buttons[b].onClick&&a.buttons[b].onClick(i,c),a.onClick&&a.onClick(i,b)})}),l.openModal(i),i[0]},l.alert=function(a,b,c){return"function"==typeof b&&(c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,buttons:[{text:l.params.modalButtonOk,bold:!0,onClick:c}]})},l.confirm=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,buttons:[{text:l.params.modalButtonCancel,onClick:d},{text:l.params.modalButtonOk,bold:!0,onClick:c}]})},l.prompt=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="text" class="modal-text-input">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){0===b&&d&&d(n(a).find(".modal-text-input").val()),1===b&&c&&c(n(a).find(".modal-text-input").val())}})},l.modalLogin=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="text" name="modal-username" placeholder="'+l.params.modalUsernamePlaceholder+'" class="modal-text-input modal-text-input-double"><input type="password" name="modal-password" placeholder="'+l.params.modalPasswordPlaceholder+'" class="modal-text-input modal-text-input-double">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){var e=n(a).find('.modal-text-input[name="modal-username"]').val(),f=n(a).find('.modal-text-input[name="modal-password"]').val();0===b&&d&&d(e,f),1===b&&c&&c(e,f)}})},l.modalPassword=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="password" name="modal-password" placeholder="'+l.params.modalPasswordPlaceholder+'" class="modal-text-input">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){var e=n(a).find('.modal-text-input[name="modal-password"]').val();0===b&&d&&d(e),1===b&&c&&c(e)}})},l.showPreloader=function(a){return l.modal({title:a||l.params.modalPreloaderTitle,text:'<div class="preloader"></div>'})},l.hidePreloader=function(){l.closeModal(".modal.modal-in")},l.showIndicator=function(){n("body").append('<div class="preloader-indicator-overlay"></div><div class="preloader-indicator-modal"><span class="preloader preloader-white"></span></div>')},l.hideIndicator=function(){n(".preloader-indicator-overlay, .preloader-indicator-modal").remove()},l.actions=function(a,b){var c,d,e,f=!1;1===arguments.length?b=a:l.device.ios?l.device.ipad&&(f=!0):n(window).width()>=768&&(f=!0),b=b||[],b.length>0&&!n.isArray(b[0])&&(b=[b]);var g;if(f){var h='<div class="popover actions-popover"><div class="popover-inner">{{#each this}}<div class="list-block"><ul>{{#each this}}{{#if label}}<li class="actions-popover-label {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}">{{text}}</li>{{else}}<li><a href="#" class="item-link list-button {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}">{{text}}</a></li>{{/if}}{{/each}}</ul></div>{{/each}}</div></div>';l._compiledTemplates.actionsPopover||(l._compiledTemplates.actionsPopover=o.compile(h));var i=l._compiledTemplates.actionsPopover(b);c=n(l.popover(i,a,!0)),d=".list-block ul",e=".list-button"}else{if(l.params.modalActionsTemplate)l._compiledTemplates.actions||(l._compiledTemplates.actions=o.compile(l.params.modalActionsTemplate)),g=l._compiledTemplates.actions(b);else{for(var j="",k=0;k<b.length;k++)for(var m=0;m<b[k].length;m++){0===m&&(j+='<div class="actions-modal-group">');var p=b[k][m],q=p.label?"actions-modal-label":"actions-modal-button";p.bold&&(q+=" actions-modal-button-bold"),p.color&&(q+=" color-"+p.color),j+='<span class="'+q+'">'+p.text+"</span>",m===b[k].length-1&&(j+="</div>")}g='<div class="actions-modal">'+j+"</div>"}s.innerHTML=g,c=n(s).children(),n("body").append(c[0]),d=".actions-modal-group",e=".actions-modal-button"}var r=c.find(d);return r.each(function(a,d){var g=a;n(d).children().each(function(a,d){var h,i=a,j=b[g][i];!f&&n(d).is(e)&&(h=n(d)),f&&n(d).find(e).length>0&&(h=n(d).find(e)),h&&h.on("click",function(a){j.close!==!1&&l.closeModal(c),j.onClick&&j.onClick(c,a)})})}),f||l.oLine truncated
|
||||
}function b(){var a=B.parents(".page-content, .panel");return 0===a.length?!1:("yes"!==a.prop("scrollHandlerSet")&&(a.on("scroll",function(){clearTimeout(C)}),a.prop("scrollHandlerSet","yes")),!0)}function c(){B.addClass("active-state")}function d(){B.removeClass("active-state")}function e(a){var b="button checkbox file image radio submit input textarea".split(" ");return document.activeElement&&a!==document.activeElement&&document.activeElement!==document.body?b.indexOf(a.nodeName.toLowerCase())>=0?!1:!0:!1}function f(a){return"input"===a.nodeName.toLowerCase()&&"file"===a.type?!1:a.className.indexOf("no-fastclick")>=0?!1:!0}function g(a){if(document.activeElement===a)return!1;var b=a.nodeName.toLowerCase(),c="button checkbox file image radio submit".split(" ");return a.disabled||a.readOnly?!1:"textarea"===b?!0:"select"===b?"android"===l.device.os?!1:!0:"input"===b&&c.indexOf(a.type)<0?!0:void 0}function h(a){if(a=n(a),a.is("label")||a.parents("label").length>0){if("android"===l.device.os){var b=l.device.osVersion.split(".");return 1*b[0]>4||1*b[0]===4&&1*b[1]>=4?!1:!0}return!1}return!0}function i(b){a(b).addClass("active-state"),"which"in b&&3===b.which&&setTimeout(function(){n(".active-state").removeClass("active-state")},0)}function j(){n(".active-state").removeClass("active-state")}function k(){n(".active-state").removeClass("active-state")}function m(d){if(A=!1,d.targetTouches.length>1)return!0;if(D=f(d.target),!D)return w=!1,!0;if("ios"===l.device.os){var g=window.getSelection();if(g.rangeCount&&(!g.isCollapsed||document.activeElement===g.focusNode))return x=!0,!0;x=!1}"android"===l.device.os&&e(d.target)&&document.activeElement.blur(),w=!0,v=d.target,u=(new Date).getTime(),s=d.targetTouches[0].pageX,t=d.targetTouches[0].pageY,"ios"===l.device.os&&(y=void 0,n(v).parents().each(function(){var a=this;a.scrollHeight>a.offsetHeight&&!y&&(y=a,y.f7ScrollTop=y.scrollTop)})),d.timeStamp-z<200&&d.preventDefault(),l.params.activeState&&(B=a(d),b(d)?C=setTimeout(c,80):c())}function o(a){if(w){var b=!1,c=l.params.fastClicksDistanceThreshold;if(c){var e=a.targetTouches[0].pageX,f=a.targetTouches[0].pageY;(Math.abs(e-s)>c||Math.abs(f-t)>c)&&(b=!0)}else b=!0;b&&(w=!1,v=null,A=!0),l.params.activeState&&(clearTimeout(C),d())}}function p(a){if(clearTimeout(C),!w)return!x&&D&&a.preventDefault(),!0;if(document.activeElement===a.target)return!0;if(x||a.preventDefault(),a.timeStamp-z<200)return!0;if(z=a.timeStamp,u=0,w=!1,"ios"===l.device.os&&y&&y.scrollTop!==y.f7ScrollTop)return!1;l.params.activeState&&(c(),setTimeout(d,0)),g(v)&&v.focus(),a.preventDefault();var b=a.changedTouches[0],e=document.createEvent("MouseEvents"),f="click";return"android"===l.device.os&&"select"===v.nodeName.toLowerCase()&&(f="mousedown"),e.initMouseEvent(f,!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null),e.forwardedTouchEvent=!0,v.dispatchEvent(e),!1}function q(){w=!1,v=null}function r(a){var b=!1;return w?(v=null,w=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(v||(b=!0),document.activeElement===v&&(b=!0),a.forwardedTouchEvent&&(b=!0),a.cancelable||(b=!0),b||(a.stopImmediatePropagation(),a.stopPropagation(),v?(h(v)||A)&&a.preventDefault():a.preventDefault(),v=null),b)}l.params.activeState&&n("html").addClass("watch-active-state");var s,t,u,v,w,x,y,z,A,B,C,D;l.support.touch?(document.addEventListener("click",r,!0),l.addGlobalEventListener("touchstart",m),l.addGlobalEventListener("touchmove",o),l.addGlobalEventListener("touchend",p),l.addGlobalEventListener("touchcancel",q)):l.params.activeState&&(l.addGlobalEventListener("mousedown",i),l.addGlobalEventListener("mousemove",j),l.addGlobalEventListener("mouseup",k))},l.initClickEvents=function(){function a(a){function b(a){return"false"===a?!1:"true"===a?!0:void 0}var c=n(this),d=c.attr("href"),e="a"===c[0].nodeName.toLowerCase();if(e)for(var f=0;f<l.params.externalLinks.length;f++){if(c.hasClass(l.params.externalLinks[f]))return;if(c[0].rel===l.params.externalLinks[f])return}if(c.hasClass("smart-select")&&l.smartSelectOpen&&l.smartSelectOpen(c),c.hasClass("open-panel")&&l.openPanel(1===n(".panel").length?n(".panel").hasClass("panel-left")?"left":"right":"right"===c.attr("data-panel")?"right":"left"),c.hasClass("close-panel")&&l.closePanel(),c.hasClass("panel-overlay")&&l.params.panelsCloseByOutside&&l.closePanel(),c.hasClass("open-popover")){var g;g=c.attr("data-popover")?c.attr("data-popover"):".popover",l.popover(g,c)}c.hasClass("close-popover")&&l.closeModal(".popover.modal-in");var h;c.hasClass("open-popup")&&(h=c.attr("data-popup")?c.attr("data-popup"):".popup",l.popup(h)),c.hasClass("close-popup")&&(h=c.attr("data-popup")?c.attr("data-popup"):".popup.modal-in",l.closeModal(h));var i;if(c.hasClass("open-login-screen")&&(i=c.attr("data-login-screen")?c.attr("data-login-screen"):".login-screen",l.loginScreen(i)),c.hasClass("close-login-screen")&&l.closeModal(".login-screen.modal-in"),c.hasClass("modal-overlay")&&(n(".modal.modal-in").length>0&&l.params.modalCloseByLine truncated
|
||||
l.addNotification=function(a){if(a){"undefined"==typeof a.media&&(a.media=l.params.notificationMedia),"undefined"==typeof a.title&&(a.title=l.params.notificationTitle),"undefined"==typeof a.subtitle&&(a.subtitle=l.params.notificationSubtitle),"undefined"==typeof a.closeIcon&&(a.closeIcon=l.params.notificationCloseIcon),"undefined"==typeof a.hold&&(a.hold=l.params.notificationHold),"undefined"==typeof a.closeOnClick&&(a.closeOnClick=l.params.notificationCloseOnClick),v||(v=document.createElement("div"));var b=n(".notifications");0===b.length&&(n("body").append('<div class="notifications list-block media-list"><ul></ul></div>'),b=n(".notifications"));var c,d=b.children("ul");c=a.custom?"<li>"+a.custom+"</li>":'<li class="notification-item notification-hidden"><div class="item-content">'+(a.media?'<div class="item-media">'+a.media+"</div>":"")+'<div class="item-inner"><div class="item-title-row">'+(a.title?'<div class="item-title">'+a.title+"</div>":"")+(a.closeIcon?'<div class="item-after"><a href="#" class="close-notification"><span></span></a></div>':"")+"</div>"+(a.subtitle?'<div class="item-subtitle">'+a.subtitle+"</div>":"")+(a.message?'<div class="item-text">'+a.message+"</div>":"")+"</div></div></li>",v.innerHTML=c;var e=n(v).children();e.on("click",function(b){var c=!1;n(b.target).is(".close-notification")||n(b.target).parents(".close-notification").length>0?c=!0:(a.onClick&&a.onClick(b,e[0]),a.closeOnClick&&(c=!0)),c&&l.closeNotification(e[0])}),a.onClose&&e.data("f7NotificationOnClose",function(){a.onClose(e[0])}),a.additionalClass&&e.addClass(a.additionalClass),a.hold&&setTimeout(function(){e.length>0&&l.closeNotification(e[0])},a.hold),d.prepend(e[0]),b.show();var f=e.height();e.css("marginTop",-f+"px"),e.transition(0);{e[0].clientLeft}return e.transition(""),e.css("marginTop","0px"),b.transform("translate3d(0, 0,0)"),e.removeClass("notification-hidden"),e[0]}},l.closeNotification=function(a){if(a=n(a),0!==a.length&&!a.hasClass("notification-item-removing")){var b=n(".notifications"),c=a.height();a.css("height",c+"px").transition(0);{a[0].clientLeft}a.css("height","0px").transition("").addClass("notification-item-removing"),a.data("f7NotificationOnClose")&&a.data("f7NotificationOnClose")(),0===b.find(".notification-item:not(.notification-item-removing)").length&&b.transform(""),a.addClass("notification-hidden").transitionEnd(function(){a.remove(),0===b.find(".notification-item").length&&b.hide()})}};var w=[];return l.initPlugins=function(){for(var a in l.plugins){var b=l.plugins[a](l,l.params[a]);b&&w.push(b)}},l.pluginHook=function(a){for(var b=0;b<w.length;b++)w[b].hooks&&a in w[b].hooks&&w[b].hooks[a](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])},l.pluginPrevent=function(a){for(var b=!1,c=0;c<w.length;c++)w[c].prevents&&a in w[c].prevents&&w[c].prevents[a](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])&&(b=!0);return b},l.pluginProcess=function(a,b){for(var c=b,d=0;d<w.length;d++)w[d].preprocess&&process in w[d].preprocess&&(c=w[d].preprocess[process](b,arguments[2],arguments[3],arguments[4],arguments[5],arguments[6]));return c},l.init=function(){l.initPlugins&&l.initPlugins(),l.getDeviceInfo&&l.getDeviceInfo(),l.initFastClicks&&l.params.fastClicks&&l.initFastClicks(),l.initClickEvents&&l.initClickEvents(),n(".page").each(function(){var a=n(this),b=a.parents("."+l.params.viewClass);if(0!==b.length){var c=b[0].f7View||!1,d=c&&c.url?c.url:!1;b&&b.attr("data-page",a.attr("data-page")||void 0),l.pageInitCallback(c,this,d,"center")}}),l.initResize&&l.initResize(),l.initPushState&&l.params.pushState&&l.initPushState(),l.initSwipeout&&l.params.swipeout&&l.initSwipeout(),l.initSortable&&l.params.sortable&&l.initSortable(),l.initSwipePanels&&l.params.swipePanel&&l.initSwipePanels(),l.params.onAppInit&&l.params.onAppInit(),l.pluginHook("appInit")},l.params.init&&l.init(),l};var a=function(){var a=function(a){var b=this,c=0;for(c=0;c<a.length;c++)b[c]=a[c];return b.length=a.length,this},b=function(b,c){var d=[],e=0;if(b&&!c&&b instanceof a)return b;if(b)if("string"==typeof b){var f=(c||document).querySelectorAll(b);for(e=0;e<f.length;e++)d.push(f[e])}else if(b.nodeType||b===window||b===document)d.push(b);else if(b.length>0&&b[0].nodeType)for(e=0;e<b.length;e++)d.push(b[e]);return new a(d)};a.prototype={addClass:function(a){if("undefined"==typeof a)return this;for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.add(b[c]);return this},removeClass:function(a){for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.remove(b[c]);return this},hasClass:function(a){return this[0]?this[0].classList.contains(a):!1},toggleClass:function(a){for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.toggle(b[c]);return this},attr:function(a,b){if("undefined"==typeof b)return this[0]?this[0].getAttribute(a):void 0;for(var c=0;c<this.length;c++)this[c].setAttribute(a,b);return tLine truncated
|
||||
@@ -1,108 +0,0 @@
|
||||
<!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>
|
||||
-758
@@ -610,603 +610,6 @@ double cs_time() {
|
||||
return now;
|
||||
}
|
||||
#ifdef MG_MODULE_LINES
|
||||
#line 1 "./src/../deps/frozen/frozen.c"
|
||||
#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 <http://cesanta.com/products.html>.
|
||||
*/
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
/* Amalgamated: #include "frozen.h" */
|
||||
|
||||
#ifdef _WIN32
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#ifndef FROZEN_REALLOC
|
||||
#define FROZEN_REALLOC realloc
|
||||
#endif
|
||||
|
||||
#ifndef FROZEN_FREE
|
||||
#define FROZEN_FREE free
|
||||
#endif
|
||||
|
||||
struct frozen {
|
||||
const char *end;
|
||||
const char *cur;
|
||||
struct json_token *tokens;
|
||||
int max_tokens;
|
||||
int num_tokens;
|
||||
int do_realloc;
|
||||
};
|
||||
|
||||
static int parse_object(struct frozen *f);
|
||||
static int parse_value(struct frozen *f);
|
||||
|
||||
#define EXPECT(cond, err_code) \
|
||||
do { \
|
||||
if (!(cond)) return (err_code); \
|
||||
} while (0)
|
||||
#define TRY(expr) \
|
||||
do { \
|
||||
int _n = expr; \
|
||||
if (_n < 0) return _n; \
|
||||
} while (0)
|
||||
#define END_OF_STRING (-1)
|
||||
|
||||
static int left(const struct frozen *f) {
|
||||
return f->end - f->cur;
|
||||
}
|
||||
|
||||
static int is_space(int ch) {
|
||||
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
|
||||
}
|
||||
|
||||
static void skip_whitespaces(struct frozen *f) {
|
||||
while (f->cur < f->end && is_space(*f->cur)) f->cur++;
|
||||
}
|
||||
|
||||
static int cur(struct frozen *f) {
|
||||
skip_whitespaces(f);
|
||||
return f->cur >= f->end ? END_OF_STRING : *(unsigned char *) f->cur;
|
||||
}
|
||||
|
||||
static int test_and_skip(struct frozen *f, int expected) {
|
||||
int ch = cur(f);
|
||||
if (ch == expected) {
|
||||
f->cur++;
|
||||
return 0;
|
||||
}
|
||||
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
||||
}
|
||||
|
||||
static int test_no_skip(struct frozen *f, int expected) {
|
||||
int ch = cur(f);
|
||||
if (ch == expected) {
|
||||
return 0;
|
||||
}
|
||||
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
||||
}
|
||||
|
||||
static int is_alpha(int ch) {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
||||
}
|
||||
|
||||
static int is_digit(int ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
static int is_hex_digit(int ch) {
|
||||
return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
|
||||
}
|
||||
|
||||
static int get_escape_len(const char *s, int len) {
|
||||
switch (*s) {
|
||||
case 'u':
|
||||
return len < 6 ? JSON_STRING_INCOMPLETE
|
||||
: is_hex_digit(s[1]) && is_hex_digit(s[2]) &&
|
||||
is_hex_digit(s[3]) && is_hex_digit(s[4])
|
||||
? 5
|
||||
: JSON_STRING_INVALID;
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
case 'b':
|
||||
case 'f':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
return len < 2 ? JSON_STRING_INCOMPLETE : 1;
|
||||
default:
|
||||
return JSON_STRING_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static int capture_ptr(struct frozen *f, const char *ptr, enum json_type type) {
|
||||
if (f->do_realloc && f->num_tokens >= f->max_tokens) {
|
||||
int new_size = f->max_tokens == 0 ? 100 : f->max_tokens * 2;
|
||||
void *p = FROZEN_REALLOC(f->tokens, new_size * sizeof(f->tokens[0]));
|
||||
if (p == NULL) return JSON_TOKEN_ARRAY_TOO_SMALL;
|
||||
f->max_tokens = new_size;
|
||||
f->tokens = (struct json_token *) p;
|
||||
}
|
||||
if (f->tokens == NULL || f->max_tokens == 0) return 0;
|
||||
if (f->num_tokens >= f->max_tokens) return JSON_TOKEN_ARRAY_TOO_SMALL;
|
||||
f->tokens[f->num_tokens].ptr = ptr;
|
||||
f->tokens[f->num_tokens].type = type;
|
||||
f->num_tokens++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int capture_len(struct frozen *f, int token_index, const char *ptr) {
|
||||
if (f->tokens == 0 || f->max_tokens == 0) return 0;
|
||||
EXPECT(token_index >= 0 && token_index < f->max_tokens, JSON_STRING_INVALID);
|
||||
f->tokens[token_index].len = ptr - f->tokens[token_index].ptr;
|
||||
f->tokens[token_index].num_desc = (f->num_tokens - 1) - token_index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* identifier = letter { letter | digit | '_' } */
|
||||
static int parse_identifier(struct frozen *f) {
|
||||
EXPECT(is_alpha(cur(f)), JSON_STRING_INVALID);
|
||||
TRY(capture_ptr(f, f->cur, JSON_TYPE_STRING));
|
||||
while (f->cur < f->end &&
|
||||
(*f->cur == '_' || is_alpha(*f->cur) || is_digit(*f->cur))) {
|
||||
f->cur++;
|
||||
}
|
||||
capture_len(f, f->num_tokens - 1, f->cur);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_utf8_char_len(unsigned char ch) {
|
||||
if ((ch & 0x80) == 0) return 1;
|
||||
switch (ch & 0xf0) {
|
||||
case 0xf0:
|
||||
return 4;
|
||||
case 0xe0:
|
||||
return 3;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* string = '"' { quoted_printable_chars } '"' */
|
||||
static int parse_string(struct frozen *f) {
|
||||
int n, ch = 0, len = 0;
|
||||
TRY(test_and_skip(f, '"'));
|
||||
TRY(capture_ptr(f, f->cur, JSON_TYPE_STRING));
|
||||
for (; f->cur < f->end; f->cur += len) {
|
||||
ch = *(unsigned char *) f->cur;
|
||||
len = get_utf8_char_len((unsigned char) ch);
|
||||
EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */
|
||||
EXPECT(len < left(f), JSON_STRING_INCOMPLETE);
|
||||
if (ch == '\\') {
|
||||
EXPECT((n = get_escape_len(f->cur + 1, left(f))) > 0, n);
|
||||
len += n;
|
||||
} else if (ch == '"') {
|
||||
capture_len(f, f->num_tokens - 1, f->cur);
|
||||
f->cur++;
|
||||
break;
|
||||
};
|
||||
}
|
||||
return ch == '"' ? 0 : JSON_STRING_INCOMPLETE;
|
||||
}
|
||||
|
||||
/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */
|
||||
static int parse_number(struct frozen *f) {
|
||||
int ch = cur(f);
|
||||
TRY(capture_ptr(f, f->cur, JSON_TYPE_NUMBER));
|
||||
if (ch == '-') f->cur++;
|
||||
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
||||
EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID);
|
||||
while (f->cur < f->end && is_digit(f->cur[0])) f->cur++;
|
||||
if (f->cur < f->end && f->cur[0] == '.') {
|
||||
f->cur++;
|
||||
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
||||
EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID);
|
||||
while (f->cur < f->end && is_digit(f->cur[0])) f->cur++;
|
||||
}
|
||||
if (f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) {
|
||||
f->cur++;
|
||||
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
||||
if ((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++;
|
||||
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
||||
EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID);
|
||||
while (f->cur < f->end && is_digit(f->cur[0])) f->cur++;
|
||||
}
|
||||
capture_len(f, f->num_tokens - 1, f->cur);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* array = '[' [ value { ',' value } ] ']' */
|
||||
static int parse_array(struct frozen *f) {
|
||||
int ind;
|
||||
TRY(test_and_skip(f, '['));
|
||||
TRY(capture_ptr(f, f->cur - 1, JSON_TYPE_ARRAY));
|
||||
ind = f->num_tokens - 1;
|
||||
while (cur(f) != ']') {
|
||||
TRY(parse_value(f));
|
||||
if (cur(f) == ',') f->cur++;
|
||||
}
|
||||
TRY(test_and_skip(f, ']'));
|
||||
capture_len(f, ind, f->cur);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compare(const char *s, const char *str, int len) {
|
||||
int i = 0;
|
||||
while (i < len && s[i] == str[i]) i++;
|
||||
return i == len ? 1 : 0;
|
||||
}
|
||||
|
||||
static int expect(struct frozen *f, const char *s, int len, enum json_type t) {
|
||||
int i, n = left(f);
|
||||
|
||||
TRY(capture_ptr(f, f->cur, t));
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i >= n) return JSON_STRING_INCOMPLETE;
|
||||
if (f->cur[i] != s[i]) return JSON_STRING_INVALID;
|
||||
}
|
||||
f->cur += len;
|
||||
TRY(capture_len(f, f->num_tokens - 1, f->cur));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* value = 'null' | 'true' | 'false' | number | string | array | object */
|
||||
static int parse_value(struct frozen *f) {
|
||||
int ch = cur(f);
|
||||
|
||||
switch (ch) {
|
||||
case '"':
|
||||
TRY(parse_string(f));
|
||||
break;
|
||||
case '{':
|
||||
TRY(parse_object(f));
|
||||
break;
|
||||
case '[':
|
||||
TRY(parse_array(f));
|
||||
break;
|
||||
case 'n':
|
||||
TRY(expect(f, "null", 4, JSON_TYPE_NULL));
|
||||
break;
|
||||
case 't':
|
||||
TRY(expect(f, "true", 4, JSON_TYPE_TRUE));
|
||||
break;
|
||||
case 'f':
|
||||
TRY(expect(f, "false", 5, JSON_TYPE_FALSE));
|
||||
break;
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
TRY(parse_number(f));
|
||||
break;
|
||||
default:
|
||||
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* key = identifier | string */
|
||||
static int parse_key(struct frozen *f) {
|
||||
int ch = cur(f);
|
||||
#if 0
|
||||
printf("%s 1 [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur);
|
||||
#endif
|
||||
if (is_alpha(ch)) {
|
||||
TRY(parse_identifier(f));
|
||||
} else if (ch == '"') {
|
||||
TRY(parse_string(f));
|
||||
} else {
|
||||
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* pair = key ':' value */
|
||||
static int parse_pair(struct frozen *f) {
|
||||
TRY(parse_key(f));
|
||||
TRY(test_and_skip(f, ':'));
|
||||
TRY(parse_value(f));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* object = '{' pair { ',' pair } '}' */
|
||||
static int parse_object(struct frozen *f) {
|
||||
int ind;
|
||||
TRY(test_and_skip(f, '{'));
|
||||
TRY(capture_ptr(f, f->cur - 1, JSON_TYPE_OBJECT));
|
||||
ind = f->num_tokens - 1;
|
||||
while (cur(f) != '}') {
|
||||
TRY(parse_pair(f));
|
||||
if (cur(f) == ',') f->cur++;
|
||||
}
|
||||
TRY(test_and_skip(f, '}'));
|
||||
capture_len(f, ind, f->cur);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int doit(struct frozen *f) {
|
||||
int ret = 0;
|
||||
|
||||
if (f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID;
|
||||
if (f->end == f->cur) return JSON_STRING_INCOMPLETE;
|
||||
|
||||
if (0 == (ret = test_no_skip(f, '{'))) {
|
||||
TRY(parse_object(f));
|
||||
} else if (0 == (ret = test_no_skip(f, '['))) {
|
||||
TRY(parse_array(f));
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
TRY(capture_ptr(f, f->cur, JSON_TYPE_EOF));
|
||||
capture_len(f, f->num_tokens, f->cur);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* json = object */
|
||||
int parse_json(const char *s, int s_len, struct json_token *arr, int arr_len) {
|
||||
struct frozen frozen;
|
||||
|
||||
memset(&frozen, 0, sizeof(frozen));
|
||||
frozen.end = s + s_len;
|
||||
frozen.cur = s;
|
||||
frozen.tokens = arr;
|
||||
frozen.max_tokens = arr_len;
|
||||
|
||||
TRY(doit(&frozen));
|
||||
|
||||
return frozen.cur - s;
|
||||
}
|
||||
|
||||
struct json_token *parse_json2(const char *s, int s_len) {
|
||||
struct frozen frozen;
|
||||
|
||||
memset(&frozen, 0, sizeof(frozen));
|
||||
frozen.end = s + s_len;
|
||||
frozen.cur = s;
|
||||
frozen.do_realloc = 1;
|
||||
|
||||
if (doit(&frozen) < 0) {
|
||||
FROZEN_FREE((void *) frozen.tokens);
|
||||
frozen.tokens = NULL;
|
||||
}
|
||||
return frozen.tokens;
|
||||
}
|
||||
|
||||
static int path_part_len(const char *p) {
|
||||
int i = 0;
|
||||
while (p[i] != '\0' && p[i] != '[' && p[i] != '.') i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
struct json_token *find_json_token(struct json_token *toks, const char *path) {
|
||||
while (path != 0 && path[0] != '\0') {
|
||||
int i, ind2 = 0, ind = -1, skip = 2, n = path_part_len(path);
|
||||
if (path[0] == '[') {
|
||||
if (toks->type != JSON_TYPE_ARRAY || !is_digit(path[1])) return 0;
|
||||
for (ind = 0, n = 1; path[n] != ']' && path[n] != '\0'; n++) {
|
||||
if (!is_digit(path[n])) return 0;
|
||||
ind *= 10;
|
||||
ind += path[n] - '0';
|
||||
}
|
||||
if (path[n++] != ']') return 0;
|
||||
skip = 1; /* In objects, we skip 2 elems while iterating, in arrays 1. */
|
||||
} else if (toks->type != JSON_TYPE_OBJECT)
|
||||
return 0;
|
||||
toks++;
|
||||
for (i = 0; i < toks[-1].num_desc; i += skip, ind2++) {
|
||||
/* ind == -1 indicated that we're iterating an array, not object */
|
||||
if (ind == -1 && toks[i].type != JSON_TYPE_STRING) return 0;
|
||||
if (ind2 == ind ||
|
||||
(ind == -1 && toks[i].len == n && compare(path, toks[i].ptr, n))) {
|
||||
i += skip - 1;
|
||||
break;
|
||||
};
|
||||
if (toks[i - 1 + skip].type == JSON_TYPE_ARRAY ||
|
||||
toks[i - 1 + skip].type == JSON_TYPE_OBJECT) {
|
||||
i += toks[i - 1 + skip].num_desc;
|
||||
}
|
||||
}
|
||||
if (i == toks[-1].num_desc) return 0;
|
||||
path += n;
|
||||
if (path[0] == '.') path++;
|
||||
if (path[0] == '\0') return &toks[i];
|
||||
toks += i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_emit_long(char *buf, int buf_len, long int value) {
|
||||
char tmp[20];
|
||||
int n = snprintf(tmp, sizeof(tmp), "%ld", value);
|
||||
strncpy(buf, tmp, buf_len > 0 ? buf_len : 0);
|
||||
return n;
|
||||
}
|
||||
|
||||
int json_emit_double(char *buf, int buf_len, double value) {
|
||||
char tmp[20];
|
||||
int n = snprintf(tmp, sizeof(tmp), "%g", value);
|
||||
strncpy(buf, tmp, buf_len > 0 ? buf_len : 0);
|
||||
return n;
|
||||
}
|
||||
|
||||
int json_emit_quoted_str(char *s, int s_len, const char *str, int len) {
|
||||
const char *begin = s, *end = s + s_len, *str_end = str + len;
|
||||
char ch;
|
||||
|
||||
#define EMIT(x) \
|
||||
do { \
|
||||
if (s < end) *s = x; \
|
||||
s++; \
|
||||
} while (0)
|
||||
|
||||
EMIT('"');
|
||||
while (str < str_end) {
|
||||
ch = *str++;
|
||||
switch (ch) {
|
||||
case '"':
|
||||
EMIT('\\');
|
||||
EMIT('"');
|
||||
break;
|
||||
case '\\':
|
||||
EMIT('\\');
|
||||
EMIT('\\');
|
||||
break;
|
||||
case '\b':
|
||||
EMIT('\\');
|
||||
EMIT('b');
|
||||
break;
|
||||
case '\f':
|
||||
EMIT('\\');
|
||||
EMIT('f');
|
||||
break;
|
||||
case '\n':
|
||||
EMIT('\\');
|
||||
EMIT('n');
|
||||
break;
|
||||
case '\r':
|
||||
EMIT('\\');
|
||||
EMIT('r');
|
||||
break;
|
||||
case '\t':
|
||||
EMIT('\\');
|
||||
EMIT('t');
|
||||
break;
|
||||
default:
|
||||
EMIT(ch);
|
||||
}
|
||||
}
|
||||
EMIT('"');
|
||||
if (s < end) {
|
||||
*s = '\0';
|
||||
}
|
||||
|
||||
return s - begin;
|
||||
}
|
||||
|
||||
int json_emit_unquoted_str(char *buf, int buf_len, const char *str, int len) {
|
||||
if (buf_len > 0 && len > 0) {
|
||||
int n = len < buf_len ? len : buf_len;
|
||||
memcpy(buf, str, n);
|
||||
if (n < buf_len) {
|
||||
buf[n] = '\0';
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int json_emit_va(char *s, int s_len, const char *fmt, va_list ap) {
|
||||
const char *end = s + s_len, *str, *orig = s;
|
||||
size_t len;
|
||||
|
||||
while (*fmt != '\0') {
|
||||
switch (*fmt) {
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case ',':
|
||||
case ':':
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\t':
|
||||
if (s < end) {
|
||||
*s = *fmt;
|
||||
}
|
||||
s++;
|
||||
break;
|
||||
case 'i':
|
||||
s += json_emit_long(s, end - s, va_arg(ap, long) );
|
||||
break;
|
||||
case 'f':
|
||||
s += json_emit_double(s, end - s, va_arg(ap, double) );
|
||||
break;
|
||||
case 'v':
|
||||
str = va_arg(ap, char *);
|
||||
len = va_arg(ap, size_t);
|
||||
s += json_emit_quoted_str(s, end - s, str, len);
|
||||
break;
|
||||
case 'V':
|
||||
str = va_arg(ap, char *);
|
||||
len = va_arg(ap, size_t);
|
||||
s += json_emit_unquoted_str(s, end - s, str, len);
|
||||
break;
|
||||
case 's':
|
||||
str = va_arg(ap, char *);
|
||||
s += json_emit_quoted_str(s, end - s, str, strlen(str));
|
||||
break;
|
||||
case 'S':
|
||||
str = va_arg(ap, char *);
|
||||
s += json_emit_unquoted_str(s, end - s, str, strlen(str));
|
||||
break;
|
||||
case 'T':
|
||||
s += json_emit_unquoted_str(s, end - s, "true", 4);
|
||||
break;
|
||||
case 'F':
|
||||
s += json_emit_unquoted_str(s, end - s, "false", 5);
|
||||
break;
|
||||
case 'N':
|
||||
s += json_emit_unquoted_str(s, end - s, "null", 4);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
fmt++;
|
||||
}
|
||||
|
||||
/* Best-effort to 0-terminate generated string */
|
||||
if (s < end) {
|
||||
*s = '\0';
|
||||
}
|
||||
|
||||
return s - orig;
|
||||
}
|
||||
|
||||
int json_emit(char *buf, int buf_len, const char *fmt, ...) {
|
||||
int len;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
len = json_emit_va(buf, buf_len, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return len;
|
||||
}
|
||||
#ifdef MG_MODULE_LINES
|
||||
#line 1 "./src/../../common/md5.c"
|
||||
#endif
|
||||
/*
|
||||
@@ -8245,167 +7648,6 @@ struct mg_str mg_mk_str(const char *s) {
|
||||
return ret;
|
||||
}
|
||||
#ifdef MG_MODULE_LINES
|
||||
#line 1 "./src/json-rpc.c"
|
||||
#endif
|
||||
/* Copyright (c) 2014 Cesanta Software Limited */
|
||||
/* All rights reserved */
|
||||
|
||||
#ifndef MG_DISABLE_JSON_RPC
|
||||
|
||||
/* Amalgamated: #include "mongoose/src/internal.h" */
|
||||
/* Amalgamated: #include "mongoose/src/json-rpc.h" */
|
||||
/* Amalgamated: #include "mongoose/deps/frozen/frozen.h" */
|
||||
|
||||
int mg_rpc_create_reply(char *buf, int len, const struct mg_rpc_request *req,
|
||||
const char *result_fmt, ...) {
|
||||
static const struct json_token null_tok = {"null", 4, 0, JSON_TYPE_NULL};
|
||||
const struct json_token *id = req->id == NULL ? &null_tok : req->id;
|
||||
va_list ap;
|
||||
int n = 0;
|
||||
|
||||
n += json_emit(buf + n, len - n, "{s:s,s:", "jsonrpc", "2.0", "id");
|
||||
if (id->type == JSON_TYPE_STRING) {
|
||||
n += json_emit_quoted_str(buf + n, len - n, id->ptr, id->len);
|
||||
} else {
|
||||
n += json_emit_unquoted_str(buf + n, len - n, id->ptr, id->len);
|
||||
}
|
||||
n += json_emit(buf + n, len - n, ",s:", "result");
|
||||
|
||||
va_start(ap, result_fmt);
|
||||
n += json_emit_va(buf + n, len - n, result_fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
n += json_emit(buf + n, len - n, "}");
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int mg_rpc_create_request(char *buf, int len, const char *method,
|
||||
const char *id, const char *params_fmt, ...) {
|
||||
va_list ap;
|
||||
int n = 0;
|
||||
|
||||
n += json_emit(buf + n, len - n, "{s:s,s:s,s:s,s:", "jsonrpc", "2.0", "id",
|
||||
id, "method", method, "params");
|
||||
va_start(ap, params_fmt);
|
||||
n += json_emit_va(buf + n, len - n, params_fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
n += json_emit(buf + n, len - n, "}");
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int mg_rpc_create_error(char *buf, int len, struct mg_rpc_request *req,
|
||||
int code, const char *message, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int n = 0;
|
||||
|
||||
n += json_emit(buf + n, len - n, "{s:s,s:V,s:{s:i,s:s,s:", "jsonrpc", "2.0",
|
||||
"id", req->id == NULL ? "null" : req->id->ptr,
|
||||
req->id == NULL ? 4 : req->id->len, "error", "code",
|
||||
(long) code, "message", message, "data");
|
||||
va_start(ap, fmt);
|
||||
n += json_emit_va(buf + n, len - n, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
n += json_emit(buf + n, len - n, "}}");
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int mg_rpc_create_std_error(char *buf, int len, struct mg_rpc_request *req,
|
||||
int code) {
|
||||
const char *message = NULL;
|
||||
|
||||
switch (code) {
|
||||
case JSON_RPC_PARSE_ERROR:
|
||||
message = "parse error";
|
||||
break;
|
||||
case JSON_RPC_INVALID_REQUEST_ERROR:
|
||||
message = "invalid request";
|
||||
break;
|
||||
case JSON_RPC_METHOD_NOT_FOUND_ERROR:
|
||||
message = "method not found";
|
||||
break;
|
||||
case JSON_RPC_INVALID_PARAMS_ERROR:
|
||||
message = "invalid parameters";
|
||||
break;
|
||||
case JSON_RPC_SERVER_ERROR:
|
||||
message = "server error";
|
||||
break;
|
||||
default:
|
||||
message = "unspecified error";
|
||||
break;
|
||||
}
|
||||
|
||||
return mg_rpc_create_error(buf, len, req, code, message, "N");
|
||||
}
|
||||
|
||||
int mg_rpc_dispatch(const char *buf, int len, char *dst, int dst_len,
|
||||
const char **methods, mg_rpc_handler_t *handlers) {
|
||||
struct json_token tokens[200];
|
||||
struct mg_rpc_request req;
|
||||
int i, n;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
n = parse_json(buf, len, tokens, sizeof(tokens) / sizeof(tokens[0]));
|
||||
if (n <= 0) {
|
||||
int err_code = (n == JSON_STRING_INVALID) ? JSON_RPC_PARSE_ERROR
|
||||
: JSON_RPC_SERVER_ERROR;
|
||||
return mg_rpc_create_std_error(dst, dst_len, &req, err_code);
|
||||
}
|
||||
|
||||
req.message = tokens;
|
||||
req.id = find_json_token(tokens, "id");
|
||||
req.method = find_json_token(tokens, "method");
|
||||
req.params = find_json_token(tokens, "params");
|
||||
|
||||
if (req.id == NULL || req.method == NULL) {
|
||||
return mg_rpc_create_std_error(dst, dst_len, &req,
|
||||
JSON_RPC_INVALID_REQUEST_ERROR);
|
||||
}
|
||||
|
||||
for (i = 0; methods[i] != NULL; i++) {
|
||||
int mlen = strlen(methods[i]);
|
||||
if (mlen == req.method->len &&
|
||||
memcmp(methods[i], req.method->ptr, mlen) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (methods[i] == NULL) {
|
||||
return mg_rpc_create_std_error(dst, dst_len, &req,
|
||||
JSON_RPC_METHOD_NOT_FOUND_ERROR);
|
||||
}
|
||||
|
||||
return handlers[i](dst, dst_len, &req);
|
||||
}
|
||||
|
||||
int mg_rpc_parse_reply(const char *buf, int len, struct json_token *toks,
|
||||
int max_toks, struct mg_rpc_reply *rep,
|
||||
struct mg_rpc_error *er) {
|
||||
int n = parse_json(buf, len, toks, max_toks);
|
||||
|
||||
memset(rep, 0, sizeof(*rep));
|
||||
memset(er, 0, sizeof(*er));
|
||||
|
||||
if (n > 0) {
|
||||
if ((rep->result = find_json_token(toks, "result")) != NULL) {
|
||||
rep->message = toks;
|
||||
rep->id = find_json_token(toks, "id");
|
||||
} else {
|
||||
er->message = toks;
|
||||
er->id = find_json_token(toks, "id");
|
||||
er->error_code = find_json_token(toks, "error.code");
|
||||
er->error_message = find_json_token(toks, "error.message");
|
||||
er->error_data = find_json_token(toks, "error.data");
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
#endif /* MG_DISABLE_JSON_RPC */
|
||||
#ifdef MG_MODULE_LINES
|
||||
#line 1 "./src/mqtt.c"
|
||||
#endif
|
||||
/*
|
||||
|
||||
-206
@@ -1067,74 +1067,6 @@ const char *c_strnstr(const char *s, const char *find, size_t slen);
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_STR_UTIL_H_ */
|
||||
/*
|
||||
* 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 <http://cesanta.com/products.html>.
|
||||
*/
|
||||
|
||||
#ifndef CS_MONGOOSE_DEPS_FROZEN_FROZEN_H_
|
||||
#define CS_MONGOOSE_DEPS_FROZEN_FROZEN_H_
|
||||
|
||||
#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 /* CS_MONGOOSE_DEPS_FROZEN_FROZEN_H_ */
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
@@ -2754,144 +2686,6 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif /* CS_MONGOOSE_SRC_HTTP_H_ */
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* === JSON-RPC
|
||||
*/
|
||||
|
||||
#ifndef CS_MONGOOSE_SRC_JSON_RPC_H_
|
||||
#define CS_MONGOOSE_SRC_JSON_RPC_H_
|
||||
|
||||
#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 *buf, int len, struct mg_rpc_request *req,
|
||||
int code);
|
||||
|
||||
typedef int (*mg_rpc_handler_t)(char *buf, int len, struct mg_rpc_request *req);
|
||||
|
||||
/*
|
||||
* 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 /* CS_MONGOOSE_SRC_JSON_RPC_H_ */
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
|
||||
Reference in new issue
Block a user