HaddingtonDynamics / Dexter

GNU General Public License v3.0
374 stars 85 forks source link

Implement a WebSocket server #55

Closed Kenny2github closed 5 years ago

Kenny2github commented 5 years ago

There is the raw socket server on port 50000, but web applications (browser JavaScript) are not able to use raw sockets. Therefore, I recommend implementing a WebSocket server.

There are a couple of ways to do this:

JamesNewton commented 5 years ago

We've already implemented a WebSocket server using Node.js. It's great to be able to work with the robot via a browser, but the speed of WebSockets is less than what we hoped for in a remote control situation, which was our main goal. https://github.com/HaddingtonDynamics/Dexter/wiki/nodejs-webserver We also linked to your scratch extensions from this page and they are VERY appreciated. We hope to make further use of the scratch system in the near future.

Kenny2github commented 5 years ago

Is such a server built into Dexter in newer releases? That's the more important part.

JamesNewton commented 5 years ago

Great question. Yes, I plan on adding the one shown here: https://github.com/HaddingtonDynamics/Dexter/wiki/nodejs-webserver#a-node-js-websocket-server But I want to make sure that will work for your scratch stuff.

Kenny2github commented 5 years ago

I mean, as long as there's something running on Dexter itself that obeys the WebSocket protocol, it'll work. And in the long run, whether it's easy to backport is of lower priority - I mean, I don't use the same Dexter every time I test and we seem to build new ones more than just once-off.

Actually, is there a way to update Dexter's firmware without rebuilding? That might prove useful later on.

JamesNewton commented 5 years ago

@Kenny2github, when you say "rebuilding" do you mean the ./pg compilation of DexRun.c? No. In the Linux world, distribution of source and local compilation is the standard. Only in Windows / Mac worlds are binaries the expected distributable. However, the current DexRun supports writing the DexRun.c to the robot via the socket interface, and future versions will support running shell scripts, so the entire update process can happen from DDE without the user having to know what is happening. #20 is the relevant discussion there.

@cfry It dawns on me that the "Run Engine" you are working on in DDE is the perfect candidate for supporting this websocket interface. As you can see, the code is NOT complex. https://github.com/HaddingtonDynamics/Dexter/wiki/nodejs-webserver#a-node-js-websocket-server

Also, it just dawned on me that as per: https://github.com/HaddingtonDynamics/Dexter/wiki/Scratch-extension Scratch actually needs a proxy running on the PC where the scratch programming is being done, NOT necessarily on the Dexter, unless the Dexter IS the PC... We have firefox on the new Dexter image... I'll have to try scratch on Dexter.

JamesNewton commented 5 years ago

I can't seem to get adobe-flash to install... @Kenny2github is there a chance of your extension supporting Scratch3 (which as I understand it, does NOT require flash)? Actually, I can't even seem to get that to load in the FireFox on Dexter. It appears to be quite out of date, but I can't update it via the OS. apt update doesn't see it as out of date. I will have to look into this more later.

JamesNewton commented 5 years ago

Scratch 3 is now supported, see wiki.

Kenny2github commented 5 years ago

Scratch 3 is supported, but the WS server is still required and as far as I can tell still not built in. And installing node is difficult when you can't connect Dexter to the Internet

JamesNewton commented 5 years ago

Ah. I believe the issue is that the websocket server must be on the same IP as Scratch3 is running. e.g. if you run a websocket proxy on Dexter, and run Scratch in a browser on a PC, then when you try to connect to the Dexter from the browser, it complains about the security issue. Am I wrong about that?

Kenny2github commented 5 years ago

You're probably (I haven't been able to test anything with a WS server on Dexter itself because we can't actually connect Dexter to the Internet and therefore can't install node and can't run the server) not wrong , CORS is no joke, but the simple fix is to add (somehow) a response header in the WS handshake like Access-Control-Allow-Origin: * so that browsers don't complain.

Currently the extension expects the server to be on localhost:3000 but if the WS server (with the header) was built in it would be simple enough to ask the user for Dexter's IP and connect to <that>:3000 or something, thus massively simplifying the process of using the extension (as installing node is complicated at best, and it's required for the server).

JamesNewton commented 5 years ago

As I understand it, that response header must be sent from the web server, e.g. from the scratch website. It's goal is to keep malicious code from running based on invalid content. So if a hacker wanted to attack your browser, and could get a <script src= tag imbedded in content on a good server, then point the src to his "evil" server, the browser would reject it. If it were possible for him to add Access-Control-Allow-Origin: to his own "evil" server, then he would do it. Instead, the "good" server has to say "I trust the evil server" by putting the header in the original response. I may be wrong, as I'm not an expert, but I think that's how that works.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin

Kenny2github commented 5 years ago

No, that's not how it works, at least not from my experimentation. Here's what I did:

First, I ran the Python http.server module directly, which runs a simple file-based server (though the response text is irrelevant here). I then went to http://example.com and put this in the console:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://' + my_computer_ip);
xhr.send()

And the response in the console was an error message saying Access to XMLHttpRequest at 'http://(my ip)/' from origin 'http://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I then set up a tiny Python server which just responds with a 200 code, an Access-Control-Allow-Origin: * header, and then the text Hello World!:

from http.server import HTTPServer, BaseHTTPRequestHandler

class Handler(BaseHTTPRequestHandler):
  def do_GET(self):
    self.send_response(200)
    self.send_header('Access-Control-Allow-Origin', '*')
    self.end_headers()
    self.wfile.write(b'Hello World!')

httpd = HTTPServer(('0.0.0.0', 80), Handler)
while 1:
  try:
    httpd.handle_request()
  except KeyboardInterrupt:
    break

After running that, I ran the JS code again, and it came through just fine: XHR finished loading: GET "http://(my ip)/". And the response was properly set: image

So I'm pretty sure that the server whose resource is being requested is the one responsible for adding the header, not the server whose content's JS is sending the request, and therefore if a WS server was built into Dexter it would only need to send the header to be usable by the Scratch extension, no matter where it's hosted.

JamesNewton commented 5 years ago

Ok, I've updated the httpd.js server so that it returns that header.

What do I need to do to test it?

JamesNewton commented 5 years ago

I've updated the httpd.js proxy server on my robot and tested it with this page: http://www.massmind.org/techref/robot/Dexter.html but it turns out that works with or without the extra header!

What do I need to do to test this with your scratch3 system?

Kenny2github commented 5 years ago

I am an idiot.

CORS doesn't apply to WebSockets, only to HTTP requests. Though the WS handshake follows the HTTP protocol, the scheme is still ws:// and so CORS doesn't apply. That being said, for any website to access files from Dexter through that server, it does need the header, so your change was warranted.

I haven't made the necessary changes to be able to test this yet, but I will at some point during the week (though I'm on vacation, so no guarantees).

JamesNewton commented 5 years ago

Hey @Kenny2github , how hard would it be to just install your extended version of Scratch 3 on Dexter? So that if you hit Dexters IP address with your browser, you would get Scratch? Is it just installing: https://github.com/Kenny2github/scratch-gui ? Or does that also then install a ton of dependencies? I'm trying to get a sense of the total "hard drive" (SD Card) space required. If it's not that much, it might just be worth adding it to the image. Especially if we could have a welcome page and then your Scratch is one option.

Kenny2github commented 5 years ago

Two problems with that:

  1. Too big. Total size on my install is 2.49 GiB.
  2. There still needs to be some sort of server running. (Does the samba folder work for that?)
JamesNewton commented 5 years ago

Hmm... 2 and a half gigs is probably more than we want to carry around on every image. How hard is it to install it on demand and remove it when not wanted? E.g. is there an easy command or two that spins it up? And can we reliably delete it and recover the space? The server in the /srv/samba/share/www folder can be spun up for that, I'm sure.

Kenny2github commented 5 years ago

Turns out I lied - the size of the "master" branch of my fork is 2.49 GB but the size of the actual built files served to the client totals only 30 MiB, which I think is suitable.

Installing Scratch on Dexter - now quite feasible. (I think there's already a server for samba share?) Just copy all the files in https://github.com/Kenny2github/scratch-gui/tree/gh-pages into some folder in /srv/samba/share/www. Using it with Dexter - still needs a WS server... JavaScript can't connect to raw sockets, period. Best case scenario would be for DexRun.c to run the WS server on some other port than the raw socket port, perhaps by using libwebsockets.

JamesNewton commented 5 years ago

Following Kenny's directions above has resulted in a working copy of scratch AND a web socket server being a regular part of Dexter. See the files in /srv/samba/share/www for the socket server and follow his directions to implement Scratch. The next standard image has all that (including scratch) on it. There are some issues (e.g. you can't save programs) but those should be addressed in a separate issue as this is for the socket server and that works. https://github.com/HaddingtonDynamics/Dexter/blob/StepAngles/Firmware/www/httpd.js

JamesNewton commented 4 years ago

I've also added a web socket interface proxy for the onboard "job engine" aka dde running on Dexter. This is separate from the web socket proxy to DexRun firmware: https://github.com/HaddingtonDynamics/Dexter/commit/8d28d192edcce2aaf3c33dd5350d06d0ae31ca0f

JamesNewton commented 4 years ago

One small change is recommended when running Kenny's scratch from Dexter: In the lib.min.js file, find the line: this._ws = new WebSocket('ws://192.168.1.142:3000'); and change it to this._ws = new WebSocket('ws://'+window.location.hostname+':3000');. This allows scratch to find Dexter no matter what it's IP address happens to be.

JamesNewton commented 3 years ago

Kamino cloned this issue to HaddingtonDynamics/OCADO