Message ID | 1520249215-31082-2-git-send-email-sbabic@denx.de |
---|---|
State | Changes Requested |
Headers | show |
Series | None | expand |
Hi Stefano, I don't think this is a good idea because the old API isn't optimal and with this step you remove the possibility to remove it in the future. For example the upload handler v1 is only needed to support the old example web page which use a uncommon upload script. Additionally it is impossible to support a swu forwarder and a modern website at the same time. Mongoose support the web socket client interface and could be used to add v2 support to the swu forwarder: https://github.com/cesanta/mongoose/tree/master/examples/websocket_chat_client Alternatively you could add a m2m mode with only enabled the needed interfaces. Am 05.03.2018 um 12:26 schrieb Stefano Babic: > V1 version of REST-API is still more suitable for M2M > updates together with the SWU forwarder handler. > However, it is not currently possible to switch > between them and the API must be selected at compile time, > and this requires different binaries. > > Let the user decides which version of API should run > with a command line parameter. > > Signed-off-by: Stefano Babic <sbabic@denx.de> > CC: Sami Hartikainen <sami.hartikainen@teleste.com> > --- > > Changes since V1: > - Drop MONGOOSE_WEB_API_V1 and MONGOOSE_WEB_API_V2 > - Add help for "api" > - Add "api" parameter to configuration file > - "/upload" URL just in case of v2 > - print API version when Webserver is started > > mongoose/Config.in | 18 -- > mongoose/Makefile | 2 - > mongoose/mongoose_interface.c | 464 ++++++++++++++++++++++-------------------- > 3 files changed, 246 insertions(+), 238 deletions(-) > > diff --git a/mongoose/Config.in b/mongoose/Config.in > index a001247..29cac90 100644 > --- a/mongoose/Config.in > +++ b/mongoose/Config.in > @@ -18,24 +18,6 @@ config MONGOOSE > > endchoice > > -choice > - prompt "Web Application Interface" > - default MONGOOSE_WEB_API_V1 > - help > - Choose the bootloader > - > -config MONGOOSE_WEB_API_V1 > - bool "Version 1 (deprecated)" > - help > - Support for version 1 > - > -config MONGOOSE_WEB_API_V2 > - bool "Version 2" > - help > - Support for version 2 > - > -endchoice > - Why not let the user decide if he want to support one or both api versions? > config MONGOOSEIPV6 > bool "IPv6 support" > default y > diff --git a/mongoose/Makefile b/mongoose/Makefile > index 77a616c..dc2d3d3 100644 > --- a/mongoose/Makefile > +++ b/mongoose/Makefile > @@ -1,9 +1,7 @@ > ifneq ($(CONFIG_WEBSERVER),) > ifneq ($(CONFIG_MONGOOSE),) > KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1 > -ifneq ($(CONFIG_MONGOOSE_WEB_API_V2),) > KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1 > -endif > ifneq ($(CONFIG_MONGOOSEIPV6),) > KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1 > endif > diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c > index 0e22671..99f9fc4 100644 > --- a/mongoose/mongoose_interface.c > +++ b/mongoose/mongoose_interface.c > @@ -32,6 +32,11 @@ > #define MG_PORT "8080" > #define MG_ROOT "." > > +enum MONGOOSE_API_VERSION { > + MONGOOSE_API_V1, > + MONGOOSE_API_V2 > +}; > + > struct mongoose_options { > char *root; > char *listing; > @@ -40,6 +45,7 @@ struct mongoose_options { > char *ssl_cert; > char *ssl_key; > #endif > + int api_version; Why you doesn't use an enum? > }; > > struct file_upload_state { > @@ -50,7 +56,9 @@ struct file_upload_state { > static struct mg_serve_http_opts s_http_server_opts; > static void upload_handler(struct mg_connection *nc, int ev, void *p); > > -#if defined(CONFIG_MONGOOSE_WEB_API_V2) > +/* > + * These functions are for V2 of the protocol > + */ > #define enum_string(x) [x] = #x > static const char *get_status_string(unsigned int status) > { > @@ -111,9 +119,166 @@ static size_t snescape(char *dst, size_t n, const char *src) > > return len; > } > -#endif > > -#if defined(CONFIG_MONGOOSE_WEB_API_V1) > +static void restart_handler(struct mg_connection *nc, int ev, void *ev_data) > +{ > + struct http_message *hm = (struct http_message *) ev_data; > + ipc_message msg = {}; > + > + (void)ev; > + > + if(mg_vcasecmp(&hm->method, "POST") != 0) { > + mg_http_send_error(nc, 405, "Method Not Allowed"); > + return; > + } > + > + int ret = ipc_postupdate(&msg); > + if (ret) { > + mg_http_send_error(nc, 500, "Failed to queue command"); > + return; > + } > + > + mg_http_send_error(nc, 201, "Device will reboot now."); > +} > + > +static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data) > +{ > + char *buf = (char *) ev_data; > + > + if (ev != MG_EV_POLL) > + return; > + > + if (!(nc->flags & MG_F_IS_WEBSOCKET)) > + return; > + > + mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); > +} > + > +static void broadcast(struct mg_mgr *mgr, char *str) > +{ > + mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1); > +} > + > +static void *broadcast_message_thread(void *data) > +{ > + for (;;) { > + ipc_message msg; > + int ret = ipc_get_status(&msg); > + > + if (!ret && strlen(msg.data.status.desc) != 0) { > + struct mg_mgr *mgr = (struct mg_mgr *) data; > + char text[4096]; > + char str[4160]; > + > + snescape(text, sizeof(text), msg.data.status.desc); > + > + snprintf(str, sizeof(str), > + "{\r\n" > + "\t\"type\": \"message\",\r\n" > + "\t\"level\": \"%d\",\r\n" > + "\t\"text\": \"%s\"\r\n" > + "}\r\n", > + (msg.data.status.error) ? 3 : 6, /* RFC 5424 */ > + text); > + > + broadcast(mgr, str); > + continue; > + } > + > + usleep(50 * 1000); > + } > + > + return NULL; > +} > + > +static void *broadcast_progress_thread(void *data) > +{ > + int status = -1; > + int source = -1; > + int step = 0; > + int percent = 0; > + int fd = -1; > + > + for (;;) { > + struct mg_mgr *mgr = (struct mg_mgr *) data; > + struct progress_msg msg; > + char str[512]; > + int ret; > + > + if (fd < 0) > + fd = progress_ipc_connect(true); > + > + ret = progress_ipc_receive(&fd, &msg); > + if (ret != sizeof(msg)) > + return NULL; > + > + if (msg.status != status || msg.status == FAILURE) { > + status = msg.status; > + > + snprintf(str, sizeof(str), > + "{\r\n" > + "\t\"type\": \"status\",\r\n" > + "\t\"status\": \"%s\"\r\n" > + "}\r\n", > + get_status_string(msg.status)); > + broadcast(mgr, str); > + } > + > + if (msg.source != source) { > + source = msg.source; > + > + snprintf(str, sizeof(str), > + "{\r\n" > + "\t\"type\": \"source\",\r\n" > + "\t\"source\": \"%s\"\r\n" > + "}\r\n", > + get_source_string(msg.source)); > + broadcast(mgr, str); > + } > + > + if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER) { > + ipc_message ipc = {}; > + > + ipc_postupdate(&ipc); > + } > + > + if (msg.infolen) { > + snprintf(str, sizeof(str), > + "{\r\n" > + "\t\"type\": \"info\",\r\n" > + "\t\"source\": \"%s\"\r\n" > + "}\r\n", > + msg.info); > + broadcast(mgr, str); > + } > + > + if ((msg.cur_step != step || msg.cur_percent != percent) && > + msg.cur_step) { > + step = msg.cur_step; > + percent = msg.cur_percent; > + > + snprintf(str, sizeof(str), > + "{\r\n" > + "\t\"type\": \"step\",\r\n" > + "\t\"number\": \"%d\",\r\n" > + "\t\"step\": \"%d\",\r\n" > + "\t\"name\": \"%s\",\r\n" > + "\t\"percent\": \"%d\"\r\n" > + "}\r\n", > + msg.nsteps, > + msg.cur_step, > + msg.cur_step ? msg.cur_image: "", > + msg.cur_percent); > + broadcast(mgr, str); > + } > + } > + > + return NULL; > +} > + > +/* > + * These functions are for V1 of the protocol > + */ > static void upload_handler_v1(struct mg_connection *nc, int ev, void *p) > { > struct mg_str *filename, *data; > @@ -162,69 +327,7 @@ static void upload_handler_v1(struct mg_connection *nc, int ev, void *p) > break; > } > } > -#endif > - > -static void upload_handler(struct mg_connection *nc, int ev, void *p) > -{ > - struct mg_http_multipart_part *mp; > - struct file_upload_state *fus; > - > - switch (ev) { > - case MG_EV_HTTP_PART_BEGIN: > - mp = (struct mg_http_multipart_part *) p; > - > - fus = (struct file_upload_state *) calloc(1, sizeof(*fus)); > - if (fus == NULL) { > - mg_http_send_error(nc, 500, "Out of memory"); > - break; > - } > - > - fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name); > - if (fus->fd < 0) { > - mg_http_send_error(nc, 500, "Failed to queue command"); > - free(fus); > - break; > - } > - > - mp->user_data = fus; > - > - break; > - > - case MG_EV_HTTP_PART_DATA: > - mp = (struct mg_http_multipart_part *) p; > - fus = (struct file_upload_state *) mp->user_data; > - > - if (!fus) > - break; > - > - ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len); > - fus->len += mp->data.len; > - > - break; > - > - case MG_EV_HTTP_PART_END: > - mp = (struct mg_http_multipart_part *) p; > - fus = (struct file_upload_state *) mp->user_data; > > - if (!fus) > - break; > - > - ipc_end(fus->fd); > - > - mg_send_response_line(nc, 200, > - "Content-Type: text/plain\r\n" > - "Connection: close"); > - mg_send(nc, "\r\n", 2); > - mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len); > - nc->flags |= MG_F_SEND_AND_CLOSE; > - > - mp->user_data = NULL; > - free(fus); > - break; > - } > -} > - > -#if defined(CONFIG_MONGOOSE_WEB_API_V1) > static void recovery_status(struct mg_connection *nc, int ev, void *ev_data) > { > ipc_message ipc; > @@ -314,163 +417,70 @@ static void post_update_cmd(struct mg_connection *nc, int ev, void *ev_data) > > nc->flags |= MG_F_SEND_AND_CLOSE; > } > -#elif defined(CONFIG_MONGOOSE_WEB_API_V2) > -static void restart_handler(struct mg_connection *nc, int ev, void *ev_data) > -{ > - struct http_message *hm = (struct http_message *) ev_data; > - ipc_message msg = {}; > - > - (void)ev; > - > - if(mg_vcasecmp(&hm->method, "POST") != 0) { > - mg_http_send_error(nc, 405, "Method Not Allowed"); > - return; > - } > - > - int ret = ipc_postupdate(&msg); > - if (ret) { > - mg_http_send_error(nc, 500, "Failed to queue command"); > - return; > - } > - > - mg_http_send_error(nc, 201, "Device will reboot now."); > -} > - > -static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data) > -{ > - char *buf = (char *) ev_data; > > - if (ev != MG_EV_POLL) > - return; > > - if (!(nc->flags & MG_F_IS_WEBSOCKET)) > - return; > - > - mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); > -} > - > -static void broadcast(struct mg_mgr *mgr, char *str) > -{ > - mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1); > -} > - > -static void *broadcast_message_thread(void *data) > +/* > + * Code common to V1 and V2 > + */ > +static void upload_handler(struct mg_connection *nc, int ev, void *p) > { > - for (;;) { > - ipc_message msg; > - int ret = ipc_get_status(&msg); > - > - if (!ret && strlen(msg.data.status.desc) != 0) { > - struct mg_mgr *mgr = (struct mg_mgr *) data; > - char text[4096]; > - char str[4160]; > - > - snescape(text, sizeof(text), msg.data.status.desc); > + struct mg_http_multipart_part *mp; > + struct file_upload_state *fus; > > - snprintf(str, sizeof(str), > - "{\r\n" > - "\t\"type\": \"message\",\r\n" > - "\t\"level\": \"%d\",\r\n" > - "\t\"text\": \"%s\"\r\n" > - "}\r\n", > - (msg.data.status.error) ? 3 : 6, /* RFC 5424 */ > - text); > + switch (ev) { > + case MG_EV_HTTP_PART_BEGIN: > + mp = (struct mg_http_multipart_part *) p; > > - broadcast(mgr, str); > - continue; > + fus = (struct file_upload_state *) calloc(1, sizeof(*fus)); > + if (fus == NULL) { > + mg_http_send_error(nc, 500, "Out of memory"); > + break; > } > > - usleep(50 * 1000); > - } > - > - return NULL; > -} > - > -static void *broadcast_progress_thread(void *data) > -{ > - int status = -1; > - int source = -1; > - int step = 0; > - int percent = 0; > - int fd = -1; > - > - for (;;) { > - struct mg_mgr *mgr = (struct mg_mgr *) data; > - struct progress_msg msg; > - char str[512]; > - int ret; > + fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name); > + if (fus->fd < 0) { > + mg_http_send_error(nc, 500, "Failed to queue command"); > + free(fus); > + break; > + } > > - if (fd < 0) > - fd = progress_ipc_connect(true); > + mp->user_data = fus; > > - ret = progress_ipc_receive(&fd, &msg); > - if (ret != sizeof(msg)) > - return NULL; > + break; > > - if (msg.status != status || msg.status == FAILURE) { > - status = msg.status; > + case MG_EV_HTTP_PART_DATA: > + mp = (struct mg_http_multipart_part *) p; > + fus = (struct file_upload_state *) mp->user_data; > > - snprintf(str, sizeof(str), > - "{\r\n" > - "\t\"type\": \"status\",\r\n" > - "\t\"status\": \"%s\"\r\n" > - "}\r\n", > - get_status_string(msg.status)); > - broadcast(mgr, str); > - } > + if (!fus) > + break; > > - if (msg.source != source) { > - source = msg.source; > + ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len); > + fus->len += mp->data.len; > > - snprintf(str, sizeof(str), > - "{\r\n" > - "\t\"type\": \"source\",\r\n" > - "\t\"source\": \"%s\"\r\n" > - "}\r\n", > - get_source_string(msg.source)); > - broadcast(mgr, str); > - } > + break; > > - if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER) { > - ipc_message ipc = {}; > + case MG_EV_HTTP_PART_END: > + mp = (struct mg_http_multipart_part *) p; > + fus = (struct file_upload_state *) mp->user_data; > > - ipc_postupdate(&ipc); > - } > + if (!fus) > + break; > > - if (msg.infolen) { > - snprintf(str, sizeof(str), > - "{\r\n" > - "\t\"type\": \"info\",\r\n" > - "\t\"source\": \"%s\"\r\n" > - "}\r\n", > - msg.info); > - broadcast(mgr, str); > - } > + ipc_end(fus->fd); > > - if ((msg.cur_step != step || msg.cur_percent != percent) && > - msg.cur_step) { > - step = msg.cur_step; > - percent = msg.cur_percent; > + mg_send_response_line(nc, 200, > + "Content-Type: text/plain\r\n" > + "Connection: close"); > + mg_send(nc, "\r\n", 2); > + mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len); > + nc->flags |= MG_F_SEND_AND_CLOSE; > > - snprintf(str, sizeof(str), > - "{\r\n" > - "\t\"type\": \"step\",\r\n" > - "\t\"number\": \"%d\",\r\n" > - "\t\"step\": \"%d\",\r\n" > - "\t\"name\": \"%s\",\r\n" > - "\t\"percent\": \"%d\"\r\n" > - "}\r\n", > - msg.nsteps, > - msg.cur_step, > - msg.cur_step ? msg.cur_image: "", > - msg.cur_percent); > - broadcast(mgr, str); > - } > + mp->user_data = NULL; > + free(fus); > + break; > } > - > - return NULL; > } > -#endif > > static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) > { > @@ -510,6 +520,12 @@ static int mongoose_settings(void *elem, void __attribute__ ((__unused__)) *dat > opts->ssl_key = strdup(tmp); > } > #endif > + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api", tmp); > + if (strlen(tmp)) { > + opts->api_version = (!strcmp(tmp, "v1")) ? > + MONGOOSE_API_V1 : > + MONGOOSE_API_V2; > + } > return 0; > } > > @@ -523,6 +539,7 @@ static struct option long_options[] = { > {"ssl-key", required_argument, NULL, 'K'}, > #endif > {"document-root", required_argument, NULL, 'r'}, > + {"api", required_argument, NULL, 'a'}, I would call it api-version to use a simple number as argument. > {NULL, 0, NULL, 0} > }; > > @@ -538,7 +555,8 @@ void mongoose_print_help(void) > "\t -C, --ssl-cert <cert> : ssl certificate to present to clients\n" > "\t -K, --ssl-key <key> : key corresponding to the ssl certificate\n" > #endif > - "\t -r, --document-root <path> : path to document root directory (default: %s)\n", > + "\t -r, --document-root <path> : path to document root directory (default: %s)\n" > + "\t -a, --api [v1|v2] : set Web protocol API to v1 (legacy) or v2 (default v2)\n", > MG_LISTING, MG_PORT, MG_ROOT); > } > > @@ -556,12 +574,14 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[]) > int choice = 0; > > memset(&opts, 0, sizeof(opts)); > + /* Set default API version */ > + opts.api_version = MONGOOSE_API_V2; > if (cfgfname) { > read_module_settings(cfgfname, "webserver", mongoose_settings, &opts); > } > > optind = 1; > - while ((choice = getopt_long(argc, argv, "lp:sC:K:r:", > + while ((choice = getopt_long(argc, argv, "lp:sC:K:r:a:", > long_options, NULL)) != -1) { > switch (choice) { > case 'l': > @@ -589,6 +609,11 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[]) > free(opts.root); > opts.root = strdup(optarg); > break; > + case 'a': > + opts.api_version = (!strcmp(optarg, "v1")) ? > + MONGOOSE_API_V1 : > + MONGOOSE_API_V2; > + break; > case '?': > default: > return -EINVAL; > @@ -619,22 +644,25 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[]) > } > > mg_set_protocol_http_websocket(nc); > - mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL)); > -#if defined(CONFIG_MONGOOSE_WEB_API_V1) > - mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler_v1, NULL)); > - mg_register_http_endpoint(nc, "/getstatus.json", MG_CB(recovery_status, NULL)); > - mg_register_http_endpoint(nc, "/rebootTarget", MG_CB(reboot_target, NULL)); > - mg_register_http_endpoint(nc, "/postUpdateCommand", MG_CB(post_update_cmd, NULL)); > -#endif > -#if defined(CONFIG_MONGOOSE_WEB_API_V2) > - mg_register_http_endpoint(nc, "/restart", restart_handler); > - mg_start_thread(broadcast_message_thread, &mgr); > - mg_start_thread(broadcast_progress_thread, &mgr); > -#endif > + switch (opts.api_version) { > + case MONGOOSE_API_V1: > + mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler_v1, NULL)); > + mg_register_http_endpoint(nc, "/getstatus.json", MG_CB(recovery_status, NULL)); > + mg_register_http_endpoint(nc, "/rebootTarget", MG_CB(reboot_target, NULL)); > + mg_register_http_endpoint(nc, "/postUpdateCommand", MG_CB(post_update_cmd, NULL)); > + break; > + case MONGOOSE_API_V2: > + mg_register_http_endpoint(nc, "/restart", restart_handler); > + mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL)); > + mg_start_thread(broadcast_message_thread, &mgr); > + mg_start_thread(broadcast_progress_thread, &mgr); > + break; > + } > > - printf("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s]\n", > + printf("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s] and API %s\n", > MG_VERSION, getpid(), s_http_port, > - s_http_server_opts.document_root); > + s_http_server_opts.document_root, > + (opts.api_version == MONGOOSE_API_V1) ? "v1" : "v2"); > > for (;;) { > mg_mgr_poll(&mgr, 100); Best regards Stefan
Hi Stefan, On 05/03/2018 20:39, Stefan Herbrechtsmeier wrote: > Hi Stefano, > > I don't think this is a good idea because the old API isn't optimal and > with this step you remove the possibility to remove it in the future. Well, I do not think so. But I am already facing compatibility issues. There are projects where swuforwarder is used to send images to underlying devices that are not connected to the (main) network. I cannot manage (and I do not know, really) if all devices will be upgraded to new version (with V2) or there will be a mix with devices still running V1 and gateway running V2. > For example the upload handler v1 is only needed to support the old > example web page which use a uncommon upload script. In my understanding, V1 is just required for compatibility reasons where swuforwarder is used. We do not require it to expose a website. > Additionally it is > impossible to support a swu forwarder and a modern website at the same > time. True - but does exist the use case ? IMHO there is no reason to run V1 as website now that V2 is mainlined. V1 is just for M2M where nobody can see website's quality. > > Mongoose support the web socket client interface and could be used to > add v2 support to the swu forwarder: > https://github.com/cesanta/mongoose/tree/master/examples/websocket_chat_client Ok, thanks for link, I take a look. > > > Alternatively you could add a m2m mode with only enabled the needed > interfaces. What do you mean ? I do not get it. > > Am 05.03.2018 um 12:26 schrieb Stefano Babic: >> V1 version of REST-API is still more suitable for M2M >> updates together with the SWU forwarder handler. >> However, it is not currently possible to switch >> between them and the API must be selected at compile time, >> and this requires different binaries. >> >> Let the user decides which version of API should run >> with a command line parameter. >> >> Signed-off-by: Stefano Babic <sbabic@denx.de> >> CC: Sami Hartikainen <sami.hartikainen@teleste.com> >> --- >> >> Changes since V1: >> - Drop MONGOOSE_WEB_API_V1 and MONGOOSE_WEB_API_V2 >> - Add help for "api" >> - Add "api" parameter to configuration file >> - "/upload" URL just in case of v2 >> - print API version when Webserver is started >> >> mongoose/Config.in | 18 -- >> mongoose/Makefile | 2 - >> mongoose/mongoose_interface.c | 464 >> ++++++++++++++++++++++-------------------- >> 3 files changed, 246 insertions(+), 238 deletions(-) >> >> diff --git a/mongoose/Config.in b/mongoose/Config.in >> index a001247..29cac90 100644 >> --- a/mongoose/Config.in >> +++ b/mongoose/Config.in >> @@ -18,24 +18,6 @@ config MONGOOSE >> endchoice >> -choice >> - prompt "Web Application Interface" >> - default MONGOOSE_WEB_API_V1 >> - help >> - Choose the bootloader >> - >> -config MONGOOSE_WEB_API_V1 >> - bool "Version 1 (deprecated)" >> - help >> - Support for version 1 >> - >> -config MONGOOSE_WEB_API_V2 >> - bool "Version 2" >> - help >> - Support for version 2 >> - >> -endchoice >> - > > Why not let the user decide if he want to support one or both api versions? Well, the user decides, but at runtime... > >> config MONGOOSEIPV6 >> bool "IPv6 support" >> default y >> diff --git a/mongoose/Makefile b/mongoose/Makefile >> index 77a616c..dc2d3d3 100644 >> --- a/mongoose/Makefile >> +++ b/mongoose/Makefile >> @@ -1,9 +1,7 @@ >> ifneq ($(CONFIG_WEBSERVER),) >> ifneq ($(CONFIG_MONGOOSE),) >> KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1 >> -ifneq ($(CONFIG_MONGOOSE_WEB_API_V2),) >> KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1 >> -endif >> ifneq ($(CONFIG_MONGOOSEIPV6),) >> KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1 >> endif >> diff --git a/mongoose/mongoose_interface.c >> b/mongoose/mongoose_interface.c >> index 0e22671..99f9fc4 100644 >> --- a/mongoose/mongoose_interface.c >> +++ b/mongoose/mongoose_interface.c >> @@ -32,6 +32,11 @@ >> #define MG_PORT "8080" >> #define MG_ROOT "." >> +enum MONGOOSE_API_VERSION { >> + MONGOOSE_API_V1, >> + MONGOOSE_API_V2 >> +}; >> + >> struct mongoose_options { >> char *root; >> char *listing; >> @@ -40,6 +45,7 @@ struct mongoose_options { >> char *ssl_cert; >> char *ssl_key; >> #endif >> + int api_version; > > Why you doesn't use an enum? I'll fix it. > >> }; >> struct file_upload_state { >> @@ -50,7 +56,9 @@ struct file_upload_state { >> static struct mg_serve_http_opts s_http_server_opts; >> static void upload_handler(struct mg_connection *nc, int ev, void *p); >> -#if defined(CONFIG_MONGOOSE_WEB_API_V2) >> +/* >> + * These functions are for V2 of the protocol >> + */ >> #define enum_string(x) [x] = #x >> static const char *get_status_string(unsigned int status) >> { >> @@ -111,9 +119,166 @@ static size_t snescape(char *dst, size_t n, >> const char *src) >> return len; >> } >> -#endif >> -#if defined(CONFIG_MONGOOSE_WEB_API_V1) >> +static void restart_handler(struct mg_connection *nc, int ev, void >> *ev_data) >> +{ >> + struct http_message *hm = (struct http_message *) ev_data; >> + ipc_message msg = {}; >> + >> + (void)ev; >> + >> + if(mg_vcasecmp(&hm->method, "POST") != 0) { >> + mg_http_send_error(nc, 405, "Method Not Allowed"); >> + return; >> + } >> + >> + int ret = ipc_postupdate(&msg); >> + if (ret) { >> + mg_http_send_error(nc, 500, "Failed to queue command"); >> + return; >> + } >> + >> + mg_http_send_error(nc, 201, "Device will reboot now."); >> +} >> + >> +static void broadcast_callback(struct mg_connection *nc, int ev, void >> *ev_data) >> +{ >> + char *buf = (char *) ev_data; >> + >> + if (ev != MG_EV_POLL) >> + return; >> + >> + if (!(nc->flags & MG_F_IS_WEBSOCKET)) >> + return; >> + >> + mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); >> +} >> + >> +static void broadcast(struct mg_mgr *mgr, char *str) >> +{ >> + mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1); >> +} >> + >> +static void *broadcast_message_thread(void *data) >> +{ >> + for (;;) { >> + ipc_message msg; >> + int ret = ipc_get_status(&msg); >> + >> + if (!ret && strlen(msg.data.status.desc) != 0) { >> + struct mg_mgr *mgr = (struct mg_mgr *) data; >> + char text[4096]; >> + char str[4160]; >> + >> + snescape(text, sizeof(text), msg.data.status.desc); >> + >> + snprintf(str, sizeof(str), >> + "{\r\n" >> + "\t\"type\": \"message\",\r\n" >> + "\t\"level\": \"%d\",\r\n" >> + "\t\"text\": \"%s\"\r\n" >> + "}\r\n", >> + (msg.data.status.error) ? 3 : 6, /* RFC 5424 */ >> + text); >> + >> + broadcast(mgr, str); >> + continue; >> + } >> + >> + usleep(50 * 1000); >> + } >> + >> + return NULL; >> +} >> + >> +static void *broadcast_progress_thread(void *data) >> +{ >> + int status = -1; >> + int source = -1; >> + int step = 0; >> + int percent = 0; >> + int fd = -1; >> + >> + for (;;) { >> + struct mg_mgr *mgr = (struct mg_mgr *) data; >> + struct progress_msg msg; >> + char str[512]; >> + int ret; >> + >> + if (fd < 0) >> + fd = progress_ipc_connect(true); >> + >> + ret = progress_ipc_receive(&fd, &msg); >> + if (ret != sizeof(msg)) >> + return NULL; >> + >> + if (msg.status != status || msg.status == FAILURE) { >> + status = msg.status; >> + >> + snprintf(str, sizeof(str), >> + "{\r\n" >> + "\t\"type\": \"status\",\r\n" >> + "\t\"status\": \"%s\"\r\n" >> + "}\r\n", >> + get_status_string(msg.status)); >> + broadcast(mgr, str); >> + } >> + >> + if (msg.source != source) { >> + source = msg.source; >> + >> + snprintf(str, sizeof(str), >> + "{\r\n" >> + "\t\"type\": \"source\",\r\n" >> + "\t\"source\": \"%s\"\r\n" >> + "}\r\n", >> + get_source_string(msg.source)); >> + broadcast(mgr, str); >> + } >> + >> + if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER) { >> + ipc_message ipc = {}; >> + >> + ipc_postupdate(&ipc); >> + } >> + >> + if (msg.infolen) { >> + snprintf(str, sizeof(str), >> + "{\r\n" >> + "\t\"type\": \"info\",\r\n" >> + "\t\"source\": \"%s\"\r\n" >> + "}\r\n", >> + msg.info); >> + broadcast(mgr, str); >> + } >> + >> + if ((msg.cur_step != step || msg.cur_percent != percent) && >> + msg.cur_step) { >> + step = msg.cur_step; >> + percent = msg.cur_percent; >> + >> + snprintf(str, sizeof(str), >> + "{\r\n" >> + "\t\"type\": \"step\",\r\n" >> + "\t\"number\": \"%d\",\r\n" >> + "\t\"step\": \"%d\",\r\n" >> + "\t\"name\": \"%s\",\r\n" >> + "\t\"percent\": \"%d\"\r\n" >> + "}\r\n", >> + msg.nsteps, >> + msg.cur_step, >> + msg.cur_step ? msg.cur_image: "", >> + msg.cur_percent); >> + broadcast(mgr, str); >> + } >> + } >> + >> + return NULL; >> +} >> + >> +/* >> + * These functions are for V1 of the protocol >> + */ >> static void upload_handler_v1(struct mg_connection *nc, int ev, void >> *p) >> { >> struct mg_str *filename, *data; >> @@ -162,69 +327,7 @@ static void upload_handler_v1(struct >> mg_connection *nc, int ev, void *p) >> break; >> } >> } >> -#endif >> - >> -static void upload_handler(struct mg_connection *nc, int ev, void *p) >> -{ >> - struct mg_http_multipart_part *mp; >> - struct file_upload_state *fus; >> - >> - switch (ev) { >> - case MG_EV_HTTP_PART_BEGIN: >> - mp = (struct mg_http_multipart_part *) p; >> - >> - fus = (struct file_upload_state *) calloc(1, sizeof(*fus)); >> - if (fus == NULL) { >> - mg_http_send_error(nc, 500, "Out of memory"); >> - break; >> - } >> - >> - fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, >> strlen(mp->file_name), mp->file_name); >> - if (fus->fd < 0) { >> - mg_http_send_error(nc, 500, "Failed to queue command"); >> - free(fus); >> - break; >> - } >> - >> - mp->user_data = fus; >> - >> - break; >> - >> - case MG_EV_HTTP_PART_DATA: >> - mp = (struct mg_http_multipart_part *) p; >> - fus = (struct file_upload_state *) mp->user_data; >> - >> - if (!fus) >> - break; >> - >> - ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len); >> - fus->len += mp->data.len; >> - >> - break; >> - >> - case MG_EV_HTTP_PART_END: >> - mp = (struct mg_http_multipart_part *) p; >> - fus = (struct file_upload_state *) mp->user_data; >> - if (!fus) >> - break; >> - >> - ipc_end(fus->fd); >> - >> - mg_send_response_line(nc, 200, >> - "Content-Type: text/plain\r\n" >> - "Connection: close"); >> - mg_send(nc, "\r\n", 2); >> - mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) >> fus->len); >> - nc->flags |= MG_F_SEND_AND_CLOSE; >> - >> - mp->user_data = NULL; >> - free(fus); >> - break; >> - } >> -} >> - >> -#if defined(CONFIG_MONGOOSE_WEB_API_V1) >> static void recovery_status(struct mg_connection *nc, int ev, void >> *ev_data) >> { >> ipc_message ipc; >> @@ -314,163 +417,70 @@ static void post_update_cmd(struct >> mg_connection *nc, int ev, void *ev_data) >> nc->flags |= MG_F_SEND_AND_CLOSE; >> } >> -#elif defined(CONFIG_MONGOOSE_WEB_API_V2) >> -static void restart_handler(struct mg_connection *nc, int ev, void >> *ev_data) >> -{ >> - struct http_message *hm = (struct http_message *) ev_data; >> - ipc_message msg = {}; >> - >> - (void)ev; >> - >> - if(mg_vcasecmp(&hm->method, "POST") != 0) { >> - mg_http_send_error(nc, 405, "Method Not Allowed"); >> - return; >> - } >> - >> - int ret = ipc_postupdate(&msg); >> - if (ret) { >> - mg_http_send_error(nc, 500, "Failed to queue command"); >> - return; >> - } >> - >> - mg_http_send_error(nc, 201, "Device will reboot now."); >> -} >> - >> -static void broadcast_callback(struct mg_connection *nc, int ev, void >> *ev_data) >> -{ >> - char *buf = (char *) ev_data; >> - if (ev != MG_EV_POLL) >> - return; >> - if (!(nc->flags & MG_F_IS_WEBSOCKET)) >> - return; >> - >> - mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); >> -} >> - >> -static void broadcast(struct mg_mgr *mgr, char *str) >> -{ >> - mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1); >> -} >> - >> -static void *broadcast_message_thread(void *data) >> +/* >> + * Code common to V1 and V2 >> + */ >> +static void upload_handler(struct mg_connection *nc, int ev, void *p) >> { >> - for (;;) { >> - ipc_message msg; >> - int ret = ipc_get_status(&msg); >> - >> - if (!ret && strlen(msg.data.status.desc) != 0) { >> - struct mg_mgr *mgr = (struct mg_mgr *) data; >> - char text[4096]; >> - char str[4160]; >> - >> - snescape(text, sizeof(text), msg.data.status.desc); >> + struct mg_http_multipart_part *mp; >> + struct file_upload_state *fus; >> - snprintf(str, sizeof(str), >> - "{\r\n" >> - "\t\"type\": \"message\",\r\n" >> - "\t\"level\": \"%d\",\r\n" >> - "\t\"text\": \"%s\"\r\n" >> - "}\r\n", >> - (msg.data.status.error) ? 3 : 6, /* RFC 5424 */ >> - text); >> + switch (ev) { >> + case MG_EV_HTTP_PART_BEGIN: >> + mp = (struct mg_http_multipart_part *) p; >> - broadcast(mgr, str); >> - continue; >> + fus = (struct file_upload_state *) calloc(1, sizeof(*fus)); >> + if (fus == NULL) { >> + mg_http_send_error(nc, 500, "Out of memory"); >> + break; >> } >> - usleep(50 * 1000); >> - } >> - >> - return NULL; >> -} >> - >> -static void *broadcast_progress_thread(void *data) >> -{ >> - int status = -1; >> - int source = -1; >> - int step = 0; >> - int percent = 0; >> - int fd = -1; >> - >> - for (;;) { >> - struct mg_mgr *mgr = (struct mg_mgr *) data; >> - struct progress_msg msg; >> - char str[512]; >> - int ret; >> + fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, >> strlen(mp->file_name), mp->file_name); >> + if (fus->fd < 0) { >> + mg_http_send_error(nc, 500, "Failed to queue command"); >> + free(fus); >> + break; >> + } >> - if (fd < 0) >> - fd = progress_ipc_connect(true); >> + mp->user_data = fus; >> - ret = progress_ipc_receive(&fd, &msg); >> - if (ret != sizeof(msg)) >> - return NULL; >> + break; >> - if (msg.status != status || msg.status == FAILURE) { >> - status = msg.status; >> + case MG_EV_HTTP_PART_DATA: >> + mp = (struct mg_http_multipart_part *) p; >> + fus = (struct file_upload_state *) mp->user_data; >> - snprintf(str, sizeof(str), >> - "{\r\n" >> - "\t\"type\": \"status\",\r\n" >> - "\t\"status\": \"%s\"\r\n" >> - "}\r\n", >> - get_status_string(msg.status)); >> - broadcast(mgr, str); >> - } >> + if (!fus) >> + break; >> - if (msg.source != source) { >> - source = msg.source; >> + ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len); >> + fus->len += mp->data.len; >> - snprintf(str, sizeof(str), >> - "{\r\n" >> - "\t\"type\": \"source\",\r\n" >> - "\t\"source\": \"%s\"\r\n" >> - "}\r\n", >> - get_source_string(msg.source)); >> - broadcast(mgr, str); >> - } >> + break; >> - if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER) { >> - ipc_message ipc = {}; >> + case MG_EV_HTTP_PART_END: >> + mp = (struct mg_http_multipart_part *) p; >> + fus = (struct file_upload_state *) mp->user_data; >> - ipc_postupdate(&ipc); >> - } >> + if (!fus) >> + break; >> - if (msg.infolen) { >> - snprintf(str, sizeof(str), >> - "{\r\n" >> - "\t\"type\": \"info\",\r\n" >> - "\t\"source\": \"%s\"\r\n" >> - "}\r\n", >> - msg.info); >> - broadcast(mgr, str); >> - } >> + ipc_end(fus->fd); >> - if ((msg.cur_step != step || msg.cur_percent != percent) && >> - msg.cur_step) { >> - step = msg.cur_step; >> - percent = msg.cur_percent; >> + mg_send_response_line(nc, 200, >> + "Content-Type: text/plain\r\n" >> + "Connection: close"); >> + mg_send(nc, "\r\n", 2); >> + mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) >> fus->len); >> + nc->flags |= MG_F_SEND_AND_CLOSE; >> - snprintf(str, sizeof(str), >> - "{\r\n" >> - "\t\"type\": \"step\",\r\n" >> - "\t\"number\": \"%d\",\r\n" >> - "\t\"step\": \"%d\",\r\n" >> - "\t\"name\": \"%s\",\r\n" >> - "\t\"percent\": \"%d\"\r\n" >> - "}\r\n", >> - msg.nsteps, >> - msg.cur_step, >> - msg.cur_step ? msg.cur_image: "", >> - msg.cur_percent); >> - broadcast(mgr, str); >> - } >> + mp->user_data = NULL; >> + free(fus); >> + break; >> } >> - >> - return NULL; >> } >> -#endif >> static void ev_handler(struct mg_connection *nc, int ev, void >> *ev_data) >> { >> @@ -510,6 +520,12 @@ static int mongoose_settings(void *elem, void >> __attribute__ ((__unused__)) *dat >> opts->ssl_key = strdup(tmp); >> } >> #endif >> + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api", tmp); >> + if (strlen(tmp)) { >> + opts->api_version = (!strcmp(tmp, "v1")) ? >> + MONGOOSE_API_V1 : >> + MONGOOSE_API_V2; >> + } >> return 0; >> } >> @@ -523,6 +539,7 @@ static struct option long_options[] = { >> {"ssl-key", required_argument, NULL, 'K'}, >> #endif >> {"document-root", required_argument, NULL, 'r'}, >> + {"api", required_argument, NULL, 'a'}, > > I would call it api-version to use a simple number as argument. > >> {NULL, 0, NULL, 0} >> }; >> @@ -538,7 +555,8 @@ void mongoose_print_help(void) >> "\t -C, --ssl-cert <cert> : ssl certificate to >> present to clients\n" >> "\t -K, --ssl-key <key> : key corresponding to >> the ssl certificate\n" >> #endif >> - "\t -r, --document-root <path> : path to document root >> directory (default: %s)\n", >> + "\t -r, --document-root <path> : path to document root >> directory (default: %s)\n" >> + "\t -a, --api [v1|v2] : set Web protocol API to >> v1 (legacy) or v2 (default v2)\n", >> MG_LISTING, MG_PORT, MG_ROOT); >> } >> @@ -556,12 +574,14 @@ int start_mongoose(const char *cfgfname, int >> argc, char *argv[]) >> int choice = 0; >> memset(&opts, 0, sizeof(opts)); >> + /* Set default API version */ >> + opts.api_version = MONGOOSE_API_V2; >> if (cfgfname) { >> read_module_settings(cfgfname, "webserver", >> mongoose_settings, &opts); >> } >> optind = 1; >> - while ((choice = getopt_long(argc, argv, "lp:sC:K:r:", >> + while ((choice = getopt_long(argc, argv, "lp:sC:K:r:a:", >> long_options, NULL)) != -1) { >> switch (choice) { >> case 'l': >> @@ -589,6 +609,11 @@ int start_mongoose(const char *cfgfname, int >> argc, char *argv[]) >> free(opts.root); >> opts.root = strdup(optarg); >> break; >> + case 'a': >> + opts.api_version = (!strcmp(optarg, "v1")) ? >> + MONGOOSE_API_V1 : >> + MONGOOSE_API_V2; >> + break; >> case '?': >> default: >> return -EINVAL; >> @@ -619,22 +644,25 @@ int start_mongoose(const char *cfgfname, int >> argc, char *argv[]) >> } >> mg_set_protocol_http_websocket(nc); >> - mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, >> NULL)); >> -#if defined(CONFIG_MONGOOSE_WEB_API_V1) >> - mg_register_http_endpoint(nc, "/handle_post_request", >> MG_CB(upload_handler_v1, NULL)); >> - mg_register_http_endpoint(nc, "/getstatus.json", >> MG_CB(recovery_status, NULL)); >> - mg_register_http_endpoint(nc, "/rebootTarget", >> MG_CB(reboot_target, NULL)); >> - mg_register_http_endpoint(nc, "/postUpdateCommand", >> MG_CB(post_update_cmd, NULL)); >> -#endif >> -#if defined(CONFIG_MONGOOSE_WEB_API_V2) >> - mg_register_http_endpoint(nc, "/restart", restart_handler); >> - mg_start_thread(broadcast_message_thread, &mgr); >> - mg_start_thread(broadcast_progress_thread, &mgr); >> -#endif >> + switch (opts.api_version) { >> + case MONGOOSE_API_V1: >> + mg_register_http_endpoint(nc, "/handle_post_request", >> MG_CB(upload_handler_v1, NULL)); >> + mg_register_http_endpoint(nc, "/getstatus.json", >> MG_CB(recovery_status, NULL)); >> + mg_register_http_endpoint(nc, "/rebootTarget", >> MG_CB(reboot_target, NULL)); >> + mg_register_http_endpoint(nc, "/postUpdateCommand", >> MG_CB(post_update_cmd, NULL)); >> + break; >> + case MONGOOSE_API_V2: >> + mg_register_http_endpoint(nc, "/restart", restart_handler); >> + mg_register_http_endpoint(nc, "/upload", >> MG_CB(upload_handler, NULL)); >> + mg_start_thread(broadcast_message_thread, &mgr); >> + mg_start_thread(broadcast_progress_thread, &mgr); >> + break; >> + } >> - printf("Mongoose web server version %s with pid %d started on >> port(s) %s with web root [%s]\n", >> + printf("Mongoose web server version %s with pid %d started on >> port(s) %s with web root [%s] and API %s\n", >> MG_VERSION, getpid(), s_http_port, >> - s_http_server_opts.document_root); >> + s_http_server_opts.document_root, >> + (opts.api_version == MONGOOSE_API_V1) ? "v1" : "v2"); >> for (;;) { >> mg_mgr_poll(&mgr, 100); > Best regards, Stefano -- ===================================================================== DENX Software Engineering GmbH, Managing Director: Wolfgang Denk HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sbabic@denx.de =====================================================================
Hi Stefano, Am 05.03.2018 um 21:14 schrieb Stefano Babic: > Hi Stefan, > > On 05/03/2018 20:39, Stefan Herbrechtsmeier wrote: >> Hi Stefano, >> >> I don't think this is a good idea because the old API isn't optimal and >> with this step you remove the possibility to remove it in the future. > Well, I do not think so. But I am already facing compatibility issues. > There are projects where swuforwarder is used to send images to > underlying devices that are not connected to the (main) network. I > cannot manage (and I do not know, really) if all devices will be > upgraded to new version (with V2) or there will be a mix with devices > still running V1 and gateway running V2. The gateway (swu forwarder) should always support the version 1 clients. >> For example the upload handler v1 is only needed to support the old >> example web page which use a uncommon upload script. > In my understanding, V1 is just required for compatibility reasons where > swuforwarder is used. We do not require it to expose a website. But this means it would be enough to support an endpoint for the upload and get status function. Maybe the swu forwarder could use the form file upload to remove the dependency to the version 1 upload function. >> Additionally it is >> impossible to support a swu forwarder and a modern website at the same >> time. > True - but does exist the use case ? > > IMHO there is no reason to run V1 as website now that V2 is mainlined. > V1 is just for M2M where nobody can see website's quality. But why you want to activate all the old function you don't need. It should be enough to activate the old upload and getstatus url. >> Mongoose support the web socket client interface and could be used to >> add v2 support to the swu forwarder: >> https://github.com/cesanta/mongoose/tree/master/examples/websocket_chat_client > Ok, thanks for link, I take a look. > >> >> Alternatively you could add a m2m mode with only enabled the needed >> interfaces. > What do you mean ? I do not get it. I mean a http gateway (m2m) mode which only enabled the needed endpoints and disables the website. >> Am 05.03.2018 um 12:26 schrieb Stefano Babic: >>> V1 version of REST-API is still more suitable for M2M >>> updates together with the SWU forwarder handler. >>> However, it is not currently possible to switch >>> between them and the API must be selected at compile time, >>> and this requires different binaries. >>> >>> Let the user decides which version of API should run >>> with a command line parameter. >>> >>> Signed-off-by: Stefano Babic <sbabic@denx.de> >>> CC: Sami Hartikainen <sami.hartikainen@teleste.com> >>> --- >>> >>> Changes since V1: >>> - Drop MONGOOSE_WEB_API_V1 and MONGOOSE_WEB_API_V2 >>> - Add help for "api" >>> - Add "api" parameter to configuration file >>> - "/upload" URL just in case of v2 >>> - print API version when Webserver is started >>> >>> mongoose/Config.in | 18 -- >>> mongoose/Makefile | 2 - >>> mongoose/mongoose_interface.c | 464 >>> ++++++++++++++++++++++-------------------- >>> 3 files changed, 246 insertions(+), 238 deletions(-) >>> >>> diff --git a/mongoose/Config.in b/mongoose/Config.in >>> index a001247..29cac90 100644 >>> --- a/mongoose/Config.in >>> +++ b/mongoose/Config.in >>> @@ -18,24 +18,6 @@ config MONGOOSE >>> endchoice >>> -choice >>> - prompt "Web Application Interface" >>> - default MONGOOSE_WEB_API_V1 >>> - help >>> - Choose the bootloader >>> - >>> -config MONGOOSE_WEB_API_V1 >>> - bool "Version 1 (deprecated)" >>> - help >>> - Support for version 1 >>> - >>> -config MONGOOSE_WEB_API_V2 >>> - bool "Version 2" >>> - help >>> - Support for version 2 >>> - >>> -endchoice >>> - >> Why not let the user decide if he want to support one or both api versions? > Well, the user decides, but at runtime... > >>> config MONGOOSEIPV6 >>> bool "IPv6 support" >>> default y >>> diff --git a/mongoose/Makefile b/mongoose/Makefile >>> index 77a616c..dc2d3d3 100644 >>> --- a/mongoose/Makefile >>> +++ b/mongoose/Makefile >>> @@ -1,9 +1,7 @@ >>> ifneq ($(CONFIG_WEBSERVER),) >>> ifneq ($(CONFIG_MONGOOSE),) >>> KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1 >>> -ifneq ($(CONFIG_MONGOOSE_WEB_API_V2),) >>> KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1 >>> -endif >>> ifneq ($(CONFIG_MONGOOSEIPV6),) >>> KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1 >>> endif >>> diff --git a/mongoose/mongoose_interface.c >>> b/mongoose/mongoose_interface.c >>> index 0e22671..99f9fc4 100644 >>> --- a/mongoose/mongoose_interface.c >>> +++ b/mongoose/mongoose_interface.c >>> @@ -32,6 +32,11 @@ >>> #define MG_PORT "8080" >>> #define MG_ROOT "." >>> +enum MONGOOSE_API_VERSION { >>> + MONGOOSE_API_V1, >>> + MONGOOSE_API_V2 >>> +}; >>> + >>> struct mongoose_options { >>> char *root; >>> char *listing; >>> @@ -40,6 +45,7 @@ struct mongoose_options { >>> char *ssl_cert; >>> char *ssl_key; >>> #endif >>> + int api_version; >> Why you doesn't use an enum? > I'll fix it. > >>> }; >>> struct file_upload_state { >>> @@ -50,7 +56,9 @@ struct file_upload_state { >>> static struct mg_serve_http_opts s_http_server_opts; >>> static void upload_handler(struct mg_connection *nc, int ev, void *p); >>> -#if defined(CONFIG_MONGOOSE_WEB_API_V2) >>> +/* >>> + * These functions are for V2 of the protocol >>> + */ >>> #define enum_string(x) [x] = #x >>> static const char *get_status_string(unsigned int status) >>> { [snip] >>> @@ -510,6 +520,12 @@ static int mongoose_settings(void *elem, void >>> __attribute__ ((__unused__)) *dat >>> opts->ssl_key = strdup(tmp); >>> } >>> #endif >>> + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api", tmp); >>> + if (strlen(tmp)) { >>> + opts->api_version = (!strcmp(tmp, "v1")) ? >>> + MONGOOSE_API_V1 : >>> + MONGOOSE_API_V2; >>> + } >>> return 0; >>> } >>> @@ -523,6 +539,7 @@ static struct option long_options[] = { >>> {"ssl-key", required_argument, NULL, 'K'}, >>> #endif >>> {"document-root", required_argument, NULL, 'r'}, >>> + {"api", required_argument, NULL, 'a'}, >> I would call it api-version to use a simple number as argument. >> >>> {NULL, 0, NULL, 0} >>> }; > Best regards Stefan
Hi Stefano, Stefan, > >> For example the upload handler v1 is only needed to support the old > >> example web page which use a uncommon upload script. > > In my understanding, V1 is just required for compatibility reasons > > where swuforwarder is used. We do not require it to expose a website. There are (custom/proprietary) user interfaces relying on V1's upload and status interfaces, not only swuforwarder. But they probably do not need swupdate to expose a website, that is true. When/if such UI's are upgraded to use V2, we cannot control, and should not force it, at least not anytime soon. > But this means it would be enough to support an endpoint for the upload > and get status function. That would mean changing V1 interface. Changing an established interface in general is a bad idea. Br, Sami
Hi Stefan, On 05/03/2018 22:42, Stefan Herbrechtsmeier wrote: > Hi Stefano, > > Am 05.03.2018 um 21:14 schrieb Stefano Babic: >> Hi Stefan, >> >> On 05/03/2018 20:39, Stefan Herbrechtsmeier wrote: >>> Hi Stefano, >>> >>> I don't think this is a good idea because the old API isn't optimal and >>> with this step you remove the possibility to remove it in the future. >> Well, I do not think so. But I am already facing compatibility issues. >> There are projects where swuforwarder is used to send images to >> underlying devices that are not connected to the (main) network. I >> cannot manage (and I do not know, really) if all devices will be >> upgraded to new version (with V2) or there will be a mix with devices >> still running V1 and gateway running V2. > > The gateway (swu forwarder) should always support the version 1 clients. Right, at least for the moment. New website is just coming with the incoming 2018.03 release, and I cannot put new things and break with the past at once. Else I get a lot of complaints from ML users (well, it is a pity...) and from customers (and I *must* avoid this). > >>> For example the upload handler v1 is only needed to support the old >>> example web page which use a uncommon upload script. >> In my understanding, V1 is just required for compatibility reasons where >> swuforwarder is used. We do not require it to expose a website. > > But this means it would be enough to support an endpoint for the upload > and get status function. Maybe the swu forwarder could use the form file > upload to remove the dependency to the version 1 upload function. So just support "/handle_post_request" and "/getstatus.json". /rebootTarget is already broken after adding privilege separation. "/postUpdateCommand" is also a hack to allow rebooting, but this can be done in other ways, too. I will tend to drop them... > >>> Additionally it is >>> impossible to support a swu forwarder and a modern website at the same >>> time. >> True - but does exist the use case ? >> >> IMHO there is no reason to run V1 as website now that V2 is mainlined. >> V1 is just for M2M where nobody can see website's quality. > > But why you want to activate all the old function you don't need. It > should be enough to activate the old upload and getstatus url. You are convincing me... > >>> Mongoose support the web socket client interface and could be used to >>> add v2 support to the swu forwarder: >>> https://github.com/cesanta/mongoose/tree/master/examples/websocket_chat_client >>> >> Ok, thanks for link, I take a look. >> >>> >>> Alternatively you could add a m2m mode with only enabled the needed >>> interfaces. >> What do you mean ? I do not get it. > > I mean a http gateway (m2m) mode which only enabled the needed endpoints > and disables the website. In the upcoming release, I will tend to drop /www, too. This signals the user which version they should use. There is no additional effort for them, because a ready-to-use website is stored under example. Anyway, meta-swupdate should reflect these changes, too. > >>> Am 05.03.2018 um 12:26 schrieb Stefano Babic: >>>> V1 version of REST-API is still more suitable for M2M >>>> updates together with the SWU forwarder handler. >>>> However, it is not currently possible to switch >>>> between them and the API must be selected at compile time, >>>> and this requires different binaries. >>>> >>>> Let the user decides which version of API should run >>>> with a command line parameter. >>>> >>>> Signed-off-by: Stefano Babic <sbabic@denx.de> >>>> CC: Sami Hartikainen <sami.hartikainen@teleste.com> >>>> --- >>>> >>>> Changes since V1: >>>> - Drop MONGOOSE_WEB_API_V1 and MONGOOSE_WEB_API_V2 >>>> - Add help for "api" >>>> - Add "api" parameter to configuration file >>>> - "/upload" URL just in case of v2 >>>> - print API version when Webserver is started >>>> >>>> mongoose/Config.in | 18 -- >>>> mongoose/Makefile | 2 - >>>> mongoose/mongoose_interface.c | 464 >>>> ++++++++++++++++++++++-------------------- >>>> 3 files changed, 246 insertions(+), 238 deletions(-) >>>> >>>> diff --git a/mongoose/Config.in b/mongoose/Config.in >>>> index a001247..29cac90 100644 >>>> --- a/mongoose/Config.in >>>> +++ b/mongoose/Config.in >>>> @@ -18,24 +18,6 @@ config MONGOOSE >>>> endchoice >>>> -choice >>>> - prompt "Web Application Interface" >>>> - default MONGOOSE_WEB_API_V1 >>>> - help >>>> - Choose the bootloader >>>> - >>>> -config MONGOOSE_WEB_API_V1 >>>> - bool "Version 1 (deprecated)" >>>> - help >>>> - Support for version 1 >>>> - >>>> -config MONGOOSE_WEB_API_V2 >>>> - bool "Version 2" >>>> - help >>>> - Support for version 2 >>>> - >>>> -endchoice >>>> - >>> Why not let the user decide if he want to support one or both api >>> versions? >> Well, the user decides, but at runtime... >> >>>> config MONGOOSEIPV6 >>>> bool "IPv6 support" >>>> default y >>>> diff --git a/mongoose/Makefile b/mongoose/Makefile >>>> index 77a616c..dc2d3d3 100644 >>>> --- a/mongoose/Makefile >>>> +++ b/mongoose/Makefile >>>> @@ -1,9 +1,7 @@ >>>> ifneq ($(CONFIG_WEBSERVER),) >>>> ifneq ($(CONFIG_MONGOOSE),) >>>> KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1 >>>> -ifneq ($(CONFIG_MONGOOSE_WEB_API_V2),) >>>> KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1 >>>> -endif >>>> ifneq ($(CONFIG_MONGOOSEIPV6),) >>>> KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1 >>>> endif >>>> diff --git a/mongoose/mongoose_interface.c >>>> b/mongoose/mongoose_interface.c >>>> index 0e22671..99f9fc4 100644 >>>> --- a/mongoose/mongoose_interface.c >>>> +++ b/mongoose/mongoose_interface.c >>>> @@ -32,6 +32,11 @@ >>>> #define MG_PORT "8080" >>>> #define MG_ROOT "." >>>> +enum MONGOOSE_API_VERSION { >>>> + MONGOOSE_API_V1, >>>> + MONGOOSE_API_V2 >>>> +}; >>>> + >>>> struct mongoose_options { >>>> char *root; >>>> char *listing; >>>> @@ -40,6 +45,7 @@ struct mongoose_options { >>>> char *ssl_cert; >>>> char *ssl_key; >>>> #endif >>>> + int api_version; >>> Why you doesn't use an enum? >> I'll fix it. >> >>>> }; >>>> struct file_upload_state { >>>> @@ -50,7 +56,9 @@ struct file_upload_state { >>>> static struct mg_serve_http_opts s_http_server_opts; >>>> static void upload_handler(struct mg_connection *nc, int ev, void >>>> *p); >>>> -#if defined(CONFIG_MONGOOSE_WEB_API_V2) >>>> +/* >>>> + * These functions are for V2 of the protocol >>>> + */ >>>> #define enum_string(x) [x] = #x >>>> static const char *get_status_string(unsigned int status) >>>> { > > [snip] > >>>> @@ -510,6 +520,12 @@ static int mongoose_settings(void *elem, void >>>> __attribute__ ((__unused__)) *dat >>>> opts->ssl_key = strdup(tmp); >>>> } >>>> #endif >>>> + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api", tmp); >>>> + if (strlen(tmp)) { >>>> + opts->api_version = (!strcmp(tmp, "v1")) ? >>>> + MONGOOSE_API_V1 : >>>> + MONGOOSE_API_V2; >>>> + } >>>> return 0; >>>> } >>>> @@ -523,6 +539,7 @@ static struct option long_options[] = { >>>> {"ssl-key", required_argument, NULL, 'K'}, >>>> #endif >>>> {"document-root", required_argument, NULL, 'r'}, >>>> + {"api", required_argument, NULL, 'a'}, >>> I would call it api-version to use a simple number as argument. >>> >>>> {NULL, 0, NULL, 0} >>>> }; >> > Best regards, Stefano
Hi Sami, Stefan, On 06/03/2018 08:48, Sami.Hartikainen@teleste.com wrote: > Hi Stefano, Stefan, > >>>> For example the upload handler v1 is only needed to support the old >>>> example web page which use a uncommon upload script. >>> In my understanding, V1 is just required for compatibility reasons >>> where swuforwarder is used. We do not require it to expose a website. > > There are (custom/proprietary) user interfaces relying on V1's upload and status > interfaces, not only swuforwarder. True. I know such a case from a customer, they embedded the REST-API in their application. At that time, client interface and swuforwarder were not yet implemented. > But they probably do not need swupdate to > expose a website, that is true. Yes, Stefan's point is correct. It should enough to expose /handle_post_request (why have I used such as url instead of a much simple /upload ? I cannot answer to myself..) and /getstatus, and the other URLs are dropped. > > When/if such UI's are upgraded to use V2, we cannot control, and should not > force it, at least not anytime soon. Yes - this is completely outside the control of this project. > >> But this means it would be enough to support an endpoint for the upload >> and get status function. > > That would mean changing V1 interface. Changing an established interface in > general is a bad idea. Well, but the V1 interface consists of the two URLs above. You are also saying that custom/proprietary interfaces rely just on upload/status. If we mainatin these, we are on the safe side, right ? My personal thought: I would not like to see new version of SWUpdate still running the old legacy website, now that a responsive Website is available, just because user have always done in that way. I would like to signal "hey, take a look, there is good news !". So removing V1 website and not needed URLs look ok to me if we arrange to not break existing (also custom) applications. Best regards, Stefano
Hi Stefano, > >> But this means it would be enough to support an endpoint for the > >> upload and get status function. > > > > That would mean changing V1 interface. Changing an established > > interface in general is a bad idea. > > Well, but the V1 interface consists of the two URLs above. You are also saying > that custom/proprietary interfaces rely just on upload/status. If we mainatin > these, we are on the safe side, right ? Most likely, yes. Perhaps add a mention in upcoming 2018.03 release notes and also somewhere in documentation that /rebootTarget and /postUpdateCommand are considered obsolete and will be removed in some future release? > My personal thought: I would not like to see new version of SWUpdate still > running the old legacy website, now that a responsive Website is available, > just because user have always done in that way. I would like to signal "hey, > take a look, there is good news !". So removing V1 website and not needed > URLs look ok to me if we arrange to not break existing (also custom) > applications. Sounds reasonable. Br, Sami
diff --git a/mongoose/Config.in b/mongoose/Config.in index a001247..29cac90 100644 --- a/mongoose/Config.in +++ b/mongoose/Config.in @@ -18,24 +18,6 @@ config MONGOOSE endchoice -choice - prompt "Web Application Interface" - default MONGOOSE_WEB_API_V1 - help - Choose the bootloader - -config MONGOOSE_WEB_API_V1 - bool "Version 1 (deprecated)" - help - Support for version 1 - -config MONGOOSE_WEB_API_V2 - bool "Version 2" - help - Support for version 2 - -endchoice - config MONGOOSEIPV6 bool "IPv6 support" default y diff --git a/mongoose/Makefile b/mongoose/Makefile index 77a616c..dc2d3d3 100644 --- a/mongoose/Makefile +++ b/mongoose/Makefile @@ -1,9 +1,7 @@ ifneq ($(CONFIG_WEBSERVER),) ifneq ($(CONFIG_MONGOOSE),) KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1 -ifneq ($(CONFIG_MONGOOSE_WEB_API_V2),) KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1 -endif ifneq ($(CONFIG_MONGOOSEIPV6),) KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1 endif diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c index 0e22671..99f9fc4 100644 --- a/mongoose/mongoose_interface.c +++ b/mongoose/mongoose_interface.c @@ -32,6 +32,11 @@ #define MG_PORT "8080" #define MG_ROOT "." +enum MONGOOSE_API_VERSION { + MONGOOSE_API_V1, + MONGOOSE_API_V2 +}; + struct mongoose_options { char *root; char *listing; @@ -40,6 +45,7 @@ struct mongoose_options { char *ssl_cert; char *ssl_key; #endif + int api_version; }; struct file_upload_state { @@ -50,7 +56,9 @@ struct file_upload_state { static struct mg_serve_http_opts s_http_server_opts; static void upload_handler(struct mg_connection *nc, int ev, void *p); -#if defined(CONFIG_MONGOOSE_WEB_API_V2) +/* + * These functions are for V2 of the protocol + */ #define enum_string(x) [x] = #x static const char *get_status_string(unsigned int status) { @@ -111,9 +119,166 @@ static size_t snescape(char *dst, size_t n, const char *src) return len; } -#endif -#if defined(CONFIG_MONGOOSE_WEB_API_V1) +static void restart_handler(struct mg_connection *nc, int ev, void *ev_data) +{ + struct http_message *hm = (struct http_message *) ev_data; + ipc_message msg = {}; + + (void)ev; + + if(mg_vcasecmp(&hm->method, "POST") != 0) { + mg_http_send_error(nc, 405, "Method Not Allowed"); + return; + } + + int ret = ipc_postupdate(&msg); + if (ret) { + mg_http_send_error(nc, 500, "Failed to queue command"); + return; + } + + mg_http_send_error(nc, 201, "Device will reboot now."); +} + +static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data) +{ + char *buf = (char *) ev_data; + + if (ev != MG_EV_POLL) + return; + + if (!(nc->flags & MG_F_IS_WEBSOCKET)) + return; + + mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); +} + +static void broadcast(struct mg_mgr *mgr, char *str) +{ + mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1); +} + +static void *broadcast_message_thread(void *data) +{ + for (;;) { + ipc_message msg; + int ret = ipc_get_status(&msg); + + if (!ret && strlen(msg.data.status.desc) != 0) { + struct mg_mgr *mgr = (struct mg_mgr *) data; + char text[4096]; + char str[4160]; + + snescape(text, sizeof(text), msg.data.status.desc); + + snprintf(str, sizeof(str), + "{\r\n" + "\t\"type\": \"message\",\r\n" + "\t\"level\": \"%d\",\r\n" + "\t\"text\": \"%s\"\r\n" + "}\r\n", + (msg.data.status.error) ? 3 : 6, /* RFC 5424 */ + text); + + broadcast(mgr, str); + continue; + } + + usleep(50 * 1000); + } + + return NULL; +} + +static void *broadcast_progress_thread(void *data) +{ + int status = -1; + int source = -1; + int step = 0; + int percent = 0; + int fd = -1; + + for (;;) { + struct mg_mgr *mgr = (struct mg_mgr *) data; + struct progress_msg msg; + char str[512]; + int ret; + + if (fd < 0) + fd = progress_ipc_connect(true); + + ret = progress_ipc_receive(&fd, &msg); + if (ret != sizeof(msg)) + return NULL; + + if (msg.status != status || msg.status == FAILURE) { + status = msg.status; + + snprintf(str, sizeof(str), + "{\r\n" + "\t\"type\": \"status\",\r\n" + "\t\"status\": \"%s\"\r\n" + "}\r\n", + get_status_string(msg.status)); + broadcast(mgr, str); + } + + if (msg.source != source) { + source = msg.source; + + snprintf(str, sizeof(str), + "{\r\n" + "\t\"type\": \"source\",\r\n" + "\t\"source\": \"%s\"\r\n" + "}\r\n", + get_source_string(msg.source)); + broadcast(mgr, str); + } + + if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER) { + ipc_message ipc = {}; + + ipc_postupdate(&ipc); + } + + if (msg.infolen) { + snprintf(str, sizeof(str), + "{\r\n" + "\t\"type\": \"info\",\r\n" + "\t\"source\": \"%s\"\r\n" + "}\r\n", + msg.info); + broadcast(mgr, str); + } + + if ((msg.cur_step != step || msg.cur_percent != percent) && + msg.cur_step) { + step = msg.cur_step; + percent = msg.cur_percent; + + snprintf(str, sizeof(str), + "{\r\n" + "\t\"type\": \"step\",\r\n" + "\t\"number\": \"%d\",\r\n" + "\t\"step\": \"%d\",\r\n" + "\t\"name\": \"%s\",\r\n" + "\t\"percent\": \"%d\"\r\n" + "}\r\n", + msg.nsteps, + msg.cur_step, + msg.cur_step ? msg.cur_image: "", + msg.cur_percent); + broadcast(mgr, str); + } + } + + return NULL; +} + +/* + * These functions are for V1 of the protocol + */ static void upload_handler_v1(struct mg_connection *nc, int ev, void *p) { struct mg_str *filename, *data; @@ -162,69 +327,7 @@ static void upload_handler_v1(struct mg_connection *nc, int ev, void *p) break; } } -#endif - -static void upload_handler(struct mg_connection *nc, int ev, void *p) -{ - struct mg_http_multipart_part *mp; - struct file_upload_state *fus; - - switch (ev) { - case MG_EV_HTTP_PART_BEGIN: - mp = (struct mg_http_multipart_part *) p; - - fus = (struct file_upload_state *) calloc(1, sizeof(*fus)); - if (fus == NULL) { - mg_http_send_error(nc, 500, "Out of memory"); - break; - } - - fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name); - if (fus->fd < 0) { - mg_http_send_error(nc, 500, "Failed to queue command"); - free(fus); - break; - } - - mp->user_data = fus; - - break; - - case MG_EV_HTTP_PART_DATA: - mp = (struct mg_http_multipart_part *) p; - fus = (struct file_upload_state *) mp->user_data; - - if (!fus) - break; - - ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len); - fus->len += mp->data.len; - - break; - - case MG_EV_HTTP_PART_END: - mp = (struct mg_http_multipart_part *) p; - fus = (struct file_upload_state *) mp->user_data; - if (!fus) - break; - - ipc_end(fus->fd); - - mg_send_response_line(nc, 200, - "Content-Type: text/plain\r\n" - "Connection: close"); - mg_send(nc, "\r\n", 2); - mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len); - nc->flags |= MG_F_SEND_AND_CLOSE; - - mp->user_data = NULL; - free(fus); - break; - } -} - -#if defined(CONFIG_MONGOOSE_WEB_API_V1) static void recovery_status(struct mg_connection *nc, int ev, void *ev_data) { ipc_message ipc; @@ -314,163 +417,70 @@ static void post_update_cmd(struct mg_connection *nc, int ev, void *ev_data) nc->flags |= MG_F_SEND_AND_CLOSE; } -#elif defined(CONFIG_MONGOOSE_WEB_API_V2) -static void restart_handler(struct mg_connection *nc, int ev, void *ev_data) -{ - struct http_message *hm = (struct http_message *) ev_data; - ipc_message msg = {}; - - (void)ev; - - if(mg_vcasecmp(&hm->method, "POST") != 0) { - mg_http_send_error(nc, 405, "Method Not Allowed"); - return; - } - - int ret = ipc_postupdate(&msg); - if (ret) { - mg_http_send_error(nc, 500, "Failed to queue command"); - return; - } - - mg_http_send_error(nc, 201, "Device will reboot now."); -} - -static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data) -{ - char *buf = (char *) ev_data; - if (ev != MG_EV_POLL) - return; - if (!(nc->flags & MG_F_IS_WEBSOCKET)) - return; - - mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); -} - -static void broadcast(struct mg_mgr *mgr, char *str) -{ - mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1); -} - -static void *broadcast_message_thread(void *data) +/* + * Code common to V1 and V2 + */ +static void upload_handler(struct mg_connection *nc, int ev, void *p) { - for (;;) { - ipc_message msg; - int ret = ipc_get_status(&msg); - - if (!ret && strlen(msg.data.status.desc) != 0) { - struct mg_mgr *mgr = (struct mg_mgr *) data; - char text[4096]; - char str[4160]; - - snescape(text, sizeof(text), msg.data.status.desc); + struct mg_http_multipart_part *mp; + struct file_upload_state *fus; - snprintf(str, sizeof(str), - "{\r\n" - "\t\"type\": \"message\",\r\n" - "\t\"level\": \"%d\",\r\n" - "\t\"text\": \"%s\"\r\n" - "}\r\n", - (msg.data.status.error) ? 3 : 6, /* RFC 5424 */ - text); + switch (ev) { + case MG_EV_HTTP_PART_BEGIN: + mp = (struct mg_http_multipart_part *) p; - broadcast(mgr, str); - continue; + fus = (struct file_upload_state *) calloc(1, sizeof(*fus)); + if (fus == NULL) { + mg_http_send_error(nc, 500, "Out of memory"); + break; } - usleep(50 * 1000); - } - - return NULL; -} - -static void *broadcast_progress_thread(void *data) -{ - int status = -1; - int source = -1; - int step = 0; - int percent = 0; - int fd = -1; - - for (;;) { - struct mg_mgr *mgr = (struct mg_mgr *) data; - struct progress_msg msg; - char str[512]; - int ret; + fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name); + if (fus->fd < 0) { + mg_http_send_error(nc, 500, "Failed to queue command"); + free(fus); + break; + } - if (fd < 0) - fd = progress_ipc_connect(true); + mp->user_data = fus; - ret = progress_ipc_receive(&fd, &msg); - if (ret != sizeof(msg)) - return NULL; + break; - if (msg.status != status || msg.status == FAILURE) { - status = msg.status; + case MG_EV_HTTP_PART_DATA: + mp = (struct mg_http_multipart_part *) p; + fus = (struct file_upload_state *) mp->user_data; - snprintf(str, sizeof(str), - "{\r\n" - "\t\"type\": \"status\",\r\n" - "\t\"status\": \"%s\"\r\n" - "}\r\n", - get_status_string(msg.status)); - broadcast(mgr, str); - } + if (!fus) + break; - if (msg.source != source) { - source = msg.source; + ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len); + fus->len += mp->data.len; - snprintf(str, sizeof(str), - "{\r\n" - "\t\"type\": \"source\",\r\n" - "\t\"source\": \"%s\"\r\n" - "}\r\n", - get_source_string(msg.source)); - broadcast(mgr, str); - } + break; - if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER) { - ipc_message ipc = {}; + case MG_EV_HTTP_PART_END: + mp = (struct mg_http_multipart_part *) p; + fus = (struct file_upload_state *) mp->user_data; - ipc_postupdate(&ipc); - } + if (!fus) + break; - if (msg.infolen) { - snprintf(str, sizeof(str), - "{\r\n" - "\t\"type\": \"info\",\r\n" - "\t\"source\": \"%s\"\r\n" - "}\r\n", - msg.info); - broadcast(mgr, str); - } + ipc_end(fus->fd); - if ((msg.cur_step != step || msg.cur_percent != percent) && - msg.cur_step) { - step = msg.cur_step; - percent = msg.cur_percent; + mg_send_response_line(nc, 200, + "Content-Type: text/plain\r\n" + "Connection: close"); + mg_send(nc, "\r\n", 2); + mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len); + nc->flags |= MG_F_SEND_AND_CLOSE; - snprintf(str, sizeof(str), - "{\r\n" - "\t\"type\": \"step\",\r\n" - "\t\"number\": \"%d\",\r\n" - "\t\"step\": \"%d\",\r\n" - "\t\"name\": \"%s\",\r\n" - "\t\"percent\": \"%d\"\r\n" - "}\r\n", - msg.nsteps, - msg.cur_step, - msg.cur_step ? msg.cur_image: "", - msg.cur_percent); - broadcast(mgr, str); - } + mp->user_data = NULL; + free(fus); + break; } - - return NULL; } -#endif static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { @@ -510,6 +520,12 @@ static int mongoose_settings(void *elem, void __attribute__ ((__unused__)) *dat opts->ssl_key = strdup(tmp); } #endif + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api", tmp); + if (strlen(tmp)) { + opts->api_version = (!strcmp(tmp, "v1")) ? + MONGOOSE_API_V1 : + MONGOOSE_API_V2; + } return 0; } @@ -523,6 +539,7 @@ static struct option long_options[] = { {"ssl-key", required_argument, NULL, 'K'}, #endif {"document-root", required_argument, NULL, 'r'}, + {"api", required_argument, NULL, 'a'}, {NULL, 0, NULL, 0} }; @@ -538,7 +555,8 @@ void mongoose_print_help(void) "\t -C, --ssl-cert <cert> : ssl certificate to present to clients\n" "\t -K, --ssl-key <key> : key corresponding to the ssl certificate\n" #endif - "\t -r, --document-root <path> : path to document root directory (default: %s)\n", + "\t -r, --document-root <path> : path to document root directory (default: %s)\n" + "\t -a, --api [v1|v2] : set Web protocol API to v1 (legacy) or v2 (default v2)\n", MG_LISTING, MG_PORT, MG_ROOT); } @@ -556,12 +574,14 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[]) int choice = 0; memset(&opts, 0, sizeof(opts)); + /* Set default API version */ + opts.api_version = MONGOOSE_API_V2; if (cfgfname) { read_module_settings(cfgfname, "webserver", mongoose_settings, &opts); } optind = 1; - while ((choice = getopt_long(argc, argv, "lp:sC:K:r:", + while ((choice = getopt_long(argc, argv, "lp:sC:K:r:a:", long_options, NULL)) != -1) { switch (choice) { case 'l': @@ -589,6 +609,11 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[]) free(opts.root); opts.root = strdup(optarg); break; + case 'a': + opts.api_version = (!strcmp(optarg, "v1")) ? + MONGOOSE_API_V1 : + MONGOOSE_API_V2; + break; case '?': default: return -EINVAL; @@ -619,22 +644,25 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[]) } mg_set_protocol_http_websocket(nc); - mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL)); -#if defined(CONFIG_MONGOOSE_WEB_API_V1) - mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler_v1, NULL)); - mg_register_http_endpoint(nc, "/getstatus.json", MG_CB(recovery_status, NULL)); - mg_register_http_endpoint(nc, "/rebootTarget", MG_CB(reboot_target, NULL)); - mg_register_http_endpoint(nc, "/postUpdateCommand", MG_CB(post_update_cmd, NULL)); -#endif -#if defined(CONFIG_MONGOOSE_WEB_API_V2) - mg_register_http_endpoint(nc, "/restart", restart_handler); - mg_start_thread(broadcast_message_thread, &mgr); - mg_start_thread(broadcast_progress_thread, &mgr); -#endif + switch (opts.api_version) { + case MONGOOSE_API_V1: + mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler_v1, NULL)); + mg_register_http_endpoint(nc, "/getstatus.json", MG_CB(recovery_status, NULL)); + mg_register_http_endpoint(nc, "/rebootTarget", MG_CB(reboot_target, NULL)); + mg_register_http_endpoint(nc, "/postUpdateCommand", MG_CB(post_update_cmd, NULL)); + break; + case MONGOOSE_API_V2: + mg_register_http_endpoint(nc, "/restart", restart_handler); + mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL)); + mg_start_thread(broadcast_message_thread, &mgr); + mg_start_thread(broadcast_progress_thread, &mgr); + break; + } - printf("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s]\n", + printf("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s] and API %s\n", MG_VERSION, getpid(), s_http_port, - s_http_server_opts.document_root); + s_http_server_opts.document_root, + (opts.api_version == MONGOOSE_API_V1) ? "v1" : "v2"); for (;;) { mg_mgr_poll(&mgr, 100);
V1 version of REST-API is still more suitable for M2M updates together with the SWU forwarder handler. However, it is not currently possible to switch between them and the API must be selected at compile time, and this requires different binaries. Let the user decides which version of API should run with a command line parameter. Signed-off-by: Stefano Babic <sbabic@denx.de> CC: Sami Hartikainen <sami.hartikainen@teleste.com> --- Changes since V1: - Drop MONGOOSE_WEB_API_V1 and MONGOOSE_WEB_API_V2 - Add help for "api" - Add "api" parameter to configuration file - "/upload" URL just in case of v2 - print API version when Webserver is started mongoose/Config.in | 18 -- mongoose/Makefile | 2 - mongoose/mongoose_interface.c | 464 ++++++++++++++++++++++-------------------- 3 files changed, 246 insertions(+), 238 deletions(-)