Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[blockly] HTTP block enhancements #2607

Merged
merged 14 commits into from
Aug 19, 2024
Merged

Conversation

mherwege
Copy link
Contributor

@mherwege mherwege commented Jun 12, 2024

Based the nice work in #2411, I further enhanced this:

  • Have a query mutator`: This mutator then allows adding query parameters in a dictionary. The values of the dictionary will be URL encoded and the query string constructed and appended to the url.
  • For HttpPostRequest and HttpPutRequest with content type application/json, allow any input type. For string input, the user is responsible to construct a valid JSON. For other input types (dictionary or any object), the code will construct a valid JSON object.
  • For HttpPostRequest and HttpPutRequest with content type application/x-www-form-urlencoded, rather than using a text input block, use a dictionary and also URL encode the parameters.
  • Add a dictionary block for headers when switching on headers (like for query and content types application/json or application/x-www-form-urlencoded)

Below are a few examples of the additional functionality.

Example 1: Query for HttpGetRequest

image

which generates:

var response;

function encodeParams(params) {
    if ((typeof params === 'string') || (params instanceof String)) return params;
    const encodedParams = Object.entries(params).map(([key, value]) => [key, encodeURIComponent(value)]);
    return encodedParams.map(p => p.join('=')).join('&');
}

response = (actions.HTTP.sendHttpGetRequest('http://openhab.org' + '?' + encodeParams({'param0': 'Value 1', 'param1': 'Value 2'}), {'header0': 'Header1', 'header1': ''}, 2000));

Example 2: HttpPostRequest with content type application/x-www-form-urlencoded

image

which generates:

var response;

function encodeParams(params) {
    if ((typeof params === 'string') || (params instanceof String)) return params;
    const encodedParams = Object.entries(params).map(([key, value]) => [key, encodeURIComponent(value)]);
    return encodedParams.map(p => p.join('=')).join('&');
}

response = (actions.HTTP.sendHttpPostRequest('http://openhab.org', 'application/x-www-form-urlencoded', encodeParams({'param0': 'Value 1', 'param1': 'Value 2'}), 3000));

Example 3: combined with result assignment to variable:

image

which generates:

var response1, response2, query, response3, response4;

function encodeParams(params) {
    if ((typeof params === 'string') || (params instanceof String)) return params;
    const encodedParams = Object.entries(params).map(([key, value]) => [key, encodeURIComponent(value)]);
    return encodedParams.map(p => p.join('=')).join('&');
}

function toJSONString(params) {
    if ((typeof params === 'string') || (params instanceof String)) return params;
    return JSON.stringify(params).replace(/^"+/, '["').replace(/"+$/, '"]');
}


response1 = (actions.HTTP.sendHttpGetRequest('http://openhab.org' + '?' + encodeParams({'param1': 'param1', 'param2': 'param2'}), {'header1': 'header1', 'header2': 'header2'}, 2000));
response2 = (actions.HTTP.sendHttpPostRequest('http://openhab.org', 'application/x-www-form-urlencoded', encodeParams({'param1': 'param1', 'param2': 'param2'}), {'X': 'Z', 'Y': 'header2'}, 3000));
query = {'key0': 'param1', 'key1': 'param2'};
response3 = (actions.HTTP.sendHttpGetRequest('http://openhab.org' + '?' + encodeParams(query), 3000));
response4 = (actions.HTTP.sendHttpPostRequest('http://openhab.org', 'application/json', toJSONString('payload'), {'X': 'Z', 'Y': 'header2'}, 3000));

@mherwege mherwege requested a review from a team as a code owner June 12, 2024 16:15
@mherwege
Copy link
Contributor Author

mherwege commented Jun 12, 2024

@stefan-hoehn This is probably not for 4.2, so if you have time in the future, I would appreciate your help on trying to resolve the issue I still have with extra blocks being created.

Issue has been resolved.

Copy link

relativeci bot commented Jun 12, 2024

#2206 Bundle Size — 10.83MiB (+0.05%).

9330a10(current) vs 17c2119 main#2205(baseline)

Warning

Bundle contains 2 duplicate packages – View duplicate packages

Bundle metrics  Change 1 change
                 Current
#2206
     Baseline
#2205
No change  Initial JS 1.89MiB 1.89MiB
No change  Initial CSS 576.5KiB 576.5KiB
Change  Cache Invalidation 17.97% 17.79%
No change  Chunks 226 226
No change  Assets 249 249
No change  Modules 2914 2914
No change  Duplicate Modules 149 149
No change  Duplicate Code 1.8% 1.8%
No change  Packages 96 96
No change  Duplicate Packages 2 2
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#2206
     Baseline
#2205
Regression  JS 9.05MiB (+0.06%) 9.04MiB
No change  CSS 862.92KiB 862.92KiB
No change  Fonts 526.1KiB 526.1KiB
No change  Media 295.6KiB 295.6KiB
No change  IMG 140.74KiB 140.74KiB
No change  HTML 1.35KiB 1.35KiB
No change  Other 871B 871B

Bundle analysis reportBranch mherwege:blockly_httpProject dashboard


Generated by RelativeCIDocumentationReport issue

@mherwege mherwege closed this Jun 13, 2024
@mherwege mherwege deleted the blockly_http branch June 13, 2024 15:55
@mherwege mherwege restored the blockly_http branch June 13, 2024 16:30
@mherwege mherwege reopened this Jun 13, 2024
@florian-h05 florian-h05 added enhancement New feature or request main ui Main UI labels Jun 16, 2024
@mherwege
Copy link
Contributor Author

mherwege commented Jun 19, 2024

This now works:
All of this works fine, except one thing I have been unable to resolve and needs an expert's eyes: When configuring a content type of application/x-www-form-urlencoded with a dictionary (automatically) attached as payload, and switching to the code tab, then switching back, an additional unconnected dictionary is put in the workspace. The same does not happen for query or headers that use the same logic to add dictionaries. Also saving, leaving and coming back to the workspace has this effect.

I was able to make this work properly by using shadow blocks for all blocks that are being added, not trying to add/remove real blocks. I don't see issues with this approach. I took the same approach for the timeout parameter, which further simplifies the code.

It is still not right: code generated when headers, query or payload dictionary is a varariable is not correct.

@mherwege mherwege marked this pull request as draft June 19, 2024 15:49
@mherwege
Copy link
Contributor Author

I believe it now all works. I create a test calling out to https://postman-echo.com to test various combinations and log the results. This all looks fine.

The code of the rule executing the various tests:

configuration: {}
triggers: []
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      blockSource: '<xml
        xmlns="https://developers.google.com/blockly/xml"><variables><variable
        id="?+Q0Ht$:z:k(`QBz9Wn{">testLabel</variable><variable
        id="yg~ReyFEj}tWQT*p_G;H">testResult</variable><variable
        id="xj6~i%!|{^S?=yJIrI2^">url</variable><variable
        id="|]W(A]f(_PT{t(13J_ur">testResponse</variable><variable
        id="52duV|E-[2o5]-8l${u,">getUrl</variable><variable
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</variable><variable
        id="tG);,}R9`:ByvF5qKUdv">putUrl</variable><variable
        id="]nI=b=x-e.p24o1]5P(V">deleteUrl</variable><variable
        id="lX3;mch[A*jvqOunQRyS">result</variable><variable
        id="L]ozw*IzahRaPX/vbxV6">paramDict</variable><variable
        id="hLS6#?3z4{_1yjb!Mo,[">paramString</variable></variables><block
        type="procedures_defnoreturn" id="86S,J.YTGfC4DX^{2Rh_" x="-58"
        y="-396"><mutation><arg name="testLabel"
        varid="?+Q0Ht$:z:k(`QBz9Wn{"></arg><arg name="testResult"
        varid="yg~ReyFEj}tWQT*p_G;H"></arg></mutation><field
        name="NAME">logRequestTest</field><comment pinned="false" h="80"
        w="160">Describe this function...</comment><statement
        name="STACK"><block type="variables_set"
        id="!6EKV*4UlU/5Fd4@hehC"><field name="VAR"
        id="|]W(A]f(_PT{t(13J_ur">testResponse</field><value name="VALUE"><block
        type="logic_ternary" id="9GD.6/WRHaK882~AtJU7"><value name="IF"><block
        type="text_isEmpty" id="S/U|ek+jSxV*h{9qF6*P"><value
        name="VALUE"><shadow type="text" id="=$yP;:o+DFBv$b,Yw(H1"><field
        name="TEXT"></field></shadow><block type="variables_get"
        id="G/6;w+%7ek}=MXkUy+9K"><field name="VAR"
        id="yg~ReyFEj}tWQT*p_G;H">testResult</field></block></value></block></value><value
        name="THEN"><block type="text" id="!AOg~vNsxA5!u,DSv4xr"><field
        name="TEXT">not a valid request</field></block></value><value
        name="ELSE"><block type="variables_get" id="-x%E./!ilN1l6aI+z)4,"><field
        name="VAR"
        id="yg~ReyFEj}tWQT*p_G;H">testResult</field></block></value></block></value><next><block
        type="oh_log" id="Na7O4}T|MD~yh_F37KZi"><field
        name="severity">info</field><value name="message"><shadow type="text"
        id="EKo!2O+@!+$1^B)9;~7d"><field name="TEXT">abc</field></shadow><block
        type="text_join" id="@3Qv|e3%ax}3l*|9jbvI"><mutation
        items="3"></mutation><value name="ADD0"><block type="variables_get"
        id="|PkVp*w+#)EVvp)t`g!n"><field name="VAR"
        id="?+Q0Ht$:z:k(`QBz9Wn{">testLabel</field></block></value><value
        name="ADD1"><block type="text" id="QR18:_d*8IYbd75{bE:."><field
        name="TEXT">: </field></block></value><value name="ADD2"><block
        type="variables_get" id="%@n22]WQ},1f;Q6MY.Xa"><field name="VAR"
        id="|]W(A]f(_PT{t(13J_ur">testResponse</field></block></value></block></value></block></next></block></statement></block><block
        type="variables_set" id="V`Aj]cv%Ys}*KE9{_HFd" x="-56" y="-83"><field
        name="VAR" id="xj6~i%!|{^S?=yJIrI2^">url</field><value
        name="VALUE"><block type="text" id="_O#%;+34Q0fTe=~IQa{B"><field
        name="TEXT">https://postman-echo.com/</field></block></value><next><block
        type="variables_set" id="W^;zPGC$CJ.CmHX^Df?|"><field name="VAR"
        id="52duV|E-[2o5]-8l${u,">getUrl</field><value name="VALUE"><block
        type="text_join" id="{pZcL7Je0QfO^D{0H%bW"><mutation
        items="2"></mutation><value name="ADD0"><block type="variables_get"
        id="sP36/*p=5bTgbF2TxR|!"><field name="VAR"
        id="xj6~i%!|{^S?=yJIrI2^">url</field></block></value><value
        name="ADD1"><block type="text" id="ghl52?5xeHsmo=!`3tzq"><field
        name="TEXT">get</field></block></value></block></value><next><block
        type="variables_set" id="/`F!e}T2DL$.d_tnl_m8"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field><value name="VALUE"><block
        type="text_join" id="mptX.t~TN[,2BSmTD$e`"><mutation
        items="2"></mutation><value name="ADD0"><block type="variables_get"
        id="%_2IBI?.}|Xpg4$3(}}*"><field name="VAR"
        id="xj6~i%!|{^S?=yJIrI2^">url</field></block></value><value
        name="ADD1"><block type="text" id="BmdLsp5RB2Q-(x!oe;bY"><field
        name="TEXT">post</field></block></value></block></value><next><block
        type="variables_set" id="XmysNwwg(P{^7UjCJ`*6"><field name="VAR"
        id="tG);,}R9`:ByvF5qKUdv">putUrl</field><value name="VALUE"><block
        type="text_join" id="fK[])}_;PJa:Y)3,(Fyh"><mutation
        items="2"></mutation><value name="ADD0"><block type="variables_get"
        id="{bR-/yYL).v}*+:|z)9i"><field name="VAR"
        id="xj6~i%!|{^S?=yJIrI2^">url</field></block></value><value
        name="ADD1"><block type="text" id="-s`PbMNOP,x~=2a;LOq1"><field
        name="TEXT">put</field></block></value></block></value><next><block
        type="variables_set" id="FRBP9VGg+S}#B=yt:T;!"><field name="VAR"
        id="]nI=b=x-e.p24o1]5P(V">deleteUrl</field><value name="VALUE"><block
        type="text_join" id="?y~q+C1Xg2D!T:z*}WTZ"><mutation
        items="2"></mutation><value name="ADD0"><block type="variables_get"
        id=")N%=XdS;|rK;aOxfKi4M"><field name="VAR"
        id="xj6~i%!|{^S?=yJIrI2^">url</field></block></value><value
        name="ADD1"><block type="text" id=";[1jFguE@b+Wjx}(+Kui"><field
        name="TEXT">delete</field></block></value></block></value><next><block
        type="variables_set" id="PK_bRj:{1Df),ic)dj^("><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="Zv67{Du$f1YP$eh-!R0_"><mutation
        hasTimeout="true" hasHeader="false" hasQuery="true"></mutation><field
        name="requestType">HttpGetRequest</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="tMV)F}cnO0KarWwoX=RZ"><field name="VAR"
        id="52duV|E-[2o5]-8l${u,">getUrl</field></block></value><value
        name="timeoutInput"><shadow type="math_number"
        id="m/MJg{).}7Du%%sT@4wm"><field name="NUM">3000</field></shadow><block
        type="math_number" id="vh@#N1a}i/hD7.jX-(-f"><field
        name="NUM">5000</field></block></value><value name="query"><shadow
        type="dicts_create_with" id="G1PcWt8S.e`qQnUyA:v1"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="=RA!HI_Ls,0b~%]?wfme"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="dicts_create_with" id="LA:=haxmFy]S6@)~04#J"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="#!1^Fah0ZMY#,3@rAYtu"><field
        name="TEXT">abc</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="sb`(jN/*l;.6:d5ZSk:N"><field name="TEXT">def &amp;
        ghi</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="@#kv]3H2.ac0gKsyj^[w"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="XDWs5tAZW@`Hcm*:9(YR"><field name="TEXT">1a. HttpGet with query
        (Dictionary) and timeout</field></block></value><value
        name="ARG1"><block type="variables_get" id=".l9j.C_Na22-c`Q*K#:S"><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="Nt!Ju;:-X51;-$vp|#:I"><field name="VAR"
        id="L]ozw*IzahRaPX/vbxV6">paramDict</field><value name="VALUE"><block
        type="dicts_create_with" id="ANN?Zj$lXrJL;Mm;AAW`"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="(SK(t%)iW(3iLn~@E[0p"><field
        name="TEXT">abc</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id=",3=Nc6G]8|,*eCKH2zsR"><field name="TEXT">def &amp;
        ghi</field></block></value></block></value><next><block
        type="variables_set" id=";Kx:j~6cOHt;5PO-3!sO"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="eD6+YkJ|nqjn417C1zr6"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="true"></mutation><field
        name="requestType">HttpGetRequest</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="Y.`t#S#}g|Vy@:F}{8h5"><field name="VAR"
        id="52duV|E-[2o5]-8l${u,">getUrl</field></block></value><value
        name="query"><shadow type="dicts_create_with"
        id="G1PcWt8S.e`qQnUyA:v1"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="=RA!HI_Ls,0b~%]?wfme"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="variables_get" id="2jUPImB+(#a%.e.Q$XCQ"><field name="VAR"
        id="L]ozw*IzahRaPX/vbxV6">paramDict</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id="l,$:ayxG239D2:I~!fKw"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="QEU$cCrIx5X]53@-7-tx"><field name="TEXT">1b. HttpGet with query
        variable (Dictionary)</field></block></value><value name="ARG1"><block
        type="variables_get" id="#@WTyI*s[,T`^Z8^yRX("><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="6LirltK$SG_gNm^5-cbO"><field name="VAR"
        id="hLS6#?3z4{_1yjb!Mo,[">paramString</field><value name="VALUE"><block
        type="text" id="`$NcK$AUewe+b_4p[z3z"><field
        name="TEXT">param1=abc&amp;param2=def%20%26%20ghi</field></block></value><next><block
        type="variables_set" id="V/Ya_7D=!ZrsafZfQJOI"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="*9JlH)d:kUM:z%n!P*%v"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="true"></mutation><field
        name="requestType">HttpGetRequest</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="q1=yxq/nF!W0U@{uE^AQ"><field name="VAR"
        id="52duV|E-[2o5]-8l${u,">getUrl</field></block></value><value
        name="query"><shadow type="dicts_create_with"
        id="G1PcWt8S.e`qQnUyA:v1"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="=RA!HI_Ls,0b~%]?wfme"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="variables_get" id=":yH=G%l8Q/mIDu*{o)@P"><field name="VAR"
        id="hLS6#?3z4{_1yjb!Mo,[">paramString</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id="~eMs?)d*{zd^*ZgVGeIU"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="U%H$2xxxs5|QY^GA+MoH"><field name="TEXT">1c. HttpGet with query
        variable (String)</field></block></value><value name="ARG1"><block
        type="variables_get" id="aL}oho0==c)$F$V((ex*"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="lBH%9YuMPhh}m%h3Ke/#"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="s6T(;JDlp_XkHC[I?hOI"><mutation
        hasTimeout="false" hasHeader="true" hasQuery="true"></mutation><field
        name="requestType">HttpGetRequest</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="[pF#tHCevR:{cZh/.DQ1"><field name="VAR"
        id="52duV|E-[2o5]-8l${u,">getUrl</field></block></value><value
        name="requestHeader"><shadow type="dicts_create_with"
        id="LkZCF@HH`;3`thkXj![#"><mutation items="2"></mutation><field
        name="KEY0">header1</field><field name="KEY1">header2</field><value
        name="ADD0"><shadow type="text" id="~=aEfPgFH[sa$*=GiM7."><field
        name="TEXT">header1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="umQC!*3xwq97vO3[7#F1"><field
        name="TEXT">header2</field></shadow></value></shadow><block
        type="dicts_create_with" id="P(3YIl|H5RV7.N$Ec=sA"><mutation
        items="2"></mutation><field name="KEY0">header1</field><field
        name="KEY1">header2</field><value name="ADD0"><shadow type="text"
        id="~=aEfPgFH[sa$*=GiM7."><field
        name="TEXT">header1</field></shadow><block type="text"
        id="{PZY]GI6aM2)K$0)=uW="><field
        name="TEXT">xyz</field></block></value><value name="ADD1"><shadow
        type="text" id="#c6D?K{,IIdKM%hC(R(#"><field
        name="TEXT">header2</field></shadow><block type="text"
        id="mC]:Z:c@d5CmOqJh-a[L"><field
        name="TEXT">uvw</field></block></value></block></value><value
        name="query"><shadow type="dicts_create_with"
        id="G1PcWt8S.e`qQnUyA:v1"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="=RA!HI_Ls,0b~%]?wfme"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="variables_get" id="sChrnj[XDl2N]P7j-(v{"><field name="VAR"
        id="L]ozw*IzahRaPX/vbxV6">paramDict</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id="*|4Ry:Mg4yM)X7W#q@Lz"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="NLiUaliYJ2]vpc4|uFaH"><field name="TEXT">1d. HttpGet with query
        variable (Dictionary) and headers</field></block></value><value
        name="ARG1"><block type="variables_get" id="h{o=Xi*)Jk(+?inOh}3{"><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="9lrgupqqhx76DK.GOrgf"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="#r?V$}$+!8E(eBc7oH2+"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">text/plain</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="kcg#m[:Jhi*qc)R9,l}_"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="text" id="Zk+7obusd#sJ{ON1||$("><field
        name="TEXT">payload</field></shadow><block type="text"
        id="^$%xMj`Z,tKMn8.,feUe"><field
        name="TEXT">abc</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id="G/cv#3LV]Un9:5VFrKY."><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="S[8MBjdD-l.)8rnxLvS|"><field name="TEXT">2a. HttpPost with
        contentType text/plain</field></block></value><value name="ARG1"><block
        type="variables_get" id="]3{9#suDI2[gd_F,ZHad"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="w:BI6`w!D;W+GD[XnLvU"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id=",Y!e3*s8*OfejaiZ/t8W"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="true"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">text/plain</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="@xMSfHC2I$MCs)EnT=`m"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="query"><shadow type="dicts_create_with"
        id="^HX,=,SSb/E(;f#%zx;X"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id=")Wnb)FpceODJ$gH-@moK"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="HJKv=s.)yh9hS523G^ql"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="dicts_create_with" id="-}A[w^_gxs3U3p3(v{S`"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="xH.Gw}n%*`[}oafuqPAo"><field
        name="TEXT">abc</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="Q@$x%OQCTCDAqF,R`UKN"><field name="TEXT">def &amp;
        ghi</field></block></value></block></value><value name="payload"><shadow
        type="text" id="Eq0~sFST)|zXR|}yh[oU"><field
        name="TEXT">payload</field></shadow><block type="text"
        id="M9p?6,lEHK*o[rkJkB66"><field
        name="TEXT">xyz</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id=";n|zye2zjgn/59iwR4q6"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="xZLvUhi(jGgeX-|{ze[l"><field name="TEXT">2b. HttpPost with
        contentType text/plain and query</field></block></value><value
        name="ARG1"><block type="variables_get" id="[v!@dQgl5[M.40[p)wbt"><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="#uh:olI;L/+@#a9)HmH%"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="xJHMX2[uI;I=)YcwmmR:"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="k0z;}3KvwkTO;x@UURbF"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="dicts_create_with"
        id="@+|3;`_dybjX6sUP8S5}"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="QeDa+QKFs(o6hQ6AVelE"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="kS/`E?`r[MaON-C3UR-a"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="dicts_create_with" id="/^l=6U:MEm4oQC$F/294"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="Vci2In!Mtpq.Eyo_s{82"><field
        name="TEXT">abc</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id=":[O(GX!v|H%X?+r#%*0."><field name="TEXT">def &amp;
        ghi</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="c5cKgXzZPz4-zV+f(w:1"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="F;f6z.!n^ITS^#:{$FEv"><field name="TEXT">2c. HttpPost with
        contentType application/json</field></block></value><value
        name="ARG1"><block type="variables_get" id=":p~8[Kj@@t6B-_/7^{G]"><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="1Shl}==/hq(I%p^I.OIr"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="Llh]Ytk985fcL:Sc;t(g"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="_WIQ,_;ZD:LdMbEECX*3"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="text" id="zp}9KhE$d3;ce9kR)KJc"><field
        name="TEXT">payload</field></shadow><block type="text"
        id="Gvt9ISgc!nJP59O5SQ;q"><field name="TEXT">{ "abc": "def"
        }</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id="yZ=~#7Fda_R%[O_i9NQv"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="AtpEyNv3JUn(b}%{%:]d"><field name="TEXT">2d. HttpPost with
        contentType application/json and string
        param</field></block></value><value name="ARG1"><block
        type="variables_get" id="rM=nMmN:UH7hkJN7L.Q?"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="{Flao{*./=:SMHuonV+K"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="pp7~$VUIy|-oriU+mj]O"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="n}q5U}!Y{`{c2}[X.wAS"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="text" id="zp}9KhE$d3;ce9kR)KJc"><field
        name="TEXT">payload</field></shadow><block type="text"
        id="dVAU~}Y-`8$JA^^kO5P*"><field name="TEXT">{ invalid json
        }</field></block></value></block></value><next><block
        type="procedures_callnoreturn" id="9M{xWSw7d(O.xf(?OtRQ"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="|7Zam8@S6?P=]=~I1?_+"><field name="TEXT">2e. HttpPost with
        contentType application/json and invalid string
        param</field></block></value><value name="ARG1"><block
        type="variables_get" id="QoT!R;g{kn@ZL%oe|^(q"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="@cgO(4-s{@}=]%6hGK:i"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="wF{)x]Kxd~XGe/z6ZkIO"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="m6AklXuP1|,0kH:ZLnGk"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="text" id="zp}9KhE$d3;ce9kR)KJc"><field
        name="TEXT">payload</field></shadow><block type="lists_create_with"
        id="^e?.oEtq.B4eu0M]v`{a"><mutation items="2"></mutation><value
        name="ADD0"><block type="dicts_create_with"
        id="}Ki7crr(zj4=Mdy$?K/r"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="Uk3]9^Buh%5av3pDlJpd"><field
        name="TEXT">abc</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="K!nmfhF)oN!{)p#]em/G"><field name="TEXT">def &amp;
        ghi</field></block></value></block></value><value name="ADD1"><block
        type="text" id="w3F@;*0Tq]{-yu#o8rpM"><field name="TEXT">xyz &amp;
        uvw</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="Bb{a6|{OI!YZ0GYfxK%!"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="]gsK_Ew~^+,Jpn]I6V2*"><field name="TEXT">2f. HttpPost with
        contentType application/json and complex object
        argument</field></block></value><value name="ARG1"><block
        type="variables_get" id="OXQF@(ZB!o{(P)rq!k,h"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="o=Tgak.eh/q(yqNTi63*"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="yC*Mq[U[F;o`LkXA8l+B"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="*tcH#/#!|zWQ-Xp,%L#W"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="text" id="zp}9KhE$d3;ce9kR)KJc"><field
        name="TEXT">payload</field></shadow><block type="oh_zdt_now"
        id="7Z)GO#i@#Caq499dU}Lj"></block></value></block></value><next><block
        type="procedures_callnoreturn" id="O_yEcT$?iJsx(C,W0b{a"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="eyx~c-Aqh`4O*!:7Vvp$"><field name="TEXT">2g. HttpPost with
        contentType application/json and object argument that converts to quoted
        string</field></block></value><value name="ARG1"><block
        type="variables_get" id="/|mZm@4#|cGQ`X!z|Xu2"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="+7aRLx:nnKK3::i-f]7q"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="^A65@{wc[.+={IzyK`5?"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="I4[zY*EdrA#q=JQW-h28"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="text" id="zp}9KhE$d3;ce9kR)KJc"><field
        name="TEXT">payload</field></shadow><block type="oh_getitem"
        id="8MMbM.rvUu,M!Xvf`,xU"><value name="itemName"><shadow type="oh_item"
        id="hy(q6%n?;~S{I1D+GXZ:"><mutation itemName="MyItem"
        itemLabel="MyItem"></mutation><field
        name="itemName">MyItem</field></shadow><block type="oh_item"
        id="Ot~S5t?Qy=G03$2*3EaB"><mutation
        itemName="Local_Weather_and_Forecast_One_Call_API_Outdoor_Temperature"
        itemLabel="Outdoor Temperature"></mutation><field
        name="itemName">Local_Weather_and_Forecast_One_Call_API_Outdoor_Temperature</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="NsBOkBxq~[3`]?$Q1ZUy"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="b3;dJGNiqqUF.D|skMMB"><field name="TEXT">2h. HttpPost with
        contentType application/json and object
        argument</field></block></value><value name="ARG1"><block
        type="variables_get" id="(L[aujbI/M`Kq3{F3+nN"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="xh6Q$i.)re@cNP*xTk6E"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="~4q%*8l7@n@|-qRi]iI%"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/x-www-form-urlencoded</field><value
        name="url"><shadow type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="|x;hY:Tzl,BRG+}{B9Cy"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="payload"><shadow type="dicts_create_with"
        id="w^[v7|`X]3iYQjhMj}sy"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="6{u}d-5{*FOvITt[A^c3"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="eXTD(Z25o:~680^~`aTg"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="dicts_create_with" id="ps9Ag8Qq/|cC5jNKYwyI"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="YV5R187]QazK38MG#?3K"><field
        name="TEXT">abc</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="R?0/U]{W4?:YdhhK36p="><field name="TEXT">def &amp;
        ghi</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="T~Q=ue;($@PJ/{D80Wyp"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="#Q5Jno{?~T.Q.n|uw9[%"><field name="TEXT">2i. HttpPost with
        contentType
        aplication/x-www-form-urlencoded</field></block></value><value
        name="ARG1"><block type="variables_get" id=")L+yjke5ER.:TsTz~fz."><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="$Yr,ixP^^ri5TSKdZm3E"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="j~PEFpP:qp]Y=L;z*!Y-"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="true"></mutation><field
        name="requestType">HttpPostRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="^q9]R5^?cByHWFMdt{S{"><field name="VAR"
        id="gemu.*hVo$T!D*n3J`*Z">postUrl</field></block></value><value
        name="query"><shadow type="dicts_create_with"
        id="epxah]ftUeU}n8,a:glr"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="K@7a%W*5q1om7VBDzeqm"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="2Hx2}PW}4SiUuGcogpAL"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="dicts_create_with" id="K?GYpn3oFp:Zy8np;O!C"><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="6bV/NAAsk-%=+EMa;{5?"><field
        name="TEXT">xyz</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="*egei6/r$wo;Zt$$AXTO"><field name="TEXT">uvw &amp;
        ghi</field></block></value></block></value><value name="payload"><shadow
        type="text" id="zBu;D|@-IN-!k@vjEE@T"><field
        name="TEXT">payload</field></shadow></value></block></value><next><block
        type="procedures_callnoreturn" id="By4L{Q-=~|||~.%zZ?SB"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="Y82z~!/?hzhdRCT#:M9n"><field name="TEXT">2j. HttpPost with
        contentType none and query</field></block></value><value
        name="ARG1"><block type="variables_get" id="9Y3WHCAh+|/KkrZ*c8Hp"><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="8u8wdrN#e?a:*/mf:c~Y"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="Y+#RUc}`9TFfqOkAqr:`"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="false"></mutation><field
        name="requestType">HttpPutRequest</field><field
        name="contentType">application/json</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="NR4Ya7P.x/qHp9CxN+?F"><field name="VAR"
        id="tG);,}R9`:ByvF5qKUdv">putUrl</field></block></value><value
        name="payload"><shadow type="text" id="Of2T+JZf5X5Js26H.+Gv"><field
        name="TEXT">payload</field></shadow><block type="dicts_create_with"
        id="s#PxDb!87+*N*gl+bfF_"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="V;vyPAd6H06X=.QX.a0M"><field
        name="TEXT">xyz</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="VY#RVL8c_6]J1R+KzY?M"><field name="TEXT">uvw &amp;
        ghi</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="Kr+}=Mt-w:+pGS8Va/2="><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="%.1jKm+jR)+BZQ{oBrjR"><field name="TEXT">3a. HttpPut with
        contentType application/json</field></block></value><value
        name="ARG1"><block type="variables_get" id="ydMJ/S}TS./bfbPHtp}v"><field
        name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value><next><block
        type="variables_set" id="vX.2.04::jOmVtjg,nI^"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field><value name="VALUE"><block
        type="oh_httprequest" id="yv]fPzj)xR9cs~-)(dg0"><mutation
        hasTimeout="false" hasHeader="false" hasQuery="true"></mutation><field
        name="requestType">HttpDeleteRequest</field><value name="url"><shadow
        type="text" id=")SOkb(t}41|NQu?4nGk-"><field
        name="TEXT">http://openhab.org</field></shadow><block
        type="variables_get" id="PGki*^HjS3E/97+$v*4}"><field name="VAR"
        id="]nI=b=x-e.p24o1]5P(V">deleteUrl</field></block></value><value
        name="query"><shadow type="dicts_create_with"
        id="Tl:i?tO.aYLg|1#oIH21"><mutation items="2"></mutation><field
        name="KEY0">param1</field><field name="KEY1">param2</field><value
        name="ADD0"><shadow type="text" id="9/L.[c+mK?1=X:kuFLn}"><field
        name="TEXT">param1</field></shadow></value><value name="ADD1"><shadow
        type="text" id="zo/CY-kcz/$7Y?qt;WL;"><field
        name="TEXT">param2</field></shadow></value></shadow><block
        type="dicts_create_with" id="y|talE+,U~KYO4^:|dD."><mutation
        items="2"></mutation><field name="KEY0">param1</field><field
        name="KEY1">param2</field><value name="ADD0"><shadow type="text"
        id="y^CmOFHlek.a*=!sNAN|"><field
        name="TEXT">param1</field></shadow><block type="text"
        id="RTdCTUU[n_w*GNRx{V:)"><field
        name="TEXT">xyz</field></block></value><value name="ADD1"><shadow
        type="text" id="^rh}K3e[E)dV_aLegh:y"><field
        name="TEXT">param2</field></shadow><block type="text"
        id="uT6kdStF21MipcyY!Uro"><field name="TEXT">uvw &amp;
        ghi</field></block></value></block></value></block></value><next><block
        type="procedures_callnoreturn" id="mzu|t/g{NxiL!d.s*{0,"><mutation
        name="logRequestTest"><arg name="testLabel"></arg><arg
        name="testResult"></arg></mutation><value name="ARG0"><block type="text"
        id="/OL$a+krZAoThknUBF4A"><field name="TEXT">4a. HttpDelete with
        query</field></block></value><value name="ARG1"><block
        type="variables_get" id="0qdqjN2h2ETy4-A+%%b0"><field name="VAR"
        id="lX3;mch[A*jvqOunQRyS">result</field></block></value></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></xml>'
      script: >
        var testLabel, testResult, url, testResponse, getUrl, postUrl, putUrl,
        deleteUrl, result, paramDict, paramString;


        // Describe this function...

        function logRequestTest(testLabel, testResult) {
          testResponse = !testResult.length ? 'not a valid request' : testResult;
          console.info(([testLabel,': ',testResponse].join('')));
        }


        function encodeParams(params) {
            if ((typeof params === 'string') || (params instanceof String)) return params;
            const encodedParams = Object.entries(params).map(([key, value]) => [key, encodeURIComponent(value)]);
            return encodedParams.map(p => p.join('=')).join('&');
        }


        function toJSONString(params) {
            if ((typeof params === 'string') || (params instanceof String)) return params;
            return JSON.stringify(params).replace(/^"+/, '["').replace(/"+$/, '"]');
        }



        url = 'https://postman-echo.com/';

        getUrl = String(url) + 'get';

        postUrl = String(url) + 'post';

        putUrl = String(url) + 'put';

        deleteUrl = String(url) + 'delete';

        result = (actions.HTTP.sendHttpGetRequest(getUrl + '?' +
        encodeParams({'param1': 'abc', 'param2': 'def & ghi'}), 5000));

        logRequestTest('1a. HttpGet with query (Dictionary) and timeout',
        result);

        paramDict = {'param1': 'abc', 'param2': 'def & ghi'};

        result = (actions.HTTP.sendHttpGetRequest(getUrl + '?' +
        encodeParams(paramDict), 3000));

        logRequestTest('1b. HttpGet with query variable (Dictionary)', result);

        paramString = 'param1=abc&param2=def%20%26%20ghi';

        result = (actions.HTTP.sendHttpGetRequest(getUrl + '?' +
        encodeParams(paramString), 3000));

        logRequestTest('1c. HttpGet with query variable (String)', result);

        result = (actions.HTTP.sendHttpGetRequest(getUrl + '?' +
        encodeParams(paramDict), {'header1': 'xyz', 'header2': 'uvw'}, 3000));

        logRequestTest('1d. HttpGet with query variable (Dictionary) and
        headers', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'text/plain', 'abc',
        3000));

        logRequestTest('2a. HttpPost with contentType text/plain', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl + '?' +
        encodeParams({'param1': 'abc', 'param2': 'def & ghi'}), 'text/plain',
        'xyz', 3000));

        logRequestTest('2b. HttpPost with contentType text/plain and query',
        result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'application/json',
        toJSONString({'param1': 'abc', 'param2': 'def & ghi'}), 3000));

        logRequestTest('2c. HttpPost with contentType application/json',
        result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'application/json',
        toJSONString('{ "abc": "def" }'), 3000));

        logRequestTest('2d. HttpPost with contentType application/json and
        string param', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'application/json',
        toJSONString('{ invalid json }'), 3000));

        logRequestTest('2e. HttpPost with contentType application/json and
        invalid string param', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'application/json',
        toJSONString([{'param1': 'abc', 'param2': 'def & ghi'}, 'xyz & uvw']),
        3000));

        logRequestTest('2f. HttpPost with contentType application/json and
        complex object argument', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'application/json',
        toJSONString((time.ZonedDateTime.now())), 3000));

        logRequestTest('2g. HttpPost with contentType application/json and
        object argument that converts to quoted string', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl, 'application/json',
        toJSONString(items.getItem('Local_Weather_and_Forecast_One_Call_API_Outdoor_Temperature')),
        3000));

        logRequestTest('2h. HttpPost with contentType application/json and
        object argument', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl,
        'application/x-www-form-urlencoded', encodeParams({'param1': 'abc',
        'param2': 'def & ghi'}), 3000));

        logRequestTest('2i. HttpPost with contentType
        aplication/x-www-form-urlencoded', result);

        result = (actions.HTTP.sendHttpPostRequest(postUrl + '?' +
        encodeParams({'param1': 'xyz', 'param2': 'uvw & ghi'}),
        'application/json', toJSONString('payload'), 3000));

        logRequestTest('2j. HttpPost with contentType none and query', result);

        result = (actions.HTTP.sendHttpPutRequest(putUrl, 'application/json',
        toJSONString({'param1': 'xyz', 'param2': 'uvw & ghi'}), 3000));

        logRequestTest('3a. HttpPut with contentType application/json', result);

        result = (actions.HTTP.sendHttpDeleteRequest(deleteUrl + '?' +
        encodeParams({'param1': 'xyz', 'param2': 'uvw & ghi'}), 3000));

        logRequestTest('4a. HttpDelete with query', result);
    type: script.ScriptAction

A (partial) screenshot of the test script:
image

And the test results when executing:

12:44:39.863 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 1a. HttpGet with query (Dictionary) and timeout: {
  "args": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225881.703",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e19-4f7b74362876d49f43d23cb6",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "cookie": "sails.sid=s%3Awq5k8yVJ2Nonu6RXBB4LVG6Jdmj4Bvba.7DAlOz%2BHuM%2BKAVI9iGTkVqXRkxFlbBf9pGNXlHZE0Jk"
  },
  "url": "https://postman-echo.com/get?param1=abc&param2=def%20%26%20ghi"
}
12:44:39.976 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 1b. HttpGet with query variable (Dictionary): {
  "args": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225881.815",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e19-7701821b1babee576a0107d0",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "cookie": "sails.sid=s%3AcHTunwMiRlWNbHj04ljb6IhQRLuKWd3A.IsXs2qMBt1bnWyM2I4t12EIhLB%2BAhFFDE4L%2FGZcdCO8"
  },
  "url": "https://postman-echo.com/get?param1=abc&param2=def%20%26%20ghi"
}
12:44:40.102 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 1c. HttpGet with query variable (String): {
  "args": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225881.944",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e19-2bf46aaa436f524d5ed32147",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "cookie": "sails.sid=s%3AeuNpTM_QgWfWP-zEXxrbqFQ04gf5JwCB.yBQz91w%2F7DiddNrUn3prD55iUJEvj6Eh95jfkzo%2B7fw"
  },
  "url": "https://postman-echo.com/get?param1=abc&param2=def%20%26%20ghi"
}
12:44:40.213 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 1d. HttpGet with query variable (Dictionary) and headers: {
  "args": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.057",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-791a50233c3918e43cd37c7c",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "header2": "uvw",
    "header1": "xyz",
    "cookie": "sails.sid=s%3AAHA_DIqkvnIIxxLPcMbcrDjTtdUqg-5a.9kdNFm0XKdxm2Ta6nJ0dq15HtwUWrQ7sqKLQ7sa0YO8"
  },
  "url": "https://postman-echo.com/get?param1=abc&param2=def%20%26%20ghi"
}
12:44:40.328 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2a. HttpPost with contentType text/plain: {
  "args": {},
  "data": "abc",
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.169",
    "content-length": "3",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-7877bdbd0d0117e90fcb17fe",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "text/plain",
    "cookie": "sails.sid=s%3AUbBg0QpY5QGdpEQmawnxn2mwMNWYpAz8.Coh0PUTMSPtYYcLrSthC10WsJSMMOEs95Bg36S6O1hY"
  },
  "json": null,
  "url": "https://postman-echo.com/post"
}
12:44:40.499 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2b. HttpPost with contentType text/plain and query: {
  "args": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "data": "xyz",
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.298",
    "content-length": "3",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-7ac76f8155c508413bb66e6a",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "text/plain",
    "cookie": "sails.sid=s%3AAvmw4J9GRZM26Fuxf0xMMMjjR1KsrpqP.i9NnXhll%2Bgh3SNAZ%2FXBLW1gkUQBR0%2F6EucnxHlq4RRI"
  },
  "json": null,
  "url": "https://postman-echo.com/post?param1=abc&param2=def%20%26%20ghi"
}
12:44:40.619 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2c. HttpPost with contentType application/json: {
  "args": {},
  "data": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.458",
    "content-length": "37",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-09393e16061948935c1b14bb",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3AxapAUrX8mfqSbaJ7EuCOeIiaxyDNScXO.zdFUoiVFIFYpf5iRRRzX%2BsKTmq%2FAAQFkY9SiuIbgWD8"
  },
  "json": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "url": "https://postman-echo.com/post"
}
12:44:40.733 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2d. HttpPost with contentType application/json and string param: {
  "args": {},
  "data": {
    "abc": "def"
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.577",
    "content-length": "16",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-72af2d86455da28b3ee9c85b",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3A7CbRCh62h-Fu3-yO8O5CLTo0Gmytd4j5.QB4YwPIdoxH9gvunvWahderdUuvaiXM5IdrcexNvPpM"
  },
  "json": {
    "abc": "def"
  },
  "url": "https://postman-echo.com/post"
}
12:44:40.847 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2e. HttpPost with contentType application/json and invalid string param: {
  "args": {},
  "data": "{ invalid json }",
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.686",
    "content-length": "16",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-4c3e58d1203b42dc20178588",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3Anp1g66m4dzl9HvTkz_qfUSbVhFhd33Xp.n0ilsnlLE3OF8PAqG5izZAo%2FeQVGqJwoOOq7ie2eLTg"
  },
  "json": null,
  "url": "https://postman-echo.com/post"
}
12:44:40.972 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2f. HttpPost with contentType application/json and complex object argument: {
  "args": {},
  "data": [
    {
      "param1": "abc",
      "param2": "def & ghi"
    },
    "xyz & uvw"
  ],
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.804",
    "content-length": "51",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-16f79dca2b12e3136a6c5e0e",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3AKJ-wbaBoUIpZm5tzfu-_vh5Ssu1DoiBl.J%2BmE2il3w7CHD2mZYZSKQKfmkvoC76L49bRHnpR0itY"
  },
  "json": [
    {
      "param1": "abc",
      "param2": "def & ghi"
    },
    "xyz & uvw"
  ],
  "url": "https://postman-echo.com/post"
}
12:44:41.092 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2g. HttpPost with contentType application/json and object argument that converts to quoted string: {
  "args": {},
  "data": [
    "2024-06-24T12:44:40.978+02:00[SYSTEM]"
  ],
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225882.936",
    "content-length": "41",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1a-44cc5c8d6b208a105b37d0d5",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3AQbicnphePXfKZo2S4xRv_VDZE1bhQl8h.HJ3046XOSadBErFXUxt%2B6YqPNJ5gcT66glbXXqa5%2B5Y"
  },
  "json": [
    "2024-06-24T12:44:40.978+02:00[SYSTEM]"
  ],
  "url": "https://postman-echo.com/post"
}
12:44:41.219 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2h. HttpPost with contentType application/json and object argument: {
  "args": {},
  "data": {
    "rawItem": {},
    "persistence": {
      "rawItem": {}
    },
    "semantics": {
      "rawItem": {}
    }
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225883.060",
    "content-length": "70",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1b-4877a37a73dbed9f1db14f7a",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3ApWOzTyjeQYI0Emwif7B1HflHSUv8RdS_.gIFI5SNAl64G%2F2RPNND4KP0xy%2B77O10WOOIRAxwGfl4"
  },
  "json": {
    "rawItem": {},
    "persistence": {
      "rawItem": {}
    },
    "semantics": {
      "rawItem": {}
    }
  },
  "url": "https://postman-echo.com/post"
}
12:44:41.334 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2i. HttpPost with contentType aplication/x-www-form-urlencoded: {
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225883.176",
    "content-length": "33",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1b-2de348cb3da9e7292fd4b77e",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/x-www-form-urlencoded",
    "cookie": "sails.sid=s%3A5xBbSIBeKWR8exF9fGK1yRYvHT74qX9j.tzFxL9ruUrkSFKwV%2F1WT2QFeYgk9yX24frsomOECm8g"
  },
  "json": {
    "param1": "abc",
    "param2": "def & ghi"
  },
  "url": "https://postman-echo.com/post"
}
12:44:41.450 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 2j. HttpPost with contentType none and query: {
  "args": {
    "param1": "xyz",
    "param2": "uvw & ghi"
  },
  "data": "payload",
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225883.293",
    "content-length": "7",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1b-14d153f15da0c6c67e8ba71d",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3A4eoEmxMeDejYaXtyPys8bhktTvBFR8gs.A6Vpt5jz5%2BEcejg6LD0UG8PTJ9PZiFOQXd%2Fwu965k94"
  },
  "json": null,
  "url": "https://postman-echo.com/post?param1=xyz&param2=uvw%20%26%20ghi"
}
12:44:41.610 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 3a. HttpPut with contentType application/json: {
  "args": {},
  "data": {
    "param1": "xyz",
    "param2": "uvw & ghi"
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225883.411",
    "content-length": "37",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1b-1db81754715a7864505efe9b",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "content-type": "application/json",
    "cookie": "sails.sid=s%3AKq3nV0m1PhL3AGWzYf44zZX0IincAQ_T.ekUbZzoOaZTpSHNqcXiB4UG89WSEFptbZhVKfxxjleo"
  },
  "json": {
    "param1": "xyz",
    "param2": "uvw & ghi"
  },
  "url": "https://postman-echo.com/put"
}
12:44:41.727 [INFO ] [enhab.automation.script.ui.0d23f2c49c] - 4a. HttpDelete with query: {
  "args": {
    "param1": "xyz",
    "param2": "uvw & ghi"
  },
  "data": {},
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t=1719225883.570",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-66794e1b-2a3c4cd979053bad7550f7d6",
    "accept-encoding": "gzip",
    "user-agent": "Jetty/9.4.54.v20240208",
    "cookie": "sails.sid=s%3AT5GD6k8ZoktGw6lUCQy-9_T8QyUsyMtE.M54VqMiyLe2wXIjShjxlmMoP3EOBsOTAcMBlGoTqmfo",
    "content-type": "application/json"
  },
  "json": null,
  "url": "https://postman-echo.com/delete?param1=xyz&param2=uvw%20%26%20ghi"
}

