Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

OpenWhisk Web Actions

DZone's Guide to

OpenWhisk Web Actions

OpenWhisk Web Actions provide an easy way to create HTTP endpoints that can be called by clients via the GET, PUT, POST or DELETE methods. Learn how!

· Web Dev Zone ·
Free Resource

Deploy code to production now. Release to users when ready. Learn how to separate code deployment from user-facing feature releases with LaunchDarkly.

The first way that you learn to call your OpenWhisk action over HTTP is a POST request that is authenticated using your API key. This key allows all sorts of write access to your account, so you never release it.

If you want to access the action over HTTP without the API key, you have two choices: Web Actions or API Gateway.

This article discusses how to use Web Actions as they are very useful and easy to get going with.

Enabling Web Actions

Web Actions provide an HTTP endpoint to your action. The format of the URL is:

https://openwhisk.ng.bluemix.net/api/v1/web/{fully qualified action name}.{type}

The fully qualified name for your action can be found using the wsk action list. For my ping action, this is /19FT_dev/P1/ping.

Note that if your action is in the default package, e.g. its name is /19FT_dev/hello, then you need to use /19FT_dev/default/hello.

Enable Access

To enable an action for web access, you need to inform OpenWhisk by using the --web switch to wsk action update (or wsk action create):

$ wsk action update P1/ping ping.swift --web true

That's all that we need to do, so we can now test it:

Calling via Curl

$ curl -i \
  https://openwhisk.ng.bluemix.net/api/v1/web/19FT_dev/P1/ping.json

HTTP/1.1 200 OK
X-Backside-Transport: OK OK
Connection: Keep-Alive
Transfer-Encoding: chunked
Server: nginx/1.11.1
Date: Sun, 26 Feb 2017 17:50:24 GMT
Content-Type: application/json; charset=UTF-8
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type
X-Global-Transaction-ID: 441895695
Set-Cookie: DPJSESSIONID=PBC5YS:1376290542; Path=/; Domain=.whisk.ng.bluemix.net

{
  "ack": "2017-03-02 17:50:24"
}

Success! As we used the .json extension, OpenWhisk automatically set the status code to 200, sent our returned dictionary as JSON, and set the correct Content-Type header. What about if we need to change the status code though?

Sending Data to the Client

To set your own status code and HTTP headers, you need to use the .http extension on the URL and change the data array that you return from your action.

To recap from my previous post, our action currently looks like this:

ping.swift

func main(args: [String:Any]) -> [String:Any] {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let now = formatter.string(from: Date())

    return ["ack": now]
}

In this action, we simply return a dictionary of the data we want to appear as JSON in the body. To use the HTTP web action type, we return a dictionary with three keys: code, headers, and body:

Dictionary of HTTP headers to send. The key is the header's name and the value is the header's value.

  • code - Number of the HTTP status code, e.g. 200.
  • headers -Dictionary of HTTP headers to send. The key is the header's name and the value is the header's value.
  • body - HTTP body as a string. The format depends on the Content-Type header that you have set. If the format is a binary one, then the string must be base64 encoded. OpenWhisk uses Spray, so consult their list to find out which are considered binary and which are text.

Let's change our action to send XML:

ping.swift

func main(args: [String:Any]) -> [String:Any] {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let now = formatter.string(from: Date())

    return [
        "body": "<ack>\(now)</ack>",
        "code": 200,
        "headers": [
            "Content-Type": "application/xml",
        ],
    ]
}

