New Dev: Request for httpPost Groovy Examples

dev:1112020-12-16 01:00:54.727 pm debugResponse.hasError(): No encoder found for request content type custom

dev:1112020-12-16 01:00:54.724 pm debugResponse.hasError(): true

dev:1112020-12-16 01:00:54.720 pm debugResponse.getStatus(): 408

dev:1112020-12-16 01:00:54.717 pm debugnullCallback() invoked with response: hubitat.scheduling.AsyncResponse@802146 and data: null

Should just be the standard HTTP content type that your API is expecting. I don't know it well, but the docs and your content make it look like application/json is what you should be using, maybe?

Ah, "text/html" seems to have allowed the request to be sent.

application/json did not work.

looks like it went through with text/html

Now to parse that out...

If JSON does work, note that you can just use a standard Groovy Map as your body, not an actual JSON string (it will do the magic for you--both there and in the response, which will be a Lazy Map back if JSON). But I'm not sure if that is the issue.

SO CLOSE!

This is hitting the device, but not passing the arg={"flashRed":500} to the device.

Note, when I use curl, it works with...

curl URL -d arg='{"flashRed":500}'

def FlashRed(){
    if (enableDebug) log.debug "Flash Red Pushed"
    def postParams = [
        uri: apiURL + "${deviceID}/cmd?access_token=${accessToken}",
        requestContentType: 'text/html',
        contentType: 'application/x-www-form-urlencoded',
        //headers: ['CustomHeader':'CustomHeaderValue'],
        //body : "arg={\"flashRed\":500}"
	]
    
    if(enableDebug) log.debug "Calling asyncHttpPost with parameters ${postParams}"
    asynchttpPost('nullCallback', postParams, [arg:"{\"flashRed\":500}"])
}

def nullCallback(response, data){
    if (enableDebug) log.debug "nullCallback() invoked with response: ${response} and data: ${data}"
    if (enableDebug) {
        log.debug "Response.getStatus(): ${response.getStatus()}"
        if (response.hasError()){
            log.debug "Response.hasError(): ${response.getErrorMessage()}"
        } else {
            log.debug "Response.getData(): ${response.getData()}"
        }
    }

Maybe I have this wrong?

asynchttpPost('nullCallback', postParams, [arg:'{"flashRed":500}'])

would that be the same as the -d argument in the curl statement above?

This thread should help you out. As I believe your "postParams" and Args should be combined into the same object/map and data should be null.

1 Like

The third parameter (your map with an arg key) is optional and is just data that will be passed to your callback method. It's not really part of the POST itself. This is probably not well documented. Some people use this to see what they POSTed (or whatever) if they use the same callback method for various calls, but you don't need to if you don't particularly care; the default null is fine then.

My guess is that you really want this as the body value in your postParams map. Also, from Cloud API | Reference Documentation | Particle, it looks like they'll accept either JSON or form-encoded data, but JSON is probably easier to work with in Groovy. If you use a content-type of JSON (the default), then a Map will automatically be converted to JSON for you, so you can use [flashRed: 500] or [arg: [flashRed: 500]] (looks odd but maybe what they want?) and not worry about String-ifying anything. It also looks like the access token as part of the URL is only accepted for GETs, not POSTs, so you may need to move that to a a custom header? (Never done that on Hubitat, but I assume it's possible.) But this is just me seeing things in the docs, with no actual familiarity or device to test with, so I could be wrong. :smiley:

2 Likes

Thanks for that, I had just figured it out.

I think I should move the access_token, but it works in curl like that, so I will see if I can get it working here first.

At this point, my firmware is receiveing [Object: Object] when I set

body : [arg: ["flashRed": 500]]

HALLELUJAH

Here was the key:
body : [arg: "{"flashRed": 500}"]

This works, so thank you!!!

However, I can't get the access_token out of the URL:

All of these cause errors on the local side:

