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

[incubator-openwhisk-devtools] branch master updated: PlatformFactory with Knative and OW impls (#234)

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

pdesai 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 6c17bdf  PlatformFactory with Knative and OW impls (#234)
6c17bdf is described below

commit 6c17bdf03c839863ea819f5f71423e20eb94c6c7
Author: Matt Rutkowski <mr...@us.ibm.com>
AuthorDate: Mon Apr 1 12:25:30 2019 -0500

    PlatformFactory with Knative and OW impls (#234)
    
    * PlatformFactory with Knative and OW impls
    
    * Get the knative impl working
    
    * Get the knative impl working
    
    * Get the knative impl working
    
    * Add OpenWhisk platform impl.
    
    * Add OpenWhisk platform impl.
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
    
    * Clean up platform exports and method signatures
---
 .gitignore                                         |  40 ++-
 README.md                                          |   1 +
 knative-build/runtimes/javascript/app.js           | 110 ++----
 .../platform/{platform.js => knative.js}           |  62 ++--
 .../runtimes/javascript/platform/openwhisk.js      |  34 ++
 .../runtimes/javascript/platform/platform.js       | 400 +++++++--------------
 knative-build/runtimes/javascript/src/service.js   |  12 +
 .../helloworldwithparams/payload-knative-init.http |  13 +
 .../helloworldwithparams/payload-knative-run.http  |  19 +
 9 files changed, 301 insertions(+), 390 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3d8fc6e..99782a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,39 @@
-openwhisk-src
-.wskprops
-
-*.iml
+# IDE
 .idea
+*.iml
+.project
+.settings
 .vscode
+*.code-workspace
 **/.sts4-cache
+
+# Go Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Kubernetes Secrets
+docker-secret.yaml
+
+# Kubernetes ServiceAccount (customized with username)
+build.yaml
+service.yaml
+
+# NodeJS
+node_modules/
+package-lock.json
+
+# Swift
+Packages/
+
+# OpenWhisk project-specific
+openwhisk-src
+.wskprops
diff --git a/README.md b/README.md
index 2903649..0f4a2eb 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ This repository is part of [Apache OpenWhisk](http://openwhisk.incubator.apache.
 * [java-action-archetype](java-action-archetype/README.md) This archetype helps to generate the Java Action template project.
 * [node-local](node-local/README.md) allows testing individual OpenWhisk functions locally, using only node.js. This is ideal if you are writing node.js functions to run in OpenWhisk, but need to emulate some of OpenWhisk's behavior in creating `params` and expecting promises.
 * [maven-java](maven-java/README.md) allows testing OpenWhisk Java Actions. This shows how to package the function dependencies e.g. external jar.
+* [knative-build](knative-build/README.md) contains Knative Build Templates along with modified versions of their respective OpenWhisk Action Runtimes that can be used to Build and Serve Knative compatible applications on Kubernetes.
 
 ## Travis builds
 
diff --git a/knative-build/runtimes/javascript/app.js b/knative-build/runtimes/javascript/app.js
index 3f0eb26..b4714d3 100644
--- a/knative-build/runtimes/javascript/app.js
+++ b/knative-build/runtimes/javascript/app.js
@@ -16,7 +16,7 @@
  */
 var dbg = require('./utils/debug');
 var DEBUG = new dbg();
-DEBUG.trace("Hello World from NodeJS runtime");
+DEBUG.trace("NodeJS runtime initializing...");
 DEBUG.dumpObject(process.env, "process.env");
 
 // __OW_ALLOW_CONCURRENT: see docs/concurrency.md
@@ -27,11 +27,6 @@ var config = {
         'requestBodyLimit': "48mb"
 };
 
-var runtime_platform = {
-    openwhisk: 'openwhisk',
-    knative: 'knative',
-};
-
 var bodyParser = require('body-parser');
 var express    = require('express');
 
@@ -53,83 +48,52 @@ var service = require('./src/service').getService(config);
 app.use(bodyParser.json({ limit: config.requestBodyLimit }));
 
 // identify the target Serverless platform
+const platformFactory = require('./platform/platform.js');
+const factory = new platformFactory(app, config, service);
 var targetPlatform = process.env.__OW_RUNTIME_PLATFORM;
 
 // default to "openwhisk" platform initialization if not defined
-if( typeof targetPlatform === "undefined") {
-    console.error("__OW_RUNTIME_PLATFORM is undefined; defaulting to 'openwhisk' ...");
-    targetPlatform = runtime_platform.openwhisk;
+// TODO export isvalid() from platform, if undefined this is OK to default, but if not valid value then error out
+if(typeof targetPlatform === "undefined") {
+    targetPlatform = platformFactory.PLATFORM_OPENWHISK;
+    console.log("__OW_RUNTIME_PLATFORM is undefined; defaulting to 'openwhisk' ...");
+}
+
+if(!platformFactory.isSupportedPlatform(targetPlatform)){
+    console.error("__OW_RUNTIME_PLATFORM ("+targetPlatform+") is not supported by the runtime.");
+    process.exit(9);
 }
 
 /**
  * Register different endpoint handlers depending on target PLATFORM and its expected behavior.
- * In addition, register request pre-processors and/or response post-processors as needed.
+ * In addition, register request pre-processors and/or response post-processors as needed
+ * to move data where the platform and function author expects it to be.
  */
-if (targetPlatform === runtime_platform.openwhisk ) {
-    app.post('/init', wrapEndpoint(service.initCode));
-    app.post('/run', wrapEndpoint(service.runCode));
-} else if (targetPlatform === runtime_platform.knative) {
-    var platformFactory = require('./platform/platform.js');
-    var platform = new platformFactory("knative", service, config);
-    platform.registerHandlers(app, platform)
-} else {
-    console.error("Environment variable '__OW_RUNTIME_PLATFORM' has an unrecognized value ("+targetPlatform+").");
-}
 
-// short-circuit any requests to invalid routes (endpoints) that we have no handlers for.
-app.use(function (req, res, next) {
-    res.status(500).json({error: "Bad request."});
-});
+var platformImpl = factory.createPlatformImpl(targetPlatform);
 
-// register a default error handler. This effectively only gets called when invalid JSON is received (JSON Parser)
-// and we do not wish the default handler to error with a 400 and send back HTML in the body of the response.
-app.use(function (err, req, res, next) {
-    console.log(err.stackTrace);
-    res.status(500).json({error: "Bad request."});
-});
+if(typeof platformImpl !== "undefined"){
 
-service.start(app);
+    platformImpl.registerHandlers(app, platformImpl);
 
-/**
- * Wraps an endpoint written to return a Promise into an express endpoint,
- * producing the appropriate HTTP response and closing it for all controllable
- * failure modes.
- *
- * The expected signature for the promise value (both completed and failed)
- * is { code: int, response: object }.
- *
- * @param ep a request=>promise function
- * @returns an express endpoint handler
- */
-function wrapEndpoint(ep) {
-    DEBUG.functionStart("wrapping: " + ep.name);
-    DEBUG.functionEnd("returning wrapper: " + ep.name);
-    return function (req, res) {
-        try {
-            ep(req).then(function (result) {
-                res.status(result.code).json(result.response);
-                DEBUG.dumpObject(result,"result");
-                DEBUG.dumpObject(res,"response");
-                DEBUG.functionEndSuccess("wrapper for: " + ep.name);
-            }).catch(function (error) {
-                if (typeof error.code === "number" && typeof error.response !== "undefined") {
-                    res.status(error.code).json(error.response);
-                } else {
-                    console.error("[wrapEndpoint]", "invalid errored promise", JSON.stringify(error));
-                    res.status(500).json({ error: "Internal error." });
-                }
-                DEBUG.dumpObject(error,"error");
-                DEBUG.dumpObject(res,"response");
-                DEBUG.functionEndError(error, "wrapper for: " + ep.name);
-            });
-        } catch (e) {
-            // This should not happen, as the contract for the endpoints is to
-            // never (externally) throw, and wrap failures in the promise instead,
-            // but, as they say, better safe than sorry.
-            console.error("[wrapEndpoint]", "exception caught", e.message);
-            res.status(500).json({ error: "Internal error (exception)." });
-            DEBUG.dumpObject(error,"error");
-            DEBUG.functionEndError(error, ep.name);
-        }
-    }
+    // short-circuit any requests to invalid routes (endpoints) that we have no handlers for.
+    app.use(function (req, res, next) {
+        res.status(500).json({error: "Bad request."});
+    });
+
+    /**
+     * Register a default error handler. This effectively only gets called when invalid JSON is received
+     * (JSON Parser) and we do not wish the default handler to error with a 400 and send back HTML in the
+     * body of the response.
+     */
+    app.use(function (err, req, res, next) {
+        console.log(err.stackTrace);
+        res.status(500).json({error: "Bad request."});
+    });
+
+    service.start(app);
+
+} else {
+    console.error("Failed to initialize __OW_RUNTIME_PLATFORM ("+targetPlatform+").");
+    process.exit(10);
 }
diff --git a/knative-build/runtimes/javascript/platform/platform.js b/knative-build/runtimes/javascript/platform/knative.js
similarity index 92%
copy from knative-build/runtimes/javascript/platform/platform.js
copy to knative-build/runtimes/javascript/platform/knative.js
index acc73bb..fb8f9b0 100644
--- a/knative-build/runtimes/javascript/platform/platform.js
+++ b/knative-build/runtimes/javascript/platform/knative.js
@@ -32,7 +32,7 @@ function preProcessInitData(env, initdata, valuedata, activationdata) {
         // TODO: Throw error if CODE is NOT defined!
         var code = (typeof env.__OW_ACTION_CODE === 'undefined') ? "" : env.__OW_ACTION_CODE;
         var binary = (typeof env.__OW_ACTION_BINARY === 'undefined') ? false : env.__OW_ACTION_BINARY.toLowerCase() === "true";
-        // TODO: deault to empty?
+        // TODO: default to empty?
         var actionName = (typeof env.__OW_ACTION_NAME === 'undefined') ? "" : env.__OW_ACTION_NAME;
         var raw = (typeof env.__OW_ACTION_RAW === 'undefined') ? false : env.__OW_ACTION_RAW.toLowerCase() === "true";
 
@@ -120,14 +120,12 @@ function preProcessActivationData(env, activationdata) {
     DEBUG.functionEnd();
 }
 
-
 /**
  * Pre-process HTTP request details, send them as parameters to the action input argument
  * __ow_method, __ow_headers, __ow_path, __ow_user, __ow_body, and __ow_query
  */
 function preProcessHTTPContext(req, valueData) {
-    DEBUG.functionStart()
-
+    DEBUG.functionStart();
     try {
         if (valueData.raw) {
             if (typeof req.body.value === "string" && req.body.value !== undefined) {
@@ -140,8 +138,7 @@ function preProcessHTTPContext(req, valueData) {
                 delete body.binary;
                 delete body.raw;
                 var bodyStr = JSON.stringify(body);
-                var bodyBase64 = Buffer.from(bodyStr).toString("base64");
-                valueData.__ow_body = bodyBase64;
+                valueData.__ow_body = Buffer.from(bodyStr).toString("base64");;
             }
             valueData.__ow_query = req.query;
         }
@@ -169,7 +166,6 @@ function preProcessHTTPContext(req, valueData) {
  */
 function preProcessRequest(req){
     DEBUG.functionStart();
-
     try{
         // Get or create valid references to the various data we might encounter
         // in a request such as Init., Activation and function parameter data.
@@ -193,22 +189,17 @@ function preProcessRequest(req){
         // process per-activation (i.e, "run") data
         preProcessActivationData(env, activationData);
 
-
-
-
     } catch(e){
         console.error(e);
         DEBUG.functionEndError(e.message);
         // TODO: test this error is handled properly and results in an HTTP error response
         throw("Unable to initialize the runtime: " + e.message);
     }
-
     DEBUG.functionEnd();
 }
 
 function postProcessResponse(result, res) {
     DEBUG.functionStart();
-
     // 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
@@ -244,27 +235,36 @@ function postProcessResponse(result, res) {
     }
 
     res.header(headers).status(statusCode).json(body);
-
     DEBUG.functionEnd();
 }
 
 
-function PlatformFactory(id, svc, cfg) {
+function PlatformKnativeImpl(platformFactory) {
+    DEBUG.functionStart();
+    DEBUG.dumpObject(platformFactory, "platformFactory" );
+
+    var http_method = {
+        get: 'GET',
+        post: 'POST',
+        put: 'PUT',
+        delete: 'DELETE',
+    };
 
-    DEBUG.dumpObject(id, "Platform" );
-    DEBUG.dumpObject(svc, "Service" );
-    DEBUG.dumpObject(cfg, "Config" );
+    const DEFAULT_METHOD = [ 'POST' ];
 
-    var service = svc;
-    //var config = cfg;  // TODO: use this to pass future config. information uniformly to any impl.
-    var isInitialized = false;
+    // Provide access to common runtime services
+    var service = platformFactory.service;
 
+    // TODO: Should we use app.WrapEndpoint()?
     this.run = function(req, res) {
 
         try {
 
+            DEBUG.dumpObject(service.initialized(),"service.initialized()");
+
+            // Process request and process env. variables to provide them in the manner
+            // an OpenWhisk Action expects them, as well as enable additional Http features.
             preProcessRequest(req);
-            console.info("isInitialized="+isInitialized);
 
             service.initCode(req).then(function () {
                 service.runCode(req).then(function (result) {
@@ -276,19 +276,12 @@ function PlatformFactory(id, svc, cfg) {
                     res.status(error.code).json(error.response);
                 } else {
                     console.error("[wrapEndpoint]", "invalid errored promise", JSON.stringify(error));
-                    res.status(500).json({ error: "Internal error." });
+                    res.status(500).json({ error: "Internal error during function execution." });
                 }
             });
         } catch (e) {
-            res.status(500).json({error: "internal error"})
+            res.status(500).json({error: "internal error during function initialization."})
         }
-    }
-
-    var http_method = {
-        get: 'GET',
-        post: 'POST',
-        put: 'PUT',
-        delete: 'DELETE',
     };
 
     this.registerHandlers = function(app, platform) {
@@ -296,7 +289,7 @@ function PlatformFactory(id, svc, cfg) {
         // default to "[post]" HTTP method if not defined
         if (typeof httpMethods === "undefined" || !Array.isArray(httpMethods)) {
             console.error("__OW_HTTP_METHODS is undefined; defaulting to '[post]' ...");
-            httpMethods = [http_method.post];
+            httpMethods = DEFAULT_METHOD;
         }
         httpMethods.forEach(function (method) {
             switch (method.toUpperCase()) {
@@ -316,7 +309,8 @@ function PlatformFactory(id, svc, cfg) {
                     console.error("Environment variable '__OW_HTTP_METHODS' has an unrecognized value (" + method + ").");
             }
         });
-    }
-};
+    };
+    DEBUG.functionEnd();
+}
 
-module.exports = PlatformFactory;
+module.exports = PlatformKnativeImpl;
diff --git a/knative-build/runtimes/javascript/platform/openwhisk.js b/knative-build/runtimes/javascript/platform/openwhisk.js
new file mode 100644
index 0000000..b72a35f
--- /dev/null
+++ b/knative-build/runtimes/javascript/platform/openwhisk.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var dbg = require('../utils/debug');
+var DEBUG = new dbg();
+
+function PlatformOpenWhiskImpl(platformFactory) {
+    DEBUG.functionStart();
+    DEBUG.dumpObject(platformFactory, "platformFactory");
+    // Provide access to common runtime services
+    var service = platformFactory.service;
+
+    this.registerHandlers = function(app, platform) {
+        app.post('/init', platformFactory.wrapEndpoint(service.initCode));
+        app.post('/run', platformFactory.wrapEndpoint(service.runCode));
+    };
+    DEBUG.functionEnd();
+}
+
+module.exports = PlatformOpenWhiskImpl;
diff --git a/knative-build/runtimes/javascript/platform/platform.js b/knative-build/runtimes/javascript/platform/platform.js
index acc73bb..2af5874 100644
--- a/knative-build/runtimes/javascript/platform/platform.js
+++ b/knative-build/runtimes/javascript/platform/platform.js
@@ -15,308 +15,150 @@
  * limitations under the License.
  */
 
-var dbg = require('../utils/debug');
-var DEBUG = new dbg();
-
-const OW_ENV_PREFIX = "__OW_";
-
 /**
- * Pre-process the incoming
+ * Runtime platform factory
+ *
+ * This module is a NodeJS compatible version of a factory that will
+ * produce an implementation module provides OpenWhisk Language
+ * Runtime functionality and is able to register endpoints/handlers
+ * allowing to host OpenWhisk Actions and process OpenWhisk Activations.
  */
-function preProcessInitData(env, initdata, valuedata, activationdata) {
-    DEBUG.functionStart();
-    try {
-        // Set defaults to use INIT data not provided on the request
-        // Look first to the process (i.e., Container's) environment variables.
-        var main = (typeof env.__OW_ACTION_MAIN === 'undefined') ? "main" : env.__OW_ACTION_MAIN;
-        // TODO: Throw error if CODE is NOT defined!
-        var code = (typeof env.__OW_ACTION_CODE === 'undefined') ? "" : env.__OW_ACTION_CODE;
-        var binary = (typeof env.__OW_ACTION_BINARY === 'undefined') ? false : env.__OW_ACTION_BINARY.toLowerCase() === "true";
-        // TODO: deault to empty?
-        var actionName = (typeof env.__OW_ACTION_NAME === 'undefined') ? "" : env.__OW_ACTION_NAME;
-        var raw = (typeof env.__OW_ACTION_RAW === 'undefined') ? false : env.__OW_ACTION_RAW.toLowerCase() === "true";
-
-        DEBUG.dumpObject(actionName, "Action name");
-        DEBUG.dumpObject(main, "Action main");
-        DEBUG.dumpObject(code, "Action code");
-        DEBUG.dumpObject(binary, "Action binary");
-        DEBUG.dumpObject(raw, "Action Raw");
-
-        // Look for init data within the request (i.e., "stem cell" runtime, where code is injected by request)
-        if (typeof(initdata) !== "undefined") {
-            if (initdata.name && typeof initdata.name === 'string') {
-                actionName = initdata.name;
-            }
-            if (initdata.main && typeof initdata.main === 'string') {
-                main = initdata.main;
-            }
-            if (initdata.code && typeof initdata.code === 'string') {
-                code = initdata.code;
-            }
-            if (initdata.binary && typeof initdata.binary === 'boolean') {
-                // TODO: Throw error if BINARY is not 'true' or 'false'
-                binary = initdata.binary;
-            }
-            if (initdata.raw && typeof initdata.raw === 'boolean') {
-                // TODO: Throw error if RAW is not 'true' or 'false'
-                raw = initdata.raw;
-            }
-        }
-
-        // Move the init data to the request body under the "value" key.
-        // This will allow us to reuse the "openwhisk" /init route handler function
-        valuedata.main = main;
-        valuedata.code = code;
-        valuedata.binary = binary;
-        valuedata.raw = raw;
 
-        // Action name is a special case, as we have a key collision on "name" between init. data and request
-        // param. data (as they both appear within "body.value") so we must save it to its final location
-        // as the default Action name as part of the activation data
-        // NOTE: if action name is not present in the action data, we will set it regardless even if an empty string
-        if( typeof(activationdata) !== "undefined" ) {
-            if ( typeof(activationdata.action_name) === "undefined" ||
-                (typeof(activationdata.action_name) === "string" && activationdata.action_name.length == 0 )){
-                activationdata.action_name = actionName;
-            }
-        }
-
-        DEBUG.dumpObject(valuedata.main, "valuedata.main");
-        DEBUG.dumpObject(valuedata.code , "valuedata.code");
-        DEBUG.dumpObject(valuedata.binary, "valuedata.binary");
-        DEBUG.dumpObject(valuedata.raw, "valuedata.raw");
+var dbg = require('../utils/debug');
+var DEBUG = new dbg();
 
-    } catch(e){
-        console.error(e);
-        DEBUG.functionEndError(e.message);
-        throw("Unable to initialize the runtime: " + e.message);
+// Export supported platform impls.
+const PLATFORM_OPENWHISK = 'openwhisk';
+const PLATFORM_KNATIVE =  'knative';
+
+const SUPPORTED_PLATFORMS = [
+    PLATFORM_OPENWHISK,
+    PLATFORM_KNATIVE
+];
+
+module.exports = class PlatformFactory {
+
+    /**
+     * Object constructor
+     * @param app NodeJS express application instance
+     * @param cfg Runtime configuration
+     * @@param svc Runtime services (default handlers)
+     */
+    constructor (app, cfg, svc) {
+        DEBUG.dumpObject(app,"app");
+        DEBUG.dumpObject(cfg,"cfg");
+        DEBUG.dumpObject(svc,"svc");
+        this._app = app;
+        this._service = svc;
+        this._config = cfg;
     }
-    DEBUG.functionEnd();
-}
 
-/**
- * Pre-process the incoming http request data, moving it to where the
- * route handlers expect it to be for an openwhisk runtime.
- */
-function preProcessActivationData(env, activationdata) {
-    DEBUG.functionStart();
-    try {
-        // Note: we move the values here so that the "run()" handler does not have
-        // to move them again.
-        Object.keys(activationdata).forEach(
-            function (k) {
-                if (typeof activationdata[k] === 'string') {
-                    var envVariable = OW_ENV_PREFIX + k.toUpperCase();
-                    process.env[envVariable] = activationdata[k];
-                    DEBUG.dumpObject(process.env[envVariable], envVariable, "preProcessActivationData");
-                }
-            }
-        );
-    } catch(e){
-        console.error(e);
-        DEBUG.functionEndError(e.message);
-        throw("Unable to initialize the runtime: " + e.message);
+    /**
+     * @returns {string[]} List of supported platforms by their string ID
+     */
+    static get SUPPORTED_PLATFORMS() {
+        return SUPPORTED_PLATFORMS;
     }
-    DEBUG.functionEnd();
-}
 
-
-/**
- * Pre-process HTTP request details, send them as parameters to the action input argument
- * __ow_method, __ow_headers, __ow_path, __ow_user, __ow_body, and __ow_query
- */
-function preProcessHTTPContext(req, valueData) {
-    DEBUG.functionStart()
-
-    try {
-        if (valueData.raw) {
-            if (typeof req.body.value === "string" && req.body.value !== undefined) {
-                valueData.__ow_body = req.body.value;
-            } else {
-                const body = Object.assign({}, req.body.value);
-                // delete main, binary, raw, and code from the body before sending it as an action argument
-                delete body.main;
-                delete body.code;
-                delete body.binary;
-                delete body.raw;
-                var bodyStr = JSON.stringify(body);
-                var bodyBase64 = Buffer.from(bodyStr).toString("base64");
-                valueData.__ow_body = bodyBase64;
-            }
-            valueData.__ow_query = req.query;
-        }
-
-        var namespace = "";
-        if (process.env[OW_ENV_PREFIX + "NAMESPACE"] !== undefined) {
-            namespace = process.env[OW_ENV_PREFIX + "NAMESPACE"];
-        }
-        valueData.__ow_user = namespace;
-        valueData.__ow_method = req.method;
-        valueData.__ow_headers = req.headers;
-        valueData.__ow_path = "";
-    } catch (e) {
-        console.error(e);
-        DEBUG.functionEndError(e.message);
-        throw ("Unable to initialize the runtime: " + e.message)
+    static get PLATFORM_OPENWHISK() {
+        return PLATFORM_OPENWHISK;
     }
-    DEBUG.functionEnd()
-}
-
 
-/**
- * Pre-process the incoming http request data, moving it to where the
- * route handlers expect it to be for an openwhisk runtime.
- */
-function preProcessRequest(req){
-    DEBUG.functionStart();
-
-    try{
-        // Get or create valid references to the various data we might encounter
-        // in a request such as Init., Activation and function parameter data.
-        let body = req.body || {};
-        let valueData = body.value || {};
-        let initData = body.init || {};
-        let activationData = body.activation || {};
-        let env = process.env || {};
-
-        // process initialization (i.e., "init") data
-        preProcessInitData(env, initData, valueData, activationData);
-
-        preProcessHTTPContext(req, valueData);
-
-        // Fix up pointers in case we had to allocate new maps
-        req.body = body;
-        req.body.value = valueData;
-        req.body.init = initData;
-        req.body.activation = activationData;
-
-        // process per-activation (i.e, "run") data
-        preProcessActivationData(env, activationData);
-
-
-
-
-    } catch(e){
-        console.error(e);
-        DEBUG.functionEndError(e.message);
-        // TODO: test this error is handled properly and results in an HTTP error response
-        throw("Unable to initialize the runtime: " + e.message);
+    static get PLATFORM_KNATIVE() {
+        return PLATFORM_KNATIVE;
     }
 
-    DEBUG.functionEnd();
-}
-
-function postProcessResponse(result, res) {
-    DEBUG.functionStart();
-
-    // 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;
-
-    // statusCode: default is 200 OK if body is not empty otherwise 204 No Content
-    if (result.response.statusCode !== undefined) {
-        statusCode = result.response.statusCode;
-        delete body['statusCode'];
+    get app(){
+        return this._app;
     }
 
-    // the default content-type for an HTTP response is application/json
-    // this default are overwritten with the action specified headers
-    if (result.response.headers !== undefined) {
-        headers = result.response.headers;
-        delete body['headers'];
+    get service(){
+        return this._service;
     }
 
-    // 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) {
-        body = result.response.body;
-        delete body['main'];
-        delete body['code'];
-        delete body['binary'];
+    get config(){
+        return this._config;
     }
 
-    // statusCode: set it to 204 No Content if body is empty
-    if (statusCode === 200 && body === "") {
-        statusCode = 204;
+    /**
+     * validate if a platform ID is a known, supported value
+     * @param id Platform Id
+     */
+    static isSupportedPlatform(id){
+        if (SUPPORTED_PLATFORMS.indexOf(id) > -1) {
+            return true;
+        }
+        return false;
     }
 
-    res.header(headers).status(statusCode).json(body);
-
-    DEBUG.functionEnd();
-}
-
-
-function PlatformFactory(id, svc, cfg) {
-
-    DEBUG.dumpObject(id, "Platform" );
-    DEBUG.dumpObject(svc, "Service" );
-    DEBUG.dumpObject(cfg, "Config" );
-
-    var service = svc;
-    //var config = cfg;  // TODO: use this to pass future config. information uniformly to any impl.
-    var isInitialized = false;
-
-    this.run = function(req, res) {
-
-        try {
-
-            preProcessRequest(req);
-            console.info("isInitialized="+isInitialized);
-
-            service.initCode(req).then(function () {
-                service.runCode(req).then(function (result) {
-                    postProcessResponse(result, res)
-                });
-            }).catch(function (error) {
-                console.error(error);
-                if (typeof error.code === "number" && typeof error.response !== "undefined") {
-                    res.status(error.code).json(error.response);
-                } else {
-                    console.error("[wrapEndpoint]", "invalid errored promise", JSON.stringify(error));
-                    res.status(500).json({ error: "Internal error." });
-                }
-            });
-        } catch (e) {
-            res.status(500).json({error: "internal error"})
+    /**
+     * Instantiate a platform implementation
+     * @param id Platform ID
+     * @returns {PlatformImpl} Platform instance (interface), as best can be done with NodeJS
+     */
+    // TODO remove "app" parameter once we have a valid openwhisk platformImpl.
+    createPlatformImpl(id){
+        DEBUG.functionStart();
+        DEBUG.dumpObject(id,"id");
+        // Load the appropriate implementation module and return reference to it
+        switch (id.toLowerCase()) {
+            case PLATFORM_KNATIVE:
+                const knPlatformImpl = require('./knative.js');
+                this._platformImpl = new knPlatformImpl(this);
+                break;
+            case PLATFORM_OPENWHISK:
+                const owPlatformImpl = require('./openwhisk.js');
+                this._platformImpl = new owPlatformImpl(this);
+                break;
+            default:
+                console.error("Platform ID is not a known value (" + id + ").");
         }
+        DEBUG.dumpObject(this._platformImpl,"platformImpl");
+        DEBUG.functionEnd();
+        return this._platformImpl;
     }
 
-    var http_method = {
-        get: 'GET',
-        post: 'POST',
-        put: 'PUT',
-        delete: 'DELETE',
-    };
-
-    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)) {
-            console.error("__OW_HTTP_METHODS is undefined; defaulting to '[post]' ...");
-            httpMethods = [http_method.post];
-        }
-        httpMethods.forEach(function (method) {
-            switch (method.toUpperCase()) {
-                case http_method.get:
-                    app.get('/', platform.run);
-                    break;
-                case http_method.post:
-                    app.post('/', platform.run);
-                    break;
-                case http_method.put:
-                    app.put('/', platform.run);
-                    break;
-                case http_method.delete:
-                    app.delete('/', platform.run);
-                    break;
-                default:
-                    console.error("Environment variable '__OW_HTTP_METHODS' has an unrecognized value (" + method + ").");
+    /**
+     * Wraps an endpoint written to return a Promise into an express endpoint,
+     * producing the appropriate HTTP response and closing it for all controllable
+     * failure modes.
+     *
+     * The expected signature for the promise value (both completed and failed)
+     * is { code: int, response: object }.
+     *
+     * @param ep a request=>promise function
+     * @returns an express endpoint handler
+     */
+    wrapEndpoint(ep) {
+        DEBUG.functionStart("wrapping: " + ep.name);
+        DEBUG.functionEnd("returning wrapper: " + ep.name);
+        return function (req, res) {
+            try {
+                ep(req).then(function (result) {
+                    res.status(result.code).json(result.response);
+                    DEBUG.dumpObject(result,"result");
+                    DEBUG.dumpObject(res,"response");
+                    DEBUG.functionEndSuccess("wrapper for: " + ep.name);
+                }).catch(function (error) {
+                    if (typeof error.code === "number" && typeof error.response !== "undefined") {
+                        res.status(error.code).json(error.response);
+                    } else {
+                        console.error("[wrapEndpoint]", "invalid errored promise", JSON.stringify(error));
+                        res.status(500).json({ error: "Internal error." });
+                    }
+                    DEBUG.dumpObject(error,"error");
+                    DEBUG.dumpObject(res,"response");
+                    DEBUG.functionEndError(error, "wrapper for: " + ep.name);
+                });
+            } catch (e) {
+                // This should not happen, as the contract for the endpoints is to
+                // never (externally) throw, and wrap failures in the promise instead,
+                // but, as they say, better safe than sorry.
+                console.error("[wrapEndpoint]", "exception caught", e.message);
+                res.status(500).json({ error: "Internal error (exception)." });
+                DEBUG.dumpObject(error,"error");
+                DEBUG.functionEndError(error, ep.name);
             }
-        });
+        }
     }
 };
-
-module.exports = PlatformFactory;
diff --git a/knative-build/runtimes/javascript/src/service.js b/knative-build/runtimes/javascript/src/service.js
index ab947e2..b0d335e 100644
--- a/knative-build/runtimes/javascript/src/service.js
+++ b/knative-build/runtimes/javascript/src/service.js
@@ -64,6 +64,18 @@ function NodeActionService(cfg) {
     }
 
     /**
+     * Indicates if we have been initialized which is determined by if we have
+     * created a NodeActionRunner.
+     * @returns {boolean}
+     */
+    this.initialized = function isIntialized(){
+        if( userCodeRunner === undefined ){
+            return false;
+        }
+        return true;
+    };
+
+    /**
      * Starts the server.
      *
      * @param app express app
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-init.http b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-init.http
new file mode 100644
index 0000000..28b4957
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-helloworld-with-params",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main(params) {return {payload: 'Hello ' + params.name + ' from ' + params.place +  '!'};}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-run.http b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-run.http
new file mode 100644
index 0000000..7a88f2c
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-run.http
@@ -0,0 +1,19 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-helloworld-with-params",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name" : "Jill",
+    "place" : "OK"
+  }
+}
+
+###