(Don't forget to update your action whenever you change it using wsk action update).

As the XML content types are considered text, we can just set the string containing our XML for the body element of our dictionary.

Let's test it:

$ curl -i -H "Accept: application/xml" \
  https://openwhisk.ng.bluemix.net/api/v1/web/19FT_dev/P1/ping.http

HTTP/1.1 200 OK
X-Backside-Transport: OK OK
Connection: Keep-Alive
Transfer-Encoding: chunked
Server: nginx/1.11.1
Date: Sun, 26 Feb 2017 19:48:35 GMT
Content-Type: application/xml
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type
X-Global-Transaction-ID: 2289807007
Set-Cookie: DPJSESSIONID=PBC5YS:1376290542; Path=/; Domain=.whisk.ng.bluemix.net

<ack>2017-02-26 19:48:35:lt;/ack>

Note, that if the Accept header doesn't contain the content type that is set in your headers, then the Web Action controller will send an error back to the client.

JSON

For application/json, we have to convert our dictionary to a JSON string and then base64 encode it. OpenWhisk's Swift environment includes SwiftyJSON, so this isn't too hard:

ping.swift

import SwiftyJSON

func main(args: [String:Any]) -> [String:Any] {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let now = formatter.string(from: Date())

    let data = ["ack": now]

    let json_body = WhiskJsonUtils.dictionaryToJsonString(jsonDict: data) ?? ""
    let base64_body = Data(json_body.utf8).base64EncodedString()

    return [
        "body": base64_body,
        "code": 200,
        "headers": [
            "Content-Type": "application/json",
        ],
    ]
}

OpenWhisk's Swift environment includes the WhiskJsonUtils class that has some useful JSON related methods. In our case, we use the dictionaryToJsonString() method to create the JSON string from our dictionary. We then convert this to a base64 encoded string using the Foundation Data class's base64EncodedString() method.

Proving that it works:

$ curl -i -H "Accept: application/json" \
  https://openwhisk.ng.bluemix.net/api/v1/web/19FT_dev/P1/ping.http 
HTTP/1.1 200 OK
X-Backside-Transport: OK OK
Connection: Keep-Alive
Transfer-Encoding: chunked
Server: nginx/1.11.1
Date: Sun, 26 Feb 2017 20:52:12 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type
X-Global-Transaction-ID: 891112175
Set-Cookie: DPJSESSIONID=PBC5YS:1376290542; Path=/; Domain=.whisk.ng.bluemix.net

{
  "ack": "2017-02-26 20:52:12"
}

Receiving Data and Reading Headers

We've talked about sending data from our action to the HTTP client, but what about receiving data from the client? This information is provided to you in the args parameter that is passed to your main() method. You can inspect args with this code:

env.swift:

func main(args: [String:Any]) -> [String:Any] {
    return args
}

We can send information to the web action using POST with a JSON payload or via query parameters in a GET request.

For a POST request, with a very simple JSON payload:

$ wsk action update P1/args args.swift --web true
ok: updated action P1/args
$ curl -H "Content-type: application/json" -d '{"foo":"bar"}' \
  https://openwhisk.ng.bluemix.net/api/v1/web/19FT_dev/P1/args.json"
{
  "__ow_meta_headers": {
    "accept": "*/*",
    "user-agent": "curl/7.51.0",
    "x-client-ip": "86.138.48.38",
    "x-forwarded-proto": "https",
    "host": "10.155.72.21:10001",
    "content-length": "13",
    "content-type": "application/json",
    "via": "1.1 EwAAAGAW5wA-",
    "x-global-transaction-id": "2130413383",
    "connection": "close",
    "x-forwarded-for": "86.138.48.38"
  },
  "__ow_meta_path": "",
  "__ow_meta_verb": "post",
  "foo": "bar"
}

As you can see, our body data ({"foo": "bar}) is just an element in the args dictionary. However, OpenWhisk has also given us some __ow_* properties with useful information. __ow_meta_verb tells us the METHOD of the HTTP message that the client sent and __ow_meta_headerscontains the HTTP headers. Note that the keys are normalized to lower case.

Armed with this knowledge, it's possible to ensure that your action works with the HTTP method(s) that you want it to and by reading the headers, you can implement things like authentication via the Authorization header or read the Accept header to ensure you return data in the right format.

Fin

That's it. OpenWhisk Web Actions provide a very easy way to create HTTP endpoints that can be called by clients via the GET, PUT, POST or DELETE methods without needing your API key.

In comparison to API Gateway, you have less control over the name of the URL, but, in exchange, you are able to set the HTTP status code and custom headers, which isn't possible in API Gateway today. Over time, I expect API Gateway to gain more features, but until it supports the ability to set the status code, I recommend Web Actions.

Note: This article was updated on 16 July 2017 as web actions are no longer experimental. This means that the URL no longer has an experimental segment and that the annotation has been promoted to --web. 

Deploy code to production now. Release to users when ready. Learn how to separate code deployment from user-facing feature releases with LaunchDarkly.

Topics:
web dev ,web actions ,openwhisk ,http endpoints

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}