22 Implementing Custom APIs

As a service developer, you use the custom code service to implement the custom APIs that your team creates for its mobile apps.

What Can I Do with Custom Code?

After you design your custom API in the API designer (or create it by uploading a RAML file), use JavaScript, the Node library, and the custom code SDK to implement it. Say, for example, that your mobile app developer has designed the following API, which has one resource (/incidents), and two endpoints (GET /incidents, and POST /incidents).

#%RAML 0.8
title: IncidentReport
version: 2.0
baseUri: /mobile/custom/incidentreport
...
/incidents:
  displayName: Incident Reports
  get:
    description: |
      Retrieves all incident reports.
...
  post:
    description: |
      Creates a new incident report.       

You, as the service developer, implement all the endpoints in the API. That is, you write code to return incident reports for the first endpoint and to store incident reports for the second endpoint.

Your custom API implementation can call platform APIs (such as Storage and Notifications), other custom APIs, and external REST and SOAP web services. And it can access the external web services either directly or through connectors.

How Does Custom Code Work?

Using the custom code service, you write JavaScript code to implement a custom API. The coding model is based on Node, which is a JavaScript framework that enables you to write server-side code and that provides a runtime environment for it. For each API endpoint, which is the resource (URI) plus the HTTP method such as GET or POST, you need a route definition that specifies how to respond to a client request to that endpoint. In other words, for each URI and HTTP method combination in your API, you need to add a JavaScript method to your custom code that handles the request. Route definitions follow the coding style promoted by Express, which is a module that runs in Node. We’ll show you how to write these methods.

After you’ve written your custom code, you package it as a Node module, and then upload it.

Note that the purpose of the examples in this chapter is to illustrate how to interface with the custom code service. The examples are not intended to teach best practices for writing Node.js REST API implementations.

Foundation of the Custom Code Service

The custom code SDK is available to custom API implementations and is what you use to call platform APIs, connectors, and other custom APIs, as described in Calling APIs from Custom Code. In addition, the custom code service is backed by the following JavaScript libraries, which you can use when you implement your custom API.

JavaScript Library Description
Node

Node provides the backbone for the custom code service. When you implement a custom API, you create a Node.js module.

Behind the scenes, a router module takes care of creating an HTTP server for a Node instance and routing the HTTP calls that come from the service to the custom API’s implementation that runs inside the instance. You don’t need to write code for this.

Request

Request is framework for Node that simplifies the making of HTTP calls. The service wraps Request calls with additional code that’s necessary for the custom code service.

Express

Express is a lightweight web application framework for Node. The custom code service uses it to expose API endpoints. To implement your custom API, you write route definitions similar to how you would use Express to write routes for a web app.

Bluebird

The custom code service uses the Bluebird promises library to implement the promises that the custom code SDK methods return.

Body-parser

The custom code service uses this library to parse incoming request bodies.

Http-proxy-agent

This module provides an http.Agent implementation that connects to a specified HTTP proxy server.

Https-proxy-agent

This module provides an http.Agent implementation that connects to a specified HTTPS proxy server.

Express-method-override.

The custom code uses this library to override the method of a request based on an X-HTTP-Method-Override header, a custom query parameter, or a post parameter.

Agentkeepalive

This library is an implementation of http.Agent that keeps connections alive for some time to reduce the number of times that TCP connections are closed, which thus saves resources.

As shown in the following table, the default library versions depend on whether your environment was provisioned from the current release or upgraded from an earlier release.

JavaScript Library Environment Provisioned from Current Release Environment Upgraded from Prior Release
Node 12.16.1 8.9.4
Request 2.83.0 2.74.0
Express 4.16.2 4.14.0
Bluebird 3.5.1 3.4.6
Agentkeepalive 3.3.0 3.1.0
Body-parser 1.18.2 1.15.2
HTTP-proxy-agent 2.0.0 1.0.0
HTTPS-proxy-agent 2.1.0 1.0.0
Method-override 2.3.10 2.3.6

If a custom API implementation isn't compatible with the default library versions for your environment, use one of the following processes to change the Node version for that implementation. See CCC_DefaultNodeConfiguration in Mobile Hub Policies and Values for the available Node versions.

  • Add a node property to the configuration section in the custom API implementation's package.json file as described in Declare the Node Version.

  • Ask your mobile cloud administrator to change the node version that is specified by the appropriate CCC_DefaultNode environment policy. You can set this policy at different scopes, such as environment scope, mobile backend scope, and API scope. Whenever you change a CCC_DefaultNode environment policy, any custom API implementation that uses that default configuration will change to the new version no later than its second REST request after the version change.

The default maximum body size for all configurations is 1MB.

Video: Node.js Technology Primer

If you don’t have experience with Node.js or you’d simply like to better understand how it works with the custom code service, take a look at this video:

Set Up Tooling for Custom Code

The custom code service is based on Node. You don’t need to install Node on your system to create custom API implementations, but you’ll need the tooling that it provides, such as the Node package manager (npm). Having Node on your system also makes it easier for you to write the code.