@mherwege mherwege marked this pull request as ready for review June 24, 2024 10:46
@mherwege mherwege changed the title [Blockly] http block enhancement [Blockly] Http block enhancement Jun 24, 2024
@stefan-hoehn
Copy link
Contributor

I promise, it is the next thing I take care of 🙏🏻

@stefan-hoehn
Copy link
Contributor

stefan-hoehn commented Jul 27, 2024

In general it works but during my first tests I started with my own test case block which I already had. It looks like this:

image

If you build this from scratch, all is good but if you start with the following already existing blocks

<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="Q]m__?A[6PtqV7H36tLm">response</variable></variables><block type="variables_set" id="V]ihp)k%*!F^X9[om],k" x="13" y="245"><field name="VAR" id="Q]m__?A[6PtqV7H36tLm">response</field><value name="VALUE"><block type="oh_httprequest" id=".gYEcH/MwMdjm_vCH=}Q"><mutation hasTimeout="true" hasHeader="true" hasQuery="false"></mutation><field name="requestType">HttpPostRequest</field><field name="contentType">text/plain</field><value name="url"><block type="text" id="H2]8ucQ2E[GFRC7}nHq+"><field name="TEXT">http://192.168.255.255:8080/rest/items/WZ_Licht_oben1</field></block></value><value name="timeoutInput"><shadow type="math_number" id="1a|p5I+WmFaB4V(aqB)f"><field name="NUM">3000</field></shadow><block type="math_number" id="j(zZ.7So+pB_sgE7Cq;;"><field name="NUM">3000</field></block></value><value name="requestHeader"><shadow type="dicts_create_with" id="e~hGDIpmQR)C;2-Y;)o="><mutation items="2"></mutation><field name="KEY0">header1</field><field name="KEY1">header2</field><value name="ADD0"><shadow type="text" id="5fANl_ZOLqpRMri~)LGp"><field name="TEXT">header1</field></shadow></value><value name="ADD1"><shadow type="text" id="_EYTx.g]LBj[00ns9d:c"><field name="TEXT">header2</field></shadow></value></shadow><block type="dicts_create_with" id="2HrQ0k?4i+[`zNrDCxvy"><mutation items="2"></mutation><field name="KEY0">X-Trace-Id</field><field name="KEY1">myId</field><value name="ADD0"><block type="text" id="0*|-D,MwgcMYX;YKzxNZ"><field name="TEXT">96a101dd-c49a-4fea-aee2-a76510f32190</field></block></value><value name="ADD1"><block type="text" id="8Uu~qy@-CB%JjW$z=NDD"><field name="TEXT">X-from-openHAB</field></block></value></block></value><value name="payload"><shadow type="text" id="iPxB0jDX@4j$|:KMjwgR"><field name="TEXT">payload</field></shadow><block type="text" id="NKg1d3#)r7BzGWy?zSbh"><field name="TEXT">ON</field></block></value></block></value></block></xml>

  • then add a query block
  • then move the http block away

