From 7427bc30bd2f62f75ef5c2d1d3f1d23360f87689 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Tue, 21 Dec 2021 16:24:24 +0530 Subject: [PATCH 1/8] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 43674b0fd0..d31bbb7e47 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -12,7 +12,7 @@ distribution = "2201.0.0" path = "../native/build/libs/http-native-2.2.0-SNAPSHOT.jar" [[platform.java11.dependency]] -path = "./lib/mime-native-2.2.0-20211220-124500-69f16e4.jar" +path = "./lib/mime-native-2.2.0-20211220-222200-7bd9f71.jar" [[platform.java11.dependency]] path = "./lib/netty-common-4.1.71.Final.jar" From 25b1d3bb8f59fc7eb971444d29be0f72cd4f1be7 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Wed, 22 Dec 2021 12:51:58 +0530 Subject: [PATCH 2/8] Improve support for x-form-urlencoded content usage with http-client --- .../tests/http_client_data_binding.bal | 3 +- .../http_client_url_encoded_content_test.bal | 76 +++++++++++++++++++ ballerina-tests/tests/test_service_ports.bal | 1 + ballerina/http_client_endpoint.bal | 12 +-- ballerina/http_commons.bal | 46 +++++++++-- ballerina/resiliency_failover_client.bal | 12 +-- ballerina/resiliency_load_balance_client.bal | 10 +-- 7 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 ballerina-tests/tests/http_client_url_encoded_content_test.bal diff --git a/ballerina-tests/tests/http_client_data_binding.bal b/ballerina-tests/tests/http_client_data_binding.bal index ce612af583..a387c88c22 100644 --- a/ballerina-tests/tests/http_client_data_binding.bal +++ b/ballerina-tests/tests/http_client_data_binding.bal @@ -396,7 +396,8 @@ service /backend on clientDBBackendListener { } resource function get xmltype() returns http:NotFound { - return {body: xml `Bad Request`}; + xml payload = xml`Bad Request`; + return {body: payload}; } resource function get jsontype() returns http:InternalServerError { diff --git a/ballerina-tests/tests/http_client_url_encoded_content_test.bal b/ballerina-tests/tests/http_client_url_encoded_content_test.bal new file mode 100644 index 0000000000..c0dcdbd85f --- /dev/null +++ b/ballerina-tests/tests/http_client_url_encoded_content_test.bal @@ -0,0 +1,76 @@ +// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// NOTE: All the tokens/credentials used in this test are dummy tokens/credentials and used only for testing purposes. + +import ballerina/http; +import ballerina/url; +import ballerina/mime; +import ballerina/test; + +final http:Client clientUrlEncodedTestClient = check new(string`http://localhost:${clientFormUrlEncodedTestPort.toString()}/databinding`); +final string expectedResponse = "key1=value1&key2=value2"; +final readonly & map payload = { + "key1": "value1", + "key2": "value2" +}; + +service /databinding on new http:Listener(clientFormUrlEncodedTestPort) { + resource function 'default .(http:Request req) returns string|error { + string payload = check req.getTextPayload(); + return url:decode(payload, "UTF-8"); + } +} + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithPost() returns error? { + string response = check clientUrlEncodedTestClient->post("", payload, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertEquals(response, expectedResponse, msg = "Found unexpected output"); +} + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithPut() returns error? { + string response = check clientUrlEncodedTestClient->put("", payload, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertEquals(response, expectedResponse, msg = "Found unexpected output"); +} + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithDelete() returns error? { + string response = check clientUrlEncodedTestClient->delete("", payload, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertEquals(response, expectedResponse, msg = "Found unexpected output"); +} + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithPatch() returns error? { + string response = check clientUrlEncodedTestClient->patch("", payload, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertEquals(response, expectedResponse, msg = "Found unexpected output"); +} + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithExecute() returns error? { + string response = check clientUrlEncodedTestClient->execute("POST", "", payload, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertEquals(response, expectedResponse, msg = "Found unexpected output"); +} diff --git a/ballerina-tests/tests/test_service_ports.bal b/ballerina-tests/tests/test_service_ports.bal index ffdf4760fa..bbd0104017 100644 --- a/ballerina-tests/tests/test_service_ports.bal +++ b/ballerina-tests/tests/test_service_ports.bal @@ -156,6 +156,7 @@ const int requestInterceptorByteArrayPayloadBindingTestPort = 9606; const int requestInterceptorWithQueryParamTestPort = 9607; const int requestInterceptorServiceConfigTestPort1 = 9608; const int requestInterceptorServiceConfigTestPort2 = 9609; +const int clientFormUrlEncodedTestPort = 9610; //HTTP2 const int serverPushTestPort1 = 9701; diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 6ea14f946e..0747016256 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -73,7 +73,7 @@ public client isolated class Client { private isolated function processPost(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->post(path, req); if (observabilityEnabled && response is Response) { @@ -100,7 +100,7 @@ public client isolated class Client { private isolated function processPut(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->put(path, req); if (observabilityEnabled && response is Response) { @@ -127,7 +127,7 @@ public client isolated class Client { private isolated function processPatch(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->patch(path, req); if (observabilityEnabled && response is Response) { @@ -154,7 +154,7 @@ public client isolated class Client { private isolated function processDelete(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->delete(path, req); if (observabilityEnabled && response is Response) { @@ -243,7 +243,7 @@ public client isolated class Client { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->execute(httpVerb, path, req); if (observabilityEnabled && response is Response) { @@ -283,7 +283,7 @@ public client isolated class Client { # + message - An HTTP outbound request or any allowed payload # + return - An `http:HttpFuture` that represents an asynchronous service invocation or else an `http:ClientError` if the submission fails remote isolated function submit(string httpVerb, string path, RequestMessage message) returns HttpFuture|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, ()); return self.httpClient->submit(httpVerb, path, req); } diff --git a/ballerina/http_commons.bal b/ballerina/http_commons.bal index 298fd6d02d..cec8a27ff5 100644 --- a/ballerina/http_commons.bal +++ b/ballerina/http_commons.bal @@ -21,6 +21,8 @@ import ballerina/io; import ballerina/observe; import ballerina/time; import ballerina/log; +import ballerina/lang.'string as strings; +import ballerina/url; final boolean observabilityEnabled = observe:isObservabilityEnabled(); @@ -37,7 +39,7 @@ public isolated function parseHeader(string headerValue) returns HeaderValue[]|C name: "parseHeader" } external; -isolated function buildRequest(RequestMessage message) returns Request|ClientError { +isolated function buildRequest(RequestMessage message, string? mediaType) returns Request|ClientError { Request request = new; if (message is ()) { request.noEntityBody = true; @@ -56,16 +58,48 @@ isolated function buildRequest(RequestMessage message) returns Request|ClientErr } else if (message is mime:Entity[]) { request.setBodyParts(message); } else { - var result = trap val:toJson(message); - if (result is error) { - return error InitializingOutboundRequestError("json conversion error: " + result.message(), result); - } else { - request.setJsonPayload(result); + match mediaType { + mime:APPLICATION_FORM_URLENCODED => { + string payload = check processUrlEncodedContent(message); + request.setTextPayload(payload, mime:APPLICATION_FORM_URLENCODED); + } + _ => { + json payload = check processJsonContent(message); + request.setJsonPayload(payload); + } } } return request; } +isolated function processUrlEncodedContent(anydata message) returns string|ClientError { + if message is map { + string[] messageParams = message.entries().toArray().'map(constructFormEntry); + string payload = strings:'join("&", ...messageParams); + string|error encodedContent = url:encode(payload, "UTF-8"); + if encodedContent is string { + return encodedContent; + } else { + return error InitializingOutboundRequestError("content encoding error: " + encodedContent.message(), encodedContent); + } + } else { + return error InitializingOutboundRequestError("unsupported content for application/x-www-form-urlencoded media type"); + } +} + +isolated function constructFormEntry([string, string] keyValPair) returns string { + return string`${keyValPair[0]}=${keyValPair[1]}`; +} + +isolated function processJsonContent(anydata message) returns json|ClientError { + var result = trap val:toJson(message); + if (result is error) { + return error InitializingOutboundRequestError("json conversion error: " + result.message(), result); + } else { + return result; + } +} + isolated function buildResponse(ResponseMessage message) returns Response|ListenerError { Response response = new; if (message is ()) { diff --git a/ballerina/resiliency_failover_client.bal b/ballerina/resiliency_failover_client.bal index 172f434f71..a5a3c70ad4 100644 --- a/ballerina/resiliency_failover_client.bal +++ b/ballerina/resiliency_failover_client.bal @@ -82,7 +82,7 @@ public client isolated class FailoverClient { private isolated function processPost(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_POST); if (result is HttpFuture) { @@ -110,7 +110,7 @@ public client isolated class FailoverClient { private isolated function processPut(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_PUT); if (result is HttpFuture) { @@ -138,7 +138,7 @@ public client isolated class FailoverClient { private isolated function processPatch(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_PATCH); if (result is HttpFuture) { @@ -166,7 +166,7 @@ public client isolated class FailoverClient { private isolated function processDelete(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_DELETE); if (result is HttpFuture) { @@ -259,7 +259,7 @@ public client isolated class FailoverClient { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performExecuteAction(path, req, httpVerb); if (result is HttpFuture) { @@ -302,7 +302,7 @@ public client isolated class FailoverClient { # + return - An `http:HttpFuture` that represents an asynchronous service invocation or else an `http:ClientError` if the submission # fails remote isolated function submit(string httpVerb, string path, RequestMessage message) returns HttpFuture|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, ()); var result = self.performExecuteAction(path, req, "SUBMIT", verb = httpVerb); if (result is Response) { return getInvalidTypeError(); diff --git a/ballerina/resiliency_load_balance_client.bal b/ballerina/resiliency_load_balance_client.bal index 238e84e090..5b2cd50d33 100644 --- a/ballerina/resiliency_load_balance_client.bal +++ b/ballerina/resiliency_load_balance_client.bal @@ -74,7 +74,7 @@ public client isolated class LoadBalanceClient { private isolated function processPost(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_POST); return processResponse(result, targetType); @@ -98,7 +98,7 @@ public client isolated class LoadBalanceClient { private isolated function processPut(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PUT); return processResponse(result, targetType); @@ -122,7 +122,7 @@ public client isolated class LoadBalanceClient { private isolated function processPatch(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PATCH); return processResponse(result, targetType); @@ -146,7 +146,7 @@ public client isolated class LoadBalanceClient { private isolated function processDelete(string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_DELETE); return processResponse(result, targetType); @@ -222,7 +222,7 @@ public client isolated class LoadBalanceClient { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) returns Response|PayloadType|ClientError { - Request req = check buildRequest(message); + Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceExecuteAction(path, req, httpVerb); return processResponse(result, targetType); From d7eed954293852783eeea893965ab8fea2d7877a Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Wed, 22 Dec 2021 13:29:07 +0530 Subject: [PATCH 3/8] Refactor test case --- .../tests/http_client_url_encoded_content_test.bal | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ballerina-tests/tests/http_client_url_encoded_content_test.bal b/ballerina-tests/tests/http_client_url_encoded_content_test.bal index c0dcdbd85f..f492a182a3 100644 --- a/ballerina-tests/tests/http_client_url_encoded_content_test.bal +++ b/ballerina-tests/tests/http_client_url_encoded_content_test.bal @@ -22,7 +22,7 @@ import ballerina/mime; import ballerina/test; final http:Client clientUrlEncodedTestClient = check new(string`http://localhost:${clientFormUrlEncodedTestPort.toString()}/databinding`); -final string expectedResponse = "key1=value1&key2=value2"; +final string expectedResponse = "URL_ENCODED_key1=value1&key2=value2"; final readonly & map payload = { "key1": "value1", "key2": "value2" @@ -30,8 +30,11 @@ final readonly & map payload = { service /databinding on new http:Listener(clientFormUrlEncodedTestPort) { resource function 'default .(http:Request req) returns string|error { + string contentType = req.getContentType(); + contentType = contentType == mime:APPLICATION_FORM_URLENCODED ? "URL_ENCODED": "INVALID"; string payload = check req.getTextPayload(); - return url:decode(payload, "UTF-8"); + string decodedContent = check url:decode(payload, "UTF-8"); + return string`${contentType}_${decodedContent}`; } } From bde41bc96e007acc08938f8fb025d9d0bfc52958 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Wed, 22 Dec 2021 13:34:23 +0530 Subject: [PATCH 4/8] Update change log --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index d7d587968d..a5c22d74f2 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed - [Rename RequestContext add function to set](https://github.com/ballerina-platform/ballerina-standard-library/issues/2414) - [Only allow default path in interceptors engaged at listener level](https://github.com/ballerina-platform/ballerina-standard-library/issues/2452) +- [Provide a better way to send with `application/x-www-form-urlencoded`](https://github.com/ballerina-platform/ballerina-standard-library/issues/1705) ## [2.0.1] - 2021-11-20 From a1a772046d77c6d90fe8c26f669ab4859c090379 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Wed, 22 Dec 2021 13:59:19 +0530 Subject: [PATCH 5/8] Add negative test case --- .../http_client_url_encoded_content_test.bal | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ballerina-tests/tests/http_client_url_encoded_content_test.bal b/ballerina-tests/tests/http_client_url_encoded_content_test.bal index f492a182a3..940b3e6d3b 100644 --- a/ballerina-tests/tests/http_client_url_encoded_content_test.bal +++ b/ballerina-tests/tests/http_client_url_encoded_content_test.bal @@ -77,3 +77,31 @@ isolated function testUrlContentWithExecute() returns error? { string response = check clientUrlEncodedTestClient->execute("POST", "", payload, mediaType = mime:APPLICATION_FORM_URLENCODED); test:assertEquals(response, expectedResponse, msg = "Found unexpected output"); } + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithIntPayload() returns error? { + string|error response = clientUrlEncodedTestClient->post("", 10, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertTrue(response is error, "Found unexpected output"); + if response is error { + test:assertEquals(response.message(), "unsupported content for application/x-www-form-urlencoded media type", msg = "Found unexpected output"); + } +} + +@test:Config { + groups: ["urlEncodedContent"] +} +isolated function testUrlContentWithJsonPayload() returns error? { + json jsonPayload = { + "key1": "val1", + "key2": [ + "val2.1", "val2.2" + ] + }; + string|error response = clientUrlEncodedTestClient->post("", jsonPayload, mediaType = mime:APPLICATION_FORM_URLENCODED); + test:assertTrue(response is error, "Found unexpected output"); + if response is error { + test:assertEquals(response.message(), "unsupported content for application/x-www-form-urlencoded media type", msg = "Found unexpected output"); + } +} From dbce5f84fed5da34d8e585ca4c4a2631a14a00e2 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Wed, 22 Dec 2021 14:38:13 +0530 Subject: [PATCH 6/8] Incorporate review suggestions --- ballerina/http_commons.bal | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ballerina/http_commons.bal b/ballerina/http_commons.bal index cec8a27ff5..c486bed699 100644 --- a/ballerina/http_commons.bal +++ b/ballerina/http_commons.bal @@ -95,9 +95,8 @@ isolated function processJsonContent(anydata message) returns json|ClientError { var result = trap val:toJson(message); if (result is error) { return error InitializingOutboundRequestError("json conversion error: " + result.message(), result); - } else { - return result; } + return result; } isolated function buildResponse(ResponseMessage message) returns Response|ListenerError { From 3a4258b2b6e0ffc3110df3bd7405a5b8eb2ddf81 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 23 Dec 2021 20:28:47 +0530 Subject: [PATCH 7/8] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index d31bbb7e47..21c556865e 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -12,7 +12,7 @@ distribution = "2201.0.0" path = "../native/build/libs/http-native-2.2.0-SNAPSHOT.jar" [[platform.java11.dependency]] -path = "./lib/mime-native-2.2.0-20211220-222200-7bd9f71.jar" +path = "./lib/mime-native-2.2.0-20211222-181300-dbf5417.jar" [[platform.java11.dependency]] path = "./lib/netty-common-4.1.71.Final.jar" From 92bbefa2fa625f533dbb34cc3516a40fe9002f36 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 23 Dec 2021 20:30:36 +0530 Subject: [PATCH 8/8] Fix logic issue in url-encoded content retrieval --- ballerina/http_commons.bal | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ballerina/http_commons.bal b/ballerina/http_commons.bal index c486bed699..05025b97f1 100644 --- a/ballerina/http_commons.bal +++ b/ballerina/http_commons.bal @@ -74,23 +74,22 @@ isolated function buildRequest(RequestMessage message, string? mediaType) return isolated function processUrlEncodedContent(anydata message) returns string|ClientError { if message is map { - string[] messageParams = message.entries().toArray().'map(constructFormEntry); - string payload = strings:'join("&", ...messageParams); - string|error encodedContent = url:encode(payload, "UTF-8"); - if encodedContent is string { - return encodedContent; - } else { - return error InitializingOutboundRequestError("content encoding error: " + encodedContent.message(), encodedContent); + do { + string[] messageParams = []; + foreach var ['key, value] in message.entries() { + string encodedValue = check url:encode(value, "UTF-8"); + string entry = string`${'key}=${encodedValue}`; + messageParams.push(entry); + } + return strings:'join("&", ...messageParams); + } on fail var e { + return error InitializingOutboundRequestError("content encoding error: " + e.message(), e); } } else { return error InitializingOutboundRequestError("unsupported content for application/x-www-form-urlencoded media type"); } } -isolated function constructFormEntry([string, string] keyValPair) returns string { - return string`${keyValPair[0]}=${keyValPair[1]}`; -} - isolated function processJsonContent(anydata message) returns json|ClientError { var result = trap val:toJson(message); if (result is error) {