body : [access_token: ${accessToken} , arg: "{\"flashRed\": 500}"]
body : [access_token: "${accessToken}" , arg: "{\"flashRed\": 500}"]
body : [[access_token: ${accessToken}] , [arg: "{\"flashRed\": 500}"]]
body : [[access_token: "${accessToken}"] , [arg: "{\"flashRed\": 500}"]]

On curl, I can get this to work this way...

curl URL -d access_token -d data

OK, looking at the curl trace, I see that the body ended up looking like this, and this worked...

    requestContentType: 'application/x-www-form-urlencoded',
    contentType: ' application/x-www-form-urlencoded',
    body : "arg={\"flashRed\":500}&access_token=${accessToken}"

Thank you for your help!

Glad you got it figured out! I'd personally still aim to use JSON instead of form-encoded, since it looks like the API supports that and at the very least that might be easier ifyou start sending more complex commands (or ones that have special characters), but if it works... :slight_smile:

I agree with using JSON... but I can't get it to work!

I get BAD REQUEST when I change this:

    requestContentType: 'application/x-www-form-urlencoded',     
    body : "arg={\"flashRed\":100}&access_token=${accessToken}" 

To this:

    requestContentType: 'application/json',
    body : "{\"arg\":\"flashRed\", \"access_token\":${accessToken}}"

And I have tried changing the use of quotes around all of these :frowning:

First, with the JSON, you can again just use a Groovy map instead of manually building the JSON string. With a JSON content type for your request, the conversion will be performed for you in both what is sent and received. That will eliminate the need to worry about quotes, for one (also: Groovy map keys are assumed to be strings by default, so you don't even need quotes around them, which may look odd to you if you know Java...or pretty much anything else). Something like [arg: [flashRed: 100]] if it really is nested like that.

Second, the JSON seems to be missing something--I don't see the 100 value that you had in the form encoding. I'd try to help more, but I have no idea if that access token is supposed to be nested inside arg or its own thing. From their docs, it looks like that may not actually be possible at all:

There are three ways to send your access token in a request.

  • In an HTTP Authorization header (always works)
  • In the URL query string (only works with GET requests)
  • In the request body (only works for POST, PUT and DELETE when body is form encoded)

Buuuut....there's that first thing. So adding something like this to your postParams may work:

headers: [Authorization: "Bearer ${accessToken}"]

(I'm not sure if this adds to or completely overwrites your headers; if so, you may need to manually specify other things like Content-Type: "application/json" here, too.)

But this is mostly just me guessing again. :smiley:

1 Like

Yeah, I missed that... but that's not important for testing this (The String gets passed to my function; in this case I am using json in the string, but that's my user code.)

Also interesting... the URL method definitely works for these POST requests.

I'll try the headers entry. That one is new to me, thank you

The headers line you sent works.

Hey- I'd like to thank you again. I was reading their docs, but not that part (I was dug down into the function portion). Somehow my searches missed it.

The other place I got confused... that third bit only works for form encoded.

The other thing that is mildly annoying is that the platform only passes the first string in the body. You can encode multiple, but it only passes the first, regardless of the name.

Then if you encode as JSON, it will error with more than one.

Kinda obvious if you know how it works, kinda confusing if you don't!

Thank you all again!

2 Likes

EDIT NEVERMIND

getJson() did the trick. Too up in my head. Time to eat.


Do you mind if I ask one more question?

in the handler, the response is:

resp.getData() = {"id":"e00fce68104e5702da03715a","connected":true,"return_value":0}

If I set my ContentType to applicaiton/json, shouldn't resp.getData() be a map?

How do I access those values? I tried resp.getData().id and resp.getData().id[0] and .value after both of those. Errors each time.

Try .getJson() instead of .getData() (the post above documents this as returning an Object for some reason--I guess not entirely wrong, but not exactly helpful--but in my experience it's Map, specifically a LazyHashMap). Otherwise, it's certainly possible to "slurp" the string from that data into an object (Map) representing the JSON, but that should be handled for you by the above.

1 Like

Figured it out 10 seconds ago. Thank you! I was reading documentation for the non async methods, they parse the json to a map before returning it.