a mutator error is thrown. See the attached video.
(Note that I have rebased the code to the current main which shouldn't make the difference but I thought I'd rather mentioned it)

new-mutator-breaks-existing-block.mov

The problem appeared pretty quick but it took me a while to reproduce it reliably.
So, this seems to break something for blocks that already exist in the mutator. Probably a nasty thing to find 😩

@mherwege
Copy link
Contributor Author

Strange that the error is thrown when moving the block away, and not already when adding the query. I will try to find what is causing this, may take some time though. Probably the right approach is to compare the code between your block, and the same block built from scratch.

@stefan-hoehn
Copy link
Contributor

At least after lots of stepping into the code and commenting parts out that you have added, I was able to narrow it down to be related to lines 134ff

if (!payloadInput) { if (!this.hasContent) { payloadInput = this.appendDummyInput('payload') } else { payloadInput = this.appendValueInput('payload') } payloadInput.appendField('with Payload') .appendField(new Blockly.FieldDropdown([ ['application/json', 'application/json'], ['none', 'none'], ['application/javascript', 'application/javascript'], ['application/xhtml+xml', 'application/xhtml+xml'], ['application/xml', 'application/xml'], ['application/x-www-form-urlencoded', 'application/x-www-form-urlencoded'], ['text/html', 'text/html'], ['text/javascript', 'text/javascript'], ['text/plain', 'text/plain'], ['text/xml', 'text/xml']], this.handleContentTypeSelection.bind(this)), 'contentType') } if (this.hasContent) { if (this.isEncodedContent) { payloadInput.setShadowDom(null) payloadInput.setCheck(['Dictionary', 'String']) this.addDictShadowBlock(payloadInput, 'param') } else { payloadInput.setShadowDom(null) if (this.isJSONContent) { payloadInput.setCheck(null) } else { payloadInput.setCheck('String') } payloadInput.setShadowDom(Blockly.utils.xml.textToDom('<shadow type="text"><field name="TEXT">payload</field></shadow>')) } }

