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
.png)
2) Configure your REST API using ZPM configuration. To do this follow this sample (watch the tag <CSPApplication>):
ZPM Code to Create REST API
<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
Very clear and objective. Thank you!
Thank you @Yuri Marx - great reference!