Written by

Software Architect at Visum
Article Yuri Marx · Dec 3, 2021 7m read

ObjectScript REST API Cookbook

Creating REST API using InterSystems ObjectScript is very easy, but some recipes can help you into this process:

1) To create your REST API extends %CSP.REST and Go to System Administration > Security > Applications > Web Applications > Click the button Create New Web Application and set the Name, REST Dispatch Class with your package and classname and choose the Allowed Authetication Methods. See this example:

 

Edit Web Application

2) Configure your REST API using ZPM configuration. To do this follow this sample (watch the tag <CSPApplication>):

 

ZPM Code to Create REST API

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="iris-api-security-mediator.ZPM">
    <Module>
      <Name>iris-api-security-mediator</Name>
      <Version>1.2.0</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="dc.Sample.PKG"/>
      <Resource Name="dc.SecurityMediator.PKG"/>
      <Dependencies>
        <ModuleReference>
          <Name>swagger-ui</Name>
          <Version>1.*.*</Version>
        </ModuleReference>
      </Dependencies>
       <CSPApplication
        Url="/crud"
        DispatchClass="dc.Sample.PersonREST"
        MatchRoles=":{$dbrole}"
        PasswordAuthEnabled="1"
        UnauthenticatedEnabled="0"
        Recurse="1"
        UseCookies="2"
        CookiePath="/crud"
       />

    </Module>
    
  </Document>
</Export>

3) To Enable CORS (call the API from a different server or domain out the API domain), set the Parameter HandleCorsRequest = 1

4) To set the Content Type use: Parameter CONTENTTYPE = "application/json"

5) To set the Charset use: Parameter CHARSET = "utf-8"

6) To intercept the requests to the API and do something before the request processing, override the ClassMethod OnPreDispatch and do validations, enrich header and content of the request, for example. See a sample (watch pContinue, is possible interrupt the processing if you set pContinue = 0):

 

OnPreDispatch sample

ClassMethod OnPreDispatch(pUrl As %String, pMethod As %String, ByRef pContinue As %Boolean) As %Status
{
  SET tSC = $$$OK
  TRY {
    
    // Set the return type according to the Accept type in the request. Default is application/json.
    IF ('..AcceptsContentType(..#CONTENTTYPEJSON)) {
      SET tSC = ..ReportHttpStatusCode(..#HTTP406NOTACCEPTABLE), pContinue=0
      QUIT
        } ELSE {   
      // This always returns json
      SET %response.ContentType=..#CONTENTTYPEJSON
        }
        
        
        // read request object into %DynamicObject format
    IF ((pMethod'="POST") && (pMethod'="PUT")) || (%request.Content="") {
      SET %request.Content = {}
    } ELSE {
      IF '$isobject(%request.Content) {
        SET tContent = %request.Content
      } ELSE {
        SET tContent = ""
        WHILE '%request.Content.AtEnd {
          SET tContent = tContent_%request.Content.Read()
        }
      }
      IF (tContent="") {
        SET %request.Content = {}
      } ELSE {
        SET tContent = $zconvert(tContent, "I", "UTF8")
        SET %request.Content = ##class(%Library.DynamicObject).%FromJSON(tContent)
      }
    }
        
  } CATCH ex {
    SET tSC = ex.AsStatus()
  }
  QUIT ##class(%iKnow.REST.Base).%ErrorHandler(tSC, .pContinue)
}

7) To change Access Management to the API override the Class Method AccessCheck, and set pAuthorized = 0 to deny access and 1 to grant access. See the sample:
 

 