If you comment these out, no exception is thrown there and only don't know yet what the issue is here.

Further if you only comment out the lower part

        /* if (this.hasContent) {
          if (this.isEncodedContent) {
            payloadInput.setShadowDom(null)
            payloadInput.setCheck(['Dictionary', 'String'])
            this.addDictShadowBlock(payloadInput, 'param')
          } else {
            payloadInput.setShadowDom(null)
            if (this.isJSONContent) {
              payloadInput.setCheck(null)
            } else {
              payloadInput.setCheck('String')
            }
            payloadInput.setShadowDom(Blockly.utils.xml.textToDom('<shadow type="text"><field name="TEXT">payload</field></shadow>'))
          }
        } */

and keep the upper part in it still throws the error. So the main culprit lies probably between 134 - 151.

@stefan-hoehn
Copy link
Contributor

Another situation I detected: If you use GET instead of POST / PUT with the query block, you don't have the payload block and then you don't have that issue which makes sense because it is related to the payload section in the code!

@stefan-hoehn
Copy link
Contributor

stefan-hoehn commented Jul 27, 2024

And this is the issue:

          if (!this.hasContent) {
            // payloadInput = this.appendDummyInput('payload')  <-- Culprit
            payloadInput = this.appendValueInput('payload')
          } else {
            payloadInput = this.appendValueInput('payload')
          }

