When getting started with RealityServer, many customers ask us the best place to begin in order to learn how RealityServer works. One of the best and most enjoyable ways we find is to explore the JSON-RPC API which remains the main way that RealityServer functionality is accessed. In this article we will provide an overview of how the RealityServer JSON-RPC API works and some of the best ways to explore and play with functionality exposed there. Whether you are new to RealityServer or a veteran user you will find some valuable pointers.
It is very likely that you have worked with several Web Services API types already in your development work. Today it is most common to encounter what are known as RESTful Web Services. These work well for implementing CRUD style functionality and general document retrieval however their stateless nature makes it difficult to efficiently work with a technology like RealityServer. Imagine if each time you requested an image RealityServer had to reload and recreate everything from scratch, while it would work there would no longer be any interactivity. To combat this, early on we took the decision to instead use RPC based Web services, with server-side state. While traditional thinking is that server-side state makes scaling difficult in the case of RealityServer the scaling considerations related to the rendering component far outweigh any overhead of state. We didn’t like the overhead and envelope size of the various SOAP protocols and XML-RPC felt harder to work with in JavaScript based clients. So we settled on JSON-RPC as the default protocol for our Web services API.
For the adventurous among you, RealityServer also has a C++ API for developing your own protocols should you really prefer not to work with JSON-RPC. You can find more information here. Our JSON-RPC protocol is implemented using the same API. As long as you can cast your protocol in a way that is command based you should be able to implement it.
In RealityServer Web Services, the fundamental element you work with is the command. The API consists of a large number of commands which perform various operations on the server side and return responses to the client. These commands can accept complex parameters and may alter the state of the server in many cases (e.g., loading a scene file). A single request to the Web services API can consist of a single command or an array of commands. When using an array of commands they are executed on the server in turn and an array of responses is returned. Each command can have an id as defined in JSON-RPC so that you can match responses with requests when using multiple commands in a single request.
The list of available commands in RealityServer should be the part of the documentation you refer to more often that anything else. You can find this at http://host:port/?doc on your RealityServer. The list is divided into groups based on the type of command and there is also an all group which lists every command available. The documentation also lists commands added by plugins or JavaScript Web Services commands and is generated live and automatically. Many of our engineers leave this page open constantly while building a RealityServer application.
You can use any tool which can send HTTP POST requests to explore the RealityServer API, however one of the best and our favourite is Postman. Available as a Chrome App on multiple platforms (and stand-alone app on Mac), Postman lets you create and send requests to RealityServer and keep a history of your past requests, even organising them into collections. Everything talked about here can also be done with just cURL or wget on the command line, but of course this isn’t as easy to use. When prototyping a RealityServer application and trying to test concepts this type of exploration is invaluable.
To use Postman for RealityServer requests set the request type to POST, use the root of your RealityServer as the URL, switch to the Body tab and select the raw radio button and change the type to JSON (application/json). You can then enter the JSON corresponding to your JSON-RPC requests in the editor and press the Send button to send the request.
Postman can also generate code snippets for many languages and run automated tests. There is a nice system of Environments as well for providing variable substitution (for example replacing hostnames when you test on different servers). This is really the tip of the iceberg so be sure to checkout the Postman website for more details. Even if you just use the basics, it is definitely a great tool for RealityServer developers.
In JSON-RPC you call named commands with optional named parameters. The parameters themselves may be JSON objects, arrays and combinations of these as well as primitive values for strings, numbers and booleans. Consult the documentation for each command for a list of the available parameters. Parameters which do not have defaults must be supplied. Here is an example of a very simple single command that just responds with the string that is passed to it.
{"jsonrpc": "2.0", "method": "echo", "params": { "input" : "Hello world!" }, "id": 1},
If you send this command to RealityServer it will generate the following response.
{ "id": 1, "jsonrpc": "2.0", "result": "Hello world!" }
Since there is only a single command in this case the id isn’t that useful (more on this later), the main part you will usually look at is the result property of the response (if the command returns something). So what happens if the command fails for some reason though? Let’s say we forget to provide the input parameter to the command, then we will get this response instead.
{ { "error": { "code": -32602, "message": "Invalid arguments. Mandatory argument \"input\" not provided." }, "id": 1, "jsonrpc": "2.0" }
Instead of a result property we now have an error property along with a code and message to tell us more about the error. This makes it easy to test for problems with commands by just looking for the error property. When using a tool like Postman it also helps to see the human readable errors so you can diagnose problems. At a basic level, this example shows you almost everything you need to communicate with RealityServer.
Quite often you will want to run multiple commands in a single request. This helps save round trip communication to the server and can also be necessary when using scopes. Sending multiple commands with JSON-RPC is actually really easy. You simply place the commands into an array like so.
[ {"jsonrpc": "2.0", "method": "create_scope", "params": { "scope_name" : "appScope" }, "id": 1}, {"jsonrpc": "2.0", "method": "use_scope", "params": { "scope_name" : "appScope" }, "id": 2}, {"jsonrpc": "2.0", "method": "import_scene", "params": { "scene_name" : "appScene", "filename" : "scenes/empty.mi" }, "id": 3}, {"jsonrpc": "2.0", "method": "delete_scope", "params": { "scope_name" : "appScope" }, "id": 4} ]
This set of commands creates a scope, instructs future commands in the batch to use that scope, loads a scene from disk and finally deletes the scope (and everything created within it). Not a terribly useful command but instructive for our case none the less. Here is the response the server will give for this batch of commands (feel free to try yourself on your RealityServer installation).
[ { "id": 1, "jsonrpc": "2.0", "result": { "__jsonclass__": [ "Scope_data", [] ], "scope_name": "appScope", "privacy_level": 1 } }, { "id": 2, "jsonrpc": "2.0", "result": {} }, { "id": 3, "jsonrpc": "2.0", "result": { "__jsonclass__": [ "Import_result", [] ], "options": "opt", "rootgroup": "rootgroup", "camera_instance": "persp", "camera": "perspShape", "imported_elements": [], "messages": [] } }, { "id": 4, "jsonrpc": "2.0", "result": {} } ]
You can see there are multiple responses in a JSON array here. This is where the id property becomes more important. If you want the response for a particular request you must check the id property since the responses may come out of order. Some commands don’t return anything in which case their result property will be an empty object. The above response also shows two commands that return custom types, Scope_data and Import_result which have specific structures (defined in the command documentation).
The above is all great for exploring the API and learning how it works as well as testing ideas and commands but what about when you actually want to start programming against it? If you are accessing it from the browser then you may want to start with our JavaScript Client Library which abstracts away the service marshalling aspects to make things simpler but more and more we are seeing customers communicating with RealityServer from another server rather than the browser. Since there are so many programming languages used for web development we obviously can’t cover them all here. However any language which can make HTTP POST requests (I struggle to think of any language in modern use that can’t) you can use the API. Languages with native support for JSON make life a lot easier of course but you can always build the JSON strings by hand if needed.
Here are some examples of sending a simple array of one command to RealityServer in different programming languages using popular libraries. For each (except the Shell example) we extract the result from the first response. Using this as a starting point it is very easy to build frameworks for calling RealityServer commands from any language. Here at migenius our current favourite is Node.js using the Jayson JSON-RPC client. It really abstracts away most of the complexity of the protocol for you (not that JSON-RPC is complex but the boilerplate can get tedious). Of course, even if your favourite language is not shown below you should be able to easily adapt these examples.
var jayson = require("jayson"); var client = jayson.client.http({ host: "127.0.0.1", port: 8080 }); var commands = [ client.request("echo", { input: "Hello world!" }) ]; client.request(commands, function(err, responses) { if (err) throw err; console.log(responses[0].result); });
var request = require("request"); var commands = [ { jsonrpc: '2.0', method: 'echo', params: { input: 'Hello world!' }, id: 1 } ]; var options = { method: 'POST', url: 'http://127.0.0.1:8080/', headers: { 'content-type': 'application/json' }, body: commands, json: true }; request(options, function (error, response, decoded) { if (error) throw new Error(error); console.log(decoded[0].result); });
<?php $curl = curl_init(); $commands = array( array( "jsonrpc" => "2.0", "method" => "echo", "params" => array( "input" => "Hello world!" ), "id" => 1 ) ); curl_setopt_array($curl, array( CURLOPT_PORT => "8080", CURLOPT_URL => "http://127.0.0.1:8080/", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => json_encode($commands), CURLOPT_HTTPHEADER => array( "content-type: application/json" ) )); $response = curl_exec($curl); $err = curl_error($curl); if ($err) { echo "cURL Error #:" . $err; } else { $decoded = json_decode($response); echo $decoded[0]->result . "\n"; }
import json import http.client conn = http.client.HTTPConnection("127.0.0.1:8080") commands = [ { 'jsonrpc': '2.0', 'method': 'echo', 'params': { 'input' : 'Hello world!' }, 'id': 1 } ] headers = { 'content-type': "application/json" } conn.request("POST", "/", json.dumps(commands), headers) res = conn.getresponse() data = res.read() decoded = json.loads(data.decode("utf-8")) print(decoded[0]['result'])
require 'uri' require 'net/http' require 'json' url = URI("http://127.0.0.1:8080/") commands = [ { :jsonrpc => "2.0", :method => "echo", :params => { :input => "Hello world!" }, :id => 1 } ] http = Net::HTTP.new(url.host, url.port) request = Net::HTTP::Post.new(url) request["content-type"] = 'application/json' request.body = commands.to_json response = http.request(request) decoded = JSON.parse(response.read_body) puts decoded[0]["result"]
curl -X POST -H "Content-Type: application/json" -d '[ {"jsonrpc": "2.0", "method": "echo", "params": { "input" : "Hello world!" }, "id": 1} ]' "http://127.0.0.1:8080/"
In order transmit images efficiently, RealityServer does something a bit non-standard with respect to JSON-RPC. For commands with a render type of Binary, for example the render command, instead of returning a JSON formatted response the server will actually return a binary, usually an image file. This may break strict JSON-RPC clients so for calls to those specific commands you may need to roll your own client. If you don’t mind some size overhead, most commands that return binary have variants that return a base64 encoded version of the binary instead within a JSON-RPC envelope. These are compatible with standard JSON-RPC clients and can be useful for other applications, however the transmitted size of the binary will be increased. Note that when using a batch of commands, if any command in the batch produces a binary then the response of the entire batch will also become a binary. Postman is clever enough to handle binary responses to the JSON requests and will display images within its UI.
In future articles we will explore how to assemble a series of RealityServer commands to perform useful operations such as loading a 3D scene, changing materials and rendering images. The principles in this article will help you to expand our examples into real-world applications. We will also cover how to implement server-side JavaScript commands which can be used to create complex application logic very quickly. If you haven’t yet obtained your evaluation copy of RealityServer contact us now for a free trial.
Paul Arden has worked in the Computer Graphics industry for over 20 years, co-founding the architectural visualisation practice Luminova out of university before moving to mental images and NVIDIA to manage the Cloud-based rendering solution, RealityServer, now managed by migenius where Paul serves as CEO.