The nodejs.org website provides installers that contain the library and some command-line tools, such as npm. You may wish to also install an integrated development environment (IDE) with Node support for features such as syntax highlighting and code completion. One free option is to install Eclipse (eclipse.org) and then add the Nodeclipse plug-in (http://nodeclipse.github.io/).

Steps to Implement a Custom API

The main steps for defining and implementing a custom API are the following:

  1. Define a custom API as described in Custom APIs.

  2. Download a JavaScript scaffold for the API. This scaffold contains stub implementations for your endpoints.

  3. Within the scaffold, fill in the appropriate JavaScript code for each function that corresponds with a given REST endpoint.

  4. Package the finished JavaScript module.

  5. Upload the module to the API Designer.

Download a JavaScript Scaffold for a Custom API

After you create your custom API, you can download a scaffold that is based on your API's RAML document, and then use the scaffold as a quick start for implementing your custom API.

The scaffold comes in the form of a Node module, the key components of which are the main JavaScript file that contains stub methods for each endpoint (resource plus HTTP method), and a package.json file, which serves as the manifest for the module.

To download the scaffold:

  1. Click This is an image of the sidebar menu. to open the side menu, click Development , and then click APIs.
  2. Open the API that you want to download.
  3. In the left navigation area of the API Designer, click Implementations.
  4. Click JavaScript Scaffold to download the zip file.
  5. On your system, unzip the downloaded file.

If you later change the API, then you can download a new scaffold based on the updated endpoints. However, any coding that you may have done and uploaded previously won’t be reflected in the new scaffold.

Writing Custom Code

The following sections show the constructs that are available to you and how to use them in your code.

Key JavaScript Constructs in Custom Code

The scaffold zip that you download from the API Designer includes a main JavaScript file, which contains the key constructs that you need to implement the custom API. Here’s an example of a main JavaScript file for a custom API, which has these resources (URIs):

  • /incidents, which supports the GET and POST HTTP methods

  • /incidents/:id, which supports the GET HTTP method

  • /incidents/:id/uniquecode, which supports the GET HTTP method

// A
module.exports = function(service) {


  //B
    service.post('/mobile/custom/incidentreport/incidents', function(req,res) {
        var result = {};
        var statusCode = 201;
        res.status(statusCode).send(result);
    });

    service.get('/mobile/custom/incidentreport/incidents', function(req,res) {
        var result = {};
        var statusCode = 200;
        res.status(statusCode).send(result);
    });

    service.get('/mobile/custom/incidentreport/incidents/:id', function(req,res) {
        var result = {};
        var statusCode = 200;
        res.status(statusCode).send(result);
    });

This example illustrates these main constructs:

  • (A) module.exports = function (service) {implementation}

    The module.exports statement is required at the beginning of all custom API implementations. It’s used to export an anonymous function with a parameter (service) through which the custom code service passes the object that’s used to expose your endpoints. The service parameter is an instance of an Express application object, and all the object’s functionality is available. Note that in Express example code, this parameter is often called app. The anonymous function contains all the API’s route definitions.

  • (B) Route definitions

    A route definition is an Express route method that associates an anonymous callback function with an endpoint (resource plus HTTP method). Its signature takes the following form:

    service.HttpMethod('URI', function (req, res)
    
    • service is the variable for the custom code service instance (or, in Express terminology, the application instance), which was defined in the module.exports = function (service) statement.

    • HttpMethod is one of the following methods corresponding to standard REST methods:

      • get

      • delete

      • head

      • options

      • patch

      • post

      • put

    • URI refers to resource defined in the API. Notice that while braces identify a parameter in the API design for the resource, you use a colon to identify a parameter in the uri. For example, if the resource is /incidentreport/incidents/{id}, then you use '/mobile/custom/incidentreport/incidents/:id' for the URI.

    • function (req, res) is a callback through which HTTP request and HTTP response objects are passed. It defines how the API responds to client requests to that endpoint. The req variable provides access to the data in the request and you can use the res variable to build the result. Node and Express provide properties and functions for those two variables, which enable you to retrieve information about their values and work with them. We talk about some of these next.

      For more information about the req and res objects, see http://expressjs.com/4x/api.html#request and http://expressjs.com/4x/api.html#response.

    The following example is a route definition for the endpoint GET /incidentreport/incidents/{id}/uniquecode, which generates a unique code.

    service.get(
      '/mobile/custom/incidentreport/incidents/:id/uniquecode',
      function (req, res) {
        console.info('get /incidentreport/incidents/' +
          req.params.id + '/uniquecode');
        res.type('application/json');
        // status defaults to 200
        res.send({'code': req.params.id + '-' + new Date().valueOf()});
      });

    Notice that the code example uses req.params.id to get the :id value from the URI. Here are some of the request properties that you typically use in your code:

    Property Description
    req.body If the request’s content type is application/JSON or application/x-www-form-urlencoded then this property contains the data that was submitted in the request body in the form of a JavaScript object. For information about accessing other types of request bodies, see Access the Body of the Request.
    req.headers A map of header names and values. The names are lower case. Often used to transport extra information in the request, such as an external identifier.
    req.params An object that contains properties that map to parameters in the endpoint’s URI. For example, if the endpoint is attachments/:collection/objects/:objectid, then you use req.params.collection and req.params.objectid to get the parameter values.

    When you use the req.params object to retrieve a parameter value, you must use the same case as the parameter in the endpoint. For example, if the endpoint parameter is {id}, then you must use req.params.id to get the value, and not req.params.Id.

    req.query The query string parameters that are passed in the URI. For example, if the request is GET /incidents?q=joe&order=desc then you use req.query.q and req.query.order to get the query parameters.

    Here are some methods that you typically use to inquire about the request:

    Method Description
    req.get(field) and req.header(field) Both these methods return the value for the header named by field. For example, req.header('content-type'). The match is case-insensitive.

    Note that req.header is an alias for req.get.

    req.is(mimeType) Boolean method that you can use to find out if the request’s Content-Type header matches the mimeType. For example, req.is('json').

The custom code service essentially creates Express application objects and then configures them with service-specific functionality (such as identity propagation and consolidated logging) before it passes them to the custom API implementation logic for further configuration. You get preconfigured Express application objects to which you add route-specific business logic.

Here we discussed only the basic usage of Express features necessary to implement the API by using routing methods to set up callbacks. However, the entirety of the Express features are available for use in custom code. Consult the Express documentation at http://expressjs.com/ to learn about the details, such as how to implement URI parameter parsing, set up multiple callback handlers, and use third-party middleware.

Access the Body of the Request

When requests that are received by the custom code have a content type of application/x-www-form-urlencoded or application/json, the payload is converted to a JavaScript object, which is then stored in req.body. For all other types, such as image/jpeg or text/html, req.body is undefined. Examples of when this occurs is when the body is a text file or an image. In those cases, when you need to access the body from the incoming request’s handler, use the data event listener and end event listener to save the body to a buffer.

The following example shows how to access the body for different content types:

if (req.is('json') || req.is('application/x-www-form-urlencoded'))
    {
       console.info('Request Body: ' + JSON.stringify(req.body));
    } else {
        var data = [];
        // Process a chunk of data. This may be called multiple times.
        req.on('data', function(chunk) {
          // Append to buffer
          data.push(chunk);
        }).on('end', function() {
          // process full message here
          var buffer = Buffer.concat(data);
          // Convert to base64, if required
          // var base64 = buffer.toString('base64');
      });
   }

To learn more about Node.js events and listeners, see https://nodejs.org/api/events.html#events_events.

Insert Logging Into Custom Code

You can use the Node console object to add logging messages to custom code, as shown in this example:

console.info(i + ' Request to get ' + url);

These messages appear in the diagnostic logs.

The custom code service wraps the console object to enable finer-grained logging. The following methods are available for logging messages at different levels:
  • console.severe

  • console.warning

  • console.info

  • console.config

  • console.fine

  • console.finer

  • console.finest

By carefully applying log levels to the messages in your code, you can simplify how you debug and administer the app. This allows you to add good debug messages, and then log them only as necessary, such as during development or when diagnosing a problem. For example, you might want to add the following log messages at the suggested log levels:

Log Message Log Level
Function entry and exit Finest
Input, such as parameters that are sent with the request Fine
Caught exceptions Severe
Uncaught exceptions Fine

To set the level at which logging is enabled for a backend, from either the mobile backend’s diagnostics page or the Diagnostics main menu, click Logs, and then click Server Settings.

Node.js has a less granular set of native methods for logging, which are also possible to use. The logging level of the native Node.js methods console.log and console.dir is equivalent to console.info. The Node.js method console.warn is equivalent to the custom-code method console.warning. The Node.js method console.error is equivalent to the custom-code method console.severe.

When you use console messages to locate problem code, know that the service’s console calls are nonblocking. That is, there’s no guarantee that logging completes before the next statement is executed. In the case of a problem that’s caused by an infinite loop, you will most likely see only the first console message that’s in the block of code before the infinite loop. Consider the following code, for example:

console.info("Log 1");
var myVar="any string";
console.info("Log 2");
myVar="a different string";
console.info("Log 3");
functionWithInfiniteLoop();

When this code is executed, it’s possible that only Log 1 appears in the diagnostic logs. Therefore, to locate an infinite loop, you must have just one console message, and you must put that message where you think it will flag the problem. If it doesn’t flag the problem, then move the message and run another test until you identify the problem code.

When you suspect an infinite loop, follow these steps:

  1. Remove or comment out all console messages.

  2. Add a logging statement as the last line before the return.

  3. Ensure that the log level for your backend is set to the same level as your logging statement, such as INFO for a console.info() message.

  4. Test the endpoint.

  5. Look in the diagnostic logs for your logging statement.

  6. If you don’t see the message, move the logging statement up one line and test the endpoint again.

  7. Repeat the previous step until the message appears in the log.

    At this point, you know that the problem statement is just below the logging statement.

Tip:

If you have several lines of code, then you can reduce the number of tests by putting the logging statement in the middle of the code block and then testing the endpoint. If you don’t get the log message, then put the logging statement in the middle of the top half. Otherwise, put the logging message in the middle of the bottom half. Test the endpoint. Repeat the test by dissecting the code blocks until you have narrowed the test to just two lines of code.

Local Data Storage

Don’t use the file system that’s associated with the virtual machine running the Node.js instance to store data, even temporarily. The virtual machines that run Node.js instances might fluctuate in number, meaning that data written to one instance's file system might be lost when individual instances are started and stopped.

To store data from custom code, you can use the Database Access API or the Storage API.

Video: Working with Node - Common Code

For a demonstration of writing Node code to implement custom APIs, take a look at the Oracle Mobile Platform video series on custom code, starting with this video:

Implementing Synchronization-Compatible APIs

If your mobile app uses the Synchronization library to enable offline use, as described in Data Offline and Sync, then here’s some information about how to make your implementation compatible with the library.

Note:

To learn how to design your API so that it is compatible with the Synchronization library, see Endpoint Requirements for Sync Compatibility and API Design Considerations.

Video: Working with Custom APIs via Data Offline & Sync

If you want an overview of how to build your custom API to have synchronization-compliant REST endpoints and data, take a look at this video:

Requirements for a Synchronization-Compatible Custom API

To ensure that the Synchronization library can synchronize with your custom API’s data, follow these rules:

Method Response Body Response Headers Response HTTP Status Codes
GET
  • To return a single item, use setItem() to put the item in the response, as described in Return Cacheable Data. Note that this method adds the Oracle-Mobile-Sync-Resource-Type header to the response and sets it to item.

  • To return a collection, use addItem() to add the items to the collection, as described in Return Cacheable Data. Note that this method associates each item with its required URI and ETag and sets the Oracle-Mobile-Sync-Resource-Type header to collection.

    If there’re no items in the collection, then you must return a body with empty items, uris, and etags arrays. For example:
    {
       items:[],
       uris:[],
       etags:[]
    }
  • Oracle-Mobile-Sync-Resource-Type: Must be set to item for a single item, or collection for an array of items. The setItem() and addItem() methods set this header automatically for items and collections. If the response body is a file, you optionally can set this header to file.

  • ETag: If the Oracle-Mobile-Sync-Resource-Type header is set to item or file, then this header must be set to the item’s ETag (in quotes).

  • Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store: Optional. See Specify Synchronization and Cache Policies.

No special requirements
PUT If the item stored on the server is different from the item in the request body, such as having a different ID in the case of an add or containing automatically calculated fields like modifiedOn, then return the stored item in the response body. Otherwise, returning the item in the response body is optional.
  • Location: If the item was added, then you must include this header, which contains the item’s URI. Otherwise, this header is optional.

  • ETag: Must contain the item’s ETag in quotes.

  • Oracle-Mobile-Sync-Resource-Type: Must be set to item for a single object. The addItem() method sets this header automatically. If the response body is a file, you optionally can set this header to file.

  • Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store: Optional. See Specify Synchronization and Cache Policies.

Note that the value in the If-Match header value dictates the actions to take and the response code to send. The Synchronization library sends * in the If-Match header when the conflict resolution policy is CLIENT_WINS. For all other conflict resolution policy configurations (that is, SERVER_WINS and PRESERVE_CONFLICT), it sends the item’s ETag. If the header isn’t present or is null, then assume *.

  • If there’s an If-Match header and its value isn’t *, then, if the item’s ETag doesn’t match the header’s value, return 412 Precondition Failed.

  • If the item to be updated no longer exists, then do one of the following:
    • If the If-Match header is *, then add the item and return 201 CREATED

    • If there’s an If-Match header and its value isn’t *, then return 404 NOT FOUND.

  • If the item was successfully updated, then return one of the standard PUT codes, such as 200 OK or 204 No Content.

POST

If the item stored in the server is different from the item in the request body, then include the stored item in the response body. Otherwise, returning the item in the response body is optional. For example, if the server adds calculated fields such as createdOn, then return the stored item in the response body.

  • Location: Must contain the item’s URI.

  • ETag: Must contain the item’s ETag in quotes.

  • Oracle-Mobile-Sync-Resource-Type: Must be set to item for a single object. The addItem() method sets this header automatically. If the response body is a file, you optionally can set this header to file.

  • Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store: Optional. See Specify Synchronization and Cache Policies.

No special requirements
DELETE No special requirements No special requirements
  • If there’s an If-Match request header and its value isn’t *, then if the ETag of the item to be deleted doesn’t match the header’s value, return 412 Precondition Failed.

    Note that the Synchronization library sends * in the If-Match header when the conflict resolution policy is CLIENT_WINS. For all other conflict resolution policy configurations, it sends the item’s ETag.

  • If the item doesn’t exist, then you can return either a 404 Not Found or a 204 No Content. The Synchronization library process is the same for both codes.

  • If the item was successfully deleted, then return one of the standard DELETE codes, such as 200 OK, 202 Accepted, or 204 No Content.

The Synchronization library uses the 412 Precondition Failed HTTP response status code and the If-Match header to implement conflict resolution policies. Basically, if the conflict resolution policy is CLIENT_WINS, then the If-Match header is set to * to indicate that the server must update or delete the resource without conflict. Otherwise, the If-Match header is set to the item’s ETag, and the custom code is expected to return 412 Precondition Failed if the ETags don’t match.

Tip:

Most methods require an ETag header in the response, and many methods require that you compare the server version’s ETag with the value in the request’s If-Match header. There are several node libraries that you can use to create ETags. For example, the NPM etag library that is available from https://www.npmjs.com/package/etag.

Return Cacheable Data

The custom code SDK provides the following methods to format your data for use by the Synchronization library. Using these methods enables the library to optimize synchronization.

oracleMobile.sync Method Description
setItem(response, item) Set the response body to the item.
addItem(response, item, uri, etag) Add the item to a collection, which will be returned in the response body in a cacheable format.
clear(response) Undoes all calls to setItem and addItem .

For a response with a single JSON object, you use setItem to format the data, as shown in this example, and you return the ETag value in the ETag header:

var etag = require('etag');
...
service.get('/mobile/custom/incidentreport/incidents/:id/syncUniquecode',
  function (req, res) {
    var item = {'code': req.params.id + '-' + new Date().valueOf()};
    res.setHeader('Etag', etag(JSON.stringify(item)));
    req.oracleMobile.sync.setItem(res,item);
    res.end();
});

For a JSON object that contains an array of items, you use addItem to add each item to the response, as shown in the next example. Note that addItem attaches a URI and an ETag value to each item in the response body. The URI must uniquely identify each item.

var etag = require('etag');
...
service.get(
  '/mobile/custom/incidentreport/statusCodes',
  function (req, res) {
    var payload = {'inroute': 'Technician is on the way'};
    req.oracleMobile.sync.addItem(
      res,
      payload,
      '/mobile/custom/incidentreport/statusCodes/inroute',
      etag(JSON.stringify(payload))
      );
    payload = {'arrived': 'Technician is on premises'};
    req.oracleMobile.sync.addItem(
      res,
      payload,
      '/mobile/custom/incidentreport/statusCodes/arrived',
      etag(JSON.stringify(payload))
      );
    payload = {'completed': 'Technician has left premises'};
    req.oracleMobile.sync.addItem(
      res,
      payload,
      '/mobile/custom/incidentreport/statusCodes/completed',
      etag(JSON.stringify(payload))
      );
    res.end();
  });

The response body for the addItem example looks like this:

{
  "items": [
    {
      "inroute": "Technician is on the way"
    },
    {
      "arrived": "Technician is on premises"
    },
    {
      "completed": "Technician has left premises"
    }
  ],
  "uris": [
    "/mobile/custom/incidentreport/statusCodes/inroute",
    "/mobile/custom/incidentreport/statusCodes/arrived",
    "/mobile/custom/incidentreport/statusCodes/completed"
  ],
  "etags": [
    "\"26-5vTpRVIO9SakJoLYEQrQ0Q\"",
    "\"27-+lktOY9aA46ySRE0O/y5Aw\"",
    "\"2c-PSRg8Cxr2rYp/9BftCmDag\""
  ]
}

When you use setItem and addItem, the response also includes this header:

Header Description Type
Oracle-Mobile-Sync-Resource-Type If the response body is JSON, then the value is item if the JSON object includes a single item. The value is collection if the JSON object contains an array of items. Note that when the response is a file, you optionally can set the value to file. When this header isn’t included in the response, the Synchronization library assumes that the type is file. That is, when this header is not set, then the MobileResource that the Synchronization library fetchObjectBuilder and fetchCollectionBuilder methods return is of type MobileFile. String

Specify Synchronization and Cache Policies

For the mobile apps that use the Synchronization library, you might want to override their settings for whether to cache the data that you return and when to expire and delete the data. For example, if the data contains private information, you might want to prevent a mobile app from caching that data. This table shows the Oracle-Mobile-Sync HTTP headers to override these settings.

Header Description Type
Oracle-Mobile-Sync-Evict

Specifies the date and time after which the expired resource should be deleted from the app’s local cache. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat.

The following synchronization policies are set for the resource object that is created from the response:

  • Eviction policy: EVICT_ON_EXPIRY_AT_STARTUP

  • Expiration policy: EXPIRE_AFTER with the expireAfter property set to date and time provided in the header value

    .
Number
Oracle-Mobile-Sync-Expires Specifies when to mark the returned resource as expired. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat. Number
Oracle-Mobile-Sync-No-Store When set to true, instructs the client to not cache the resource. Boolean

Call Web Services and APIs from Custom Code

Your implementation can access these types of APIs and services:

  • Platform APIs: The custom code SDK includes methods for accessing the platform services. For example, you use database.get() to retrieve data from the Database service.

  • Custom APIs: The custom code SDK includes custom methods for interacting with all the other custom APIs that are in your environment.

  • Connector APIs: The custom code SDK includes connector methods for interacting with connectors.

  • External web services: Typically, you create connector APIs with which to interact with external services, but you also can connect with remote web services directly from custom code.

    If you need to make a third-party web service call that doesn’t require you to shape the data, and you don’t need integrated diagnostics, tracking, or analytics for that call, then you might choose to call the service directly instead of setting up a connector. You can call a web service directly from your custom code using Node APIs such as the HTTP API. The only supported protocols for making calls to the Internet from custom code are HTTP and HTTPS. For information about the Node HTTP API, see nodejs.org/api/http.html.

When deciding whether to use a connector or make a direct call, consider that if the third-party web service changes its API, then a connector requires just one change, whereas with direct calls, you must make sure you find and change all the direct calls. Also, consider that if you’re testing against a test web service, you’ll have to modify the URLs for the direct calls when you switch to the production service.

Package Custom Code into a Module

After you’ve written custom code to implement an API, and before you upload and deploy it, follow these steps to package the implementation:

  1. Declare the implementation version in the package.json manifest file.

  2. Optionally declare the Node version in the package.json file.

  3. Declare in the package.json file the API dependencies on other modules.

  4. Run the Node.js package manager (npm) to download the dependencies.

  5. Put the root folder and its implementation files in a zip file.

Required Artifacts for an API Implementation

An API implementation is packaged as a zip archive containing, at minimum, the following artifacts:

  • A root directory that has the name of the custom code module.

  • The package.json file. Within this file, you specify in JSON format the name of the module and any dependencies that your custom code has, such as any connector APIs.

    By Node convention, this file must be within the root directory.

  • At least one JavaScript file that contains the implementation code.

  • If there are any additional modules that you are using (in addition to Express and the base Node features), then a node_modules directory containing those modules.

package.json Contents

Like all npm packages, custom API implementations require that you identify the project and its dependencies in a package manifest named package.json. Here’s an example of the syntax and the properties of a package.json file for a custom API implementation:

{
  "name" : "incidentreports",
  "version" : "1.0.0",
  "description" : "FixItFast Incident Reports API",
  "main" : "incidentreports.js",
  "dependencies": {
    "async": "0.9.0"
  },
  "oracleMobile" : {
    "dependencies" : {
      "apis" : {"/mobile/custom/employees" : "3.5.1"},
      "connectors" : {"/mobile/connector/RightNow": "1.0"}
    }
  }
}

The key attributes are the following:

name

A descriptive name for the implementation. The name can contain only characters that can be used in a URI. It may not start with a period (.) or underscore (_). The value of this attribute in combination with the value of the version attribute must be unique among all API implementations.

version

The version of the implementation. If you provide a new version of an implementation, then this attribute should be incremented and the name value should stay the same.

description

An optional description of the implementation.

main

The name of the main JavaScript file that implements the API. If this file isn’t in the same folder as the package.json file, then use a path name that’s relative to the package.json folder.

dependencies

The specification of dependencies to other Node modules required for the implementation. When you have such dependencies, use npm to install those modules in this directory.

oracleMobile / dependencies / api

The specification of the version for a custom API or a connector API that you reference in your custom code.

Declare the API Implementation Version

Use the version attribute in the package.json file for the custom code module to specify the implementation version, as shown in the following example:

{
  "name" : "incidentreport",
  "version" : "1.0.0",
  "description" : "Incident Report Custom API",
  "main" : "incidentreport.js",
  "oracleMobile" : {
    "dependencies" : {
      "apis" : { },
      "connectors" : {"/mobile/connector/RightNow": "1.0"}
    }
  }
}

If you have previously uploaded an implementation and that implementation is still in Draft state, then you can continue to upload modified implementations without incrementing the version number. After you publish a version, that version is final. If you want to make changes to a published implementation, then you must increment the version number.

You can publish implementations independently of APIs, and you can increment their version numbers separately as well. This lets you make changes to a published implementation, such as minor modifications or bug fixes, without requiring the API itself to be updated.

To create another version of an API implementation, change the version attribute, such as "version": "1.0.1", and then upload a zip file of the modified implementation. When you upload a new version of an implementation, it becomes the default version (active implementation) for that API. You can change the default version in the API’s Implementations page.

If the new version is backward-compatible, then use a minor incremental increase. For example, if the previous version is 1.3, then the updated version number could be 1.4 or 1.7. If the new version isn’t backward-compatible, then use a major incremental increase. For example, if the previous version is 1.3, then the updated version number could be 2.0 or 2.1.

Declare the Node Version

The CCC_DefaultNodeConfiguration environment policy defines the instance's default node version. To use a version of the Node library other than the default Node version, add a node property to the configuration section as shown in this example:

{
  "name" : "incidentreport",
  "version" : "1.0.0",
  "description" : "Incident Report Custom API",
  "main" : "incidentreport.js",
  "oracleMobile" : {
    "configuration" : {
      "node" : "6.10"    }
  }
}

Package Additional Libraries with Your Implementation

If your API implementation depends on other JavaScript modules, such as Async, then you must add them to your custom code zip file. The additional modules aren’t shared across APIs. For example, you must include the Async module in every implementation package that uses it. Your implementation can't use any modules that depend on installing a binary (executable) on the server.

  1. In the package.json file for the implementation module, declare the modules that the implementation module depends on. Specify both the module name and the version number in the following format:
    "dependencies": {
         "<module-name>":"<version-number>",
         "<module-name>":"<version-number>",
    },
  2. In the directory containing the package.json file for the custom code module, run:
    npm install
    

    This command downloads the stated dependencies from the public npm repository and places them in the node_modules subdirectory.

    If the module on which you’re creating the dependency is in a folder on your file system instead of in the public npm repository, add the path to the folder as an argument to the command:

    npm install <folder-name>

    For more information on using the npm package manager, see https://docs.npmjs.com/cli/install.

  3. Package the whole folder containing the package.json file in a zip archive.

Upload the Custom Code Module

  1. On your system, create a zip archive of the root folder that contains the required artifacts for the implementation:
    • The package.json file
    • At least one JavaScript file that contains the implementation code.
    • If necessary, a node_modules sub directory that contains the additional modules that the implementation uses.
  2. From the API Catalog, open the custom API that the custom code implements.
  3. In the left navigation bar, click Implementations.
  4. At the bottom of the API Implementation page, click Upload an implementation archive, and upload the zip archive.

Tip:

You also can use the Mobile Hub Custom Code Test Tools to upload an implementation from the command line.

Testing and Debugging Custom Code

You can test and debug your custom code directly within the UI. It’s also possible to test your custom code outside of the UI.

Test with Mock Data

When you create a custom API, you get a mock implementation, which application developers can use to test their mobile applications while you are implementing the custom code. When you call an endpoint for a mock implementation, it returns the request example, if one has been provided.

The mock implementation is the default implementation until you upload an implementation. Whenever you upload an implementation, it is automatically deployed as the default implementation. You can always change this, including reverting to the mock implementation, for testing purposes. To change the default implementation, select it on the Implementations page and click Set as Default.

You can create example (mock) data to provide default request and response bodies for the test UI. You can use either the API Designer or the RAML to add example (mock) data. To provide an example for an endpoint from the API Designer, from the Endpoints page, go to the desired method, click either the Requests tab or the Responses tab, select the appropriate media type, and then enter the mock data in the Example tab.

Here's an example of providing mock data in the RAML.

/status:
      get:
        description: |
          Gets status of specified report.         
        responses:
          200:
            description: |
              OK.             
            body:
              application/json:
                example: |
                  { "code": "New",
                     "notes": "My hot water tank's model is AB234"
                  }

Test Custom Code from the UI

As soon as you upload your custom API implementation, you can test it.

  1. Click Test in the API Designer.
    The test page displays all the operations.
  2. From the endpoints list, click the operation that you want to test.
  3. If the endpoint has parameters, then enter the required parameters and any optional parameters that you want to test.
  4. If the endpoint accepts a request body, then provide the body or click Use Example.
  5. Select a backend.
  6. Optionally, select an API version.
  7. Select the authentication method. If you select Current User, then the authentication method is OAuth Consumer.
  8. If you aren’t using anonymous access, then you must provide a user name and the password. This user must have been assigned one of the roles that can access the endpoint. If the endpoint doesn’t have any roles configured for it, then the user must belong to a role that’s associated with the API. In addition, the user must have one of the roles that is associated with the backend that you use to test the endpoint. You can see these roles in the backend’s Security page.
  9. Click Test Endpoint.
    The Response Status section displays the status and the response. Click Request to see the request URI and headers.

You also can test an API from the API Catalog and from a backend.

Debug Offline with the Mobile Hub Custom Code Test Tools

Oracle Mobile Hub offers a set of custom code test tools that you can use to iteratively debug your custom code. You can download the tools from the downloads page on OTN.

The core of the tools is an npm module that enables you to run an offline custom code container, run tests on the code, and package and deploy an implementation back to Mobile Hub.

Detailed instructions on using the tools are located in the README.MD file that is packaged within the omce-tools.zip.

Tools for Testing Custom Code Outside the UI

You can use tools that were designed for testing web services to test custom code, such as cURL.

The way you remotely access an API endpoint depends on the type of authentication that you want to use. See:

When you create a custom API, a mock implementation is created automatically. You can use this mock implementation for testing before you implement the custom code. You also can use the mock implementation to configure a response for a mobile application test case. After you have uploaded an implementation, you can switch to the mock implementation for testing purposes by making it the default. For more information, see Test with Mock Data.

If your request is in a test suite, then you can put the name of the test suite in the Oracle-Mobile-Diagnostic-Session-ID header. The name appears as the app session ID in the log messages. This lets you filter the log data on the Logs page by entering the test suite name in the Search text box. Also, when you are viewing a message’s details, you can click the app session ID in the message to view all the messages with that ID. For more information about using the Oracle-Mobile-Diagnostic-Session-ID header, see How Client SDK Headers Enable Device and Session Diagnostics.

Note that the API must either allow anonymous access or be associated with at least one role. If neither of these is true, then you will get an unauthenticated error.

View Custom Code Log Messages

When your API implementation doesn’t return the expected results, use the diagnostic logs to troubleshoot the problem.

To pinpoint where the error occurred, click This is an image of the sidebar menu. to open the side menu. Next, click the Diagnostics menu, and then click Request History. Next, to find the request, click View related log entries View Related Log Entries icon in the Related column, and then select Log Messages Related by API Request. To see a message’s details, click the time stamp. From the Message Details dialog, you can click the up and down arrows to see all the related log messages.

You can get to the Request History page from either the Diagnostics menu or a mobile backend’s Diagnostics page. Note that if there isn’t sufficient information in a request to enable the service to determine the associated backend, then the related log messages appear only in the Logs page that is available from the Diagnostics menu.

Every message is tagged with a request correlation ID that associates all messages for a request. When you view a message’s details, you can click the request correlation ID to see the other messages for the same request.

If you don’t see any messages that help identify the source of the problem, then you can change to a finer level for logging messages. From the Logs page, click Server Settings, change the log level for the mobile backend, and then rerun the test.

Let's use the following endpoint to see how to custom code logging works. In this code, the database.insert method makes a PUT request to /mobile/platform/database/objects/FIF_Incidents.

service.post('/mobile/custom/incidentreport/incidents', function (req, res) {
  req.oracleMobile.database.insert('FIF_Incidents', req.body).then(
    function (result) {
        res.status(result.statusCode).send(result.result);
    },
    function (error) {
      res.status(result.statusCode).send(error.error);
    }
  );
});

The service always logs a message whenever a call ends, regardless of the log level setting. In the following figure, the bottom (earliest) message was logged when the POST request to /platform/database/objects/{table} ended. The top (later) message was logged when the service.post call to /mobile/custom/incidentreport/incidents ended.

Description of custom-code-loginfo-ext.png follows
Description of the illustration custom-code-loginfo-ext.png

Get Finer-Grained Request and Response Log Messages

To get the maximum amount of log messages, set the log level to FINEST. With this level, the service logs the following messages:

  • A FINEST message, which contains the HTTP verb and URI, whenever a request is received by any of the custom API’s endpoints

  • A FINEST message, which contains the HTTP verb, URI, and status code, whenever a response is sent by any of the custom API’s endpoints

  • A FINEST message, which contains the HTTP verb and URI, whenever a request is sent to another platform or custom API.

  • A FINEST message, which contains the HTTP verb, URI, and status code, whenever a response is received from a call to another platform or custom API.

If the CCC_LogBody environment policy is set to true and the log level is FINEST, then the following occurs:

  • If a request body exists, then the FINEST message that contains the request’s HTTP verb and URI also shows the body.

  • If a response body exists and the response status code is less than 400, then the FINEST message that contains the HTTP verb, URI, and status code for the response also shows the body.

  • If a response body exists and the response status code is 400 or higher, then the response body is logged in a separate message. Immediately after, it logs the FINEST message for the response. The message type is either WARNING or SEVERE, depending on the status code.

Description of custom-code-logfinest-body-ext.png follows
Description of the illustration custom-code-logfinest-body-ext.png

Note that setting the log level to FINEST might have a negative effect on performance.

Log Request and Response Bodies

If you would like to see the bodies of the requests and responses, then ask your mobile cloud administrator to change the CCC_LogBody environment policy to true. When you do this, the service logs a CCC message whenever a body is passed in a request or a response as shown here:

Description of custom-code-loginfo-body.png follows
Description of the illustration custom-code-loginfo-body.png

When you set the log level to Info, the service logs the request bodies with a message type of INFO. Response bodies are logged with a message type that corresponds to the response status. For example, if the response status is 401, then the log message that contains the response body has a message type of WARNING.

Note that setting the CCC_LogBody environment policy to true might have a negative effect on performance.

By default, the body is truncated after 512 characters. Use the CCC_LogBodyMaxLength environment policy to change the maximum body length. To always include the full message, no matter how long it is, set CCC_LogBodyMaxLength to -1.

Minimize the Performance Cost of Logging Bodies

If you are concerned about the performance cost of logging bodies, but you want to see the request and response bodies for exceptional cases, you can configure your instance to log request and response bodies only when the status code is 400 or higher.

To limit request and response body messages to exceptional cases:

  • Set the CCC_LogBody environment policy to true.
  • Set the logging level to WARNING or SEVERE.

With these settings, whenever there is a status code of 400 or higher, a message is logged for both the request and the response. Both messages are logged at the time that the response is received. The message type is WARNING or SEVERE, depending on the status code. The message shows the body, if there is one.

Create Custom Log Messages

To help with debugging, you can use the console object from the custom code SDK to insert logging into your implementation.

Troubleshooting Custom API Implementations

When a test fails for a request, examine the response’s HTTP status code and the returned data to identify the issue. Status codes in the 200 range indicate success. Status codes in the 400 range indicate a client error where the calling client has done something the server doesn't expect or won’t allow. Depending on the 4XX error, this may require fixing custom code, giving a user the necessary privileges, or reconfiguring the server to allow requests of that type, for example. Status codes in the 500 range indicate that the server encountered a problem that it couldn't resolve. For example, the error might require reconfiguring server settings. Here are some common standard HTTP error codes and their meanings:

Status Code Description

400 BAD REQUEST

General error when fulfilling the request would cause an invalid state, such as missing data or a validation error.

401 UNAUTHORIZED

Error due to a missing or invalid authentication token.

403 FORBIDDEN

Error due to user not having authorization or if the resource is unavailable.

404 NOT FOUND

Error due to the resource not being found.

405 METHOD NOT ALLOWED

Error that although the requested URL exists, the HTTP method isn’t applicable.

500 INTERNAL SERVER ERROR

General error when an exception is thrown on the server side.

You can use the request’s log entries to pinpoint where the error occurred as described in View Custom Code Log Messages. To learn how to identify custom code syntax errors from the log, see Diagnose Syntax Errors.

If you don’t see any messages that identify the source of the problem, then you can change to a finer level for logging messages. From the Logs page, click Server Settings, change the log level for the mobile backend, and then rerun the test. If you’re troubleshooting custom code, then you can add your own log messages to the custom code to help identify the code that’s causing the problem. See Insert Logging Into Custom Code.

Tip:

If, in a request, you set the Oracle-Mobile-Diagnostic-Session-ID header to an identifier for the suite, that value is displayed in the message detail as the app session ID. If you click the app session ID in a message detail, then you can then click the up and down arrows to view all the messages for that ID. You can also enter the ID in the Search field to display only the log messages with that ID. For more information about using the Oracle-Mobile-Diagnostic-Session-ID header, see How Client SDK Headers Enable Device and Session Diagnostics.

Diagnose Syntax Errors

When a request failure is caused by a syntax error, you can details about that error from the diagnostic logs.

From the Diagnostic page, find and open the Message Detail dialog box for the syntax error message, and look at the Message Text , which provides the module and line number where the error occurred, as shown here:


Description of custom-code-syntax-error.png follows
Description of the illustration custom-code-syntax-error.png

If you’d like to see the stack traces for custom code syntax errors in request responses, then ask your mobile cloud administrator to change the CCC_SendStackTraceWithError environment policy to true. When you do this, you’ll see a request response like the following example whenever a request results in a syntax error in the custom code. The stack trace shows the line number where the error occurred.

{"message": "Custom Code Problem: ReferenceError: nonExist is not defined\n at /scratch/aime/mobile/mobile_ccc/custom_code_modules/ccc2455344468806884059/incidentreports/incidentreports.js:354:17\n at callbacks (/scratch/aime/mobile/mobile_ccc/mcs-node-router/node_modules/express/lib/router/index.js:164:37)\n  ..."
}

Common Custom Code Errors

The following topics discuss common errors, possible causes, and solutions.

Custom Code Problem parsing JSON: Error: request entity too large

This error is typically caused by a request body that’s larger than the JSON body parser’s default maximum input, which is 1MB.

To change the JSON body parser limit for Node 6.10 and later, add this code to the implementation’s main JavaScript file, and set the desired limit:

var bodyParser = require('body-parser');
module.exports = function(service) {
      service._router.stack[3].handle = bodyParser.json({limit: '2mb'})
};

To change the JSON body parser limit for Node 0.10, add this code to the implementation’s main JavaScript file, and set the desired limit:

var bodyParser = require('body-parser');
module.exports = function(service) {
    service.stack[3] = { route: "", handle: express.json({limit: '2mb'})
};
Custom Code Problem in oracleMobile.rest callback: Argument error, options.body

When you use a generic REST method from the custom code SDK, the common cause for this error is assigning a JavaScript object to optionsList.body, where optionsList is the first parameter in a call to req.oracleMobile.rest.post(optionsList, handler).

The solution is to do one of the following:

  • Store the object in optionsList.json, instead of optionsList.body. This solution automatically converts the object to a JSON string and sets relevant parts of the request, such as the content type and length. For example:

    optionsList.json = {first: 'John', last: 'Doe'};
  • Use JSON.stringify to convert the object to a JSON string before setting the optionsList.body value. For example:

    optionsList.body = JSON.stringify(first: 'John', last: 'Doe'}; 
    optionsList.headers = {'Content-Type': 'application/json');
Your custom code container is in the process of recovering from an unhandled error in a earlier request

This issue occurs when a previous request results in an uncaught exception. When you receive this response, rerun the current request. It should succeed as soon as the system has recovered from the uncaught exception for the previous request.

You should examine the logs for the previous requests to see if you can find the cause of the uncaught exception.

Connection fails due to untrusted URL

To protect client apps, the service passes all external URLs through McAfee Web Gateway v7.x/6.9.x (Cloud), which requires that all external URLs are trusted. This requirement applies to external service URLs for connector APIs as well as those that you access directly from custom code.

Attempting to connect with an untrusted connector endpoint results in a 403 error, which might be wrapped in a 500 error.

To resolve the issue, add the untrusted URL to the list of trusted URLS for McAfee Web Gateway v7.x/6.9.x (Cloud) at http://trustedsource.org/.  Note that the process can take from three to five business days.

database.getAll(table, options, httpOptions) doesn’t return all the rows in a table

This issue occurs when there are more rows in the table than the Database_MaxRows environment policy allows the service to return. The default value is 1000.

Ask your mobile cloud administrator to increase the Database_MaxRows value.

This mobile user doesn't have the necessary permissions to call this endpoint

In the UI, open the API and click Security. If Login Required is turned on and Enterprise is selected, then look at the roles that have been configured. If no roles are configured, then no one has permission to log in to the mobile backend. If one or more roles are configured, ensure that the user has a necessary role.

What Happens When a Custom API Is Called?

You might be curious about how the service handles calls to a custom API. Here is a high-level summary. When the service receives a custom API request, it sends the request to the custom code service. The custom code service then directs the request to one of the following:

  • Custom code container for the API implementation: A container is a Node instance. This container wraps the custom API implementation with JavaScript that handles tasks such as server startup, authentication, authorization, and logging. There is one container for each deployed version of an implementation for each associated mobile backend version.

  • Custom code agent: The agent controls the creation and destruction of custom code containers, controls server startup, and exposes the REST endpoints for creating and destroying a container.

Basically, a custom API implementation is launched on demand in a container that is instantiated by the custom code agent. This container, which runs in Node, handles the requests and returns the responses.

When the custom code calls a platform API or a connector API, it makes the call back through the service, and then the service routes the call to that API. If the call is to a different custom API, then the service routes the call to that API’s container if it exists, or it creates the container and then routes the call to it.