Article
Eduard Lebedyuk · Aug 12, 2020 3m read

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).

30
1 1 4 250
Log in or sign up to continue

Replies

Hi Eduard,

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.