docs.intersystems.com
Home  /  Application Development: Additional Options  /  Creating REST Services  /  Creating REST Services


Creating REST Services
Creating REST Services
[Back]  [Next] 
InterSystems: The power behind what matters   
Search:  


This chapter describes how to build InterSystems IRIS™ REST services using the %CSP.REST class. This class enables you to create a REST service.
This chapter contains the following sections:
The %CSP.REST Class
The %CSP.REST class, which is a subclass of %CSP.Page allows you to implement REST services. It provides the ability to:
To implement a REST service, you extend the %CSP.REST class. Your extension of the class provides the URLMap, can optionally set the UseSession parameter, and provides class methods to perform the REST operations. You can define more than one subclass of %CSP.REST in a namespace. Each subclass that has its own entry point must have its own CSP web application. You define the CSP web application and specify its security in the Web Application page (click System Administration > Security > Applications > Web Applications). When you define the CSP web application, you set the Dispatch Class to the name of the custom subclass of %CSP.REST and specify the first part of the URL for the REST call as the name of the application.
Creating the URL Map for REST
The XDATA UrlMap associates the REST call with the method that implements the service. It can either directly send the call to a method based on the contents of the URL or it can forward the call to another subclass of %CSP.REST based on the URL. If the web application is handling a small number of related services, you can send the call directly to the method that implements it. However, if the web application is handling a large number of disparate services, you can define separate subclasses of %CSP.REST, each of which handles a set of related services. Then the subclass of %CSP.REST that handles the web application simply forwards the REST call to the appropriate subclass.
If the subclass of %CSP.REST is sending the call directly to the methods, the URLMap contains a Routes definition that contains a series of Route elements. Each Route specifies a class method to be called for the specified URL and HTTP operation. Typically REST uses the GET, POST, PUT, or DELETE operations, but you can specify any HTTP operation. The URL can optionally include parameters that are specified as part of the REST URL and passed to the specified method as parameters.
If the subclass of %CSP.REST is forwarding the calls to other subclasses of %CSP.REST, the UrlMap contains a Routes definition that contains a series of Map elements. The Map statement forwards all calls with the specified prefix to its associated %CSP.REST subclass, which will then implement the behavior. It can implement the behavior by sending the call directly to a method or by forwarding it to another subclass.
InterSystems IRIS compares the incoming REST URL with the Route URL property or the Map Prefix property. It starts at the first Route or Map element and continues to test each following element until it finds a match. Once it finds a match it either sends the incoming call to the call specified in the Route or forwards the URL to the class specified in the Map. In both cases, it ignores any elements in the Routes after the matching element; consequently, the order of the elements in the Routes is significant. If an incoming URL could match multiple elements of the Routes, InterSystems IRIS uses the first matching element and ignores any subsequent possible matches.
URLMap with Route Elements
InterSystems IRIS compares the incoming URL and the HTTP request method to each Route element in Routes. It calls the method specified in the first matching Route element. The Route element has three parts:
For example, the DocServer sample defines the following Route:
XData UrlMap
{
<Routes>
  <Route Url="/echo" Method="POST" Call="Echo" Cors="false" />
This specifies that the REST call will end with /echo and use the POST method. It will call the Echo class method in the REST.DocServer class that defines the REST service. The Cors property is optional, has a value of "true" or "false" and controls CORS header processing. See Configuring a REST Service to Use CORS for details on using CORS.
The complete REST URL consists of the following pieces:
For the preceding example, the complete REST call as shown by a TCP tracing utility is:
POST /csp/samples/docserver/echo HTTP/1.1
Host: localhost:52773
The following Route definition defines two parameters, namespace and class, in the URL:
<Route Url="/class/:namespace/:classname" Method="GET" Call="GetClass" />
A REST call URL starts with /csp/samples/docserver/class/ and the next two elements of the URL specify the two parameters. The GetClass method uses these parameters as the namespace and the class name that you are querying. For example, the REST call:
http://localhost:52773/csp/samples/docserver/class/samples/Cinema.Review
calls the GetClass method and specifies “samples” and “Cinema.Review” as the parameter values. The definition of the GetClass method starts with:
/// This method returns the class text for the named class
ClassMethod GetClass(
  pNamespace As %String,
  pClassname As %String) As %Status
{
You can define a different method for each HTTP request method for a single URL. For example, you could define the following:
<Route Url="/request" Method="GET" Call="GetRequest" />
<Route Url="/request" Method="POST" Call="PostRequest" />
With these routes, if the URL /csp/samples/docserver/request is called with an HTTP GET method, the GetRequest() method is invoked. If it is called with an HTTP POST method, the PostRequest() method is invoked. In the DocServer sample, both HTTP request methods are handled by a single Call method. The Route definitions in the sample is
<Route Url="/request" Method="GET" Call="Request" />
<Route Url="/request" Method="POST" Call="Request" />
In this case, the Request() method handles the call for either a GET or a POST operation. If this method needs to distinguish between a GET and a POST operation, it can do this by examining the CSP request object, which contains the text of the URL.
If you want to separate the code implementing the REST services from the %CSP.REST dispatch code, you can define the methods implementing the REST services in another class and specify the class and method in the Call element.
Regular Expressions in the Route Map
You can use regular expressions within the route map. InterSystems suggests that you do so only if there is no other way to define the REST service to meet your needs. This section provides the details. (For information on regular expressions in ObjectScript, see Regular Expressions in Using ObjectScript.)
Internally, the :parameter-name syntax for defining parameters in the URL is implemented using regular expressions. Each segment specified as :parameter-name is converted to a regular expression that contains a repeating matching group, specifically to the ([^/]+) regular expression. This syntax matches any string (of non-zero length), as long as that string does not include the / (slash) character. So the GetClass() sample, which is Url="/class/:namespace/:classname", is equivalent to:
<Route Url="/class/([^/]+)/([^/]+)" Method="GET" Call="GetClass" />
where there are two matching groups that specify two parameters.
In most cases this format provides enough flexibility to specify the REST URL, but advanced users can use the regular expression format directly in the route definition. The URL must match the regular expression, and each matching group, which is specified by a pair of parentheses, defines a parameter to be passed to the method.
For example, consider the following route map:
<Routes>
<Route Url="/Move/:direction" Method="GET" Call="Move" />
<Route Url="/Move2/(east|west|north|south)" Method="GET" Call="Move" />
</Routes>
For the first route, the parameter can have any value. No matter what value the parameter has, the Move() method is called. For the second route, the parameter must be one of east west north or south; if you call the second route with a parameter value other than those, the Move() method is not called, and REST service returns a 404 error because the resource cannot be found.
This simple example is meant only to demonstrate the difference between the usual parameter syntax and a regular expression. In the case discussed here, there is no need for a regular expression because the Move() method can (and should) check the value of the parameter and respond appropriately. In the following cases, however, a regular expression is helpful:
URLMap with Map Elements
InterSystems IRIS compares the incoming URL to the prefix in each Map element in Routes. It forwards the incoming REST call to the %CSP.REST subclass specified in the first matching Map element. That class processes the remainder of the URL, typically calling the method that implements the service. The Map element has two parts:
Consider the following URLMap that contains three Map elements.
XData UrlMap
{
  <Routes>
    <Map Prefix="/coffee/sales" Forward="MyLib.coffee.SalesREST"/>
    <Map Prefix="/coffee/repairs" Forward="MyLib.coffee.RepairsREST"/>
    <Map Prefix="/coffee" Forward="MyLib.coffee.MiscREST"/>
  </Routes>
}
This UrlMap forwards the REST call to one of three subclasses of %CSP.REST: MyLib.coffee.SalesREST, MyLib.coffee.RepairsREST, or MyLib.coffee.MiscREST.
The complete REST URL to call one of these REST services consists of the following pieces:
For example, the following REST call:
http://localhost:52773/coffeeRESTSvr/coffee/sales/reports/id/875
matches the first map with the Prefix /coffee/sales and forwards the REST call to the MyLib.coffee.SalesREST class. That class will look for a match for the remainder of the URL, "/reports/id/875".
As another example, the following REST call:
http://localhost:52773/coffeeRESTSvr/coffee/inventory/machinetype/drip
matches the third map with the Prefix /coffee and forwards the REST call to the MyLib.coffee.MiscREST class. That class will look for a match for the remainder of the URL, "/inventory/machinetype/drip".
Note:
In this URLMap example, if the Map with the Prefix="/coffee" was the first map, all REST calls with /coffee would be forwarded to the MyLib.coffee.MiscREST class even if they matched one of the following Map elements. The order of the Map elements in Routes is significant.
Specifying the Data Format
You can define your REST service to handle data in different formats, such as JSON, XML, text, or CSV. A REST call can specify the form that it expects data it is sending by specifying a ContentType element in the HTTP request and can request the return data format by specifying an Accept element in the HTTP request.
In the DocServer sample, the GetNamespaces() method checks if the REST call requested JSON data with the following:
If $Get(%request.CgiEnvs("HTTP_ACCEPT"))="application/json"
Localizing a REST Service
Any string value returned by a REST service can be localized, so that the server stores multiple versions of the strings in different languages. Then when the service receives an HTTP request that includes the HTTP Accept-Language header, the service responds with the appropriate version of the string.
To localize a REST service:
  1. Within your implementation code, rather than including a hardcoded literal string, use an instance of the $$$Text macro, providing values for the macro arguments as follows:
    For example, instead of this:
     set returnvalue="Hello world"
    Include this:
     set returnvalue=$$$TEXT("Hello world","sampledomain","en-us")
  2. If you omit the domain argument to $$$Text macro, also include the DOMAIN class parameter within the REST service class. For example:
    Parameter DOMAIN = "sampledomain"
  3. Compile the code. When you do so, the compiler generates entries in the message dictionary for each unique instance of the $$$Text macro.
    The message dictionary is a global and so can be easily viewed (for example) in the Management Portal. There are class methods to help with common tasks.
  4. When development is complete, export the message dictionary for that domain or for all domains.
    The result is one or more XML message files that contain the text strings in the original language.
  5. Send these files to translators, requesting translated versions.
  6. When you receive the translated XML message files, import them into the same namespace from which the original was exported.
    Translated and original texts coexist in the message dictionary.
  7. At runtime, the REST service chooses which text to return, based on the HTTP Accept-Language header.
For more information, see the article String Localization and Message Dictionaries.
Using a Web Session with REST
The UseSession parameter controls whether InterSystems IRIS uses a new web session for each REST service call or preserves a web session across multiple REST service calls. One of the goals of REST is to be stateless, that is no knowledge is stored on the server from one REST call to the next. Having a web session preserved across REST calls breaks the stateless paradigm, but there are two reasons why you might want to preserve a web session:
To enable using a single web session over multiple REST calls, set the UseSession parameter to 1 in your subclass of %CSP.REST that is defined as the dispatch class for a CSP web application. For example:
Class REST.MyServices Extends %CSP.REST { 
Parameter UseSession As Integer = 1
Authentication in REST
If your REST service is accessing confidential data, you should use authentication. You can use any of the two following forms of authentication:
REST Applications and OAuth 2.0
You can authenticate a REST application via OAuth 2.0. To do so, you can create and use ZAUTHENTICATE routine. InterSystems provides a sample routine, REST.ZAUTHENTICATE.mac, that you can copy and modify. This routine is part of the Samples-Security sample on GitHub (https://github.com/intersystems/Samples-Security). You can download the entire sample as described in Downloading Samples for Use with InterSystems IRIS,” but it may be more convenient to simply open the routine on GitHub and copy its contents.
To use this sample:
  1. Configure the resource server containing the REST application as an OAuth 2.0 resource server.
  2. Load the sample routine REST.ZAUTHENTICATE.rtn into the %SYS namespace as a routine named ZAUTHENTICATE
  3. Modify value of applicationName in the new routine and make other changes as needed.
  4. Allow delegated authentication for %Service.CSP.
  5. Make sure that the web application (for the REST application) is configured to use delegated authentication.
Also see Optionally Defining Delegated Authentication for the Web Client in the chapter Using an InterSystems IRIS Web Application as an OAuth 2.0 Client in Using OAuth 2.0 and OpenID Connect.
Supporting CORS in REST Services
Cross-origin Resource Sharing (CORS) allows a script running in another domain to access an InterSystems IRIS REST service. Typically, when a browser is running a script from one domain, it allows XMLHttpRequest calls to that same domain but disallows them when they are made to another domain. This browser behavior restricts someone from creating a malicious script that can misuse confidential data. The malicious script could allow the user to access information in another domain using permissions granted to the user, but then, unknown to the user, make other use of confidential information. To avoid this security problem, browsers generally do not allow this kind of cross-domain call.
Without using CORS, a web page with a script accessing REST services typically must be in the same domain as the server providing the REST services. In some environments, it is useful to have the web pages with scripts in a different domain than the servers providing the REST services. CORS enables this arrangement.
The following provides a simplified description of how a browser can handle an XMLHttpRequest with CORS:
  1. A script in a web page in domain DomOne contains an XMLHttpRequest to an InterSystems IRIS REST service that is in domain DomTwo. The XMLHttpRequest has a custom header for CORS.
  2. A user views this web page and runs the script. The user’s browser detects the XMLHttpRequest to a domain different from the one containing the web page.
  3. The user’s browser sends a special request to the InterSystems IRIS REST service that indicates the HTTP request method of the XMLHttpRequest and the domain of the originating web page, which is DomOne in this example.
  4. If the request is allowed, the response contains the requested information. Otherwise, the response consists only of headers indicating that CORS did not allow the request.
InterSystems IRIS supports CORS by passing the HTTP headers and allows you to configure whether a REST service allows the CORS header. You must write code that defines when to allow a CORS request. For example, you can provide a white-list containing domains that contain only trusted scripts. InterSystems IRIS does provides a simple default implementation for documentation purposes but it allows any CORS request. You should not enable CORS processing for confidential data using this default implementation.
To write the code that controls CORS requests, you override the OnHandleCorsRequest() method in your %CSP.REST subclass.
This chapter contains the following sections:
Configuring a REST Service to Use CORS
You control whether a REST service supports CORS with the %CSP.REST HandleCorsRequest parameter and with the Cors attribute on the route element in the UrlMap.
To enable or disable CORS header processing for all REST services defined in the %CSP.REST subclass, set the HandleCorsRequest parameter to "true" to enable CORS processing or to "false", which is the default, to disable CORS processing.
To enable or disable CORS header processing independently for each Route in the UrlMap, set the HandleCorsRequest parameter to "" (empty string) and specify Cors="true" in a Route element to enable CORS processing and Cors="false" in a Route element to disable CORS processing.
If a %CSP.REST subclass forwards a REST request to a second %CSP.REST subclass that contains a Route matching that REST URL, the HandleCorsRequest parameter in the %CSP.REST subclass containing the matching Route controls the behavior of the CORS processing.
If CORS processing is disabled for an incoming REST URL with CORS headers, %CSP.REST rejects the incoming request.
Important:
An IRIS REST service supports the OPTIONS request (the CORS preflight request), which is used to determine whether a REST service supports CORS. This request is executed by the CSPSystem user. This user should have READ permission on any databases used by the REST service; if not, the service will respond with an HTTP 404 error.
Overriding the OnHandleCorsRequest Method
The default implementation of the OnHandleCorsRequest() method does not do any filtering and simply passes the CORS header to the external server and returns the response. You may want to restrict access to origins that are listed in a domain white list or to restrict what request methods are allowed. You do this by overriding the OnHandleCorsRequest() method in your %CSP.REST subclass.
All URL requests that match Route elements in the UrlMap , are processed with the single OnHandleCorsRequest() method that is defined in the class. If you need to have different implementations of the OnHandleCorsRequest() method for different REST URL requests, you should use Forward to send the requests to different subclasses of %CSP.REST.
To implement the OnHandleCorsRequest() method, you must be familiar with the details of the CORS protocol. This section identifies the parts of the default OnHandleCorsRequest() method implementation and identifies the lines that handle the origin, credentials, header, and request method.
The following code from the %CSP.REST.HandleDefaultCorsRequest() method gets the origin and use it to set the response header. One possible way to handle this is to test the origin against a white list and only use it to set the response header if the domain is allowed. If it is not allowed, you can set the response header to an empty string.
    #; Get the origin
     Set tOrigin=$Get(%request.CgiEnvs("HTTP_ORIGIN"))

     #; Allow requested origin
         Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Origin",tOrigin) 
The following lines specify that the authorization header should be included.
    #; Set allow credentials to be true
    Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Credentials","true")
The following lines get the headers and the request method from the incoming request. Add code to test if these headers and request method are allowed. If they are allowed, use them to set the response headers.
    #; Allow requested headers
    Set tHeaders=$Get(%request.CgiEnvs("HTTP_ACCESS_CONTROL_REQUEST_HEADERS"))
    Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Headers",tHeaders)

    #; Allow requested method
    Set tMethod=$Get(%request.CgiEnvs("HTTP_ACCESS_CONTROL_REQUEST_METHOD"))
    Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Method",tMethod)
Note:
The %CSP.REST.HandleDefaultCorsRequest() method provides a simple default implementation for documentation purposes only. You should not enable CORS processing for confidential data using this default implementation.