I am just not sure why you did this?

Additionally if you add logs, you can see that the content-Type detection is not working reliably when you change the dropdown selection.

if (!this.hasContent) {
            // payloadInput = this.appendDummyInput('payload')  <-- Culprit
            payloadInput = this.appendValueInput('payload')
            console.info('hasContent')
          } else {
            payloadInput = this.appendValueInput('payload')
            console.info('NO Content')
          }

Now, I got the idea:

No content:

image

versus

image

So basically the issue is the switch between having a field or an input.

I am running out of time to fix it now but I hope that should be enough hints to solve it. 🤞🏻

@mherwege
Copy link
Contributor Author

if (!this.hasContent) {
// payloadInput = this.appendDummyInput('payload') <-- Culprit
payloadInput = this.appendValueInput('payload')
console.info('hasContent')
} else {
payloadInput = this.appendValueInput('payload')
console.info('NO Content')
}

I think you switched the message in the log. The first message should be 'NO Content', the second 'hasContent'.

I used the appendDummyInput indeed to remove the input connection when there is no content (content type none). That also makes sure a block that is already connected to the input will get disconnected properly. If you do not do that, it stays connected and may be the wrong type. So the line is necessary.

@mherwege
Copy link
Contributor Author

mherwege commented Jul 29, 2024

