/* * 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 \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; }