Async HTTP calls

Asynchronous calls are now available in the Hubitat Elevation system.

The following methods are available:

void asynchttpGet(String callbackMethod, Map params, Map data = null)
void asynchttpPost(String callbackMethod, Map params, Map data = null)
void asynchttpPut(String callbackMethod, Map params, Map data = null)
void asynchttpDelete(String callbackMethod, Map params, Map data = null)
void asynchttpPatch(String callbackMethod, Map params, Map data = null)
void asynchttpHead(String callbackMethod, Map params, Map data = null)

Possible values for params are as such:

name value
uri (required) A URI or URL of the endpoint to send a request to
path Request path that is merged with the URI.
query Map of URL query parameters.
headers Map of HTTP headers.
requestContentType The value of the Content-Type request header. Defaults to 'application/json'.
contentType The value of the Accept request header. Defaults to the value of the requestContentType parameter if not specified.
body The request body to send. Can be a string, or if the requestContentType is "application/json", a Map or List (will be serialized to JSON).

The callback method should be defined as such:

def processCallBack(response, data) {
}

the name of the callback method can be whatever you choose, just pass it's name to the async method and the system will call it with the results of the http call and also any data you wish to pass to the method.

The first parameter to the callback method (response) is an AsyncResponse object. The methods available on this object are as follows:

Method Description
int getStatus() The status code of the response from the call
Map<String, String> getHeaders() A map of the headers returned from the call
String getData() String value of the response body from the call
String getErrorData()
String getErrorJson()
String getErrorMessage()
GPathResult getErrorXml()
Object getJson()
GPathResult getXml()
boolean hasError()

Below is an example of code which would be used to send a POST to a server and the callback method to process the response:

def sendAsynchttpPost() {
    def postParams = [
		uri: "http://httpbin.org/post",
		requestContentType: 'application/json',
		contentType: 'application/json',
		headers: ['CustomHeader':'CustomHeaderValue'],
		body : ["name": "value"]
	]
    
	asynchttpPost('myCallbackMethod', postParams, [dataitem1: "datavalue1"])
}

def myCallbackMethod(response, data) {
    if(data["dataitem1"] == "datavalue1")
    	log.debug "data was passed successfully"
    log.debug "status of post call is: ${response.status}"
}
8 Likes

Could you provide an example of what's expected for params and data? For example, how do I handle providing specific header values and is data the equivalent of the old "body" attribute?

I've editied the post above to add more information, we will continue to document the methods as we go. Some of the methods above still require a description and I hope to get those filled out in the following days. For now I think there is enough to get you started.

1 Like

Hey @chuck.schwer, has everything been sorted with callback bug you mentioned above. I have a driver I want to update but would rather wait till the kinks have been worked out before starting.

ah, yes that was fixed in the latest release.

1 Like

@chuck.schwer , I changed the Lifx driver I was porting over to HE to synchronous http methods because async was not supported at the time. I am now attempting to use the async but am running into issues.

I am getting different data types returned when using the the async get. Please see code below and advise.

With sync method below, response.data returns the data type "class java.util.ArrayList" which can them be processed as a list

def sendLIFXInquiry() {

	def params = [
        uri: "https://api.lifx.com",
		path: "/v1/lights/" + getGroups(),
        requestContentType: "application/json",
        headers: ["Content-Type": "application/x-www-form-urlencoded", "Authorization": "Bearer ${token}"]
    ]
    
    try {
    httpGet(params) { response ->
        log.info response.getStatus()
        if(response.getStatus() == 200 || response.getStatus() == 207) {
			log("Response received from LFIX in the getReponseHandler.", "DEBUG")
        
        	log("Response ${response.getData()}", "DEBUG")
        
       		response.getData().each {
        		log("${it.label} is ${it.power}.", "TRACE")
        		log("Bulb Type: ${it.product.name}.", "TRACE")
        		log("Has variable color temperature = ${it.product.capabilities.has_variable_color_temp}.", "TRACE")

With the async method below the response is "class java.lang.String" , which can't be processed the same.....response.json has the same result.

def sendLIFXCommand(commands) {

	setLastCommand(commands)
    log.info commands
    def params = [
        uri: "https://api.lifx.com",
		path: "/v1/lights/" + getGroups() + "/state",
        requestContentType: "application/json",
        headers: ["Content-Type": "application/json", "Accept": "application/json", "Authorization": "Bearer ${token}"],
        body: commands
    ]
    log.info params
    asynchttpPut('putResponseHandler', params)
}

def getResponseHandler(response, data) {

    if(response.getStatus() == 200 || response.getStatus() == 207) {
		log("Response received from LFIX in the getReponseHandler.", "DEBUG")
        
        log("Response ${response.data}", "DEBUG")
        
       	response.data.each {
        	log("${it.label} is ${it.power}.", "TRACE")
        	log("Bulb Type: ${it.product.name}.", "TRACE")
        	log("Has variable color temperature = ${it.product.capabilities.has_variable_color_temp}.", "TRACE")

Ah, you found a bug with the AsyncResponse object. It should be returning a Map or List of the parsed json from data. It is not, I'll put in an issue for this and it should be fixed in the next release.

4 Likes

2 posts were split to a new topic: Need help converting to async http call

A post was split to a new topic: Getting error in async callback method

Hello, I'm looking for a way to retrieve data with unicode characters (ISO-8859-1). I've failed to do so, as asynchttpGet always seems to drop the extra unicode data: all unicode characters revert to U+FFFD, with either getData() or getXML(). And I've failed to find anything to add to the request header, to help with this. Any way to properly retrieve an http response with ISO-8859-1 characters in it? For reference, it works with java.net.URL; even if the charset is wrong, at least, unicode data is never lost - I can play with the encoding after fetching it.

@chuck.schwer is there a way to know how many http requests are in flight?
What happens if I run two, the older one gets killed? If not, do I have a way to kill it?

Chuck no longer works for Hubitat. @gopher.ny may be able to answer your question above.

No it won’t kill it. You can have multiple simultaneous calls. No you can’t kill an HTTP call it will timeout or complete. Why would you want to kill it? Once you send it you can’t prevent the server from processing it. What’s the use case? As far as how many in flight? Create a variable and increment it when you make a call and decrement it when it completes. Then you’d have a counter. Hubitat doesn’t provide a built in method. Again, kind of curious of what you’re trying to do here?

Thanks for your answer.
I'm calling a web service that holds on requests until they either time out or the server has new data. I do track with a state variable but also have a scheduler that restart the process in case of failures elsewhere. I just wanted to make sure that I'm not leaving lingering calls. It shouldn't be a big deal but I like clean environments.

Got it. A long polling scenario. So sounds like you’re goal is to prevent accidentally polling multiple times simultaneously? I’d suggest using a synchronized() lock on a variable to make sure you only ever trigger once.

1 Like