Serverless HTTP handlers with OpenWhisk

In this article, I’ll describe a new OpenWhisk feature: web actions. In short, you can now write HTTP handlers using OpenWhisk actions.

An action is “code” (e.g., a function) that runs in response to an HTTP request. As a reminder: actions receive a JSON object as input and produce a JSON object as a result. Using web actions, it is possible to implement HTTP handlers that respond with headers, status code, and content of different types. The web action must still return a JSON object, but the OpenWhisk system (“controller”) will treat such actions differently if its result includes one or more of the following as top level JSON properties:

  1. headers: a JSON object where the keys are header-names and the values are string values for those headers (default is no headers).
  2. code: a valid HTTP status code (default is 200 OK).
  3. body: a string which is either plain text or a base64 encoded string (for binary data).

The controller will pass along the action-specified headers, if any, to the HTTP client when terminating the request/response. Similarly the controller will respond with the given status code when present. Lastly, the body is passed along as the body of the response. Unless a content-type header is declared in the action result’s headers, the body is passed along as is if it’s a string (or results in an error otherwise). When the content-type is defined, the controller will determine if the response is binary data or plain text and decode the string using a base64 decoder as needed. Should the body fail to decoded correctly, an error is returned to the caller.

Here is an example of a web action that performs an HTTP redirect:

function main() {
return {
headers: { location: "http://openwhisk.org" },
code: 302 }
}

And here is an example for setting a cookie:

function main() {
return {
headers: {
"Set-Cookie": "UserID=Jane; Max-Age=3600; Version=" },
code: 200,
body: "<html><body><h3>hello</h3></body></html" }
}

Or returning an image/png:

function main() {
let png = <base 64 encoded string>
return {
headers: { "Content-Type": "image/png" },
code: 200,
body: png };
}
It is important to remember there are predefined system limits on the size of an action result. Large objects should not be sent inline through OpenWhisk, but instead deferred to an object store, for example.

A web action, when invoked, receives all the HTTP request information available as additional parameters to the action input argument. They are:

  1. __ow_meta_verb: the HTTP method of the request.
  2. __ow_meta_headers: the request headers.
  3. __ow_meta_path: the unmatched path of the request.

The request may not override any of the named __ow_ parameters above; doing so will result in a failed request with status equal to 400 Bad Request.

The JSON body of the request is passed to the action as its argument. With web actions, there is also added support for query parameters and form data.


Web actions are currently available through a specific API path. As we gather feedback from usage in the field, we expect to make these features available as part of the core API for invoking actions. Here is an illustrative example in Node.js which returns a HTML response:

$ echo 'function main(args) {
var msg = "you didn&#39;t tell me who you are."
if (args.name) {
msg = `hello ${args.name}!`
}
return {body:
`<html><body><h3><center>${msg}</center></h3></body></html>`}
}'> hello.js

The following command will deploy this code to OpenWhisk as a web action using the wsk command line tool. The fully qualified name of the action in this example is /guest/demo/hello, where guest is the namespace (your namespace will vary), demo is a package name, and hello is the action name:

$ wsk package create /guest/demo                    # if necessary
$ wsk action create /guest/demo/hello hello.js \
--annotation web-export true # new annotation

The web-export annotation allows the action to be accessible as a web action via a new REST interface https://APIHOST/api/v1/experimental/web/ followed by the fully qualified name of the action which must include its package name, or default if the action is not in a named package. The web action API path may be used with curl or wget without an API key. It may even be entered directly in your browser (for Bluemix, the APIHOST is openwhisk.ng.bluemix.net).

You’ll notice in the screenshot above that the action name is followed by a .http extension which declares the expected content type of the response. In addition, it is possible to pass query parameters to the action, as in the case with ?name=Jane. These are new features afforded by web actions, which are explained below alongside other new features we are making available through the new API path.

  1. Content extensions: the request must specify its desired content type as one of .json.html.text or .http. This is done by adding an extension to the action name in the URI, so that an action /guest/demo/hello is referenced as /guest/demo/hello.http for example to receive an HTTP response back (as in this example).
  2. Projecting fields from the result: the path that follows the action name is used to project out one or more levels of the response. For example, 
    /guest/demo/hello.html/body. This allows for example an action which returns a dictionary {body: "..." } to project the body property and directly return its value instead. The projection path follows an absolute path model (as in XPath).
  3. Query and body parameters as input: the action receives query parameters as well as parameters in the request body. The precedence order for merging parameters is: package parameters, action parameters, query parameter, body parameters with each of these overriding any previous values in case of overlap. As shown in the example /guest/demo/hello.http?name=Jane passes the argument {name: "Jane"} to the action.
  4. Form data: in addition to the standard application/json, web actions may receive URL encoded from data application/x-www-form-urlencoded dataas input.
  5. Activation via multiple HTTP verbs: a web action may be invoked via one of four HTTP methods: GET, POST, PUT or DELETE.

The .json and .http extensions do not require a projection path. The .text and .html extensions do — however for convenience, the default path is assumed to match the extension name. So to invoke a web action and receive an .html response, the action must respond with a JSON object that contains a top level property called html (or the response must be in the explicitly given path as in /guest/demo/hello.html/body).

A web action runs directly in response to an API call without any authentication. The owner of the action incurs the cost of activation.

To disable a web action, it’s enough to remove the annotation or set it to false.

$ wsk action update /guest/demo/hello --annotation web-export false

With web actions, we also introduced the ability to protect action parameters against accidental or intentional mutation by query or body parameters. Using the final annotation on an action seals all action parameters with predefined values and makes those parameters immutable. For example, to seal the name parameter in the HTML example shown earlier:

$ wsk action update /guest/demo/hello \
--param name Jane \
--annotation final true \
--annotation web-export true

The result of these changes is that name is bound to Jane and may not be overridden by query or body parameters because of the final annotation. Any attempt to override a final parameter will result in a 400 Bad Request response.


You can try these new feature on IBM Bluemix. If you want to reach us or share your thoughts, we’re available on Slack for feedback. To learn more, also consider checking us out on GitHub.