mimik Developer Documentation

Understanding the edgeEngine Context Object

Purpose

The purpose of this document is to describe the form and function of the Context Object as well as the other objects that are injected automatically into microservices running under the edgeEngine Runtime.

Intended Readers

The intended readers of this document are software developers, system engineers, application architects, deployment personnel, and other technical professionals who are concerned with the conceptual and practical aspects of an edgeEngine Context Object as well as the other objects that are provided the edgeEngine Runtime..

What You Will Learn from this Document

After reading this document, you will:

  • Understand what an edgeEngine Context Object is
  • Understand the attributes and functions of an edgeEngine Context Object
  • Understand how to use the edgeEngine Context Object when programming a microservice
  • Under how to use the request object that is also made available to microservices running on devices that use the edgeEngine Runtime

What You Need to Know Before You Start

In order to get the full benefit from reading this document, you need to have:

  • A general understanding of microservices and the deployment of microservices in a distributed environment
  • An understanding of the edgeEngine Runtime and how it supports microservices
  • An understanding of microservices as implemented under the edgeEngine Runtime

Understanding edgeEngine Context Object

The edgeEngine Context Object is a JavaScript object that's available to a microservice running on an edgeEngine Runtime enabled device.

The edgeEngine Runtime provides both built-in HTTP client and built-in HTTP web server. The internal HTTP web server provides easy access to APIs that allows a microservice to interact with mimik Global Services as well as the edgeEngine Service Mesh. The internal web server is exposed via localhost to microservices running in the edgeEngine enable device. The built-in HTTP client can send requests to localhost as well as to URLs external to the microservice.

In addition, the edgeEngine Context Object provides storage and network connection capabilities. (See Figure 1, below.)

context-object-to-edgeEngine Runtime
Figure 1: The context object is injected into the incoming request handlers by the edgeEngine Runtime

The Structure of the edgeEngine Context Object

The edgeEngine Context Object is composed of a number of root level properties. The properties are env, storage, http and edge. These root level properties have subordinate properties that are either scalar, object or function types. Listing 1 below shows the structure of the edgeEngine Context Object with root level and subordinate properties. In addition, Listing 1 also shows the objects that are used as parameters by the Context Object subordinate objects and the request object. The request object is injected into the HTTP method handlers that a developer programs when creating a microservice.

context
├── env
├── storage
│ ├── getItem(key)
│ ├── setItem(key, value)
│ ├── eachItem(callback(key, value))
│ ├── removeItem(key)
│ ├── saveFile(fileName, base64EncodedString)
│ └── deleteFile(fileName)
├── http
│ └── request(httpClientOption)
└── edge
├── decryptEncryptedNodesJson(decryptNodesOption)
└── requestBep(requestBepOption)
request
├── url [read-only]
├── method [read-only]
├── authorization [read-only]
├── body [read-only]
└── handleFormRequest(formRequestOption)
response
├── statusCode
├── end(body)
└── writeMimeFile(filePath,mimeType)
httpClientOption
├── data
├── url
├── httpMethod
├── authorization
├── success(result)
└── error(result)
decryptNodesOption
├── type
├── token
├── data
├── success(result)
└── error(result)
requestBepOption
├── success(result)
└── error(result)
formRequestOptions
├── found(key, filename)
├── get(key, value)
└── store(path, size)

Listing 1 The structure of the edgeEngine Context Object

To learn the details of the context object and associated objects read the edgeEngine Context Object Reference.

Working with the edgeEngine Context Object's http object

One of the benefits that the edgeEngine Runtime provides by way of the Context object is that developers have direct access to an HTTP client that's built right into the edgeEngine Runtime. Operationally this means that developers do NOT need to create an HTTP client in order to facilitate communication from the given microservice to another endpoint.

The built-in HTTP client is represented by the context.http object. Developer can execute an HTTP request using the context.http.request(httpClientOptions) method.

Listing 2 below shows an example of using the Context Object's built-in HTTP client to make an HTTP GET call to the endpoint http://www.example.com/data. Notice the http.request() function is called at Line 2. The options for the http.request() function are declared from Lines 3 - 11.

1: app.post('/data', (request, response) => {
2: context.http.request(({
3: type: 'GET',
4: url: 'http://www.example.com/data',
5: authorization: 'Bearer token123',
6: success: (result) => {
7: response.end(result.data);
8: },
9: error: (err) => {
10: console.log(err);
11: }
12: }));
13: });

Listing 2: Using the builtin HTTP client to make a call to another endpoint on the Internet

The structure of the httpClientOptions parameter for the http.request() function is as follows:

{
url: string, // the target URL for the request
authorization: string, // the authorization header setting
success: function(response){}, // the function that gets called upon success,
// response is the response from target URL
error: function(err){} // the function that gets called upon err
// err is the err object passed to the function
}
To learn the details of the http object read the edgeEngine Context Object Reference for the http object.

Working with the edgeEngine Context Object storage object

The edgeEngine Context Object provides a microservice with the capability to save data within an edge microservice using storage that is built into the edgeEngine Runtime. The mechanism to save data is the context.storage object.

Listing 3 below shows an example of using the edgeEngine Context Object storage object to save data within a microservice. Notice that a reference to the storage object is extracted from the context at Line 2. Notice that the data is then saved using the function setItem(key, value) at Line 6. Data is saved to internal storage as a key value pair.

1: function makeTokenModel(context) {
2: const { storage } = context;
3: const PREFIX = '#token#';
4:
5: function insert(data) {
6: storage.setItem(`${PREFIX}${data.id}`, JSON.stringify(data));
7: return data;
8: }

Listing 3: Using the edgeEngine Context Object's storage object to save data

To learn the details of the storage object read the edgeEngine Context Object Reference for the storage object.

Working with the edgeEngine Context Object env object

The edgeEngine Context Object has a property called env that describes the environment variable that's in force in the device running the edgeEngine Runtime.

Listing 4 below demonstrates how to use the edgeEngine Context Object's env property to extract information from the host's environment variables to implement security verification logic.

1: function SecurityHandler(req, definition, apikey, next) {
2: const { ownerCode } = req.context.env;
3: if (apikey !== ownerCode) {
4: next(new Error('incorrect ownerCode'));
5: return;
6: }
7:
8: next();
9: }

Listing 4: Using the edgeEngine Context Object's env to get information from environment variables


The essential concept to understand about the edgeEngine Context Object is that the object provides runtime services and environment information to the developer, making programming a microservice running under the edgeEngine Runtime a more manageable undertaking.

Using the Context Object to Support multipart/form-data Requests

The HTTP web server that's built into the edgeEngine Runtime and available to the microservice running on an edgeEngine enabled devices is designed to support incoming multipart/form-data requests. A multipart/form-data request is useful when a microservice needs to store form submissions that have a variety of data types within a single request. An example of a multipart/form-data request is saving an image file along some textual data, as shown in the HTML example below:

<form action="/form" method="post" enctype="multipart/form-data">
name: <input type="text" name="person"><br>
file: <input type="file" name="myphoto"><br>
<input type="submit" value="Submit">
</form>

Also, a multipart/form-data request can be executed using curl like so:

curl -F person=anonymous -F myphoto=@myfile.png http://localhost:8083

Once multipart/form-data is submitted to a webserver, it's converted to a request that segments the various data types and fields. The result of the segmentation looks similar to the following:

1: POST /form
2: Content-Type: multipart/form-data; boundary=--------------------------d74496d6695555e
3:
4: --------------------------d74496d6695555e
5: Content-Disposition: form-data; name="person"
6:
7: Bobby Boy
8: --------------------------d74496d6695555e
9: Content-Disposition: form-data; name="myphoto"; filename="myfile.png"
10: Content-Type: image/png
11:
12: 10011101001100100100100010
13: 10010010001000100100010000
14: 11111100011001001010101001
15: and many more bits and bytes...
16: --------------------------d74496d6695555e--

Once segmented, a multipart/form-data request can be processed by the webserver within the edgeEngine Runtime.

A microservice running on an edgeEngine Runtime enabled device receives multipart/form-data from an external source by way of the request parameter that's injected into a given HTTP method handler for the microservice.

Take a look at the snippet of Node.js code below. It's an example of a microservice's HTTP POST handler. The request is represented by the req parameter in Line 1.

1: app.post('/', (req, res) => {
2: //some handler code
3: });

In typical client-server web applications, a programmer extracts the multipart/form-data from the request and processes it according to the particular need at hand. However, the webserver in the edgeEngine Runtime works differently. It has default behavior for processing multipart/form-data built in. This default behavior is executed with the programmer calls the method request.handleFormRequest(formRequestOptions).

Figure 2 below illustrates the the default behavior for processing a multipart/form-data request using the method request.handleFormRequest(formRequestOptions).

context-object-handleFormRequest
Figure 2: multipart/form-data requests are subject to a special workflow under the edgeEngine Runtime

The pseudo code below describes the logic of the internal behavior for processing a multipart/form-data request using the method request.handleFormRequest(formRequestOptions).

1: //This is the internal behavior within
2: // handleFormRequest(formRequestOptions)
3: function handleFormRequest(formRequestOptions) {
4: const onFound = formRequestOptions.found;
5: const onGet = formRequestOptions.get;
6: const onStore = formRequestOptions.store;
7: //traverse each part of the multipart form
8: do {
9: //get a segment (a.k.a part)
10: const segment = getNextSegmentFromStream();
11:
12: const name = segment.name;
13: const filename = segment.filename;
14:
15: //Call the onFound callback provided by
16: //the programmer
17: const todo = onFound(name, filename);
18:
19: if (todo.action == "store") {
20: //if there are bytes, save them in the
21: //part returned from the todo object
22: const bytesSaved = segment.saveBytesToFile(todo.path);
23:
24: //pass the "save" metadata to the callback
25: //provided by the programmer
26: onStore(path, bytesSaved);
27: } else if (todo.action == "get") {
28: const value = segment.getText();
29: //pass the name and value of the
30: // associated with the segment to
31: //the callback provided by the developer
32: onGet(name, value);
33: }
34: else if (todo.action == "skip") {
35: //If the todo action is "skip" move ont
36: //to the next segment in the multipart
37: //form
38: continue;
39: }
40: else if (todo.action == "abort") {
41: //If the todo action is "abort"
42: //exit the process
43: break;
44: }
45: }
46: while(gotMoreSegments())
47: }

Listing 4: Pseudo code that describes the internal behavior of the method handleFormRequest(formRequestOptions)

Notice declaration and assignment of value the variable onFound, onGet and onStore at Line 4, Line 5 and Line 6. The variables represent callback functions that get executed within the internal web server in reaction to events that occur when processing a multipart/form-data request. Developers can enhance multipart/form-data request behavior by defining logic in each callback function that gets executed when particular events occur internally within the handleFormRequest(formRequestOptions) method. These callback functions are assigned to the parameter formRequestOptions and passed to handleFormRequest() accordingly.

As mentioned above, developers can enhance processing multipart/form-data by way of the method handleFormRequest(formRequestOptions). Developers create behavior in callback functions that are assigned to the parameter formRequestOptions. There are three callback functions a developer can program. The callback functions are represented by the properties formRequestOptions.found, formRequestOptions.get and formRequestOptions.store.

The following describes the details relevant to programming the callback functions that are passed to handleFormRequest(formRequestOptions).


This callback function gets executed as each part in a multipart/form-data request is processed. When the part is a simple name-value pair, only the name is passed onto the callback function. When the part is a file submission, the value of the name attribute in the submitted form segment is passed onto the callback. Also, the value of the filename attribute is passed onto the callback. Additionally the callback function will return a value of type formRequestToDo.

Example:

formRequestOptions.found = (name, filename) => {
const formToDo = null;
if(filename) {
console.log(`Received a multipart/form-data for a file - name : ${name}, filename: ${filename}`);
//if it's a photo of a cat, don't process it
if(filename.startsWith(name.toLowerCase()) == 'cat'){
formToDo = {action: 'skip'};
}else{
formToDo = {action: 'store', path: filename};
}
}
else {
console.log(`Received a multipart/form-data for text - name : ${name}`);
formToDo = {action: 'get'};
}
return formToDo;
};

This callback function gets executed when an internal get event is fired when processing a part of a multipart/form-data request that has text. The callback gets passed the name and value attributes as defined in the form, like so:

<form action="/form" method="post" enctype="multipart/form-data">
.
.
name: <input type="text" name="person"><br>
.
.
</form>

Example:

formRequestOptions.get = (name, value) => {
console.log(`Received a multipart/form-data for a text - name : ${name}, value: ${value}`)
}

Received a multipart/form-data for a text - name : person, value: Bobby Boy


This callback function gets executed when an internal store event is fired when saving a files that are submitted in a multipart/form-data request.

When the method handleFormRequest(formRequestOptions) is called from within a request, the webserver that is internal to the edgeEngine Runtime will by default save the submitted files to disk. the location of the file and the size of the file in bytes will be passed to the callback store callback function via the parameters of path and bytesSaved respectively.

Example:

formRequestOptions.store = (path, bytesSaved) => {
console.log(`Saved a file in a multipart/form-data request - path : ${path}, bytesSaved: ${bytesSaved}`)
}

Saved a file in a multipart/form-data request - path : /current/edge-engine/directory/.edge/.edge-mcm/containers/3b31ca35-f3fc-41b5-a0a9-cdf50c749089-photos-v1/myphoto.png, bytesSaved: 568120


For more information about using the request.handleFormRequest() method to support multipart/form-data requests read the edgeEngine Context Object Reference.

Using the Context Object to Support data decryption

All data that flows between the edgeEngine Runtime via the HTTP client, other edge devices and the mimik Service Mesh on the backend is encrypted. The context object provides the edge helper object to facilitate decrypting the data received from the mimik Service Mesh. Developers use the context.edge.decryptEncryptedNodesJson(decryptNodesOptions) method to decrypt such data. (See Figure 4, below.)

context-object-edge-decrypt-nodes
Figure 3: The edge.decryptEncryptedNodesJson() method is a utility function that decrypts data received from the mimik Service Mesh

The method context.edge.decryptEncryptedNodesJson(decryptNodesOption) is asynchronous. Thus, developers decrypt the data by applying handler methods to the success and error properties of the decryptNodesOptions object that gets passes as a parameter to context.edge.decryptEncryptedNodesJson().

The code below in Listing 5 is an example of how the method context.edge.decryptEncryptedNodesJson(decryptNodesOption) is used to decrypt data retrieved from the mimik Service Mesh. Take a look at Listing 6. Notice that a request is made to the discovery service that is built into the edgeEngine time. This discovery service can locate other nodes within the edgeEngine Service Mesh. The http request is executed at Line 2.

Notice at Line 20 that the encrypted data that is returned by HTTP request is passed to the function context.edge.decryptEncryptedNodesJson(). The decrypted data is passed onto the success callback function at Line 24.

1: app.get('/localDevices', (req, res) => {
2: context.http.request(({
3: url: 'http://127.0.0.1:8083/mds/v1/nodes?clusters=linkLocal',
4: success: function (response) {
5: if (!request.authorization) {
6: response.status = 403;
7: response.end(JSON.stringify({ 'msg': 'missing bearer token' }, 2, null));
8: return;
9: }
10: const authorization = request.authorization.split(' ');
11: if (authorization.length < 2) {
12: response.status = 403;
13: response.end(JSON.stringify({ 'msg': 'missing bearer token' }, 2, null));
14: return;
15: }
16: const token = authorization[1];
17: const nodes = JSON.parse(r.data);
18: const data = JSON.stringify(nodes.data);
19:
20: context.edge.decryptEncryptedNodesJson({
21: type: 'local',
22: data,
23: token,
24: success: function (result) { // success option
25: response.end(JSON.stringify(JSON.parse(result.data), null, 2));
26: },
27: error: function (err) {
28: response.end(err.message);
29: }
30: });
31: },
32: error: function (err) {
33: response.end(err.message);
34: }
35: }));
36: });

Listing 5: Processing encrypted data returned from the mimik Service Mesh

For more information about using the context.edge object's edge.decryptEncryptedNodesJson() method to decrypt data received from mimik Global Services, read the edgeEngine Context Object Reference for the edge object.
© 2022 mimik Technology Inc. All Rights Reserved