Navigation

Table Of Contents

RESTful Front-end

Agnos has (currently in beta-phase) a RESTful front-end, which allows one to utilize an Agnos-exposed service through unsupported languages. Since Agnos currently supports only a limited set of target languages, the RESTful front-end by-passes that bottleneck and allows one to consume Agnos-services from any language that has basic URL and XML/JSON libraries.

This front-end is basically a distinct, light-weight web server that serves as a proxy to the back-end, AKA the Agnos server that’s behind. It is based on the based on the “runtime reflection” that’s built into the Agnos protocol (see INFO_REFLECTION), so no static code generation is required.

The front-end is a simply command-line utility, called restful-agnos, that accepts a python-bindings module (as generated by agnosc) and a connection to the back-end server (either a hostname:port pair or a path to a library mode-server). The front-end then connects to the back-end, and exposes its interface in HTTP-friendly fashion. Both XML and JSON can be used as the encoding formats, which allows application writers to choose what’s easiest for them. Once you set up a RESTful front-end server, all you need to access it is a library that can GET and POST to URLs.

Command-line Utility

After you install libagnos-python, the script restful-agnos is added to your bin directory (you can also find it in the repository, under libagnos/python/bin). This script takes the following command-line arguments:

HTTP server options:

  • --bindaddr=ADDR - the address the HTTP server should bind to. Optional; the default is 0.0.0.0.
  • -p INT or --port=INT - the TCP port that the HTTP server should bind to. Required.
  • -m FILENAME or --module=FILENAME - the python bindings module (as generated by agnosc) of the service to expose. Required.

Back-end server options (you must specify exactly one of them):

  • --exec FILENAME - the back-end Agnos server to run (in library mode). Required if neither --sock nor --url are given.
  • --sock HOST:PORT - the TCP endpoint of the back-end Agnos server to connect to. The Agnos server must be listening on this host and port. Required if neither --exec nor --url are given.
  • --url URL - the URL of the back-end Agnos server (a server than run behind another HTTP server). Required if neither --exec nor --sock are given.

Protocol Format

Since REST is not a well-defined standard, Agnos uses rather simple encoding rules, described below. Note that the encoding rules for XML and JSON are different.

XML

Primitives

The primitive types (int, float, bool, str, buffer and date) are XML-encoded in the following manner: <type value="textual-value" />. For example, the string “hello” is encoded as <str value="hello" />. Dates are ISO format-encoded, so a date such as 22 August 2011, 18:12:20.858307 is encoded as <date value="2011-08-22T18:12:20.858307" />.

Containers

Lists and sets are encoded as a sequence of values. For instance, a list of integers such as [1,2,3] would be XML-encoded like so: <list><int value="1"/><int value="2"/><int value="3"></list>. A set would simply use <set> instead of <list>.

Maps are encoded as a sequence of items, each consisting of a key and a value. The format is like so: <map><item><key>XXX</key><value>YYY</value><item>...</item></map>``, so for instance, the map {"foo" : 1, "bar" : 2} would be encoded as <map><item><key><str value="foo" /></key><value><int value="1" /></value></item><item><key> <str value="bar" /></key><value><int value="2" /></value></item></map>.

HeteroMaps follow the same rules as maps, only they use <heteromap> instead of map.

and HeteroMaps are contained in <map> o

Enums

Enums are encoded as <enum type="enum-name" member="member-name">. For example, the NY member of the enum

enum State {
    TX,
    NY,
    CA,
    AR,
}

would be encoded as <enum type="State" member="NY" />.

Records

Records are encoded as <record type="record-name">...attributes...</record>, where attributes is a sequence of <attr name="attribute-name">XXX</attr> elements. For example, an address record, made of a state (enum), a city (string), a street (string) and a number (int), is encoded like so:

<record type="Address">
<attr name="state"><enum type="State" member="NY" /></attr>
<attr name="city"><str value="Albany" /></attr>
<attr name="street"><str value="Main" /></attr>
<attr name="number"><int value="1728" /></attr>
</record>

Proxies

Instance proxies are encoded as <proxy type="class-name" url="relative-path">, where class-name is the class’s name as defined in the IDL and relative-path is the path of the object, as exposed by the RESTful front-end. For example, a PersonProxy might be encoded like so: <proxy type="Person" url="/objs/1234" />

JSON

Primitives

Primitives JSON types are encoded “natively”, as defined by the JSON specification. These include Agnos’s bool, int8, int16, int32, int64, float, and string. For instance, the string hello is simply encoded as "hello" in JSON.

Agnos byte-buffers are encoded as a JSON dictionary with a type and a value. The type is set of buffer and the value is a Base64 encoded string. For example, the buffer [03 1e f4] is encoded as {"type" : "buffer", value = "Ax70"}

