Question
max simpson · Mar 21, 2021

How to cache REST Responses in IRIS?

Hi All,
I am implementing REST API's in IRIS using the below link as a guide. 
I chose this approach so i could have a single entry point for requests regardless of namespace

https://community.intersystems.com/post/restful-exception-handling 

a sample form the HIHLib.REST.Server class i am using is below. my question is does IRIS and CSP cache responses for requests to the CSP.REST class?
and if so what parameters or properties do i need to enable?

i notice the %CSP.Page class that %CSP.REST inherits from has an EXPIRES parameter that can be set, and %CSP.Response has a VaryByParam and Expires setting.
so from what i understand if i set the response VaryByParam setting to "*" and Expires to 300 then the same REST requests with the same parameters would return the same cached results for the next 5 minutes.

the problem with this approach would be that i need some way for the data being changed in the IRIS database to reset the response expiry. 

i.e. 
1. 15:00 calling application runs a GET request for patient?MRN=A
2. 15:00 CSP Web application calls HIHLib.REST.Server class and forwards request on to namespace for handling (via business services,process and operations)
3. 15:005 response is returned with EXPIRES 300 and VaryByParam set to *
4. 15:10 a seperate internall process results in patient with MRN A - first name changed from pete to bob
5. 15:15 calling application runs a GET request for patient?MRN=A
6. 15:15 web application checks response expiry and returns cached response from step 3 with first name pete

any advice on how you have implemented or reference to articles would be appreciated. at this point the implementation is new and load is light, but i'm concious of trying to make the response process as efficient as possible and reduce unnecessary load

One thought i've had is adding a staging table that the  HIHLib.REST.Server class could refer to with response params, expiry and response. I could use the OnPreDispatch to check if there's an entry in the table and current time is less than expiryDate it could just return the cached response. which would mean the process at step 4 could update the staging table to clear the row and effectively clear the cache for that resource. effectively it's just moving the cache logic from CSP web gateway (which is where it appears the cache logic is - i could be wrong) to my HIHLib.REST.Server class, but at least that way it saves unnecessary load from business processes and operations. 

/// inspiration for this class from this post https://community.intersystems.com/post/restful-exception-handling
Class HIHLib.REST.Server Extends %CSP.REST
{ Parameter HandleCorsRequest = 1; Parameter CONTENTTYPE = "application/json"; XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/:nameSpace/:service/:serviceVersion/:methodName" Method="GET" Call="InvokeEnsembleService"/>
<Route Url="/:nameSpace/:service/:serviceVersion/:methodName" Method="PUT" Call="InvokeEnsembleService"/>
<Route Url="/:nameSpace/:service/:serviceVersion/:methodName" Method="POST" Call="InvokeEnsembleService"/>
</Routes>
} ClassMethod InvokeEnsembleService(nameSpace As %String, service As %String, serviceVersion As %String, methodName As %String) As %Status
{
#dim status As %Status = $$$OK // act as an Oauth resource server and validate the JWT token in the API call https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GOAUTH_resource
set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
    if $$$ISOK(sc) {
    
    set isValidAccessToken=##class(%SYS.OAuth2.Validation).ValidateJWT("HIH",accessToken)
    if isValidAccessToken write !, "isValidAccessToken Huzzah!",! }
    
    //Azure AD doesnt currently support token introspection
    else {
        write "Error Getting Access Token="_$system.Status.GetErrorText(sc),!
    } try { set status = ..GetParametersFromCSPRequestAsListOfObjects(%request,.parameterList)
#dim serviceName As %String = nameSpace _ "." _ "REST" _ "." _ service _ "." _ "v" _ serviceVersion _ "." _ "Service"
set originalNameSpace = nameSpace
set $NAMESPACE = nameSpace
set status = ##class(Ens.Director).CreateBusinessService(serviceName, .restService)
throw:$$$ISERR(status) ##class(HIHLib.REST.Exceptions.NotARegisteredService).%New()
set restRequest = ##class(HIHLib.Messages.REST.Request).%New()
set restRequest.Parameters = parameterList do ..PopulateRequestHeader(%request,methodName,.restRequest) #dim jsonPayload As %String = ""
#dim jsonFormatter As %JSON.Formatter = ##class(%JSON.Formatter).%New()
set status = jsonFormatter.FormatToString(%request.Content,.jsonPayload)
set restRequest.JSONpayload = jsonPayload set status = restService.ProcessRESTRequest(restRequest,.response) If $$$ISERR(response.Status) 
do ##class(%REST.Impl).%ReportRESTError(response.HttpStatusCode,response.Status,1)
return status
} if (response.JSONpayload '= "") {
    //format the response, it may have already been formatted but format it again just in case
    set status = jsonFormatter.FormatToString(response.JSONpayload,.jsonResponse)
write !, jsonResponse
} set $NAMESPACE = originalNameSpace // need to be able to return an error status without any error text i.e. an empty 404 response.
//but cant get it to work
#; Set %response.Status=response.HttpStatusCode
#; write !, "response.HttpStatusCode:" _ response.HttpStatusCode //return ..ReportHttpStatusCode(response.HttpStatusCode)
}
catch exception {
    set httpErrorCode = $case(exception.%ClassName(1),
               ##class(HIHLib.REST.Exceptions.NotARegisteredService).%ClassName(1):503,
                                  :500)
                                  
return ..ReportHttpStatusCode(httpErrorCode,exception.AsStatus())
} return status
}
Product version: IRIS 2019.1
00
1 0 2 140
Log in or sign up to continue

Replies

It's not a server who checks for expiry, but a client. So your flow works like this:

1. 15:00 calling application runs a GET request for patient?MRN=A
2. 15:00 CSP Web application calls HIHLib.REST.Server class and forwards request on to namespace for handling (via business services,process and operations)
3. 15:00:05 response is returned with EXPIRES 300 and VaryByParam set to *
4. 15:10 a seperate internall process results in patient with MRN A - first name changed from pete to bob
5. 15:12 calling application runs a GET request for patient?MRN=A
6. 15:12 web browser (on a client side) sees that patient?MRN=A request is cached locally and still valid due to EXPIRES 300. Web browser is returning a cached value to a calling application without going to the server.

And server would never get a second request (that's the point of the EXPIRING header after all).

Expires controls how long the response is valid. It's for things like HTML pages which are static.

If you think that a response can change every time a client requests it, you should not add an EXPIRING header.

Thanks @Eduard Lebedyuk I didn't realise those settings were client side.  Then to cache responses I'm going to need add a staging table as described toward the bottom of my answer