Web Browsers and Servers

Overview

Robot Raconteur supports using Web Sockets to communicate with services. The Web Socket protocol is an extension to HTTP that allows for full-duplex communication between a client and a server. The Robot Raconteur TCP transport is capable of using Web Sockets for both client and server communication. This allows for services to be accessed from web browsers, and for services to be hosted on web servers. Robot Raconteur supports using standard HTTPS encryption and certificate chains for secure communication.

Allowing Web Socket Connections

Web sockets use “origin” based security. The origin is the domain name of the web page that is making the connection. The server can allow or deny connections based on the origin. Robot Raconteur uses origin control to prevent cross-site scripting attacks. where random web pages attempt to connect to a service.

By default, the following origins are allowed:

  • null

  • file://

  • chrome-extension://

  • http://robotraconteur.com

  • http://robotraconteur.com:80

  • http://*.robotraconteur.com

  • http://*.robotraconteur.com:80

  • https://robotraconteur.com

  • https://robotraconteur.com:443

  • https://*.robotraconteur.com

  • https://*.robotraconteur.com:443

For pages loaded off the filesystem, the origin is either null or file://. For pages loaded from a web server, the origin is the domain name of the server. The port number is also sometimes included in the origin.

Any other origin, including localhost, will be denied by default. To allow additional origins, the command line argument --robotraconteur-tcp-ws-add-origin= or the TcpTransport::RegisterWebSocketAllowedOrigin() API function can be used. The command line arguments are process by the ServerNodeSetup instance that is used to initialize the node. The origin control is configured on the service side.

For example, to start Reynard the Robot and allow connections from http://localhost:8000, run the following:

python -m reynard_the_robot --robotraconteur-tcp-ws-add-origin=http://localhost:8000

Robot Raconteur Web Browser Clients using Pyodide

Pyodide is a Python runtime for the web. Pyodide allows for Python scripts to be executed in a web browser. It is implemented using Web Assembly (WASM). Robot Raconteur is included as a standard package in Pyodide so there is no installation required. The Robot Raconteur package uses HTTP Web Sockets to communicate with services.

The following is an example of a simple web page that uses Pyodide to connect to a Reynard the Robot and provides a simple user interface:

Click to view the Pyodide example code
 1# reynard_the_robot_client_pyodide.py - Reynard the Robot client using Pyodide
 2
 3from RobotRaconteur.Client import *
 4import js
 5import asyncio
 6import traceback
 7
 8
 9async def main():
10    status_div = js.document.getElementById("status")
11    status_div.innerHTML = "Connecting..."
12    error_log = js.document.getElementById("error_log")
13    try:
14
15        RRN.SetLogLevel(RR.LogLevel_Debug)
16
17        # All Robot Raconteur calls must use asynchronous variants
18        # in Pyodide because browsers are single threaded
19
20        # Connect to Reynard the Robot service using websocket
21        url = 'rr+tcp://localhost:29200?service=reynard'
22        c = await RRN.AsyncConnectService(url, None, None, None, None)
23
24        received_messages_div = js.document.getElementById("received_messages")
25
26        def new_message(msg):
27            received_messages_div.innerHTML += f"{msg}<br>"
28
29        # Connect a callback function to listen for new messages
30        c.new_message += new_message
31
32        # Handle when the send_message button is clicked
33        send_button = js.document.getElementById("send_message")
34
35        async def do_send_message():
36            try:
37                message = js.document.getElementById("message").value
38                await c.async_say(message, None)
39            except Exception as e:
40                error_log.innerHTML += f"Error: {traceback.format_exc()}" + "<br>"
41        send_button.onclick = lambda _: asyncio.create_task(do_send_message())
42
43        teleport_button = js.document.getElementById("teleport")
44
45        async def do_teleport():
46            try:
47                x = float(js.document.getElementById("teleport_x").value) * 1e-3
48                y = float(js.document.getElementById("teleport_y").value) * 1e-3
49                await c.async_teleport(x, y, None)
50            except Exception as e:
51                error_log.innerHTML += f"Error: {traceback.format_exc()}" + "<br>"
52        teleport_button.onclick = lambda _: asyncio.create_task(do_teleport())
53
54        status_div.innerHTML = "Connected"
55
56        # Run loop to update Reynard position
57        while True:
58            try:
59                # Read the current state using a wire "peek". Can also "connect" to receive streaming updates.
60                state, _ = await c.state.AsyncPeekInValue(None)
61                js.document.getElementById(
62                    "reynard_position").innerHTML = f"x: {state.robot_position[0]}, y: {state.robot_position[1]}"
63            except:
64                status_div.innerHTML = "Error"
65                error_log.innerHTML += f"Error: {traceback.format_exc()}" + "<br>"
66            await asyncio.sleep(0.1)
67
68    except:
69        status_div.innerHTML = "Error"
70        error_log.innerHTML += f"Error: {traceback.format_exc()}" + "<br>"
71
72asyncio.create_task(main())
 1<!-- reynard_the_robot_client_pyodide.html - Reynard the Robot client using Pyodide -->
 2<!doctype html>
 3<html>
 4  <head>
 5      <script src="https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.js"></script>
 6  </head>
 7  <body>
 8    <h1>Reynard the Robot Pyodide Client</h1>
 9    <h3>Status</h3>
10    <div id="status">Loading...</div>
11
12    <h3>Reynard Position</h3>
13    <div id="reynard_position"></div>
14
15    <h3>Teleport Reynard</h3>
16    x: <input id="teleport_x" placeholder="0.0"/> y: <input id="teleport_y" placeholder="0.0"/> <button id="teleport">Teleport</button>
17
18    <h3>Send a message to Reynard</h3>
19    <input id="message" cols="50"/>
20    <button id="send_message">Send</button>
21
22    <h3>New Messages from Reynard</h3>
23    <div id="received_messages"></div>
24
25    <h3>Error Log</h3>
26    <div id="error_log"></div>
27
28    <script type="text/javascript">
29    async function main()
30    {
31        // Initialize pyodide, load the required packages, and run the main script
32        let pyodide = await loadPyodide();
33        await pyodide.loadPackage(["numpy","micropip","Jinja2","RobotRaconteur","pyyaml"]);
34        pyodide.runPython(await (await fetch("/reynard_the_robot_client_pyodide.py")).text());
35    }
36    main()
37    </script>
38  </body>
39</html>

Robot Raconteur Web Browser Clients using Robot Raconteur Web

Robot Raconteur clients can be implemented in Java Script using Robot Raconteur Web and the H5 C# to JavaScript compiler. See the Robot Raconteur Web repository for more information.

Robot Raconteur Web Servers

Robot Raconteur Web is a pure C# implementation of the Robot Raconteur transport system that can be used inside an ASP.NET Core web server. This allows for Robot Raconteur clients to connect to standard web servers without any specialized infrastructure. See the Robot Raconteur Web repository for more information.

The rr+wss://wstest2.wasontech.com/ server is a minimal example of a Robot Raconteur running inside a standard web server, and is used for testing and demonstration purposes.

The following examples demonstrate connecting to the rr+wss://wstest2.wasontech.com/ server:

 1# web_https_client.py - Example of connecting to a standard
 2# web server using HTTPS and WebSockets
 3
 4from RobotRaconteur.Client import *
 5
 6# Connect to the service using rr+wss, the secure WebSocket transport
 7url = 'rr+wss://wstest2.wasontech.com/robotraconteur?service=testobj'
 8c = RRN.ConnectService(url)
 9
10assert c.add_two_numbers(2, 4) == 6