I noticed a strange behavior regarding the APIFunction
when used with URLDispatcher
. It does not allow me to access the body of a HTTP request payload. Similar question was asked here. However the answer does not help.
Let me provide a simple example. We prepare a test body
as a "JSON"
string and define the APIFunction
which returns the body of the HTTP request payload after importing it as a "JSON"
string.
api$Test =
APIFunction[{},
Block[{s},
s = HTTPRequestData[{"Method", "Headers", "Body"}];
ImportString[s[[3]],"JSON"]
]&
];
Create a test body
. Now testing the sanity of the APIFunction
is straightforward.
body = ExportString[
{"User-1"->{"Name"->"Rob","Age"->28},"User-2"->{"Name"->"Bob","Age" ->32}},"JSON"];
GenerateHTTPResponse[
api$Test, {"Method" -> "POST",
"Headers" -> {"accept" -> "*/*","content-type" ->"application/json"},
"Body" -> body}]["Body"]
{"User-1" -> {"Name" -> "Rob", "Age" -> 28}, "User-2" -> {"Name" -> "Bob", "Age" -> 32}}
Now let's try exposing the above APIFunction
with URLDispatcher
and access it locally using the builtin toy web server.
$Rules1 = URLDispatcher[{"/Test" -> api$Test}];
ws=HTTPHandling`StartWebServer[$Rules1,"Port"-> 7028]
With the web server running green, we can make an identical HTTP request that we used with GenerateHTTPResponse
and our function api$Test
. The APIFunction
now used behind the URLDispatcher
should return the same result as above.
exp = HTTPRequest[
"http://localhost:7028/Test",<|"Method" -> "POST",
"Headers" -> {"accept" -> "*/*","content-type" -> "application/json"},
"Body" -> body|>];
URLExecute[exp]
However one will get an ImportString::string
error as the APIFunction
can not access the body (which is None
at this point not a "JOSN"
string) thus it can not import the body as json using ImportString
.
Am I doing something wrong or it is a bug of the toy web server or even the URLDispatcher
? One can see that the "Header"
is accessible.
{Accept->{/},Content-Length->{130},User-Agent->{Wolfram HTTPClient 11.1},Content-Type->{application/json}}
The specified "Method"
is also accessible by the APIFunction
but just like a magic, only the "Body"
becomes None
. Above was tested in mma version 11.1.1.0.
Update 1
I tried tracing the error
Trace[URLExecute[exp], TraceBackward -> True]
and found the following.
ImportString::string: First argument None is not a string.
ByteArray::lend: The argument at position 1 in ByteArray[#ContentData] should be a vector of unsigned byte values or a Base64-encoded string.
HTTPResponse::encfailed: Failed to encode HTTPResponse body
HTTPResponse::encfailed: Failed to encode HTTPResponse body
HTTPResponse::encfailed: Failed to encode HTTPResponse body
Strangely the error ByteArray::lend
appears only during the first time evaluation. Passing the body as Base64-encoded string should not help and it does not either. The URLUtilities
package might have some problem with reading the HTTP response. Experts can shade some lights!
Update 2
As suggested in the answer from @b3m2a1 checking out the Main.m
shows that the "AsyncHandler"
of the WebServer
is working with a data where the body part of the HTTP request seems to be already missing.
In the definition of the (ws:WebServer[_?AssociationQ])["AsyncHandler"]
we can see the local variable json
should contain the response.
json = Association[ImportString[FromCharacterCode[Normal[msg["Data"]]], "JSON"]]
However it seems the message coming from the MQTT subscriber is having corrupt "Data"
and failing to capture the body part of the HTTP POST request.