.. _sec:model-api: ************* 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 :ref:`sec: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. .. table:: Valid parameter types for exposed methods :align: left ================== ============================================ =========== ABS type URLencoded format JSON format ================== ============================================ =========== ``Bool`` literal upper- or lowercase JSON boolean ``true`` / ``false``: ``?p=True``, ``?p=true``, ``?p=False``, ``?p=false`` ``Int`` a string of digits, e.g., ``?p=42`` JSON integer ``Float`` a floating-point number, e.g., ``?p=3.14`` JSON float ``String`` URLEncoded text, e.g., ``?p=Hello%20World!`` JSON string ``List`` not supported JSON list, elements are converted as per this table ``Map`` 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. .. table:: Method return value conversion :align: left ================================================================== =========== ABS type JSON result format ================================================================== =========== ``Bool`` JSON boolean value ``String`` JSON string value ``Int`` JSON integer ``Rat`` JSON float, via integer division. The behavior is unspecified if the ``Rat`` value is outside of floating point range. ``Float`` JSON float ``List`` JSON list, with elements converted per this table. ``Set`` JSON list, guaranteed to contain no duplicate elements, with elements converted per this table. ``Map`` JSON object, with keys generated from their ABS counterpart via ``toString()``, values converted per this table. Datatype with at least one named or annotated constructor argument JSON object (see below) Others JSON string via ABS ``toString()`` ================================================================== =========== 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 .. table:: Customizing datatype return value conversion :align: left ============================================================== ==================== ABS definition Sample JSON-encoded value ============================================================== ==================== ``data D1 = D1(String, Int);`` ``"D1(\"x\", 1)"``, via ``toString()`` ``data D2 = D2(String key, Int);`` ``{ "key":"x" }`` ``data D3 = D3([HTTPName: "effective key"] String key, Int);`` ``{ "effective key":"x" }`` ============================================================== ==================== 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 method - ``parameters``: an array with one object per parameter, each with the following entries: - ``name``: name of the parameter - ``type``: type of the parameter - ``return``: return type of the method .. _sec:calling-exposed-methods: Calling exposed methods ======================= Exposed methods are called by querying a URL of the form:: http://.../call// 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`` from Javascript: .. code-block:: 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 :ref:`sec:customizing-url-prefixes`). 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 (:ref:`sec: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 :ref:`timed-abs-and-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: .. code-block:: html .. _sec:customizing-url-prefixes: 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 .. note::: In the ``index.html`` file, do not use an initial solidus (``/``) to refer to static files and method calls, since such http requests will not work when the model is run with an url prefix. Instead of ````, write ````.