diff mbox series

[V2,2/2] mongoose: set V1 or V2 at runtime

Message ID 1520249215-31082-2-git-send-email-sbabic@denx.de
State Changes Requested
Headers show
Series None | expand

Commit Message

Stefano Babic March 5, 2018, 11:26 a.m. UTC
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(-)

Comments

Stefan Herbrechtsmeier March 5, 2018, 7:39 p.m. UTC | #1
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
Stefano Babic March 5, 2018, 8:14 p.m. UTC | #2
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
=====================================================================
Stefan Herbrechtsmeier March 5, 2018, 9:42 p.m. UTC | #3
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
Hartikainen, Sami March 6, 2018, 7:48 a.m. UTC | #4
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
Stefano Babic March 6, 2018, 9:05 a.m. UTC | #5
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
Stefano Babic March 6, 2018, 9:17 a.m. UTC | #6
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
Hartikainen, Sami March 6, 2018, 10:47 a.m. UTC | #7
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 mbox series

Patch

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);