Syntax of asynchttpPut with contents type application/x-www-form-urlencoded

I've got a PUT method that works using Postman, but I cannot get the syntax correct in asynchttpPut for it to work.

What I've got in Postman is:

As you can see from the screenshot, the request is using content-type "application/x-www-form-urlencoded" but the response is using "application/json". So I've tried using the parameter "requestContentType" in the asyncHttpPut call, but it results in a 403 - Forbidden error. I've also tried "contentType" with the same outcome. My asyncHttpPut call is using the following parameter map:

[uri:http://172.16.110.119/api/v2/configurations, headers:[Auth-Token:e***********************5], requestContentType:application/x-www-form-urlencoded, body:[EM_USOC:40]]

Can someone point me to the, most likely very obvious, issue with my asynchttpPut call?

Try adding a “Accept” request header where value is "application/json". That parameter tells the remote host you want a json response.

Can we see the actual code for the call?
Block out any personal info (which I see you already do :wink: )

That did unfortunately not work. Error message now changed to "408 - Unexpected keyword args: accept" (tried with capitalised 'Accept' as well, same error). Parameters used now were:

[uri:http://172.16.110.119/api/v2/configurations, headers:[Auth-Token:e************************5], requestContentType:application/x-www-form-urlencoded, body:[EM_USOC:40], accept:application/json]

Of course:

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}"], requestContentType: "application/x-www-form-urlencoded", body: ["EM_USOC": "$backupBuffer"]]
asynchttpPut("putDataHandler", parameterMap)

Perhaps of note is that the following parameter map used to work, but then it stopped working:

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}"], query: ["EM_USOC": "$backupBuffer"]]

The only change between these two code snippets is that now I'm trying to add the requestContentType parameter as well.

I don't know if it stopped working when I upgraded from HE v2.3.5 to 2.3.8 or if there has been a change to the API I'm accessing. What I do know, however, is that using Postman, I can get it to work by just setting the content type to "application/x-www-form-urlencoded"

I have almost figured it out now. Rather than using the "requestContentType" parameter, I use the parameter "Content-Type" inside the header instead:

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: ["EM_USOC": "$backupBuffer"]]

That now produces a "200" response but not with the response I expected.

What I have realised now is that my request from HE is also missing the "Content-Length" parameter (see Postman snapshot in the first post). I have figured out how to calculate the value for the length (trial and error using Postman), but not how to add it to the parameter map in HE. If I add it to the headers:

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "$contentLength" ], query: ["EM_USOC": "$backupBuffer"]]

I get the error "408 - Unknown Exception"

I cannot find a documented parameter to use for Content-Length, but trying the following:

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: ["EM_USOC": "$backupBuffer"], "Content-Length": "$contentLength"]
def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: ["EM_USOC": "$backupBuffer"], contentLength: "$contentLength"]
def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: ["EM_USOC": "$backupBuffer"], length: "$contentLength"]

all gave me an "408 - Unexpected keyword args" error

Anyone know how to add Content-Length to the request?

I would not try to calculate Content-Length manually. It should automatically be calculated and applied on the sending side by the Hubitat network stack.

Two thoughts:

Have you tried the synchronous version (httpPut())? Just to rule out any differences in behavior in the Hubitat implementations between those two.

I would try just specifying the body directly instead of either query or body as a Map. If you specify body: "EM_USOC=${backupBuffer}", does it work?

Thanks @tomw. Whenever I use 'body' (whether map or not, or Content-Type or not):

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], body: "EM_USOC=${backupBuffer}"]
def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], body: ["EM_USOC":"$backupBuffer"]]
def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}"], body: ["EM_USOC":"$backupBuffer"]]
def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}"], body: "EM_USOC=${backupBuffer}"]

I get the error message "408 - No encoder found for request content type */*".

Using query directly (no map):

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: "EM_USOC=${backupBuffer}"]
def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: "EM_USOC:${backupBuffer}"]

generates the error message "408 - class org.codehaus.groovy.runtime.GStringImpl cannot be cast to class java.util.Map (org.codehaus.groovy.runtime.GStringImpl is in unnamed module of loader 'app'; java.util.Map is in module java.base of loader 'bootstrap')"

I'll give the synchronous method a go.

Did you try using both? contentType would be application/json and requestContentType would be your application/x-www-form-urlencoded

You dont seem to be using the correct parameters per the Hubitat docs? asyncHttpPut is not documented but the Post method is,

def parameterMap = [ uri: "$URI", requestContentType: "application/x-www-form-urlencoded", contentType:"application/json", headers: ["Auth-Token": "${settings.APIToken}"], query: ["EM_USOC": "$backupBuffer"]]

Thanks Jeff. Yeah, I've tried with both as well to no avail. Using the parameter map you provided:

def parameterMap = [ uri: "$URI", requestContentType: "application/x-www-form-urlencoded", contentType:"application/json", headers: ["Auth-Token": "${settings.APIToken}"], query: ["EM_USOC": "$backupBuffer"]]

generates the error message "400 - Bad Request"

changing out query for body:

def parameterMap = [ uri: "$URI", requestContentType: "application/x-www-form-urlencoded", contentType:"application/json", headers: ["Auth-Token": "${settings.APIToken}"], body: ["EM_USOC": "$backupBuffer"]]

gives me "403 - Forbidden"

It feels to me that the closest I've come is:

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}", "Content-Type": "application/x-www-form-urlencoded"], query: ["EM_USOC":"$backupBuffer"]]

Which gives me a 200 response, but with an "empty" value of "{}". This is the same response I get using Postman if I remove the "Content-Length" header from Postman. But I don't know if this is the issue with asynchttpput since I cannot determine whether or not it automatically calculates and supplies the Content-Length parameter in the call.

I'm off to bed now, but being Saturday tomorrow, I'll have some time to get the synchronous method a go

What is this API you are trying to use? Does it have online docs?

No, no online docs. One page of local docs though:

The following parameter map used to work (this has just recently stopped working):

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "${settings.APIToken}"], query: ["EM_USOC": "$backupBuffer"]]

This now errors out with "400 - Bad Request". I don't know exactly when it stopped working as I haven't kept close tabs on this particular function of my solar panel driver, but I did just upgrade the hub from 2.3.5 to 2.3.8. However, I cannot say for sure that the HE upgrade had anything to do with it since I don't know when it stopped working for me. I cannot see anything in the release notes pertaining to any asynchttp methods. It can well be that the firmware of the inverter has been updated, as updates are automatically applied.

Yay! Got it in the end. httpPut works where asynchHttpPut doesn't. With httpPut the following parameter maps work (they both failed with "403 - Forbidden" with asynchHttpPut):

def parameterMap = [ uri: "$URI", headers: ["Auth-Token": "$APIToken"], requestContentType: "application/x-www-form-urlencoded", body: ["EM_USOC": "$backupBuffer"]]
def parameterMap = [ uri: "$URI", requestContentType: "application/x-www-form-urlencoded", contentType:"application/json", headers: ["Auth-Token": "$APIToken"], body: ["EM_USOC": "$backupBuffer"]]

Are the synchronous and asynchronous versions of httpPut not supposed to work the same? Is this a bug? Or could it somehow be that the API that is accessed requires a synchronous request?

Probably a bug in the async method. They do have some slightly different options but should work the same in the end.

Thanks all for your help and pointers!

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.