You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by mr...@apache.org on 2019/04/01 22:13:14 UTC

[incubator-openwhisk-devtools] branch master updated: adding support for binary response (specifically images) (#235)

This is an automated email from the ASF dual-hosted git repository.

mrutkowski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-devtools.git


The following commit(s) were added to refs/heads/master by this push:
     new 97fa248  adding support for binary response (specifically images) (#235)
97fa248 is described below

commit 97fa24813bd3232b0310790729269942fece336b
Author: Priti Desai <pd...@us.ibm.com>
AuthorDate: Mon Apr 1 15:13:09 2019 -0700

    adding support for binary response (specifically images) (#235)
    
    * adding support for binary response
    
    * adding support for CORS
    
    * adding support for CORS
---
 .../runtimes/javascript/buildtemplate.yaml         |  4 ++
 .../runtimes/javascript/platform/knative.js        | 82 ++++++++++++++++++++--
 .../tests/webactionoptions/data-init-run.json      | 19 +++++
 .../webactionoptions/payload-knative-init-run.http | 24 +++++++
 4 files changed, 125 insertions(+), 4 deletions(-)

diff --git a/knative-build/runtimes/javascript/buildtemplate.yaml b/knative-build/runtimes/javascript/buildtemplate.yaml
index 82de16e..ab80f99 100644
--- a/knative-build/runtimes/javascript/buildtemplate.yaml
+++ b/knative-build/runtimes/javascript/buildtemplate.yaml
@@ -34,6 +34,9 @@ spec:
   - name: OW_HTTP_METHODS
     description: list of HTTP methods, any combination of [GET, POST, PUT, and DELETE], default is [POST]
     default: "[POST]"
+  - name: OW_ACTION_RAW
+    description: flag to indicate raw HTTP handling, interpret and process an incoming HTTP body directly
+    default: "false"
   steps:
   - name: add-ow-env-to-dockerfile
     image: "gcr.io/kaniko-project/executor:debug"
@@ -50,6 +53,7 @@ spec:
         ENV __OW_ACTION_MAIN "${OW_ACTION_MAIN}"
         ENV __OW_ACTION_BINARY "${OW_ACTION_BINARY}"
         ENV __OW_HTTP_METHODS "${OW_HTTP_METHODS}"
+        ENV __OW_ACTION_RAW "${OW_ACTION_RAW}"
       EOF
   - name: build-openwhisk-nodejs-runtime
     image: "gcr.io/kaniko-project/executor:latest"
diff --git a/knative-build/runtimes/javascript/platform/knative.js b/knative-build/runtimes/javascript/platform/knative.js
index fb8f9b0..cf08873 100644
--- a/knative-build/runtimes/javascript/platform/knative.js
+++ b/knative-build/runtimes/javascript/platform/knative.js
@@ -19,6 +19,7 @@ var dbg = require('../utils/debug');
 var DEBUG = new dbg();
 
 const OW_ENV_PREFIX = "__OW_";
+const CONTENT_TYPE = "Content-Type";
 
 /**
  * Pre-process the incoming
@@ -198,14 +199,23 @@ function preProcessRequest(req){
     DEBUG.functionEnd();
 }
 
-function postProcessResponse(result, res) {
+function postProcessResponse(req, result, res) {
     DEBUG.functionStart();
+
+    var content_types = {
+        json: 'application/json',
+        html: 'text/html',
+        png: 'image/png',
+        svg: 'image/svg+xml',
+    };
+
     // After getting the result back from an action, update the HTTP headers,
     // status code, and body based on its result if it includes one or more of the
     // following as top level JSON properties: headers, statusCode, body
     let statusCode = result.code;
     let headers = {};
     let body = result.response;
+    let contentTypeInHeader = false;
 
     // statusCode: default is 200 OK if body is not empty otherwise 204 No Content
     if (result.response.statusCode !== undefined) {
@@ -220,6 +230,27 @@ function postProcessResponse(result, res) {
         delete body['headers'];
     }
 
+    // addressing content-type v/s Content-Type
+    // marking 'Content-Type' as standard inside header
+    if (headers.hasOwnProperty(CONTENT_TYPE.toLowerCase())) {
+        headers[CONTENT_TYPE] = headers[CONTENT_TYPE.toLowerCase()];
+        delete headers[CONTENT_TYPE.toLowerCase()];
+    }
+
+    //  If a content-type header is not declared in the action result’s headers,
+    //  the body is interpreted as application/json for non-string values,
+    //  and text/html otherwise.
+    if (!headers.hasOwnProperty(CONTENT_TYPE)) {
+        if (result.response.body !== undefined && typeof result.response.body == "string") {
+            headers[CONTENT_TYPE] = content_types.html;
+        } else {
+            headers[CONTENT_TYPE] = content_types.json;
+        }
+    } else {
+        contentTypeInHeader = true;
+    }
+
+
     // body: a string which is either a plain text, JSON object, or a base64 encoded string for binary data (default is "")
     // body is considered empty if it is null, "", or undefined
     if (result.response.body !== undefined) {
@@ -229,12 +260,39 @@ function postProcessResponse(result, res) {
         delete body['binary'];
     }
 
+    //When the content-type is defined, check if the response is binary data or
+    // plain text and decode the plain text using a base64 decoder whenever needed.
+    // Should the body fail to decoded correctly, return an error to the caller.
+    if (contentTypeInHeader && headers[CONTENT_TYPE].lastIndexOf("image", 0) === 0) {
+        if (typeof body === "string") {
+            body = Buffer.from(body, 'base64')
+            headers["Content-Transfer-Encoding"] = "binary";
+        }
+        // TODO: throw an error if body can not be decoded
+    }
+
+
     // statusCode: set it to 204 No Content if body is empty
     if (statusCode === 200 && body === "") {
         statusCode = 204;
     }
 
-    res.header(headers).status(statusCode).json(body);
+    if (!headers.hasOwnProperty('Access-Control-Allow-Origin')) {
+        headers['Access-Control-Allow-Origin'] = '*';
+    }
+    if (!headers.hasOwnProperty('Access-Control-Allow-Methods')) {
+        headers['Access-Control-Allow-Methods'] = 'OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH';
+    }
+    // the header Access-Control-Request-Headers is echoed back as the header Access-Control-Allow-Headers if it is present in the HTTP request.
+    // Otherwise, a default value is generated.
+    if (!headers.hasOwnProperty['Access-Control-Allow-Headers']) {
+        headers['Access-Control-Allow-Headers'] = 'Authorization, Origin, X - Requested - With, Content - Type, Accept, User - Agent';
+        if (typeof req.headers['Access-Control-Request-Headers'] !== "undefined") {
+            headers['Access-Control-Allow-Headers'] = req.headers['Access-Control-Request-Headers'];
+        }
+    }
+
+    res.header(headers).status(statusCode).send(body);
     DEBUG.functionEnd();
 }
 
@@ -248,6 +306,7 @@ function PlatformKnativeImpl(platformFactory) {
         post: 'POST',
         put: 'PUT',
         delete: 'DELETE',
+        options: 'OPTIONS',
     };
 
     const DEFAULT_METHOD = [ 'POST' ];
@@ -268,7 +327,7 @@ function PlatformKnativeImpl(platformFactory) {
 
             service.initCode(req).then(function () {
                 service.runCode(req).then(function (result) {
-                    postProcessResponse(result, res)
+                    postProcessResponse(req, result, res)
                 });
             }).catch(function (error) {
                 console.error(error);
@@ -287,10 +346,22 @@ function PlatformKnativeImpl(platformFactory) {
     this.registerHandlers = function(app, platform) {
         var httpMethods = process.env.__OW_HTTP_METHODS;
         // default to "[post]" HTTP method if not defined
-        if (typeof httpMethods === "undefined" || !Array.isArray(httpMethods)) {
+        if (typeof httpMethods === "undefined") {
+            console.error("__OW_HTTP_METHODS is undefined; defaulting to '[post]' ...");
+            httpMethods = DEFAULT_METHOD;
+        } else {
+            if (httpMethods.startsWith('[') && httpMethods.endsWith(']')) {
+                httpMethods = httpMethods.substr(1, httpMethods.length);
+                httpMethods = httpMethods.substr(0, httpMethods.length -1);
+                httpMethods = httpMethods.split(',')
+            }
+        }
+        // default to "[post]" HTTP method if specified methods are not valid
+        if (!Array.isArray(httpMethods) || !Array.length) {
             console.error("__OW_HTTP_METHODS is undefined; defaulting to '[post]' ...");
             httpMethods = DEFAULT_METHOD;
         }
+
         httpMethods.forEach(function (method) {
             switch (method.toUpperCase()) {
                 case http_method.get:
@@ -305,6 +376,9 @@ function PlatformKnativeImpl(platformFactory) {
                 case http_method.delete:
                     app.delete('/', platform.run);
                     break;
+                case http_method.options:
+                    app.options('/', platform.run);
+                    break;
                 default:
                     console.error("Environment variable '__OW_HTTP_METHODS' has an unrecognized value (" + method + ").");
             }
diff --git a/knative-build/runtimes/javascript/tests/webactionoptions/data-init-run.json b/knative-build/runtimes/javascript/tests/webactionoptions/data-init-run.json
new file mode 100644
index 0000000..d045bed
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionoptions/data-init-run.json
@@ -0,0 +1,19 @@
+{
+  "init": {
+    "name" : "nodejs-web-action-options",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main(params) { if (params.__ow_method == 'OPTIONS') { return { headers: { 'Access-Control-Allow-Methods': 'OPTIONS, GET', 'Access-Control-Allow-Origin': 'example.com' }, statusCode: 200 }}}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-options",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionoptions/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/webactionoptions/payload-knative-init-run.http
new file mode 100644
index 0000000..5556d70
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionoptions/payload-knative-init-run.http
@@ -0,0 +1,24 @@
+OPTIONS http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-web-action-options",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main(params) { if (params.__ow_method == 'OPTIONS') { return { headers: { 'Access-Control-Allow-Methods': 'OPTIONS, GET', 'Access-Control-Allow-Origin': 'example.com' }, statusCode: 200 }}}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-options",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
+
+###