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'spackage.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 aCCC_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.
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:
-
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.
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:
- Click to open the side menu, click Development , and then click APIs.
- Open the API that you want to download.
- In the left navigation area of the API Designer, click Implementations.
- Click JavaScript Scaffold to download the zip file.
- 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 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 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 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').
-
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.
-
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:
-
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.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.
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 |
|
|
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 |
|
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 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
.
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 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 |
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:
-
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 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 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. - 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.
Upload the Custom Code Module
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.
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 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 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.
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 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-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 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 totrue
. - Set the logging level to
WARNING
orSEVERE
.
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.
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 |
---|---|
|
General error when fulfilling the request would cause an invalid state, such as missing data or a validation error. |
|
Error due to a missing or invalid authentication token. |
|
Error due to user not having authorization or if the resource is unavailable. |
|
Error due to the resource not being found. |
|
Error that although the requested URL exists, the HTTP method isn’t applicable. |
|
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 theOracle-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 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.