RestConsole
From OpenSimulator
m (more common word?) |
(→NOTE) |
||
(12 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
− | ==About== | + | {{Quicklinks|RestConsole}}{{ReleaseInfo}} |
− | The REST console makes remote administration of the various | + | == About == |
+ | The REST console makes remote administration of the various OpenSimulator services possible. | ||
The interface allows sending commands to the server and retrieving command output. Sending and receiving data is done through RESTful HTTP calls. | The interface allows sending commands to the server and retrieving command output. Sending and receiving data is done through RESTful HTTP calls. | ||
Line 10: | Line 11: | ||
The sample console client, OpenSim.ConsoleClient.exe, shows how this is done. | The sample console client, OpenSim.ConsoleClient.exe, shows how this is done. | ||
− | ==Usage== | + | == Usage == |
In order to use the remote console, start up OpenSimulator services with parameter <tt>-console rest</tt>. | In order to use the remote console, start up OpenSimulator services with parameter <tt>-console rest</tt>. | ||
Line 19: | Line 20: | ||
mono OpenSim.exe -console rest | mono OpenSim.exe -console rest | ||
− | ==Syntax== | + | == Syntax == |
We take the OpenSimulator services address <nowiki>http://foo.bar:8002</nowiki> as example here. | We take the OpenSimulator services address <nowiki>http://foo.bar:8002</nowiki> as example here. | ||
− | First start a new session by sending a HTTP POST request. User name and password should match the settings for <tt>ConsoleUser</tt> and <tt>ConsolePass</tt> in section <tt>[Network]</tt> of OpenSim.ini.<br> | + | First start a new session by sending a HTTP POST request. User name and password should match the settings for <tt>ConsoleUser</tt> and <tt>ConsolePass</tt> in section <tt>[Network]</tt> of OpenSim.ini.<br /> |
Parameters: USER, PASS | Parameters: USER, PASS | ||
<nowiki>http://foo.bar:8002/StartSession/</nowiki> | <nowiki>http://foo.bar:8002/StartSession/</nowiki> | ||
Return: (XML) <ConsoleSession><SessionID></SessionID><Prompt></Prompt></ConsoleSession> | Return: (XML) <ConsoleSession><SessionID></SessionID><Prompt></Prompt></ConsoleSession> | ||
− | Now we got the SessionID, which can be used to send a command and to receive output. First, retrieve the console scrollback buffer.<br> | + | Now we got the SessionID, which can be used to send a command and to receive output. First, retrieve the console scrollback buffer.<br /> |
Parameters: none | Parameters: none | ||
<nowiki>http://foo.bar:8002/ReadResponses/<SessionID>/</nowiki> | <nowiki>http://foo.bar:8002/ReadResponses/<SessionID>/</nowiki> | ||
− | Return: (XML) <ConsoleSession><Line Number=x></Line></ConsoleSession><br> | + | Return: (XML) <ConsoleSession><Line Number=x Level=l Prompt=p Command=c Input=i></Line></ConsoleSession><br /> |
The reply contains all lines currently in the buffer. Subsequent fetches will only retrieve new lines. The fetch will hold for up to 30 seconds if there is no data, then return an error. The client is expected to try again (polling). | The reply contains all lines currently in the buffer. Subsequent fetches will only retrieve new lines. The fetch will hold for up to 30 seconds if there is no data, then return an error. The client is expected to try again (polling). | ||
+ | The attributes are as follows: | ||
+ | Number = The line number (1 based) | ||
+ | Level = The level this is to be logged as | ||
+ | Prompt = bool, true if the output on this line is a prompt | ||
+ | Command = bool, true if the prompt is for a command | ||
+ | Input = bool, true if the output is eachoed input | ||
− | Use the SessionID as ID parameter, and send a POST request again.<br> | + | Use the SessionID as ID parameter, and send a POST request again.<br /> |
Parameters: ID, COMMAND | Parameters: ID, COMMAND | ||
<nowiki>http://foo.bar:8002/SessionCommand/</nowiki> | <nowiki>http://foo.bar:8002/SessionCommand/</nowiki> | ||
− | Return: (XML) <ConsoleSession><Result></Result></ConsoleSession><br> | + | Return: (XML) <ConsoleSession><Result></Result></ConsoleSession><br /> |
If everything went well, the command should have been executed. Try another command. | If everything went well, the command should have been executed. Try another command. | ||
− | When you want to close down the connection, send a POST request again.<br> | + | When you want to close down the connection, send a POST request again.<br /> |
Parameters: ID | Parameters: ID | ||
<nowiki>http://foo.bar:8002/CloseSession/</nowiki> | <nowiki>http://foo.bar:8002/CloseSession/</nowiki> | ||
− | Return: (XML) <ConsoleSession><Result></Result></ConsoleSession><br> | + | Return: (XML) <ConsoleSession><Result></Result></ConsoleSession><br /> |
The session is closed, and you have to log in again, when you want to send a command again. | The session is closed, and you have to log in again, when you want to send a command again. | ||
− | = | + | = Clients = |
− | == | + | == C# == |
− | + | A C# client is included with the Opensimulator distribution. It is rudimentary and not updated to the latest protocol versions. | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | ===JavaScript/HTML=== | + | == Qt == |
+ | |||
+ | The Qt 5 based client is at https://github.com/MelanieT/OpenSimConsoleClient | ||
+ | |||
+ | = Binaries = | ||
+ | Windows: http://opensimulator.org/downloads/ConsoleClient.zip | ||
+ | Mac: http://opensimulator.org/downloads/ConsoleClient.dmg | ||
+ | Linux: TBD | ||
+ | |||
+ | = Examples = | ||
+ | |||
+ | == NOTE == | ||
+ | |||
+ | Some of the examples below are for an older version of the interface. They may work to a degree but need to be revised. | ||
+ | |||
+ | === node.js === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | /* | ||
+ | | This piece of code is published by thomax (tx0h) (c) 2018 under the | ||
+ | | Artistic License 1.0 (http://www.perlfoundation.org/artistic_license_1_0) | ||
+ | | | ||
+ | | I removed my previous python approach because blueman made a better | ||
+ | | version and it was outdated. Here is a node.js variant. | ||
+ | */ | ||
+ | |||
+ | const readline = require('readline'); | ||
+ | var Client = require('node-rest-client').Client; | ||
+ | var client = new Client(); | ||
+ | var session_id; | ||
+ | var base_url = "http://127.0.0.1:9000"; | ||
+ | |||
+ | |||
+ | // set http header | ||
+ | var headers = { | ||
+ | "Content-Type": "application/x-www-form-urlencoded", | ||
+ | "User-Agent": "nodejs-console/0.01" | ||
+ | }; | ||
+ | |||
+ | // open session | ||
+ | function open_session() { | ||
+ | var args = { | ||
+ | data: { | ||
+ | "USER": "Test", | ||
+ | "PASS": 'secret' | ||
+ | }, | ||
+ | headers, | ||
+ | }; | ||
+ | |||
+ | client.post(base_url + "/StartSession/", args, function (data, response) { | ||
+ | session_id = data.ConsoleSession.SessionID; | ||
+ | read_session_buffer(); | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // close session | ||
+ | function close_session() { | ||
+ | args = { | ||
+ | data: { "ID": session_id }, | ||
+ | headers | ||
+ | }; | ||
+ | |||
+ | client.post(base_url + "/CloseSession/", args, function (data, response) { | ||
+ | console.log("close_session: "+response); | ||
+ | }); | ||
+ | |||
+ | console.log('Have a great day!'); | ||
+ | process.exit(0); | ||
+ | } | ||
+ | |||
+ | // read all the lines from the console buffer and print them | ||
+ | function read_session_buffer() { | ||
+ | args = { | ||
+ | data: { "ID": session_id }, | ||
+ | headers | ||
+ | }; | ||
+ | |||
+ | client.post(base_url + "/ReadResponses/"+session_id+"/", args, function (data, response) { | ||
+ | if(data.ConsoleSession.Line) { | ||
+ | var i; | ||
+ | for(i=0; i < data.ConsoleSession.Line.length; i++) { | ||
+ | if(data.ConsoleSession.Line[i]._) { | ||
+ | console.log(data.ConsoleSession.Line[i]._); | ||
+ | } | ||
+ | } | ||
+ | rl.prompt(); | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // execute the command from the prompt | ||
+ | function execute_cmd(cmd) { | ||
+ | args = { | ||
+ | data: { | ||
+ | "ID": session_id, | ||
+ | "COMMAND": cmd | ||
+ | }, | ||
+ | headers | ||
+ | }; | ||
+ | |||
+ | client.post(base_url + "/SessionCommand/", args, function (data, response) { | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | |||
+ | // here we go. | ||
+ | open_session(); | ||
+ | |||
+ | // prepare readline | ||
+ | const rl = readline.createInterface({ | ||
+ | input: process.stdin, | ||
+ | output: process.stdout, | ||
+ | prompt: 'REST CONSOLE> ' | ||
+ | }); | ||
+ | rl.prompt(); | ||
+ | |||
+ | // hunt for events | ||
+ | rl.on('line', (line) => { | ||
+ | switch (line.trim()) { | ||
+ | case 'quit': | ||
+ | close_session(); | ||
+ | break; | ||
+ | default: | ||
+ | if(line.length > 0) { | ||
+ | execute_cmd(line.trim()); | ||
+ | } | ||
+ | break; | ||
+ | } | ||
+ | read_session_buffer(); | ||
+ | rl.prompt(); | ||
+ | }).on('close', () => { | ||
+ | close_session(); | ||
+ | }); | ||
+ | </source> | ||
+ | |||
+ | === Python (Updated) === | ||
+ | |||
+ | <source lang="python"> | ||
+ | #!/usr/bin/python | ||
+ | # This piece of code is published by thomax (txOh) (c) 2010 under the | ||
+ | # Artistic License 1.0 (http://www.perlfoundation.org/artistic_license_1_0) | ||
+ | # Modifications (c) 2013 BlueWall | ||
+ | |||
+ | import urllib, urllib2 | ||
+ | import xml.dom.minidom | ||
+ | |||
+ | class UserConsoleClient(): | ||
+ | |||
+ | def __init__(self, addr): | ||
+ | self.addr = addr | ||
+ | url = self.addr + 'StartSession/' | ||
+ | |||
+ | params = urllib.urlencode({ | ||
+ | 'USER': 'username', # REST username | ||
+ | 'PASS': 'userpass' # REST password | ||
+ | }) | ||
+ | data = urllib2.urlopen(url, params).read() | ||
+ | print data | ||
+ | |||
+ | dom = xml.dom.minidom.parseString(data) | ||
+ | elem = dom.getElementsByTagName('SessionID') | ||
+ | self.sessionid = elem[0].childNodes[0].nodeValue | ||
+ | elem = dom.getElementsByTagName('Prompt') | ||
+ | self.prompt = elem[0].childNodes[0].nodeValue + '# ' | ||
+ | |||
+ | def close(self): | ||
+ | url = self.addr + 'CloseSession/' | ||
+ | params = urllib.urlencode({ | ||
+ | 'ID': self.sessionid | ||
+ | }) | ||
+ | print urllib2.urlopen(url, params).read() | ||
+ | |||
+ | def do_cmd(self, cmd): | ||
+ | url = self.addr + 'SessionCommand/' | ||
+ | params = urllib.urlencode({ | ||
+ | 'ID': self.sessionid, | ||
+ | 'COMMAND': cmd | ||
+ | }) | ||
+ | data = urllib2.urlopen(url, params).read() | ||
+ | dom = xml.dom.minidom.parseString(data) | ||
+ | elem = dom.getElementsByTagName('Result') | ||
+ | return elem[0].childNodes[0].nodeValue | ||
+ | |||
+ | def read_buffer(self): | ||
+ | url = self.addr + 'ReadResponses/' + self.sessionid + '/' | ||
+ | params = urllib.urlencode({ | ||
+ | 'ID': self.sessionid | ||
+ | }) | ||
+ | |||
+ | # print urllib2.urlopen(url, params).read() | ||
+ | |||
+ | result = urllib2.urlopen(url, params).read() | ||
+ | dom = xml.dom.minidom.parseString(result) | ||
+ | |||
+ | elem = dom.getElementsByTagName('Line') | ||
+ | for line in elem: | ||
+ | x0 = line.childNodes[0].nodeValue | ||
+ | x1 = str.replace(str(x0),':','~',2) | ||
+ | x2 = x1.split("~") | ||
+ | for lv in x2[2:]: | ||
+ | if not '+++' in lv: | ||
+ | print lv | ||
+ | |||
+ | # set the base url to the REST console (with port) | ||
+ | console = UserConsoleClient('http://example.com:8003/') | ||
+ | console.read_buffer() | ||
+ | print 'quit with a "."' | ||
+ | cmd = "" | ||
+ | while cmd != ".": | ||
+ | if cmd != "": | ||
+ | if 'OK' in console.do_cmd(cmd): | ||
+ | console.read_buffer() | ||
+ | cmd = raw_input("%s " % console.prompt) | ||
+ | |||
+ | console.close() | ||
+ | |||
+ | </source> | ||
+ | |||
+ | === JavaScript/HTML === | ||
<pre> | <pre> |
Latest revision as of 06:06, 10 April 2018
Contents |
[edit] About
The REST console makes remote administration of the various OpenSimulator services possible.
The interface allows sending commands to the server and retrieving command output. Sending and receiving data is done through RESTful HTTP calls.
While sending is very straightforward, receiving is not. Receiving uses reverse HTTP, performing a long poll to a CAPS URI.
In order to make the protocol more efficient, the help functionality has been pushed to the client side. Rather than sending each keystroke to the server, only validated command lines are sent. To make this possible, the server sends a burst-on-connect of data, which is the tree of allowed commands and their help information. This can be used by the client to create the "help" command output locally as well as provide command line help interactively.
The sample console client, OpenSim.ConsoleClient.exe, shows how this is done.
[edit] Usage
In order to use the remote console, start up OpenSimulator services with parameter -console rest.
Example for Robust server in grid mode:
mono Robust.exe -console rest
Example for simulators (both in grid and standalone mode):
mono OpenSim.exe -console rest
[edit] Syntax
We take the OpenSimulator services address http://foo.bar:8002 as example here.
First start a new session by sending a HTTP POST request. User name and password should match the settings for ConsoleUser and ConsolePass in section [Network] of OpenSim.ini.
Parameters: USER, PASS
http://foo.bar:8002/StartSession/
Return: (XML) <ConsoleSession><SessionID></SessionID><Prompt></Prompt></ConsoleSession>
Now we got the SessionID, which can be used to send a command and to receive output. First, retrieve the console scrollback buffer.
Parameters: none
http://foo.bar:8002/ReadResponses/<SessionID>/
Return: (XML) <ConsoleSession><Line Number=x Level=l Prompt=p Command=c Input=i></Line></ConsoleSession>
The reply contains all lines currently in the buffer. Subsequent fetches will only retrieve new lines. The fetch will hold for up to 30 seconds if there is no data, then return an error. The client is expected to try again (polling).
The attributes are as follows:
Number = The line number (1 based) Level = The level this is to be logged as Prompt = bool, true if the output on this line is a prompt Command = bool, true if the prompt is for a command Input = bool, true if the output is eachoed input
Use the SessionID as ID parameter, and send a POST request again.
Parameters: ID, COMMAND
http://foo.bar:8002/SessionCommand/
Return: (XML) <ConsoleSession><Result></Result></ConsoleSession>
If everything went well, the command should have been executed. Try another command.
When you want to close down the connection, send a POST request again.
Parameters: ID
http://foo.bar:8002/CloseSession/
Return: (XML) <ConsoleSession><Result></Result></ConsoleSession>
The session is closed, and you have to log in again, when you want to send a command again.
[edit] Clients
[edit] C#
A C# client is included with the Opensimulator distribution. It is rudimentary and not updated to the latest protocol versions.
[edit] Qt
The Qt 5 based client is at https://github.com/MelanieT/OpenSimConsoleClient
[edit] Binaries
Windows: http://opensimulator.org/downloads/ConsoleClient.zip Mac: http://opensimulator.org/downloads/ConsoleClient.dmg Linux: TBD
[edit] Examples
[edit] NOTE
Some of the examples below are for an older version of the interface. They may work to a degree but need to be revised.
[edit] node.js
/* | This piece of code is published by thomax (tx0h) (c) 2018 under the | Artistic License 1.0 (http://www.perlfoundation.org/artistic_license_1_0) | | I removed my previous python approach because blueman made a better | version and it was outdated. Here is a node.js variant. */ const readline = require('readline'); var Client = require('node-rest-client').Client; var client = new Client(); var session_id; var base_url = "http://127.0.0.1:9000"; // set http header var headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "nodejs-console/0.01" }; // open session function open_session() { var args = { data: { "USER": "Test", "PASS": 'secret' }, headers, }; client.post(base_url + "/StartSession/", args, function (data, response) { session_id = data.ConsoleSession.SessionID; read_session_buffer(); }); } // close session function close_session() { args = { data: { "ID": session_id }, headers }; client.post(base_url + "/CloseSession/", args, function (data, response) { console.log("close_session: "+response); }); console.log('Have a great day!'); process.exit(0); } // read all the lines from the console buffer and print them function read_session_buffer() { args = { data: { "ID": session_id }, headers }; client.post(base_url + "/ReadResponses/"+session_id+"/", args, function (data, response) { if(data.ConsoleSession.Line) { var i; for(i=0; i < data.ConsoleSession.Line.length; i++) { if(data.ConsoleSession.Line[i]._) { console.log(data.ConsoleSession.Line[i]._); } } rl.prompt(); } }); } // execute the command from the prompt function execute_cmd(cmd) { args = { data: { "ID": session_id, "COMMAND": cmd }, headers }; client.post(base_url + "/SessionCommand/", args, function (data, response) { }); } // here we go. open_session(); // prepare readline const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'REST CONSOLE> ' }); rl.prompt(); // hunt for events rl.on('line', (line) => { switch (line.trim()) { case 'quit': close_session(); break; default: if(line.length > 0) { execute_cmd(line.trim()); } break; } read_session_buffer(); rl.prompt(); }).on('close', () => { close_session(); });
[edit] Python (Updated)
#!/usr/bin/python # This piece of code is published by thomax (txOh) (c) 2010 under the # Artistic License 1.0 (http://www.perlfoundation.org/artistic_license_1_0) # Modifications (c) 2013 BlueWall import urllib, urllib2 import xml.dom.minidom class UserConsoleClient(): def __init__(self, addr): self.addr = addr url = self.addr + 'StartSession/' params = urllib.urlencode({ 'USER': 'username', # REST username 'PASS': 'userpass' # REST password }) data = urllib2.urlopen(url, params).read() print data dom = xml.dom.minidom.parseString(data) elem = dom.getElementsByTagName('SessionID') self.sessionid = elem[0].childNodes[0].nodeValue elem = dom.getElementsByTagName('Prompt') self.prompt = elem[0].childNodes[0].nodeValue + '# ' def close(self): url = self.addr + 'CloseSession/' params = urllib.urlencode({ 'ID': self.sessionid }) print urllib2.urlopen(url, params).read() def do_cmd(self, cmd): url = self.addr + 'SessionCommand/' params = urllib.urlencode({ 'ID': self.sessionid, 'COMMAND': cmd }) data = urllib2.urlopen(url, params).read() dom = xml.dom.minidom.parseString(data) elem = dom.getElementsByTagName('Result') return elem[0].childNodes[0].nodeValue def read_buffer(self): url = self.addr + 'ReadResponses/' + self.sessionid + '/' params = urllib.urlencode({ 'ID': self.sessionid }) # print urllib2.urlopen(url, params).read() result = urllib2.urlopen(url, params).read() dom = xml.dom.minidom.parseString(result) elem = dom.getElementsByTagName('Line') for line in elem: x0 = line.childNodes[0].nodeValue x1 = str.replace(str(x0),':','~',2) x2 = x1.split("~") for lv in x2[2:]: if not '+++' in lv: print lv # set the base url to the REST console (with port) console = UserConsoleClient('http://example.com:8003/') console.read_buffer() print 'quit with a "."' cmd = "" while cmd != ".": if cmd != "": if 'OK' in console.do_cmd(cmd): console.read_buffer() cmd = raw_input("%s " % console.prompt) console.close()
[edit] JavaScript/HTML
<html> <head> <!-- This JavaScript code is published by Marck (c) 2010 under a --> <!-- Creative Commons Attribution 3.0 Germany License --> <!-- http://creativecommons.org/licenses/by/3.0/de/ --> <script type="text/javascript"> var sessionId; var hostUrl; function StartSession(url, user, password) { hostUrl = url; var response = SendRequest("/StartSession/", "USER=" + user + "&PASS=" + password); sessionId = response.getElementsByTagName("SessionID")[0].firstChild.nodeValue; }; function ReadResponses() { var response = this.SendRequest("/ReadResponses/" + sessionId + "/", ''); var lines = response.getElementsByTagName("Line"); for (var i = 0; i < lines.length; ++i) { var element = document.createElement("div"); element.appendChild(document.createTextNode(lines[i].firstChild.nodeValue)); document.getElementById("output").appendChild(element); }; document.getElementById("output").scrollTop = document.getElementById("output").scrollHeight; }; function Command(cmd) { void SendRequest("/SessionCommand/", "ID=" + sessionId + "&COMMAND=" + cmd); }; function CloseSession() { void SendRequest("/CloseSession/", "ID=" + sessionId); }; function SendRequest(path, data) { var request = new XMLHttpRequest(); request.open("POST", hostUrl + path, false); request.send(data); return request.responseXML; }; window.onunload = CloseSession; </script> </head> <body> <form action="#" onsubmit=" StartSession(this.address.value, this.user.value, this.password.value); ReadResponses(); return false"> <input name="address" type="text" value="http://localhost:9000" /> <input name="user" type="text" value="ConsoleUser" /> <input name="password" type="text" value="ConsolePass" /> <input type="submit" value="Login" /> </form> <div id="output" style="height:15em; border:thin solid; overflow:auto;"></div> <form action="#" onsubmit=" Command(this.command.value); ReadResponses(); return false"> <input type="text" name="command" value="Enter command here" size="40"/> <input type="submit" value="Send Command" /> </form> </body> </html>