Agnos dates are encoded in as a dictionary with type and a value. The type is set to datetime and the value is an ISO format-encoded string. For example, the date 22 August 2011, 17:09:58.910686 is encoded as {"type": "datetime", "value": "2011-08-22T17:09:58.910686"}.

Lists

Agnos lists, such as list[T] are encoded in native JSON lists. For instance, the Agnos list ["hello", "world"] (of type list[str]) is encoded as ["hello", "world"].

Containers

  • set[T] is encoded in JSON as a dictionary with a type and a value. The type is set to "set" and value is a list of T elements. For instance, the set {1,2,3} of type set[int] is encoded as {"type": "set", "value": [1, 2, 3]}.
  • map[K, V] is encoded in JSON as a dictionary with a type and a value. The type``m is set to ``map and value is a list of K-and-V pairs. Each pair is a list of its own. This is done because the keys of JSON dictionaries must be strings, while in Agnos they can be anything. For instance, the dictionary {1 : "hello", 2 : "world"} is encoded as {"type": "map", "value": [[1, "hello"], [2, "world"]]}
  • heteromap is encoded in JSON as a dictionary with a type and a value. The type``m is set to ``map and value is a list of K-and-V pairs. Each pair is a list of its own. For instance, the HeteroMap {"foo" : 17, 19 : 3.1415926, "bar" : None} is encoded as {"type": "heteromap", "value": [[19, 3.1415926000000001], ["foo", 17], ["bar", null]]}

Enums

Enums are encoded as a JSON dictionary with a type, a name and a member keys. The type is set to enum, the name is the name of the enum, and member is the name of the enum-member. For instance, passing NY (as defined in this enum)

enum State {
    TX,
    NY,
    CA,
    AR,
}

is JSON-encoded like so: {"type": "enum", "name": "State", "member": "NY"}

Records

Records are encoded in JSON as a dictionary with a type (set to record), a name (set to the record’s name, as defined in the IDL), and a value, which is a dictionary of the record’s fields. For example, an address record, made of a state (enum), a city (string), a street (string) and a number (int), is encoded like so:

{"type": "record", "name": "Address", "value":
    {"state" : {"type" : "enum", name = "State", member = "NY"},
    "city" : "Albany", "street" : "Main", "number" : 1782}
}

Proxies

Instance proxies are encoded in JSON as a dictionary with a type (set to proxy), a name (set to the class’s name as defined in the IDL), and a url set to the (relative) path of the object, as exposed by the RESTful front-end. For example, a PersonProxy might be encoded like so: {"type": "proxy", "name": "Person", "url": "/objs/1234"}.

Example Usage

Assume you have a service called MyService, for whom you’ve generated MyService_bindings.py using agnosc, and a server program (that implements this service) that’s called myserver.py.

Run

$ restful-agnos -p 12000 -m MyService_bindings.py --exec "python myserver.py -m lib"

Now you can point your browser at http://localhost:12000/ and explore the exposed service. It would show you something like so

{"type": "map", "value": [["info", "this is the RESTful server's root"],
["functions_url", "/funcs"], ["objects_url", "/objs"], ["service", {"type": "map",
"value": [["SERVICE_NAME", "MyService"], ["SUPPORTED_VERSIONS", []],
["IDL_MAGIC", "d9c0cdeaa37fd00f508e6913e1ee6a3355d07b52"]]}]]}

As you can see, the default encoding format is JSON. You can specify the format you want to work with by explictly setting the format query parameter. For instance, go to http://localhost:12000/?format=xml (or ?format=json).

You may also have noticed two URLs in the information above: functions_url is the relative path of the list of functions exposed by this service, and objects_url is a map of URLs to objects.

Point your browser at http://localhost:12000/funcs. You will see a list of functions. Suppose that one of them is called add and that it takes two parameters, int a and int b and returns their sum. Point your browser at http://localhost:12000/funcs/foo – you will see a message such as this one:

use POST to invoke function
path = /funcs/foo

With GET you can only inspect the service, but not invoke functions. In order to invoke a function, you will need to POST to that URL above, passing the function’s arguments as a dictionary in the HTTP payload. For example:

POST /funcs/foo HTTP/1.1
Host: localhost:12000
Content-Length: 42

{type="map", value=[["a", 11], ["b", 12]]}

The HTTP front-end will decode your request and invoke the add function of the back-end, passing it a = 11 and b = 12. You will get a result such as this:

HTTP/1.1 200 OK
Content-Length: 2

23

The server answers your request in the same format that you sent it – JSON for JSON, XML for XML.

If a function returns a proxy object, the result will look like {"type": "proxy", "name": "Person", "url": "/objs/1234"}. You can then point your browser at http://localhost:12000/objs/1234 and inspect the returned object. You can issue a GET for reading attributes, at the attribute’s URL, e.g., http://localhost:12000/objs/1234/myattr.

In order to set attributes or invoke methods of that object – use POST, in the same way described above (only the URL would be /objs/1234/myattr or /objs/1234/myfunc).