Search This Blog

Showing posts with label rest. Show all posts
Showing posts with label rest. Show all posts

Saturday, September 21, 2019

Handling failure on REST calls

When calling other services through REST you always need to antecipate failure. It might be a network issue or the failure might be due to the service being down.

The default behaviour

In OpenAF when making a REST call if it fails it will look similar to this:
var res = $rest().get("http://127.0.0.1:12345");
if (isDef(res.error)) {
    logErr("There was an error contacting the service: " + res.error);
} else {
    // Process the result
}
Showing $rest() function returning error
Nevertheless you can add the throwExceptions flag so you can handle it differently:
try {
    var res = $rest({ 
        throwExceptions: true
    })
    .get("http://127.0.0.1:12345");

    // Process the result
} catch(e) {
    logErr("There was an error contacting the service: " + String(e));
}
Showing $rest() function with throwExceptions flag set to true

Another simpler, more elegant, way

But your code starts getting full of exception handling and you just wanted a non-critical information for which a default reply it's okay. Let's say you have a service that returns an array of favourite fruits given a user.
The expected behaviour when everything is working would be:
Showing $rest() function calling a service a returning a user and a fruits array
So your code could look like this:
addFavouriteFruitsToDashboard(
    $rest()
    .post("http://127.0.0.1:12345/getFruits", { user: currentUser })
);
The only problem is if the service fails. Then you will have to either check the result or try/catch the function addFavouriteFruitsToDashboard call. But the $rest() shortcut can handle that for you with the option default. This option let's you define a default map in case something goes wrong. You still get the error entry but you can choose to handle it or note.
The previous code now can look more like this:
addFavouriteFruitsToDashboard(
    $rest({
        default: { 
            user  : currentUser, 
            fruits: []
        }
    })
    .post("http://127.0.0.1:12345/getFruits", { user: currentUser })
);
So in case of error, you will always have, at least, an empty fruits array. Because calling the _$rest()_function now with an error on the server (like turning it off) results in:
Showing $rest() function calling a service a returning a user, an empty fruits array and a error

By the way...

By the way, if you want to test it yourself and need a quick dirty rest service you just have to run the following lines:
ow.loadServer();

var hs = ow.server.httpd.start(12345);
ow.server.httpd.route(hs, { 
    "/getFruits": r => {
        return ow.server.rest.reply("/getFruits", r, 
            (idxs, data, req) => { 
                return { 
                    user: data.user, 
                    fruits: [ 
                        'banana', 
                        'apple', 
                        'orange' 
                    ] 
                } 
            }
        )
    }
});

log("I'm ready!");
ow.server.daemon();

Sunday, September 8, 2019

Quickly build a REST service in OpenAF

Whenever you need a REST service on a rush you can have a fully functional REST service with OpenAF in a couple of minutes.

1. The function(s)

Let's start with a sample function in OpenAF that you need it to be available as a REST service:

function addNumbers(inputMap) {
    inputMap   = _$(inputMap).isMap().default({});
    inputMap.a = _$(inputMap.a).default(0);
    inputMap.b = _$(inputMap.b).default(0);

    return {
        a: Number(inputMap.a),
        b: Number(inputMap.b),
        res: Number(inputMap.a) + Number(inputMap.b)
    }
}

Advice: it's easier if it receives a map and returns a map.

Now save the addNumbers function in mylib.js.

2. Install some helper oPacks

$ opack install ojob-common
$ opack install openaf-templates

3. Setup the main oJob

Copy the openaf-templates/ojobs/restServices/restServices.yaml to your current folder, together with mylib.js from step 1, with the name main.yaml.

$ cp openaf-templates/ojobs/restServices/restServices.yaml main.yaml

Now let's edit the main.yaml:

  1. Change the piddir line to "piddir: &PIDDIR myService.pid"
  2. On the "Prepare my service" job change to something like this:
  - name: Prepare my service
    to  : REST Service
    args: 
      uri       : /add     # That's your new URI
      port      : *PORT

      # Your code for the GET verb
      execGET   : |
        loadLib("mylib.js");
        return addNumbers(request.params);

      # Your code for the POST verb
      execPOST  : |
        loadLib("mylib.js");
        return addNumbers(data);
      execPUT   : "return { result: 0 }"
      execDELETE: "return { result: 0 }"

You can quickly test it by executing:

$ ojob main.yaml

Now, on your favourite REST client, execute something similar to:

$ curl "http://127.0.0.1:8090/add?a=5&b=5"
{"a":5,"b":5,"res":10}
$ curl -XPOST "http://127.0.0.1:8090/add" -d "{'a':1,'b':3}" -H "Content-Type: application/json"
{"a":1,"b":3,"res":4}

It's working!

Let's docker it

Create a Dockerfile:

FROM openaf/openaf-ojobc

COPY mylib.js /openaf/mylib.js
COPY main.yaml /openaf/main.yaml

Build it:

$ docker build . -t myservice

Start it:

$ docker run --rm -ti -p 8090:8090 myservice

Test it:

$ curl "http://127.0.0.1:8090/add?a=5&b=5"
{"a":5,"b":5,"res":10}
$ curl -XPOST "http://127.0.0.1:8090/add" -d "{'a':1,'b':3}" -H "Content-Type: application/json"
{"a":1,"b":3,"res":4}

It's that easy.

Thursday, August 22, 2019

Making REST service calls from OpenAF

OpenAF includes several ways to make HTTP connections and retrieve data from them. An usually the most usefull connections are to services that expose some kind of REST API "talking" in JSON. To make it easy in OpenAF to make these calls and parse the corresponding output the shortcut $rest was created.

Simple examples

GET

Performing a simple REST GET operation:

> $rest().get("https://ifconfig.co/json");
{
  "ip": "1.2.3.4",
  "country": "Ireland",
  "country_eu": true,
  "country_iso": "IE",
  "city": "Dublin",
  "hostname": "ec2-1-2-3-4.eu-west-1.compute.amazonaws.com",
  "latitude": 53.3338,
  "longitude": -6.2488,
  "asn": "AS16509",
  "asn_org": "Amazon.com, Inc."
}

The return is a parsed json object ready to use.

POST

Performing a REST POST operation you can pass the JSON body directly (in this example the httpbin.org service will echo the post request made):

> $rest().post("https://httpbin.org/post", { a: 1, b: "xyz", c: true }).json
{
  "a": 1,
  "b": "xyz",
  "c": true
}

PUT

The same goes for the PUT verb.

> $rest().put("https://httpbin.org/put", { a: 1, b: "xyz", c: true }).json
{
  "a": 1,
  "b": "xyz",
  "c": true
}

PATCH

And the PATCH verb.

> $rest().patch("https://httpbin.org/patch", { a: 1, b: "xyz", c: true }).json
{
  "a": 1,
  "b": "xyz",
  "c": true
}

DELETE

Finally, the DELETE verb.

> $rest().delete("https://httpbin.org/delete").json
null

Passing resource/query elements

There are two ways:

  1. Using the URI path (resource)
// https://some.server/restAPI/category/abc/article/1234
$rest().get("https://some.server/restAPI", { category: "abc", article: 1234 });

$rest().post("https://some.server/restAPI", { 
    article: 1234, title: "My article" 
}, { category: "abc", article: 1234 });

$rest().put("https://some.server/restAPI", { 
    article: 1234, title: "My article updated" 
}, { category: "abc", article: 1234 });

$rest().delete("https://some.server/restAPI", { category: "abc", article: 1234 });
  1. Using the query string
// https://some.server/restAPI?category=abc&article=1234
$rest({ uriQuery: true }).get("https://some.server/restAPI", { category: "abc", article: 1234 });

$rest({ uriQuery: true }).post("https://some.server/restAPI", { 
    article: 1234, title: "My article" 
}, { category: "abc", article: 1234 });

$rest({ uriQuery: true }).put("https://some.server/restAPI", { 
    article: 1234, title: "My article updated" 
}, { category: "abc", article: 1234 });

$rest({ uriQuery: true }).delete("https://some.server/restAPI", { category: "abc", article: 1234 });

What if the body is URL encoded?

When you need the request body to be "application/x-www-form-urlencoded" and instead of "application/json" you can use the option urlEncode=true.

> $rest({ urlEncode: true }).post("https://httpbin.org/post", { a: 1, b: "xyz" }).form
{
  "a": "1",
  "b": "xyz"
}

This was a simple introduction to the $rest shortcut. Check all the available options (e.g. authentication, timeout, exception control, etc...) executing "help $rest.get" from an openaf-console.

Using arrays with parallel

OpenAF is a mix of Javascript and Java, but "pure" javascript isn't "thread-safe" in the Java world. Nevertheless be...