Tuesday, 15 August 2017

Getting my head around REST APIs

Almost every product with which I've worked recently, including: -
  • IBM Business Process Manager
  • IBM Operational Decision Manager (Rules)
  • IBM API Connect
  • IBM WebSphere Liberty Profile (Collectives)
  • IBM DataPower Gateway
leverages Representational State Transfer (REST) APIs, so I've got no excuse but to L-E-A-R-N.

Whilst working through the DataPower tutorial here: -


I needed to get a handle on how to drive REST, both in the context of the IDG administration tools, which have a pretty RESTful set of APIs: -


I thus enabled the REST Management service: -

configure; rest-mgmt; admin-state enabled; exit; exit;

Whilst the tutorial recommends the use of the HTTPie tool, I prefer to use curl which is baked into macOS, and doesn't require tools such as brew or port.

So here's my crib of some of the IDG REST APIs, via curl : -

Summary of the Management APIs

curl -k --request GET -url https://localhost:5554/mgmt/ -u admin:admin

Summary of the Status Indicators

curl -k --request GET -url https://localhost:5554/mgmt/status/ -u admin:admin

List available domains

curl -k --request GET -url https://localhost:5554/mgmt/domains/config/ -u admin:admin

Check the CPU usage

curl -k --request GET -url https://localhost:5554/mgmt/status/default/CPUUsage -u admin:admin

As you can imagine, this is a TINY example of the power of the REST APIs.

Then I moved onto the next phase of the tutorial, where we are creating a Multi-Protocol Gateway to "proxy" REST calls through the IDG to IBM Bluemix, using a sample service for Pokemon.

The service is available on Bluemix here: -


and, when poked with a browser, which issues a GET request, simply returns a list of Pokemons in JSON format.

Initially, I tickled the same URL using a REST client plugin called Restlet which sits nicely inside Google Chrome.

Hitting the same URL with a GET request gives me the same list of Pokemon, albeit in a nice UI: -


As part of the tutorial, I needed to add a new Pokemon to the list, using the POST method.

Whilst I could use Restlet for this, I wanted to use curl, so I tested the GET request: -


[{"data":{"moves":"fast"},"height":"34","name":"charmeleon","weight":432,"id":"2"},{"data":{"moves":"very fast"},"height":"90","name":"venusaur","weight":80,"id":"3"},{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"1"},{"data":{"moves":"fast"},"height":"50","name":"bulbasuar","weight":100,"id":"4"},{"data":{"moves":"slow"},"height":"70","name":"charmander","weight":122,"id":"9ed78062996515b4db7e1b78d73208b0"}]

I then added a new Pokemon: -

curl --request POST --url https://pokemons.mybluemix.net/api/pokemons --header 'accept: application/json' --header 'content-type: application/json'   --data '{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"}'

{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"}

and retrieved the updated list: -

[{"data":{"moves":"fast"},"height":"34","name":"charmeleon","weight":432,"id":"2"},{"data":{"moves":"very fast"},"height":"90","name":"venusaur","weight":80,"id":"3"},{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"1"},{"data":{"moves":"fast"},"height":"50","name":"bulbasuar","weight":100,"id":"4"},{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"},{"data":{"moves":"slow"},"height":"70","name":"charmander","weight":122,"id":"9ed78062996515b4db7e1b78d73208b0"}]

When I (perhaps stupidly) re-ran the POST request with the SAME data, I got this: -

curl --request POST --url https://pokemons.mybluemix.net/api/pokemons --header 'accept: application/json' --header 'content-type: application/json'   --data '{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"}'

{"error":{"name":"Error","status":409,"message":"Document update conflict. (duplicate?)","error":"conflict","reason":"Document update conflict.","scope":"couch","statusCode":409,"request":{"method":"POST","headers":{"content-type":"application/json","accept":"application/json"},"uri":"https://XXXXXX:XXXXXX@757241f7-e17e-4fc1-b206-91919dd9ced5-bluemix.cloudant.com/pokemon_db","body":"{\"data\":{\"moves\":\"slow\"},\"height\":\"70\",\"name\":\"ivysaur\",\"weight\":200,\"_id\":\"6\",\"loopback__model__name\":\"pokemon\"}"},"headers":{"cache-control":"must-revalidate","content-type":"application/json","date":"Tue, 15 Aug 2017 15:36:22 GMT","x-couch-request-id":"e7ffd29868","x-content-type-options":"nosniff","x-cloudant-backend":"bm-dal-standard3","via":"1.1 lb1.bm-dal-standard3 (Glum/1.37.0)","strict-transport-security":"max-age=31536000","statusCode":409,"uri":"https://XXXXXX:XXXXXX@757241f7-e17e-4fc1-b206-91919dd9ced5-bluemix.cloudant.com/pokemon_db"},"errid":"non_200","description":"couch returned 409"}}

which kinda makes sense.

To prove this, I used the DELETE method: -

{"count":1}

retrieved the now updated list: -


[{"data":{"moves":"fast"},"height":"34","name":"charmeleon","weight":432,"id":"2"},{"data":{"moves":"very fast"},"height":"90","name":"venusaur","weight":80,"id":"3"},{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"1"},{"data":{"moves":"fast"},"height":"50","name":"bulbasuar","weight":100,"id":"4"},{"data":{"moves":"slow"},"height":"70","name":"charmander","weight":122,"id":"9ed78062996515b4db7e1b78d73208b0"}]

and then reinserted ID 6: -

curl --request POST --url https://pokemons.mybluemix.net/api/pokemons --header 'accept: application/json' --header 'content-type: application/json'   --data '{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"}'

{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"}

and (finally) retrieved the now updated list: -


[{"data":{"moves":"fast"},"height":"34","name":"charmeleon","weight":432,"id":"2"},{"data":{"moves":"very fast"},"height":"90","name":"venusaur","weight":80,"id":"3"},{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"1"},{"data":{"moves":"fast"},"height":"50","name":"bulbasuar","weight":100,"id":"4"},{"data":{"moves":"slow"},"height":"70","name":"ivysaur","weight":200,"id":"6"},{"data":{"moves":"slow"},"height":"70","name":"charmander","weight":122,"id":"9ed78062996515b4db7e1b78d73208b0"}]

All of this is of use, because it's enabled me to get a better handle on how I can drive REST from a browser, from a plugin ( Restlet in my case ) and from the command-line, using curl.

Which is all nice.

For the record, here's how I test an API deployed to IBM API Connect: -

curl -k --request POST \
    --url https://apimdev2019.hursley.ibm.com/daves-org/sb/DecisionService/rest/HelloWorldProject/1.0/HelloWorld \
    --header 'accept: application/json' \
    --header 'content-type: application/json' \
    --data '{"request":"David M M Hay"}'


4 comments:

Ben Langhinrichs said...

A useful post for those of us not quite entirely comfortable with REST and all it implies. Thanks!

Dave Hay said...

Hey Ben

Thanks for the kind comments. Yeah, there is always so much to learn, which keeps us young/busy/tired :-)

Cheers, Dave

Anonymous said...

Another good and popular client is PostMan https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop

Dave Hay said...

Keith

Good point, it's not one that I've made much use of it, thus far, but it's on the to-do list, along with a myriad of other things :-)

Thanks for your feedback, Dave

Note to self - use kubectl to query images in a pod or deployment

In both cases, we use JSON ... For a deployment, we can do this: - kubectl get deployment foobar --namespace snafu --output jsonpath="{...