Deny or grant access based into rules configured into XData declaration

 
ClassMethod AccessCheck(Output pAuthorized As %Boolean = 0) As %Status
{
 
  Do ##super()
 
  Set message = {}
 
  Set tSC = $$$OK
 
  Set message.verb = %request.Method

  Set message.url = %request.URL

  Set message.url = "/"_$REPLACE(message.url, %request.Application, "")

  Set message.application = %request.Application
 
  Set methodName = ""
  Do ..GetClassMethodName(message.url, %request.Method, .methodName)
 
  Set message.method = methodName

  Do ##class(dc.SecurityMediator.XDataUtil).GetXDataContent($CLASSNAME(), methodName, .xdata)

  Do ..GetSecurityRules(xdata, .rules, .roles, .header, .operator)

  Set UserRoles = $LISTFROMSTRING($ROLES,",")
  Set RolesAllowed = UserRoles
 
  Set HasRole = 0
  Set HasHeader = 0

  If $FIND(xdata, "@security") > 0 && $FIND(xdata, "roles:") {
    Set RolesAllowed = $LISTFROMSTRING(roles,",")

    For RoleIdx=1:1:$LISTLENGTH(UserRoles) {
      If $LISTFIND(RolesAllowed, $LIST(UserRoles, RoleIdx)) {
        Set HasRole = 1
        Quit
      }
    }
  } Else {
    Set HasRole = 1
  }

  If $FIND(xdata, "@security") > 0 && $FIND(xdata, "header:") {
    Set HeaderKey = $PIECE(header,"=")
    Set HeaderKey = $ZSTRIP(HeaderKey, "<>W")
    Set HeaderValue = $PIECE(header,"=",2)
    Set HeaderValue = $ZSTRIP(HeaderValue, "<>W")

    If $Get(%request.CgiEnvs(HeaderKey)) = HeaderValue {
      Set HasHeader = 1
    } Else {
      Set HasHeader = 0
    }
  } Else {
    Set HasHeader = 1
  }

  If HasRole {
    Set pAuthorized = 1
    Do $SYSTEM.Security.Audit("SecurityMediator","Authorization", "SecurityMediator",message.%ToJSON(),"User authorized")
  } Else {
    Set pAuthorized = 0
    Set message.error = $USERNAME_" is not authorized for this request. User Roles Allowed is not in User Roles"
    Do $SYSTEM.Security.Audit("SecurityMediator","Authorization", "SecurityMediator",message.%ToJSON(),"User not authorized")
    Write message.%ToJSON()
  }

  If HasHeader {
    Set pAuthorized = 1
  } Else {
    Set pAuthorized = 0
    Set message.error = header_" is required in the request header"
    Write message.%ToJSON()
  }

  Return tSC
}

8) To set HTTP Code, override the Class Method ReportHttpStatusCode. See the sample:

 

Spoiler

ClassMethod ReportHttpStatusCode(pHttpStatus, pSC As %Status = {$$$OK}) As %Status
{
  Set %response.Status=pHttpStatus
 
  If $$$ISERR(pSC) Do ..outputStatus(pSC)
  /*
  If (+pHttpStatus>=400) {
    Set %response.ContentType = "application/json"
    SET pResult = {
      "error": ($PIECE(pHttpStatus, " ", 2, *))
    }
    Return ..%ProcessResult($$$OK, pResult)
  }*/
      
  Return $$$OK
}

9) To generate the swagger documentation to your REST API, write comments above the your Class Methods and above your REST mappings and implement the Method SwaggerSpec. See:

 

Swagger Documentation generation

ClassMethod SwaggerSpec() As %Status
{
  Do ##class(%REST.API).GetWebRESTApplication($NAMESPACE, %request.Application, .swagger)
  Do swagger.info.%Remove("x-ISC_Namespace")
  Set swagger.basePath = "/crud"
  Set swagger.info.title = "InterSystems IRIS REST CRUD demo"
  Set swagger.info.version = "0.1"
  Set swagger.host = "localhost:52773"
  Return ..%ProcessResult($$$OK, swagger)
}

10) To mapping your endpoint to your REST API follow the Intersystems document https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_csprest#GREST_urlmap_map and see this sample:
 

 

REST API Mapping Sample

 
Class dc.Sample.PersonREST Extends Sample.REST.Base
{

Parameter Version = "1.0.6";

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<!-- Server Info -->
<Route Url="/" Method="GET" Call="GetInfo" Cors="true"/>
<!-- Get all records of Person class -->
<Route Url="/persons/all" Method="GET" Call="GetAllPersons"/>
<!-- Get all records of Person class -->
<Route Url="/persons/populate" Method="GET" Call="Populate"/>
<!-- Swagger specs -->
<Route Url="/_spec" Method="GET" Call="SwaggerSpec" />
<!-- GET method to return JSON for a given person id-->
<Route Url="/persons/:id" Method="GET" Call="GetPerson"/>
<!-- Update a person with id-->
<Route Url="/persons/:id" Method="PUT" Call="UpdatePerson"/>
<!-- Delete a person with id-->
<Route Url="/persons/:id" Method="DELETE" Call="DeletePerson"/>
<!-- Create a person-->
<Route Url="/persons/" Method="POST" Call="CreatePerson"/>

</Routes>
}

 

 

11) To get query parameters use: %request.CgiEnvs(HeaderKey), where header key is the name of the query string.

To see these tips in action, download and use my app, Security Mediator in: https://openexchange.intersystems.com/package/API-Security-Mediator

Comments

Marcelo Witt · Feb 21, 2022

Very clear and objective. Thank you!

0