The model API¶
ABS includes support for querying a running model. When creating
objects, an annotation HTTPName makes them available via HTTP
requests from outside the model under the name given by the
annotation. Methods that are annotated HTTPCallable can be
invoked from the outside on such objects. Additionally, datatypes can
be annotated to influence how they are converted to JSON when they are
returned from such methods.
Note
The Model API might accept connections only from local (loopback) addresses, i.e., from the same machine.
Starting the model API¶
When an ABS model is started with the -p parameter naming a port
number, the model will listen on the specified port for requests. In
the following example, we compile a model my-model.abs and start
the model API on port 8080:
$ absc --java my-model.abs -o my-model.jar
$ java -jar my-model.jar -p 8080
# alternatively, using the Erlang backend:
$ absc --erlang my-model.abs
$ gen/erl/run -p 8080
Shutting down the model API¶
When running a model with the model API activated, it will not return to the command line after the simulation has finished. Instead, the model will keep listening for requests and method calls.
A running model can be terminated manually from the console (for
example, via pressing Ctrl-C), or by requesting the URL /quit.
The following command will terminate a model running on port 8080:
$ curl localhost:8080/quit
Exposing objects¶
The Model API can be used to access objects of the running model, if these models are exposed.
Objects are exposed via a HTTPName annotation. In the following
example, two objects of class C are exposed with the names C1
and C2 respectively. The HTTPName annotation can be used on
assignment statements, variable declarations and new expression
statements:
[HTTPName: "C1"] new C();
[HTTPName: "C2"] I x = new C();
Exposing methods¶
The Model API can be used to call methods of exposed objects, if those methods are exposed in their interface definition. Exposed methods have some restrictions for which arguments they can accept – their argument values need to be serialized via JSON or URL parameters when called via the Model API.
In an interface declaration, a HTTPCallable annotation exposes the
annotated method such that it is callable from outside, given an
exposed object that implements that interface:
interface I {
[HTTPCallable] String method(String param1, Int param2);
}
The following sections specify how values of various types are converted between Model API requests and ABS. See Calling exposed methods for the format of a Model API method call itself.
Encoding method parameters¶
Exposed methods can be called via HTTP requests, with parameters passed either via URL encoding or via a JSON method body.
It is a compile-time error if the method takes parameters whose types are not supported.
ABS type |
URLencoded format |
JSON format |
|---|---|---|
|
literal upper- or lowercase
|
JSON boolean |
|
a string of digits, e.g., |
JSON integer |
|
a floating-point number, e.g., |
JSON float |
|
URLEncoded text, e.g., |
JSON string |
|
not supported |
JSON list, elements are converted as per this table |
|
not supported |
JSON map, values are converted as per this table |
All others |
not supported |
not supported |
Return value format¶
The method’s return value is returned in the body of the query response. The method can have any return type.
Method call results will be returned as a string via the ABS
toString() function, except for the types enumerated in the
following table.
ABS type |
JSON result format |
|---|---|
|
JSON boolean value |
|
JSON string value |
|
JSON integer |
|
JSON float, via integer division. The behavior is unspecified if the |
|
JSON float |
|
JSON list, with elements converted per this table. |
|
JSON list, guaranteed to contain no duplicate elements, with elements converted per this table. |
|
JSON object, with keys generated from their ABS counterpart via |
Datatype with at least one named or annotated constructor argument |
JSON object (see below) |
Others |
JSON string via ABS |
User-defined datatypes are encoded depending on the presence of
accessor functions and HTTPName annotations. If the datatype
definition contains neither, values will be encoded as strings via
toString(). If at least one accessor function or HTTPName
annotation is present, values will be encoded as objects, with the
HTTPName annotation value (or accessor function name, if no
annotation is present) as key. Unnamed constructor argument values
will not be contained in the JSON object.
Example of encoding of user-defined data types
ABS definition |
Sample JSON-encoded value |
|---|---|
|
|
|
|
|
|
Querying object state¶
The Model API can inspect the state of exposed objects, i.e., the names and values of the object’s fields.
The following query returns the names of all exposed objects:
GET http://localhost:8080/o
Inspecting an object state directly can be useful for debugging. The
following query returns a JSON map of the state of the object exposed
as C1, with object fields as keys:
GET http://localhost:8080/o/C1
The following query returns a JSON map containing the value of
C1’s field, with "field" as key:
GET http://localhost:8080/o/C1/field
When querying for an unknown object or an unknown field, the HTTP request will produce a 404 response code.
Listing the exposed methods of an object¶
The following query returns, for an object exposed as C1, a JSON
array of objects with metadata about callable functions:
GET http://localhost:8080/call/C1
Each entry in the returned array will be a JSON object with the following keys:
name: the name of the exposed methodparameters: an array with one object per parameter, each with the following entries: -name: name of the parameter -type: type of the parameterreturn: return type of the method
Calling exposed methods¶
Exposed methods are called by querying a URL of the form:
http://.../call/<objectname>/<methodname>
Parameters are passed to methods either as query parameters in the URL or in a JSON map passed in as the body of a POST request. For duplicate arguments, parameter values in the URL override values given in the JSON body.
The following query produces the return value of the method call
method("value", 50) by invoking it on the object exposed as
C1:
GET http://localhost:8080/call/C1/method?param1=value¶m2=50
This query can be invoked from the shell in two ways, using the curl
command, either using query parameters or a JSON body:
$ curl http://localhost:8080/call/C1/method?param1=value\¶m2=50
$ curl -d "{ 'param1': 'value', 'param2': 50 }" http://localhost:8080/call/C1/method
Of course, the Model API can not only be invoked via curl but also
from other programs. The following example shows how to call a method
testConfig that takes an argument mylist of type List<Int>
from Javascript:
fetch("call/Model/testConfig", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify( { mylist: [1, 2, 3] } ),
})
.then(response => response.json())
.then(result => {
console.log("Result: " + JSON.stringify(result));
})
.catch(error => {
console.error("Error: " + error);
});
Note
Note the missing solidus (/) at the beginning of the
query url – this makes the request robust against custom
url prefixes (see Customizing the model API URL).
Care must be taken to disable timeouts on the HTTP client when querying for long-running methods in this way.
When querying for unknown objects or methods, the HTTP request will produce a 404 response code.
When querying with invalid method parameters, the HTTP request will produce a 400 response code.
When the invoked method throws an exception, the HTTP request will produce a 500 response code.
The model API and timed ABS¶
The simulated clock of Timed ABS (Timed ABS) is accessible via the Model API.
The current value of the clock can be obtained with the following request:
GET http://localhost:8080/clock/now
The result is a JSON object with a key 'result' mapping to the
current value of the clock.
If the model was started with a clock limit (see Timed ABS and the model API), the limit can be increased via a request like the following:
GET http://localhost:8080/clock/advance?by=50
The result is a JSON object with a key 'result' mapping to the new
clock limit.
This call will always increase the clock limit by the given amount,
even if the clock had not yet reached the previous limit. I.e., when
now() = 10 and the limit is 20, after the call the limit will be
70, the same as when the clock was already stopped at the limit of 20
when the call was received by the Model API.
Note that increasing the clock limit if the model was not started with an initial limit has no effect.
Customizing the browser-based visualization¶
Since the Model API is implemented via HTTP, it can be accessed from a
web browser. The --modelapi-index-file command-line switch is
used to supply an index.html file at compile-time:
$ absc --java --modelapi-index-file ./index.html *.abs
When running a model on port 8080 and accessing
http://localhost:8080/ from a browser, the contents of that file
will be displayed.
Sometimes it is necessary to add additional files for visualization,
e.g., CSS files, images or JavaScript libraries. The contents of one
directory can be added to the model via the --modelapi-static-dir
command-line switch:
$ absc --java --modelapi-index-file ./index.html --modelapi-static-dir ./support-files/ *.abs
The contents of the given directory are copied at compile-time. The
files within that directory are available within the Model API below
the static/ path. For example, a file
./support-files/js/d3.js will be accessible as
http://localhost:8080/static/js/d3.js, and can be referred within
the index.html file like this:
<script type="text/javascript" src="static/js/d3.js"></script>
Customizing the model API URL¶
Note
This functionality is currently only available in the Erlang backend; patches are welcome.
It is sometimes necessary to change the url under which the model
publishes the given index.html file and static files. When
starting a model, the parameter --url-prefix can be used to insert
a prefix to these paths, and to the /call and /o URLs
described above.
As an example, the following command will make the index file given at compile-time available at http://localhost:8080/abcd/5080/index.html:
$ gen/erl/run -p 8080 --url-prefix /abcd/5080