Using Runtime Context object to access HTTP resources and pass custom configuration params to Oracle FaaS (fn project)

Pathikreet Dutta
9 min readMar 12, 2021

--

Oracle Functions is based on Fn Project. Fn Project is an open source, container native, serverless platform that can be run anywhere — any cloud or on-premises. Fn Project is easy to use, extensible, and performant. You can download and install the open source distribution of Fn Project, develop and test a function locally, and then use the same tooling to deploy that function to Oracle Functions.

You can access Oracle Functions using the Console, the CLI, and the REST API. You can invoke the functions you deploy to Oracle Functions using the Fn CLI or the OCI CLI or the OCI SDKs or by making signed HTTP requests or from other Oracle Cloud Services(see how).

The code in functions you deploy to Oracle Functions will typically require values for different parameters. Some pre-defined parameters are available to your functions as environment variables. But you’ll often want your functions to use parameters that you’ve defined yourself. Or you might even have a use case to access the HTTP resources. For example, you might have to access OCI Object storage to read / write some files. Or you might have to read the incoming HTTP request or set a custom response header or HTTP status code.

Here comes the Runtime Context Object to the rescue. This Context object (ctx) is an optional parameter that can be passed by the fn code to the callback function defined to handle the request.

In this article, we will learn how to leverage the ctx object in NodeJS. In NodeJS, a new function is initialized with a wrapper application called func.js by default. The image shows the creation of a new function and the contents of the wrapper function func.js that is generated.

Credit: Lucas Jellema

The input parameter that is defined in the callback function will be provided by the Fn framework when a request is handled. This parameter contains the HTTP Body of the request, typically JSON content in a POST request.

If we want to leverage the runtime context object (ctx), then we will need to add another optional input parameter to the callback function as below:

Credit: Lucas Jellema

This ctx object contains the complete Function invocation details. Click here for complete docs. The ctxobject contains other context information about the request such as:

  • ctx.config : An Object containing function config variables (both environment and user defined) (read only)
  • ctx.headers : an object containing input headers for the event as lists of strings (read only)
  • ctx.deadline : a Date object indicating when the function call must be processed by
  • ctx.callID : The call ID of the current call
  • ctx.fnID : The Function ID of the current function
  • ctx.memory : Amount of ram in MB allocated to this function
  • ctx.contentType : The incoming request content type (if set, otherwise null)
  • ctx.setResponseHeader(key,values...) : Sets a response header to one or more values
  • ctx.addResponseHeader(key,values...) : Appends values to an existing response header
  • ctx.responseContentType : set/read the response content type of the function (read/write)
  • ctx.httpGateway : The HTTP Gateway context for this function (if set)

As mentioned earlier, for this article we will cover 2 different use cases:

  1. Accessing HTTP resources and setting custom HTTP response codes.
  2. Passing custom configuration parameters to function.

Accessing HTTP resources and setting custom HTTP response code

To access HTTP resources (headers, method, etc.) we can leverage the HTTPGatewayContext(hctx) object from the Context(ctx) object. The HTTPGatewayContext object has a similar interface to Context but accesses only the HTTP headers of the function:

  • hctx.requestURL : Get the http request URL of the function as received by the gateway (null if not set)
  • hctx.method : Get the HTTP request method used to invoke the gateway
  • hctx.headers : Get the HTTP headers of the incoming request (read-only)
  • hctx.statusCode : Set the the HTTP status code of the HTTP response
  • hctx.setResponseHeader(key,values..) : Set custom response headers as key/value pairs
  • hctx.addResponseHeader(key,values) : Set/add response header

We can access/read the HTTP headers, for example “Authorization”, using the hctx.headers method of the HTTPGatewayContext object as depicted below in the snippet:

const fdk=require('@fnproject/fdk');fdk.handle(function(input, ctx){

let hctx = ctx.httpGateway;
console.log("Request URL" , hctx.requestURL);

console.log("Authorization header:" , hctx.getHeader("Authorization"));
console.log( hctx.headers); // prints e.g. { "Content-Type": ["application/json"],"Accept":["application/json","text/plain"] }
});

We can also set the outbound response headers and the HTTP status code can be modified in a similar way:

const fdk=require('@fnproject/fdk');

fdk.handle(function(input, ctx){
let hctx = ctx.httpGateway;

hctx.setResponseHeader("Location","http://example.com");
hctx.statusCode = 201;
});

Being able to set a custom HTTP response code is particularly helpful in custom validation and error handling within the function code. Let me explain the above statement.

If you invoke an Fn code via the signed HTTP request in OCI, Fn framework returns a HTTP response code in line with the execution of the Fn Code. This means that Fn server will throw back a HTTP response code just like any other HTTP server would. Now consider a use case where you need to call another REST based third party from within the Fn code. That third party API will return an HTTP response code. But Fn framework will not propagate the response code that it received from the third party. Instead it will overwrite that with its own response code. For example, your third party API can return a response code of 201 for a successful scenario, but the Fn framework will always return a status of 200 to your caller in this scenario. Thus you are not aware of the actual response code from the third party in your caller application.

To mitigate this roadblock, again HTTPGatewayContext object comes to play. We can leverage the hctx.statusCode method and set the response code received from the third party API in it. The Fn framework will then set this as a custom HTTP response header with the name as Fn-Http-Status.

Similarly, you can leverage the above technique for data validations and error handling as well. See below:

const fdk=require('@fnproject/fdk');
const httpRequest = require('https');
fdk.handle(async (input, ctx) => {
let hctx = ctx.httpGateway;
let data = {
title: 'foo',
body: 'bar',
userId: 1,
};

try{
// Call ThirdParty API
console.log('Calling Third Party API');
const apiRes = await callThirdParty(data, "https://jsonplaceholder.typicode.com/posts", "POST");

//Set "Fn-Http-Status" as a custom HTTP response header
hctx.statusCode = apiRes.status;
return apiRes.payload;
} catch (error) {
console.log('Promise Rejected Reason :: ', error);
//Set "Fn-Http-Status" Code as a response header
hctx.statusCode = 500;
return {
'Errors': {
'Error': [
{
'ReasonCode': 'ORACLE-FN_INTERNAL_ERR',
'Description': error.stack
}
]
}
};
}
});
async function callThirdParty(payload, uri, httpMethod) {
return new Promise(function (resolve, reject) {
let responseData = '';

var requestHeaders = {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json'
};

const options = {
method: httpMethod,
headers: requestHeaders
};
const request = httpRequest.request(uri, options, response => {
console.log('Status : ', response.statusCode);
//console.log('Headers : ', response.headers);
response.on('data', dataChunk => {
responseData += dataChunk;
});
response.on('end', () => {
//console.log('Response :: ', responseData);
try {
var apiResponse = {
'status': response.statusCode,
'headers': response.headers,
'payload': JSON.parse(responseData)
};
resolve(apiResponse);
} catch (error) {
//Handle all Non JSON responses returned for HTTP 5xx
var errorResponse = {
'status': response.statusCode,
'error': error,
'payload': responseData
};
reject(errorResponse);
}

});
});
request.on('error', error => {
console.log('ERROR :: ', error);
reject(error);
});
request.write(JSON.stringify(payload));
request.end();
});
};

Full example code repository: https://github.com/pathikreet/ocifunctions/tree/master/NodeJS/FnRuntimeContextSample

Passing custom configuration parameters to function

While developing any solution or product, you might have come across a use cases to have some constant parameters that can be used across the application or the function.

Let us consider a use case where you need to connect to the OCI Private Object Storage to read/write a file in the Object Storage. Details on how to connect to OCI Private Object Storage from your running Fn container is not in scope of this article. In a nutshell you will need to use Resource Principal made available to your running functions container by OCI. Full demo code base for the same is attached below.

So to connect to OCI Object storage, the constant parameters that will be needed across all instances of your Fn container are namespace and bucketName. We can define these 2 constants a configuration parameters on either,

  • application-wide, meaning they are passed to every function in an application
  • function-specific, meaning they are passed to the particular function for which they are defined (function-specific parameters override application-wide parameters with the same name)

To create custom configuration parameters, you can use:

  • the config: section of a function's func.yaml file, to define function-specific custom configuration parameters
  • the Console and the Fn Project CLI, to define both application-wide and function-specific custom configuration parameters

Oracle Functions combines all the custom configuration parameters (both application-wide and function-specific) in the application into a single, serially-encoded configuration object with a maximum allowable size of 4Kb.

In this article, we will describe how to set custom configuration parameters using the OCI Console and subsequently how to access them in the Fn code using the ctx object.

To check how to add configuration parameters using the Fn Project CLI or the OCI API click here

Setting Configuration parameters using the OCI Console

To specify custom configuration parameters to pass to functions using the Console:

  1. Log in to the Console as a functions developer.
  2. In the Console, open the navigation menu. Under Solutions and Platform, go to Developer Services and click Functions.
  3. Select the region you are using with Oracle Functions. Oracle recommends that you use the same region as the Docker registry that’s specified in the Fn Project CLI context (see 6. Create an Fn Project CLI Context to Connect to Oracle Cloud Infrastructure).
  4. Select the compartment specified in the Fn Project CLI context (see 6. Create an Fn Project CLI Context to Connect to Oracle Cloud Infrastructure). The Applications page shows the applications defined in the compartment.
  5. Click the name of the application containing functions to which you want to pass custom configuration parameters:
  6. To pass one or more custom configuration parameters to every function in the application, click Configuration to see the Configuration section for the application.
  7. To pass one or more custom configuration parameters to a particular function, click the function’s name to see the Configuration section for the function.
  8. In the Configuration section, specify details for the first custom configuration parameter:
  9. Key: The name of the custom configuration parameter. The name must only contain alphanumeric characters and underscores, and must not start with a number. For example, namespace
  10. Value: A value for the custom configuration parameter. The value must only contain printable unicode characters. For example, jdoe
  11. Click the plus button to save the new custom configuration parameter.
  12. Oracle Functions combines the key-value pairs for all the custom configuration parameters (both application-wide and function-specific) in the application into a single, serially-encoded configuration object with a maximum allowable size of 4Kb. You cannot save the new custom configuration parameter if the size of the serially-encoded configuration object would be greater than 4Kb.
  13. (Optional) Enter additional custom configuration parameters as required.

Accessing Custom Configuration parameters inside the Fn container

Once the configuration parameters are set, we now need to access/read the configuration parameter from the same Context object by leveraging the ctx.config method.

We have set 2 custom configuration parameters as namespace and bucketName. Please check the snippet below on how to read and print them:

const fdk=require('@fnproject/fdk');fdk.handle(async (input, ctx) => {
let config = ctx.config;

console.log('OCI Namespace : ', config.namespace);
console.log('OCI Bucket Name : ', config.bucketName);

});

Both application-wide and function configuration parameters can be accessed by the above mentioned technique using the ctx.config object.

Full example code repository: https://github.com/pathikreet/ocifunctions/tree/master/NodeJS/OCIRestRPAuth

Happy Learning !!!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response