@stefan-hoehn I figured out what the issue was. The payload field was not added at the right position when switching from GET to POST (it should be after the query field, but it wasn't always). That should be fixed.
I also found bugs in the hasContent checks and reworked that part. In my tests so far, I didn't encounter the issue anymore.

I have not rebased yet. Let me know if I should do so.

@stefan-hoehn
Copy link
Contributor

Great, good teamwork 😎
(Note: mutators can be beasts 😉)

Yes, go ahead and rebase.
I will then continue with the testing.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
@mherwege
Copy link
Contributor Author

@stefan-hoehn I rebased and also simplified the code further. There were a number of fields that could be replaced by variables inside methods, reducing the complexity. From my perspective, it is ready for further testing.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
@florian-h05
Copy link
Contributor

@stefan-hoehn What's the status here? Ready for my review?

@stefan-hoehn
Copy link
Contributor

@florian-h05 my bad, I had actually notices Mark's chances but forgot about retesting it. Definitely go for a review now, while I will retest it!

@stefan-hoehn
Copy link
Contributor

I retested the issue and the PR now looks good to me. @florian-h05 your turn :D

Copy link
Contributor

@florian-h05 florian-h05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

One minor question and one comment:

this.appendValueInput('url')
.setCheck('String')
.appendField(imageTimeoutField, 'imgTimeout')
.appendField(imageHeaderField, 'imgHeader')
.appendField(imageQueryField, 'imgHeader')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently can‘t check how it looks like (only have my iPad with me), but is it correct that we have the imgHeader string here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No (even though it isn't really used to reference it later) - it should be imgQuery

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, should be changed, I didn’t notice in testing as the name reference is not used. I am not behind a PC, so difficult to do at the moment, but will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@florian-h05 resolved now

@@ -183,4 +266,23 @@ export default function (f7, isGraalJs) {
throw new Error(unavailMsg)
}
}

function encodeParams (params) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to have this code inside something like a HTTP request builder inside openhab-js, but it‘s fine for me to merge this PR as is and adjust the code later once we have implemented this.
I will have limited time the next weeks so I won‘t implement that anytime soon (and hopefully don‘t forget that).

@florian-h05 florian-h05 added this to the 4.3 milestone Aug 18, 2024
@florian-h05 florian-h05 changed the title [Blockly] Http block enhancement [blockly] HTTP block enhancements Aug 18, 2024
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Copy link
Contributor

@florian-h05 florian-h05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@florian-h05 florian-h05 merged commit b0d9ad7 into openhab:main Aug 19, 2024
8 checks passed
@mherwege mherwege deleted the blockly_http branch August 19, 2024 11:42
@stefan-hoehn
Copy link
Contributor

@mherwege And finally a big "thank you" for the nice contribution!

@stefan-hoehn
Copy link
Contributor

(btw, "we" should not forget to write some documentation about it 😜)

@ghys
Copy link
Member

ghys commented Aug 31, 2024

Just getting acquainted and this is brilliant, thanks to @mherwege and all involved!

My only suggestion would be to change the labels to something less technical like this:

image

@mherwege
Copy link
Contributor Author

mherwege commented Sep 1, 2024

Just getting acquainted and this is brilliant, thanks to @mherwege and all involved!

My only suggestion would be to change the labels to something less technical like this:

image

Yes, may indeed be cleaner. I will see if I can do it without breaking already existing Blockly rules using it. @stefan-hoehn what do you think?

florian-h05 pushed a commit that referenced this pull request Sep 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request main ui Main UI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants