- Key Concepts
- Understanding the Runtime Context Object
Understanding the Runtime 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 mim OE 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 First, the developer downloads the latest version of the mim OE Runtime in the mimik GitHub repository found here. Then, the developer will write custom code that deploys it as a service. That service creates the mim OE instance as a subprocess. Context Object as well as the other objects that are provided the mim OE Runtime..
What You Will Learn from this Document
After reading this document, you will:
- Understand what a mim OE Context Object is
- Understand the attributes and functions of a mim OE Context Object
- Understand how to use the mim OE 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 mim OE 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 mim OE Runtime and how it supports microservices
- An understanding of microservices as implemented under the mim OE Runtime
Understanding mim OE Context Object
The mim OE Context Object is a JavaScript object that's available to a microservice running on a mim OE Runtime enabled device.
The mim OE 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 edge Service Mesh. The internal web server is exposed via localhost
to microservices running in the mim OE enable device. The built-in HTTP client can send requests to localhost
as well as to URLs external to the microservice.
In addition, the mim OE Context Object provides storage and network connection capabilities. (See Figure 1, below.)
Figure 1: The context object is injected into the incoming request handlers by the mim OE Runtime |
The Structure of the mim OE Context Object
The mim OE 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 mim OE 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)mim-OE│ ├── 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 mim OE Context Object
Working with the mim OE Context Object's http
object
One of the benefits that the mim OE Runtime provides by way of the Context object is that developers have direct access to an HTTP client that's built right into the mim OE 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 built-in 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 requestauthorization: string, // the authorization header settingsuccess: function(response){}, // the function that gets called upon success,// response is the response from target URLerror: function(err){} // the function that gets called upon err// err is the err object passed to the function}
Working with the mim OE Context Object storage
object
The mim OE Context Object provides a microservice with the capability to save data within an edge microservice using storage that is built into the mim OE Runtime. The mechanism to save data is the context.storage
object.
Listing 3 below shows an example of using the mim OE 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 mim OE Context Object's storage
object to save data
Working with the mim OE Context Object env
object
The mim OE Context Object has a property called env
that describes the environment variable that's in force in the device running the mim OE Runtime.
Listing 4 below demonstrates how to use the mim OE 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 mim OE Context Object's env
to get information from environment variables
The essential concept to understand about the mim OE Context Object is that the object provides runtime services and environment information to the developer, making programming a microservice running under the mim OE Runtime a more manageable undertaking.
Using the Context Object to Support multipart/form-data Requests
The HTTP web server that's built into the mim OE Runtime and available to the microservice running on a mim OE 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 /form2: Content-Type: multipart/form-data; boundary=--------------------------d74496d6695555e3:4: --------------------------d74496d6695555e5: Content-Disposition: form-data; name="person"6:7: Bobby Boy8: --------------------------d74496d6695555e9: Content-Disposition: form-data; name="myphoto"; filename="myfile.png"10: Content-Type: image/png11:12: 1001110100110010010010001013: 1001001000100010010001000014: 1111110001100100101010100115: and many more bits and bytes...16: --------------------------d74496d6695555e--
Once segmented, a multipart/form-data
request can be processed by the webserver within the mim OE Runtime.
A microservice running on a mim OE 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 code3: });
Understanding the internal workflow for a multipart/form-data
request
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 mim OE 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)
.
Figure 2: multipart/form-data requests are subject to a special workflow under the mim OE 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 within2: // 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 form8: 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 by16: //the programmer17: const todo = onFound(name, filename);18:19: if (todo.action == "store") {20: //if there are bytes, save them in the21: //part returned from the todo object22: const bytesSaved = segment.saveBytesToFile(todo.path);23:24: //pass the "save" metadata to the callback25: //provided by the programmer26: onStore(path, bytesSaved);27: } else if (todo.action == "get") {28: const value = segment.getText();29: //pass the name and value of the30: // associated with the segment to31: //the callback provided by the developer32: onGet(name, value);33: }34: else if (todo.action == "skip") {35: //If the todo action is "skip" move ont36: //to the next segment in the multipart37: //form38: continue;39: }40: else if (todo.action == "abort") {41: //If the todo action is "abort"42: //exit the process43: 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.
Programming the callback functions for handleFormRequest()
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).
formRequestOptions.found(name, filename)
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 itif(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;};
formRequestOptions.get(name, value)
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
formRequestOptions.store(path, bytesSaved)
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 mim OE 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
Using the Context Object to Support data decryption
All data that flows between the mim OE Runtime via the HTTP client, other edge devices and the edge Service Mesh on the backend is encrypted. The context
object provides the edge
helper object to facilitate decrypting the data received from the edge Service Mesh. Developers use the context.edge.decryptEncryptedNodesJson(decryptNodesOptions)
method to decrypt such data. (See Figure 4, below.)
Figure 3: The edge.decryptEncryptedNodesJson() method is a utility function that decrypts data received from the edge 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 edge Service Mesh. Take a look at Listing 6. Notice that a request is made to the discovery service that is built into the mim OE Runtime. This discovery service can locate other nodes within the edge 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 option25: 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 edge Service Mesh