diff mbox series

channel_curl: Optionally return body on PUT

Message ID 20230517084626.104774-1-christian.storm@siemens.com
State Accepted
Delegated to: Stefano Babic
Headers show
Series channel_curl: Optionally return body on PUT | expand

Commit Message

Storm, Christian May 17, 2023, 8:46 a.m. UTC
PUT may return the sent document applied. This is
currently not returned to channel_curl consumers.

Signed-off-by: Christian Storm <christian.storm@siemens.com>
---
 corelib/channel_curl.c     | 10 ++++--
 suricatta/server_general.c |  3 ++
 suricatta/server_hawkbit.c |  1 +
 suricatta/server_lua.c     | 70 ++++++++++++++++++--------------------
 4 files changed, 45 insertions(+), 39 deletions(-)

Comments

Stefano Babic May 17, 2023, 9:41 a.m. UTC | #1
Hi Christian,

On 17.05.23 10:46, 'Christian Storm' via swupdate wrote:
> PUT may return the sent document applied. This is
> currently not returned to channel_curl consumers.
> 

Is it a proprietary solution ? I do not see any reference about body 
response in RFC7231 (HTTP 1.1).

Sure, there are HTTP codes that current implementation does not support.

> Signed-off-by: Christian Storm <christian.storm@siemens.com>
> ---
>   corelib/channel_curl.c     | 10 ++++--
>   suricatta/server_general.c |  3 ++
>   suricatta/server_hawkbit.c |  1 +
>   suricatta/server_lua.c     | 70 ++++++++++++++++++--------------------
>   4 files changed, 45 insertions(+), 39 deletions(-)
> 
> diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
> index 1207304e..24a259e3 100644
> --- a/corelib/channel_curl.c
> +++ b/corelib/channel_curl.c
> @@ -342,6 +342,8 @@ channel_op_res_t channel_map_http_code(channel_t *this, long *http_response_code
>   	case 429: /* Bad Request, i.e., too many requests. Try again later. */
>   		return CHANNEL_EAGAIN;
>   	case 200:
> +	case 201:
> +	case 204:
>   	case 206:
>   	case 226:
>   		return CHANNEL_OK;
> @@ -1103,7 +1105,7 @@ static channel_op_res_t channel_put_method(channel_t *this, void *data)
>   
>   	CURLcode curlrc = curl_easy_perform(channel_curl->handle);
>   	if (curlrc != CURLE_OK) {
> -		ERROR("Channel put operation failed (%d): '%s'", curlrc,
> +		ERROR("Channel PUT operation failed (%d): '%s'", curlrc,
>   		      curl_easy_strerror(curlrc));
>   		result = channel_map_curl_error(curlrc);
>   		goto cleanup_header;
> @@ -1116,7 +1118,11 @@ static channel_op_res_t channel_put_method(channel_t *this, void *data)
>   	if (channel_data->nocheckanswer)
>   		goto cleanup_header;
>   
> -	channel_log_reply(result, channel_data, NULL);
> +	channel_log_reply(result, channel_data, &outdata);
> +
> +	if (result == CHANNEL_OK) {
> +	    result = parse_reply(channel_data, &outdata);
> +	}
>   
>   cleanup_header:
>   	outdata.memory != NULL ? free(outdata.memory) : (void)0;
> diff --git a/suricatta/server_general.c b/suricatta/server_general.c
> index 1c8e777e..6f4bce8b 100644
> --- a/suricatta/server_general.c
> +++ b/suricatta/server_general.c
> @@ -324,6 +324,9 @@ static void *server_progress_thread (void *data)
>   		if (logbuffer) {
>   			channel_data.request_body = logbuffer;
>   			channel_data.method = CHANNEL_PUT;
> +			/* .format is already specified in channel_data_defaults,
> +			 * but being explicit doesn't hurt. */
> +			channel_data.format = CHANNEL_PARSE_NONE;
>   			channel_data.content_type = "application/text";
>   			result = map_channel_retcode(channel->put(channel, (void *)&channel_data));
>   			if (result != SERVER_OK)
> diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
> index 726ef191..a400c12c 100644
> --- a/suricatta/server_hawkbit.c
> +++ b/suricatta/server_hawkbit.c
> @@ -1590,6 +1590,7 @@ static server_op_res_t server_send_target_data(void)
>   	}
>   
>   	channel_data_reply.url = url;
> +	channel_data_reply.format = CHANNEL_PARSE_NONE;
>   	channel_data_reply.request_body = json_reply_string;
>   	TRACE("URL=%s JSON=%s", channel_data_reply.url, channel_data_reply.request_body);
>   	channel_data_reply.method = CHANNEL_PUT;
> diff --git a/suricatta/server_lua.c b/suricatta/server_lua.c
> index 579ddc70..bdf34084 100644
> --- a/suricatta/server_lua.c
> +++ b/suricatta/server_lua.c
> @@ -717,49 +717,45 @@ static int channel_do_operation(lua_State *L, channel_method_t op)
>   	push_result(L, result);
>   	lua_pushnumber(L, result);
>   	lua_newtable(L);
> -	if (op == CHANNEL_GET || channel_data.method == CHANNEL_POST ||
> -	    channel_data.method == CHANNEL_PATCH) {
> -		push_to_table(L, "http_response_code", channel_data.http_response_code);
> -		push_to_table(L, "format",             channel_data.format);
> -		#ifdef CONFIG_JSON
> -		if (channel_data.format == CHANNEL_PARSE_JSON) {
> -			lua_pushstring(L, "json_reply");
> -			if (!channel_data.json_reply ||
> -			    !json_to_table(L, channel_data.json_reply)) {
> -				lua_pushnil(L);
> -			}
> -			lua_settable(L, -3);
> -
> -			if (channel_data.json_reply &&
> -			    json_object_put(channel_data.json_reply) != 1) {
> -				ERROR("JSON object should be freed but was not.");
> -			}
> -		}
> -		#endif
> -		if (channel_data.format == CHANNEL_PARSE_RAW) {
> -			lua_pushstring(L, "raw_reply");
> -			if (!channel_data.raw_reply) {
> -				lua_pushnil(L);
> -			} else {
> -				lua_pushstring(L, channel_data.raw_reply);
> -				free(channel_data.raw_reply);
> -			}
> -			lua_settable(L, -3);
> +	push_to_table(L, "http_response_code", channel_data.http_response_code);
> +	push_to_table(L, "format",             channel_data.format);
> +	#ifdef CONFIG_JSON
> +	if (channel_data.format == CHANNEL_PARSE_JSON) {
> +		lua_pushstring(L, "json_reply");
> +		if (!channel_data.json_reply ||
> +			!json_to_table(L, channel_data.json_reply)) {
> +			lua_pushnil(L);
>   		}
> +		lua_settable(L, -3);
>   
> -		lua_pushstring(L, "received_headers");
> -		lua_newtable(L);
> -		if (!LIST_EMPTY(channel_data.received_headers)) {
> -			struct dict_entry *entry;
> -			LIST_FOREACH(entry, channel_data.received_headers, next) {
> -				lua_pushstring(L, dict_entry_get_key(entry));
> -				lua_pushstring(L, dict_entry_get_value(entry));
> -				lua_settable(L, -3);
> -			}
> +		if (channel_data.json_reply &&
> +			json_object_put(channel_data.json_reply) != 1) {
> +			ERROR("JSON object should be freed but was not.");
> +		}
> +	}
> +	#endif
> +	if (channel_data.format == CHANNEL_PARSE_RAW) {
> +		lua_pushstring(L, "raw_reply");
> +		if (!channel_data.raw_reply) {
> +			lua_pushnil(L);
> +		} else {
> +			lua_pushstring(L, channel_data.raw_reply);
> +			free(channel_data.raw_reply);
>   		}
>   		lua_settable(L, -3);
>   	}
>   
> +	lua_pushstring(L, "received_headers");
> +	lua_newtable(L);
> +	if (!LIST_EMPTY(channel_data.received_headers)) {
> +		struct dict_entry *entry;
> +		LIST_FOREACH(entry, channel_data.received_headers, next) {
> +			lua_pushstring(L, dict_entry_get_key(entry));
> +			lua_pushstring(L, dict_entry_get_value(entry));
> +			lua_settable(L, -3);
> +		}
> +	}
> +	lua_settable(L, -3);
>   	dict_drop_db(&header_send);
>   	dict_drop_db(&header_receive);
>   

Regards,
Stefano
Storm, Christian May 17, 2023, 11:59 a.m. UTC | #2
Hi Stefano,

> > PUT may return the sent document applied. This is
> > currently not returned to channel_curl consumers.
> > 
> 
> Is it a proprietary solution ? I do not see any reference about body response
> in RFC7231 (HTTP 1.1).

True. The HTTP standard does not mandate that there is a document
returned with a response. In fact, if the HTTP return code
(semantically) resembles the body content, it's even wasteful.
[This is the reason for defaulting to CHANNEL_PARSE_NONE]

However, applications/standards built on top of HTTP add new "rules":
For example, https://jsonapi.org/format/#document-top-level reads:
"A JSON object MUST be at the root of every JSON:API request and
 response document containing data."
Now, a PUT response may not contain data, so it must not strictly
contain (valid) JSON...
Anyway, there are others, e.g., jQuery that assumes that if JSON is
sent it gets back (valid) JSON qua implementation.

Let aside this, it can be considered an optimization for backends
that implement this behavior to spare you a GET as you get the GET's
(JSON) content in response to your PUT request. This is not proprietary
but may also not always be implemented, hence it's an optional feature.


So, it's not defined / left unspecified in the HTTP standard, yes, but
it's also not violating it. It's not a proprietary SWUpdate extension
but an opt-in feature given your backend supports this.
It's coming from standards that build on HTTP as mere transport channel.


Kind regards,
  Christian
Stefano Babic May 17, 2023, 1:31 p.m. UTC | #3
Hi Christian,

On 17.05.23 13:59, 'Christian Storm' via swupdate wrote:
> Hi Stefano,
> 
>>> PUT may return the sent document applied. This is
>>> currently not returned to channel_curl consumers.
>>>
>>
>> Is it a proprietary solution ? I do not see any reference about body response
>> in RFC7231 (HTTP 1.1).
> 
> True. The HTTP standard does not mandate that there is a document
> returned with a response. In fact, if the HTTP return code
> (semantically) resembles the body content, it's even wasteful.
> [This is the reason for defaulting to CHANNEL_PARSE_NONE]
> 

Ok

> However, applications/standards built on top of HTTP add new "rules":
> For example, https://jsonapi.org/format/#document-top-level reads:
> "A JSON object MUST be at the root of every JSON:API request and
>   response document containing data."
> Now, a PUT response may not contain data, so it must not strictly
> contain (valid) JSON...
> Anyway, there are others, e.g., jQuery that assumes that if JSON is
> sent it gets back (valid) JSON qua implementation.
> 
> Let aside this, it can be considered an optimization for backends
> that implement this behavior to spare you a GET as you get the GET's
> (JSON) content in response to your PUT request. This is not proprietary
> but may also not always be implemented, hence it's an optional feature.

Ok

> 
> 
> So, it's not defined / left unspecified in the HTTP standard, yes, but
> it's also not violating it.

That's the point, yes. Fine.

> It's not a proprietary SWUpdate extension
> but an opt-in feature given your backend supports this.
> It's coming from standards that build on HTTP as mere transport channel.
> 

Best regards,
Stefano
diff mbox series

Patch

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index 1207304e..24a259e3 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -342,6 +342,8 @@  channel_op_res_t channel_map_http_code(channel_t *this, long *http_response_code
 	case 429: /* Bad Request, i.e., too many requests. Try again later. */
 		return CHANNEL_EAGAIN;
 	case 200:
+	case 201:
+	case 204:
 	case 206:
 	case 226:
 		return CHANNEL_OK;
@@ -1103,7 +1105,7 @@  static channel_op_res_t channel_put_method(channel_t *this, void *data)
 
 	CURLcode curlrc = curl_easy_perform(channel_curl->handle);
 	if (curlrc != CURLE_OK) {
-		ERROR("Channel put operation failed (%d): '%s'", curlrc,
+		ERROR("Channel PUT operation failed (%d): '%s'", curlrc,
 		      curl_easy_strerror(curlrc));
 		result = channel_map_curl_error(curlrc);
 		goto cleanup_header;
@@ -1116,7 +1118,11 @@  static channel_op_res_t channel_put_method(channel_t *this, void *data)
 	if (channel_data->nocheckanswer)
 		goto cleanup_header;
 
-	channel_log_reply(result, channel_data, NULL);
+	channel_log_reply(result, channel_data, &outdata);
+
+	if (result == CHANNEL_OK) {
+	    result = parse_reply(channel_data, &outdata);
+	}
 
 cleanup_header:
 	outdata.memory != NULL ? free(outdata.memory) : (void)0;
diff --git a/suricatta/server_general.c b/suricatta/server_general.c
index 1c8e777e..6f4bce8b 100644
--- a/suricatta/server_general.c
+++ b/suricatta/server_general.c
@@ -324,6 +324,9 @@  static void *server_progress_thread (void *data)
 		if (logbuffer) {
 			channel_data.request_body = logbuffer;
 			channel_data.method = CHANNEL_PUT;
+			/* .format is already specified in channel_data_defaults,
+			 * but being explicit doesn't hurt. */
+			channel_data.format = CHANNEL_PARSE_NONE;
 			channel_data.content_type = "application/text";
 			result = map_channel_retcode(channel->put(channel, (void *)&channel_data));
 			if (result != SERVER_OK)
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index 726ef191..a400c12c 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -1590,6 +1590,7 @@  static server_op_res_t server_send_target_data(void)
 	}
 
 	channel_data_reply.url = url;
+	channel_data_reply.format = CHANNEL_PARSE_NONE;
 	channel_data_reply.request_body = json_reply_string;
 	TRACE("URL=%s JSON=%s", channel_data_reply.url, channel_data_reply.request_body);
 	channel_data_reply.method = CHANNEL_PUT;
diff --git a/suricatta/server_lua.c b/suricatta/server_lua.c
index 579ddc70..bdf34084 100644
--- a/suricatta/server_lua.c
+++ b/suricatta/server_lua.c
@@ -717,49 +717,45 @@  static int channel_do_operation(lua_State *L, channel_method_t op)
 	push_result(L, result);
 	lua_pushnumber(L, result);
 	lua_newtable(L);
-	if (op == CHANNEL_GET || channel_data.method == CHANNEL_POST ||
-	    channel_data.method == CHANNEL_PATCH) {
-		push_to_table(L, "http_response_code", channel_data.http_response_code);
-		push_to_table(L, "format",             channel_data.format);
-		#ifdef CONFIG_JSON
-		if (channel_data.format == CHANNEL_PARSE_JSON) {
-			lua_pushstring(L, "json_reply");
-			if (!channel_data.json_reply ||
-			    !json_to_table(L, channel_data.json_reply)) {
-				lua_pushnil(L);
-			}
-			lua_settable(L, -3);
-
-			if (channel_data.json_reply &&
-			    json_object_put(channel_data.json_reply) != 1) {
-				ERROR("JSON object should be freed but was not.");
-			}
-		}
-		#endif
-		if (channel_data.format == CHANNEL_PARSE_RAW) {
-			lua_pushstring(L, "raw_reply");
-			if (!channel_data.raw_reply) {
-				lua_pushnil(L);
-			} else {
-				lua_pushstring(L, channel_data.raw_reply);
-				free(channel_data.raw_reply);
-			}
-			lua_settable(L, -3);
+	push_to_table(L, "http_response_code", channel_data.http_response_code);
+	push_to_table(L, "format",             channel_data.format);
+	#ifdef CONFIG_JSON
+	if (channel_data.format == CHANNEL_PARSE_JSON) {
+		lua_pushstring(L, "json_reply");
+		if (!channel_data.json_reply ||
+			!json_to_table(L, channel_data.json_reply)) {
+			lua_pushnil(L);
 		}
+		lua_settable(L, -3);
 
-		lua_pushstring(L, "received_headers");
-		lua_newtable(L);
-		if (!LIST_EMPTY(channel_data.received_headers)) {
-			struct dict_entry *entry;
-			LIST_FOREACH(entry, channel_data.received_headers, next) {
-				lua_pushstring(L, dict_entry_get_key(entry));
-				lua_pushstring(L, dict_entry_get_value(entry));
-				lua_settable(L, -3);
-			}
+		if (channel_data.json_reply &&
+			json_object_put(channel_data.json_reply) != 1) {
+			ERROR("JSON object should be freed but was not.");
+		}
+	}
+	#endif
+	if (channel_data.format == CHANNEL_PARSE_RAW) {
+		lua_pushstring(L, "raw_reply");
+		if (!channel_data.raw_reply) {
+			lua_pushnil(L);
+		} else {
+			lua_pushstring(L, channel_data.raw_reply);
+			free(channel_data.raw_reply);
 		}
 		lua_settable(L, -3);
 	}
 
+	lua_pushstring(L, "received_headers");
+	lua_newtable(L);
+	if (!LIST_EMPTY(channel_data.received_headers)) {
+		struct dict_entry *entry;
+		LIST_FOREACH(entry, channel_data.received_headers, next) {
+			lua_pushstring(L, dict_entry_get_key(entry));
+			lua_pushstring(L, dict_entry_get_value(entry));
+			lua_settable(L, -3);
+		}
+	}
+	lua_settable(L, -3);
 	dict_drop_db(&header_send);
 	dict_drop_db(&header_receive);