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?
Using JavaScript, Node, and the custom code SDK, you can implement the APIs that have been designed in the API Designer (or by means of a RAML document). 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.
Note:
To use your implementation (custom code) in a mobile backend, you must first define the custom API as described in Custom API Design. The implementation is then accessed by your apps through calls to the API's endpoints.
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.
For more information about route definitions, see Key JavaScript Constructs in Custom Code. For information about the Express coding style, see http://expressjs.com/starter/basic-routing.html
. For information about Node, see www.nodejs.org
. If you’re interested in how the custom code service handles custom API requests and responses, then see What Happens When a Custom API Is Called?
Note:
Note: 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.What's the Foundation for 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 next table, the default library versions depend on whether your environment was provisioned from the current release or upgraded from an earlier release. This table lists the default versions of the libraries for this release and the prior release.
JavaScript Library | Environment Provisioned from Current Release | Environment Upgraded from Prior Release |
---|---|---|
Node | 8.9.4 | 6.10.0 |
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 your custom API implementation isn't compatible with the default library versions for your environment, do one of the following to change the versions for that implementation:
-
Add a
node
property to the configuration section in the custom API implementation'spackage.json
file as described in Declaring the Node Version. You can set it to0.10
,6.10
or8.9
. The Node version in thepackage.json
file overrides theCCC_DefaultNodeConfiguration
environment policy for that custom API implementation. -
Ask your mobile cloud administrator to change the node version that is specified by the appropriate
CCC_DefaultNodeConfiguration
environment policy. The choices are0.10
,6.10
, and8.9
. You can set this policy at different scopes, such as environment scope, mobile backend scope, and API scope. Whenever you change aCCC_DefaultNodeConfiguration
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.
Note:
The default maximum body size for all configurations is 1MB. To learn how to increase the maximum body size, see Custom Code Problem parsing JSON: Error: request entity too large.Setting 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://www.nodeclipse.org/
).
Steps to Implement a Custom API
The main steps for defining and implementing a custom API are the following:
-
Define a custom API as described in Custom APIs.
-
Download a JavaScript scaffold for the API. This scaffold contains stub implementations for your endpoints.
-
Within the scaffold, fill in the appropriate JavaScript code for each function that corresponds with a given REST endpoint.
-
Package the finished JavaScript module.
-
Upload the module to the API Designer.
Downloading 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.
Note:
Instead of downloading the scaffold, you can have it pushed directly to a Git repository. See Managing Custom Code in Git.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:
Note:
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 theGET
andPOST
HTTP methods -
/incidents/:id
, which supports theGET
HTTP method -
/incidents/:id/uniquecode
, which supports theGET
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 themodule.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 theuri
. For example, if the resource is/incidentreport/incidents/{id}
, then you use'/mobile/custom/incidentreport/incidents/:id'
for theURI
. -
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. Thereq
variable provides access to the data in the request and you can use theres
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
andres
objects, seehttp://expressjs.com/4x/api.html#request
andhttp://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
orapplication/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 Accessing 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 usereq.params.collection
andreq.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 usereq.params.id
to get the value, and notreq.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 usereq.query.q
andreq.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)
andreq.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 forreq.get
.req.is(mimeType)
Boolean method that you can use to find out if the request’s Content-Type
header matches themimeType
. For example,req.is('json').
Note:
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.
Accessing 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
.
Inserting 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.
-
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 Administration page, click Logs, and then click (Log Level).
To learn how to view the logs, see Accessing Logging Messages for Custom Code.
Note:
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:
-
Remove or comment out all console messages.
-
Add a logging statement as the last line before the return.
-
Ensure that the log level for your backend is set to the same level as your logging statement, such as
INFO
for aconsole.info()
message. -
Test the endpoint.
-
Look in the diagnostic logs for your logging statement.
-
If you don’t see the message, move the logging statement up one line and test the endpoint again.
-
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.Storing Data Locally
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, which is described in Accessing the Database Access API from Custom Code, or the Storage API, which is described in Accessing the Storage API from Custom Code.
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, as described in Building Apps that Work Offline Using the Synchronization Library, follow these rules:
Method | Response Body | Response Headers | Response HTTP Status Codes |
---|---|---|---|
GET |
|
|
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.
|
|
Note that the value in the
|
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 |
|
No special requirements |
DELETE | No special requirements | No special requirements |
|
If you want to learn more about how the Synchronization library uses the 412 Precondition Failed
HTTP response status code and the If-Match
header to implement conflict resolution policies, see Synchronization Library Process Flow. 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 anETag
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
.
Returning 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 |
Specifying 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 The following synchronization policies are set for the resource object that is created from the response:
|
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 |
Calling Web Services and APIs from Custom Code
Your custom code will most likely need to access one or more of the following types of APIs and services:
-
Platform APIs: Your custom code can connect with platform services, such as Storage, Notifications, and Location, through their APIs.
-
Custom APIs: Your custom code can interact with all the other custom APIs that are in your environment.
-
Connector APIs: Your custom code can serve as wrappers for connector APIs.
-
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.
Calling APIs from Custom Code discusses how to access platform, custom, and connector APIs 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. For information about the Node HTTP API, see nodejs.org/api/http.html
.
Note that HTTP and HTTPS are the only supported protocols for making calls to the Internet from custom code.
Note:
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.Packaging Custom Code into a Module
-
Declare the implementation version in the
package.json
manifest file. -
Optionally declare the Node version in the
package.json
file. -
Declare in the
package.json
file the API dependencies on other modules. -
Run the Node.js package manager (npm) to download the dependencies.
-
Put all the 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. See package.json Contents for information on the contents and syntax on thepackage.json
file.Note:
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. See Packaging Additional Libraries with Your Implementation.
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 theversion
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 thepackage.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. See Packaging Additional Libraries with Your Implementation. - oracleMobile / dependencies / api
-
The specification of the version for a custom API or a connector API that you reference in your custom code.
Declaring 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.
For more information about publishing and deploying APIs, see Lifecycle Scenarios.
Declaring the Node Version
To use a version of the Node library other than the instance’s default version, add a node property to the configuration
section as shown in the following example:
{
"name" : "incidentreport",
"version" : "1.0.0",
"description" : "Incident Report Custom API",
"main" : "incidentreport.js",
"oracleMobile" : {
"configuration" : {
"node" : "6.10" }
}
}
To learn about the default Node version and the available node versions, see CCC_DefaultNodeConfiguration
in Environment Policies and Their Values.
Packaging 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.
Uploading the Custom Code Module
- On your system, prepare the required artifacts for the implementation, as described in Required Artifacts for an API Implementation.
- From the API Catalog, open the custom API that the custom code implements.
- In the left navigation bar, click Implementations.
- At the bottom of the API Implementation page, click Upload an implementation archive, and then go to the implementation zip file on your system.
Note:
You also can upload an implementation from the command line. See Offline Debugging with the MCS Custom Code Test Tools.Managing Custom Code in Git
When you first generate a JavaScript scaffold for your implementation, you can have it pushed directly to a Git repository.
Setting Up a Git Repository for Custom Code
As a mobile cloud administrator, you can specify a Git repository in which to store your team’s custom API implementations. This enables your team’s developers to put their API implementations under version control starting with the creation of the JavaScript scaffold for the API.
You can use either an existing Git repository or create a new one in Oracle Developer Cloud Service to use. To do the latter, see Using Git in Oracle Developer Cloud Service in Using Developer Cloud Service.
Designating a Git Repository for Custom Code
- Click to open the side menu and select Administration.
- Click the link for Developer Cloud Service Git Integration.
- In the dialog, enter the URL for the Git repository in the Source Code Git Repository field and click Save.
Setting Up a Git Repository in Oracle Developer Cloud Service
If you don’t already have a Git repository for your custom code, you can set one up in Oracle Developer Cloud Service.
-
Make sure that you have a user account in Oracle Developer Cloud Service.
If you have a user account, you will have received an email notification with your account details.
If you don’t have a user account, contact your team’s identity domain administrator to set up an account for you (and for anybody else who will be using the repository). You need the Developer Service User role (
DEVELOPER_USER
) in Oracle Developer Cloud Service. -
Sign in to Oracle Developer Cloud Service.
-
Set up a project to hold the Git repository by following the steps at Creating a Project in the Oracle Developer Cloud Service docs.
As part of creating the project, a Git repository will be created.
See Managing Git Repositories in Oracle Developer Cloud Service.
Generating a Scaffold in a Git Repository
- Click to open the side menu, and then click APIs.
- Open the API that you want to implement.
- In the left navigation area of the API Designer, click Implementations.
- Click New Implementation > Push to Git Repository.
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.
Testing 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 is 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"
}
Testing Custom Code from the UI
As soon as you upload your custom API implementation, you can test it by clicking Test in the API Designer. You can also test from the API Catalog by selecting the API, and then clicking Test.
The test page displays all the operations. Click an operation, fill out the necessary fields, and then click Test Endpoint.
If the API isn’t configured for anonymous access, then you must provide a user name and the password. The 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. You can learn more about roles and anonymous access at Security in Custom APIs.
Note:
The API must either allow anonymous access or be associated with at least one role. If neither of these is true, then you’ll get an unauthenticated error.For detailed steps on how to test the API, see Testing a Platform or Custom API from the UI.
Offline Debugging with the MCS Custom Code Test Tools
Within the zip file of the client SDK for each platform is the mcs-tools.zip
file, which contains the MCS Custom Code Test Tools that you can use to iteratively debug your custom code.
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 MCS.
Detailed instructions on using the tools are located in the README.MD
file that is packaged within the mcs-tools.zip
.
You can get the client SDKs and the accompanying test tools from the Oracle Technology Network’s MCS download page.
Other Tools for Testing Custom Code Outside of the UI
Besides the Custom Code Test Tools, you can use tools that were designed for testing web services, such as cURL. To learn how to test your custom API from these tools, see Testing Platform and Custom APIs Remotely.
Note:
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.Accessing Logging Messages for Custom Code
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 to open the side menu. Next, click Administration, and then click Request History. Next, find the request, click View related log entries 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 Administration page 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 Administration page.
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. Click Log Level in the Logs page, change the log level for the mobile backend, and then rerun the test.
To learn about the different types of log messages and how to filter and correlate messages, see Viewing Log Messages. For use cases for diagnosing custom code and connector issues, see Diagnosing Custom Code.
Let's use the following endpoint to see how to custom code logging works. In this code, the ums.updateUser
method makes a PUT
request to /platform/users/~
.
service.put(
'/mobile/custom/incidentreport/key',
function (req, res) {
req.oracleMobile.ums.updateUser({key: req.body.key}).then(
function (result) {
res.send(result.statusCode, result.result);
},
function (error) {
res.send(error.statusCode, 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 PUT
request to /platform/users/~
ended. The top (later) message was logged when the service.put
call to /mobile/custom/incidentreport/key
ended.
Description of the illustration custom-code-loginfo.png
Logging Request and Response Messages
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. The following figure shows the log entries for a request to the example endpoint. In addition to the standard call messages, this log also has a message for each of the following events (reading from the bottom (earliest) up):
-
When the
PUT /mobile/custom/incidentreport/key
request was received. -
When the
ums.updateUser
method made thePUT /platform/users/~
request . -
When the response was returned by the
PUT /platform/users/~
operation. -
When the response was returned by the
PUT /mobile/custom/incidentreport/key
operation.
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
.
Description of the illustration custom-code-loginfo-body.png
Note that setting the CCC_LogBody
environment policy to true
might have a negative effect on performance.
Note:
By default, the body is truncated after 512 characters. Use theCCC_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.
Getting More Details
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 theFINEST
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 theFINEST
message for the response. The message type is eitherWARNING
orSEVERE
, depending on the status code.
Description of the illustration custom-code-logfinest-body.png
Note that setting the log level to FINEST
might have a negative effect on performance.
Minimizing 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, set the CCC_LogBody
environment policy to true
, and 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.
Description of the illustration custom-code-log-warning-body.png
Creating Custom Log Messages
To help with debugging, you can use the console
object in your code to generate your own messages, as described in Inserting Logging Into Custom Code, and then view them from the logs.
Troubleshooting Custom API Implementations
The following topics provide information about diagnosing and resolving common problems in custom code.
Diagnosing Syntax Errors
If a request failure is caused by a syntax error, then the Message Detail dialog box for the associated log message displays the module and line number where the error occurs, as shown here:
Description of the illustration custom-code-syntax-error.png
To learn about accessing log messages, see Viewing Log Messages.
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
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 options.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.