Projects

Basic Python webserver for the BeagleBone

Last updated: Jan. 28, 2012, 11:52 p.m.

The official Python logo

The BeagleBone comes with a basic webserver written in Javascript in the bonescript.js file. This is run by node.js which is a browser-less javascript engine. This took a bit of reading about to get my head around because the idea of javascript being used to write server side scripts was a bit odd, but the whole server seemed implausible. Unfortunately when I tried to make this do what I wanted to do for the BigTrak project, node.js wouldn't work and seems to be in some sort of version conflict but has to be maintained at this version for the Cloud9 IDE software. Instead I decided to write my own web page server in Python.

The server is a very simple single threaded construction, it listens for connections on port 80 (needs to be run as root because port 80 is a special purpose port), and, depending on the URL and request type does something and returns. There's nothing BeagleBone or even ARM specific here, one of the great advantages of a scripting language. At startup the server makes a list of files that are in a folder called "public", these are stored as actual files that can be served up later on rather than special URLs that trigger code. This is useful for serving CSS files and javascript etc. I've used as few libraries as I can in this so I just wrote a quick absolute to relative URL converter that takes a fully qualified URL and splits it down to a local path (e.g. http://www.example.com/wibble is converted to /wibble). This makes my life a bit easier as I don't have to know every variant of IP address and symbolic name that my server might run on, it's an attempt at being better w3c compliant, which is overkill really on a little embedded thing like this.

The magic_mime() function just returns a MIME type string based on the file extension, there's nothing clever here, the file is never tested to see if it really is what it says it is but it gives a quick way of deciding if something is an html file or a stylesheet when serving real files.

post2dict() function takes a POST form that's been effectively URL encoded (this wouldn't work for multipart/form data types) and returns the elements as a Python dictionary with any escape characters put back to their literals.

The listener() function does the main work, after setting up the socket to listen on it goes into an infinite loop, waiting for connections. Each connection is handled serially, so don't expect this to deal with running a real website, if someone has connected to it and is waiting for a page it will just ignore any more attempts to connect.

Receiving data from a TCP socket isn't as easy as reading a file, there's no indication when you've got it all (you could just set a timeout and wait a long time but that's slow), and you have to read it in chunks because of the way the underlying buffer structure is built. To do it right, you need to read all of the HTTP headers, take note of the "Content-Length" header which tells you how much extra data (POST form data for example) is after the headers. The end of the headers is defined as a blank line so after receiving the first buffer full of data my code goes into an infinite loop with a "break" if it finds a complete line blank (i.e. two newlines with only white space between). After that is a simple byte counting loop until all the form data is received.

Once a full request is received, the code decides what to do with it, this is done primarily based on the URL requested.

Handled URLs

/run

This is the "do it" URL, you send your code to this URL in a POST request. This causes it to dump your code to a temporary file and then calls a separate instance of Python using the subprocess module to run the logo_interp.py interpreter. If you request the page when the subprocess is still running it serves up a page that just says "Running..." at the moment, I'm thinking a nice highlighted version of the code showing you where it's got to would be a better page to return here. The "Running..." page has a meta-refresh tag set in the HTML, this causes it to keep refreshing the page automatically every 5 seconds, if the process has finished the server redirects the request to the index page so you can go back to editing the code.

Real files

If the request is the name of a real file that was stored when the server was started, the server looks up the file type with magic_mime() and returns the contents of the file. This handles all the stylesheet and possible future javascript support files.

/

Like most web servers if you access the root page it serves up "index.html" from the public folder. It fills in the code field with the text of the last program run though so if you just ran a program and got redirected back here after it finished you can start editing instead of having to save your code on your local machine.

404!

If none of the handled URLs have matched the server responds with a 404 page of course.

Hand coding all this with such low level constructs as the socket library and string operations is not a very modder friendly way to build code but it is very quick for me as I don't need to read documentation about other people's modules. It also keeps the dependencies down which can be handy when you are working on an embedded platform.

AttachmentLast UpdateSize
Python source codeAug. 26, 2014, 10:11 p.m.4.2 KB
Section:
Projects
Tags:
Python,
software,
TCP,
web server,
HTTP

Comments

How did you set the BBB to start with your webserver?

25 Jun 2013 - 01:06 ST

Hi there. GREAT tutorial! I'd like to try something similar myself. How did you configure the BBB to start with your own webserver, instead of the default one? Thanks for any advice.
ST

Re: How did you set the BBB to start with your webserver?

04 Jul 2013 - 21:29 nathan

The details of how I auto-started the server were in another post, near the bottom it has a section "Making it Autostart" WiFi hotspot and DHCP from a BeagleBone. This is a long while back now, I can't quite remember but I think I replaced or altered the startup script in <code>/etc/systemd/system/multi-user.target.wants/</code>. This was using the original release BeagleBone SD image so whether things have changed by now or the BeagleBone Black has a different startup arrangement, I don't know.

Posting comments is not currently possible. If you want to discuss this article you can reach me on twitter or via email.


Contact

Email: nathan@nathandumont.com

Mastodon: @hairymnstr@mastodon.social