Calling production from a REST Broker
Productions often need to receive REST requests.
Here's how to do that.
1. Create proxy Service:
/// Empty BS we would use to send Produciton requests
Class production.ProxyService Extends Ens.BusinessService
{
}
2. Add it to production as RESTService (or any other name).
3. Write your rest broker (docs, more docs)
4. In your handler method create the message and call target BP/BO like this
Include Ensemble
Class test.REST Extends %CSP.REST
{
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/launchProcess/:processName" Method="POST" Call="launchProcess"/>
</Routes>
}
ClassMethod launchProcess(processName As %String) As %Status
{
#dim sc As %Status = $$$OK
// BS from which we would call BP
#dim serviceName As %String = "RESTService"
#dim message = ##class(Ens.StringContainer).%New(%request.Data.Read($$$MaxStringLength))
set sc = ..invokeHostAsync(processName, message, serviceName)
quit sc
}
/// Call production host in async mode
/// Thirs arg is BS caller, testing service by default - testing must be enabled in that case
ClassMethod invokeHostAsync(targetHostName As %String, message As %Persistent, serviceName As %String = "EnsLib.Testing.Service", sessionId As %Integer = "") As %Status
{
#dim sc As %Status
#dim prod As Ens.Config.Production = ..getCurrentProduction()
if '$isObject(prod) quit $$$ERROR($$$GeneralError, "Produciton is not running")
#dim item As Ens.Config.Item = ..findConfigItem(targetHostName)
if '$isObject(item) quit $$$ERROR($$$GeneralError, "Host " _ targetHostName _ " not found")
#dim service As Ens.BusinessService
set sc = ##class(Ens.Director).CreateBusinessService(serviceName, .service)
if $$$ISERR(sc) quit sc
if (sessionId '= "") set service.%SessionId = sessionId
set sc = service.SendRequestAsync(targetHostName, message)
quit sc
}
/// Get current production
ClassMethod getCurrentProduction() As Ens.Config.Production
{
#dim sc As %Status
#dim prodName As %String
#dim prodState As %Integer
// Get produciton name
if '##class(Ens.Director).IsProductionRunning(.prodName) quit ""
// Open produciton by name
#dim prod As Ens.Config.Production = ##class(Ens.Config.Production).%OpenId(prodName, , .sc)
if $$$ISERR(sc)
{
$$$LOGERROR($System.Status.GetErrorText(sc))
quit ""
}
quit prod
}
/// Find host by name
ClassMethod findConfigItem(name As %String, businessType As %String = "", enabledOnly As %Boolean = 0) As Ens.Config.Item
{
#dim sc As %Status
// Get current production
#dim prod As Ens.Config.Production = ..getCurrentProduction()
if '$isObject(prod) quit ""
// Search for our target
#dim item As Ens.Config.Item
#dim result As Ens.Config.Item = ""
for i = prod.Items.Count():-1:1
{
set item = prod.Items.GetAt(i)
if '$isObject(item) continue
if ((businessType '= "") && (item.BusinessType() '= businessType)) || (item.Name '= name) || (enabledOnly && 'item.Enabled) continue
set result = item
quit
}
}
}
And now this call:
POST host:port/webapp/launchProcess/BPName
{ "prop":"val"}
Would send a Ens.StringContainer message to the BPName.
Additionally REST broker can convert json to a persistent object and send that (it would be preferable).
Firstly, thank you for sharing this article. Great information !
I am trying to work out best way of Error Handling within a REST Business Operation and Business Process. My Business Operation makes a call to the third party API to do certain things. I need to return the response I get back from the third party to the clinet. Possible repsonses are,
- 200 in which case I return requested data e.g. Patient demographics
- 404 Patient not found
- 500 - Internal Server Error
This is what my Business Operation currently looks like,
Class rest.admin.bo.GetPatient Extends EnsLib.REST.Operation
{ Parameter INVOCATION = "Queue"; /// remove URL from SETTINGS
Parameter SETTINGS = "-URL";
Method GetPatient(pRequest As rest.admin.msg.rq.Patient, Output pResponse As rest.admin.msg.rp.Patient) As %Status
{
set tSC = $$$OK
try {
set HTTPrequest=##class(%Net.HttpRequest).%New()
set HTTPrequest.Https=1
set HTTPrequest.SSLConfiguration=..Adapter.SSLConfig
do HTTPrequest.SetHeader("Accept","application/json")
do HTTPrequest.SetHeader("Authorization","Bearer "_pRequest.AuthToken)
// Set URL PArameters
do HTTPrequest.InsertParam("urn",pRequest.urn)
// Issue the call
set tSC=..Adapter.SendFormData(.tHttpResponse, "GET", HTTPrequest)
// Instantiate the interoperability response object
set pResponse = ##class(rest.admin.msg.rp.Patient).%New()
set pResponse.HttpStatusCode = tHttpResponse.StatusCode
set pResponse.HttpStatusText = $$$StatusDisplayString(tSC)
// Handle an error condition
If $$$ISERR(tSC) {
if $IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&(tHttpResponse.Data.Size) {
Set tSC=$$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
}
} else {
// Status=200.
If $IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&tHttpResponse.Data.Size {
// use JSON Import capabilities to correlate JSON stream to properties.
do pResponse.%JSONImport(tHttpResponse.Data) // Set values additional properties here if needed
}
}
} catch{
Set tSC=$$$SystemError
} Quit tSC
} XData MessageMap
{
<MapItems>
<MapItem MessageType="rest.admin.msg.rp.Patient">
<Method>GetPatient</Method>
</MapItem>
</MapItems>
} }
Now, when the BO gets any sort of error, the operation shows an error and so does the process and same error goes back to the clinet e.g. in event of 503 client sees,
{
"errors": [
{
"code": "<Ens>ErrGeneral",
"domain": "Ens",
"error": "ERROR <Ens>ErrGeneral: ERROR <Ens>ErrHTTPStatus: Received non-OK status 503 from remote HTTP server: 'HTTP/1.1 503 Service Unavailable':<html><body><b>Http/1.1 Service Unavailable</b></body> </html>",
"id": "EnsErrGeneral",
"params": [
"ERROR <Ens>ErrHTTPStatus: Received non-OK status 503 from remote HTTP server: 'HTTP/1.1 503 Service Unavailable':<html><body><b>Http/1.1 Service Unavailable</b></body> </html>"
]
}
],
"summary": "ERROR <Ens>ErrGeneral: ERROR <Ens>ErrHTTPStatus: Received non-OK status 503 from remote HTTP server: 'HTTP/1.1 503 Service Unavailable':<html><body><b>Http/1.1 Service Unavailable</b></body> </html>"
}
I need to catch this excpetion and modify the json response that goes back to the client. But I am not sure what is the best way to do that. Is it something that is best done in BO or BP ?
Any help or guidance on this will be much appreciated!
Utsavi
The best place to do that is your REST broker.
After you got your error from BP, just create your own error and pass that.
Thank you for the tip! Its working well.
I have posted a question for advise on best practices when developing REST APIs and Production using IRIS.
https://community.intersystems.com/post/best-practices-implementing-rest...
Would you mind providing your thoughts please? thanks