Failure of render() in http endpoint callback

I have no idea what the problem is, since the call in question was copied from the Example OAuth app, and in that application the call to render() in the callback routine works, and produces the correct output in a browser.

Apologies for the slight wall of code, I've trimmed this down to the minimum necessary to reproduce the issue, but it's still 120 LOC.

/*
 * Thermostat controller
 *
 * This switches the selected thermostat between wake and sleep modes based on the selected times of day
 * In addition it exposes an endpoint that allows switching to a specific mode: home or away, with
 * an optional delay in hours before making the change
 */

import java.time.*
import java.text.*
import java.util.*

definition(
    name: "Thermostat Test",
    namespace: "dgnuff",
    author: "David Goodenough",
    description: "Thermostat controller",
    category: "Utility",
    iconUrl: "",
    iconX2Url: ""
)

preferences
{
    section("Endpoint:")
    {
        paragraph("<a href=\"${getUrl('setpresence')}\">Local LAN link to set presence: [home|away]</a>")
    }
}

mappings
{
    path("/setpresence/:presence") { action: [ GET: "presenceEndpoint"] }
}

def installed()
{
    log.debug("Installed with settings: ${settings}")
    initialize()
}

def updated()
{
    log.debug("Updated with settings: ${settings}")
    unsubscribe()
    initialize()
}

def initialize()
{
    initEndpoint()
}

// Called in response to a hit on the endpoint.  presence should be "home" or "away" with an optional delay given in hours as "home;8" using ';' as the delimiter
def presenceEndpoint()
{
    def elements = (params.presence + ";0").split(";")
    def presence = elements[0] ?: "unknown"
    def delayStr = elements[1] ?: "0"
    def delay = delayStr.isInteger() ? delayStr.toInteger() : 0
    if (delay < 0)
    {
        delay = 0
    }
    else if (delay > 48)
    {
        delay = 48
    }

    def message = "Unknown presence: accepted values are 'home' and 'away'"
    log.debug("presenceEndpoint() called, presence is ${presence}, delay is ${delay}")
    if (presence == "home")
    {
        state.pendingPresence = PRESENCE_HOME
        Date now = new Date()
        state.pendingTick = now.getTime() + delay * 3600000
        message = "Presence set to HOME in " + delay + " hour" + (delay == 1 ? "" : "s")
    }
    else if (presence == "away")
    {
        state.pendingPresence = PRESENCE_AWAY
        Date now = new Date()
        state.pendingTick = now.getTime() + delay * 3600000
        message = "Presence set to AWAY in " + delay + " hour" + (delay == 1 ? "" : "s")
    }
    
    log.debug("presenceEndpoint() ${message}")
    def payload = "<html><head><title>Thermostat mode</title></head><body><p>" + message + "</p></body></html>"
    
    def result = render(contentType: "text/html", data: payload, status: 200)
    log.debug("result: ${result}")
}

private getUrl(path)
{
	def url = ("${getFullLocalApiServerUrl()}/$path/presence?access_token=${state.accessToken}")
	return url
}

private initEndpoint()
{
    if (!state.accessToken)
    {
        log.debug("Creating access token")
		try
        {
			def accessToken = createAccessToken()
			if (accessToken)
            {
                state.accessToken = accessToken
                log.debug("Access token created: $accessToken")
			}
		}
		catch(e)
        {
            log.debug("Error creating access token $e")
            log.debug("Did you forget to enable OAuth for this app?")
		}
	}
	return state.accessToken
}

The bottom line is that the call to render() on line 90 doesn't return anything to the client. I've tried it with a browser, I've use curl.exe to run it, and even taken wireshark to the problem: the response is chunked data consisting of a single empty chunk. The log.debug calls in presenceEndpoint() produce output in the Logs window, so I know it's getting that far. And the return value from render() seems to be a map containing the parameters with one extra value: renderMethiod: true. No indication of any errors there.

I believe that the render must be the return value of the function, so after logging it, add a line:

return result
4 Likes

That was indeed the problem. The reason the sample I copied worked is because groovy returns the last value evaluated in a function, and in the sample code the render() was the last thing done, so the return was implicit. In the "non-trimmed" version of the app, there's even more going on after the render call, so of course the return value was lost.