Working with OCI Functions and Python: Request Context and Data

Working with OCI Functions and Python: Request Context and Data

Recently, I was working on a project where I had a function that needed to handle GET, POST, and DELETE methods differently and I started searching on how could I know in my Function the request method.

While searching the internet, I stumbled upon the Fn Python FDK source code, and I saw some information about the request context in here.

Let’s look at some information available within a running function.

Function Handler

When working with OCI Functions using the following programming languages: Go, Java, Node.js, Python, or Ruby, a Function Development Kit (FDK) is available. In a couple of words, the FDKs are libraries that simplify the life of developers.

I’ll be using the Python FDK in my examples.

Let’s look at a hello-world sample Python Function:

import json
import io

from fdk import response


def handler(ctx, data: io.BytesIO=None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name")
    except (Exception, ValueError) as ex:
        print(str(ex))
        pass

    return response.Response(
        ctx, response_data=json.dumps(
            {"message": "Hello {0}".format(name)}),
        headers={"Content-Type": "application/json"}
    )

Note: if you want to see how to set up your development environment for OCI Functions, check this article.

This handler method is invoked when your Function is called by other services.

If we take a look at the handler, there are two parameters passed: ctx and data.

  • ctx is a context object that provides information about the function and request-specific attributes
  • data is an object passed by the trigger request containing the payload – the HTTP request body, when calling the function using HTTP

Data

When the function is triggered by an HTTP request, a payload can be sent within the request body – the payload can be up to 6 Mb.

As we see in the example above, the data inside the body can be read as follows:

body = json.loads(data.getvalue())

Context

The interesting part is in the ctx object which offers a handful of attributes that give a lot of useful information:

  • app_id, type: str
    • this is the OCID of the application
  • fn_id, type: str
    • this is the OCID of the function
  • call_id, type: str
    • unique identifier of the request
  • content_type, type: str
    • request content type
  • deadline, type: str
    • request deadline
  • config, type: dict
    • Configuration data about the application & function
  • headers, type: str
    • HTTP headers included with the request
  • request_url, type: str
    • The URL used for the request
  • method, type: str
    • The HTTP method used for the request
  •  fn_format, type: str
    • Format of communication

Let’s look at an example:

import io
import json
import logging

from fdk import response


def handler(ctx, data: io.BytesIO=None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name")
    except (Exception, ValueError) as ex:
        print(str(ex))
    
    return response.Response(
        ctx, response_data=json.dumps(
            {"Message": "Hello {0}".format(name),
            "ctx.AppID" : ctx.AppID(),
            "ctx.FnID" : ctx.FnID(),
            "ctx.CallID" : ctx.CallID(),
            "ctx.Config" : dict(ctx.Config()),
            "ctx.Headers" : ctx.Headers(),
            "ctx.Deadline" : ctx.Deadline(),
            "ctx.RequestURL": ctx.RequestURL(),
            "ctx.Method": ctx.Method(),
            "ctx.Format" : ctx.Format()}),
        headers={"Content-Type": "application/json"}
    )

I’m going to return the context information in the response.

I have exposed my function through an API Gateway, and this is the response I get when invoking that endpoint via Postman:

{
    "Message": "Hello World",
    "ctx.AppID": "ocid1.fnapp.oc1.eu-frankfurt-1.aaaaaaaaagphdvq",
    "ctx.FnID": "ocid1.fnfunc.oc1.eu-frankfurt-1.aaaaaaaaabbfw6b",
    "ctx.CallID": "01E7G1XCBF1BT0E1GZJ000J0VY",
    "ctx.Config": {
        "PATH": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "HOSTNAME": "7b84d608d05e",
        "FN_LISTENER": "unix:/tmp/iofs/lsnr.sock",
        "FN_FORMAT": "http-stream",
        "FN_APP_ID": "ocid1.fnapp.oc1.eu-frankfurt-1.aaaaaaaaagphdv",
        "FN_CPUS": "100m",
        "FN_FN_ID": "ocid1.fnfunc.oc1.eu-frankfurt-1.aaaaaaaaabbfw6",
        "FN_MEMORY": "256",
        "FN_TYPE": "sync",
        "OCI_RESOURCE_PRINCIPAL_RPST": "/.oci-credentials/rpst",
        "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM": "/.oci-credentials/private.pem",
        "OCI_RESOURCE_PRINCIPAL_VERSION": "2.2",
        "OCI_RESOURCE_PRINCIPAL_REGION": "eu-frankfurt-1",
        "OCI_REGION_METADATA": "{\"realmDomainComponent\":\"eu-frankfurt-1.oraclecloud.com\",\"realmKey\":\"oc1\",\"regionIdentifier\":\"eu-frankfurt-1\",\"regionKey\":\"FRA\"}",
        "LANG": "C.UTF-8",
        "GPG_KEY": "0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D",
        "PYTHON_VERSION": "3.6.10",
        "PYTHON_PIP_VERSION": "20.0.2",
        "PYTHON_GET_PIP_URL": "https://github.com/pypa/get-pip/raw/d59197a3c169cef378a22428a3fa99d33e080a5d/get-pip.py",
        "PYTHON_GET_PIP_SHA256": "421ac1d44c0cf9730a088e337867d974b91bdce4ea2636099275071878cc189e",
        "PYTHONPATH": "/function:/python",
        "HOME": "/home/fn"
    },
    "ctx.Headers": {
        "host": "localhost",
        "user-agent": "PostmanRuntime/7.24.1",
        "transfer-encoding": "chunked",
        "content-type": "application/octet-stream",
        "date": "Mon, 04 May 2020 14:37:19 GMT",
        "fn-call-id": "01E7G1XCBF1BT0E1GZJ000J0VY",
        "fn-deadline": "2020-05-04T14:37:50Z",
        "accept": "*/*",
        "accept-encoding": "gzip",
        "authorization": "Signature version=\"1\",keyId=\"ocid1.tenancy.oc1..aaaaaaaa3ed2rov4/ocid1.user.oc1..aaaaaaaajw4xye/09:54:ba:d5:2b:36:5c\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host\",signature=\"ep/Pa1oPng+MWYlODdQ32i/lYqIgEk5onO+zaUe1e1IYxyrQc/BEzeyIZ0wcYqbggZ2qA==\"",
        "cache-control": "no-cache",
        "connection": "keep-alive",
        "orwarded": "for=0.0.0.0",
        "ost": "gbs2yh2kfafgxz4a.apigateway.eu-frankfurt-1.oci.customer-oci.com",
        "ostman-token": "256b1-72f4-49ad-ab3e-2c6de462",
        "x-forwarded-for": "0.0.0.0",
        "x-real-ip": "0.0.0.0",
        "fn-http-method": "GET",
        "fn-http-request-url": "/v1/hello",
        "fn-intent": "httprequest",
        "fn-invoke-type": "sync",
        "oci-subject-id": "ocid1.apigateway.oc1.eu-frankfurt-1.amaaaaaaxycoxjy",
        "oci-subject-tenancy-id": "ocid1.tenancy.oc1..aaaaaaaa3ed2rov",
        "oci-subject-type": "resource",
        "opc-request-id": "/35B9F56E3F7CE314021DEFB1F6D38AC1/01E7G1XCAV1BT0E1GZJ000J0VX",
        "x-content-sha256": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
    },
    "ctx.Deadline": "2020-05-04T14:37:50Z",
    "ctx.RequestURL": "/v1/hello",
    "ctx.Method": "GET",
    "ctx.Format": "http-stream"
}

In my use case, the ctx.Method was exactly what I was looking for – using this, I could know if the call was a GET, POST, or DELETE.

As OCI Functions is based on Fn Project which is an open-source tool, you can always found out more by browsing the code.

The Python FDK can be found here.

The code can be found on GitHub as well.

Ionut Adrian Vladu

I enjoy building python scripts for…everything! I am a Cloud enthusiast and I like to keep up with technology. When I'm not behind a computer, I like taking photos -- Visit My 500px profile -- or sit back and enjoy Formula 1 race weekends. Currently, working as a Tech Cloud Specialist @ Oracle
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments