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
1% web_https_client.py - Example of connecting to a standard
2% web server using HTTPS and WebSockets
3
4% Connect to the service using rr+wss, the secure WebSocket transport
5url = 'rr+wss://wstest2.wasontech.com/robotraconteur?service=testobj';
6c = RobotRaconteur.ConnectService(url);
7
8assert(c.add_two_numbers(2, 4) == 6);
9
10RobotRaconteur.DisconnectService(c);
1// web_https_client.py - Example of connecting to a standard
2// web server using HTTPS and WebSockets
3
4using System;
5using RobotRaconteur;
6using System.Threading;
7using System.Threading.Tasks;
8using System.Linq;
9using System.Diagnostics;
10
11// Initialize the client node
12using (var node_setup = new ClientNodeSetup(args))
13{
14 // Connect to the service using rr+wss, the secure WebSocket transport
15 var c = (experimental.wstest.testobj)RobotRaconteurNode.s.ConnectService(
16 "rr+wss://wstest2.wasontech.com/robotraconteur?service=testobj");
17
18 Debug.Assert(c.add_two_numbers(2, 4) == 6);
19}
1// web_https_client.py - Example of connecting to a standard
2// web server using HTTPS and WebSockets
3
4#include <stdio.h>
5#include <iostream>
6#include <RobotRaconteur.h>
7#include "robotraconteur_generated.h"
8
9// Only use the RR alias in cpp files. Do not use it in header files.
10namespace RR = RobotRaconteur;
11
12int main(int argc, char* argv[])
13{
14 // Use RobotRaconteur::NodeSetup to initialize Robot Raconteur
15 RR::ClientNodeSetup node_setup(ROBOTRACONTEUR_SERVICE_TYPES, argc, argv);
16
17 // Connect to the service using rr+wss, the secure WebSocket transport
18 std::string url = "rr+wss://wstest2.wasontech.com/robotraconteur?service=testobj";
19 auto c = RR::rr_cast<experimental::wstest::testobj>(RR::RobotRaconteurNode::s()->ConnectService(url));
20
21 assert(c->add_two_numbers(2, 4) == 6);
22
23 std::cout << "web_https_client example complete" << std::endl;
24
25 return 0;
26}