Personal IPv6/IPv4 Python Webserver
Why have a personal webserver? Easy, distribution of files. Sure you could use a thumb drive, but if you want to distribute a Pi image to everyone in the meeting, putting it on a personal webserver will make the transfer go much faster.
Why support IPv6 and IPv4? Not only is it cool, but it is very easy to do. IPv6 is the future of the internet, setting up a little personal webserver that supports both protocols will help you learn about the the future.
Why Python? Python is a wonderful programming language, and getting only better with version 3. There are libraries for most needs, including one which serves up the web. And it runs just about anywhere that Python runs (Windows, Linux, BSD, Mac, Pi, ODROID, etc)
Python module SimpleHTTPServer
The python module SimpleHTTPServer
supports IPv4 out of the box with the simple command:
python -m SimpleHTTPServer
However it does not support IPv6. There is no one line equivalent to support IPv6, so a small script is required.
Looking at the code
The ipv6-httpd
script is comprised of 4 sections:
- Initialization of the HTTPServer object (from SimpleHTTPServer library)
- Creation of a custom handler to support Virtual Paths
- Signal handling to permit cleaner quitting of the program
- Class creation (of HTTPServerV6) to support IPv6
As with all Python scripts, the details roll backwards from the bottom. Initialization occurs in main
def main():
global server
server = HTTPServerV6(('::', listen_port), MyHandler)
print('Listening on port:' + str(listen_port) + '\nPress ^C to quit')
server.serve_forever()
os._exit(0)
A custom handler is created to support Virtual Paths. In this handler, if the URL includes the path /ip, the browser client’s IP address is returned in an short HTML page. Otherwise, if the path is a directory, an index of the directory will be returned, or if the path is a file, the file will be returned (e.g. downloaded)
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
# if path is /ip then print client IP address (v4 or v6)
if self.path == '/ip':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
answer = 'Your IP address is ' + self.client_address[0]
# convert string 'answer' to bytes for buffer output
self.wfile.write(str.encode(answer))
return
else:
return SimpleHTTPRequestHandler.do_GET(self)
Signal handling makes exiting the program a little cleaner. Because server.serve_forever()
does go on forever (an infinite loop), there must be a way to end the loop. There is a nice call sever.shutdown()
however than hangs the server, it doesn’t exit, it doesn’t continue to serve, it is just stuck in dead lock.
It is designed to be used in a multi-threaded program, which is beyond the scope of the personal webserver. So in order to shut down cleanly (without a stack trace), we trap the ^C (or SIGINT) and quit from there.
# signal handlder for SIGINT
def sigint_handler(signal, frame):
shutdown_requested = True
print("\nCaught SIGINT, dying")
os._exit(0)
# register SIGINT signal handler
signal.signal(signal.SIGINT, sigint_handler)
Finally the IPv6 part
This is all good, but it still doesn’t serve over IPv6. In order to support IPv6, we use a bit of object oriented inheritance trickery to modify the default of an existing class HTTPServer
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
This creates a new class (which is used in our server
) with the address_family set to AF_INET6 (aka IPv6).
Using IPv6 mapped addresses to support IPv4
So how does it work for IPv4 if the address_family has been set to IPv6? The designers foresaw this problem, and created mapped addresses which are IPv4 addresses embedded inside an IPv6 address. An example for 192.168.1.11
would become a mapped address of ::FFFF:192.168.1.11
. When an IPv4 packet arrives on the listening port (8080), the IP stack converts the IPv4 address to a mapped address and forwards it to the application which is listening to IPv6-only.
Running the code
Now that we have a server which supports both IPv4 and IPv6, all we need to do is cd
to the directory we wish to share, and start the server
$ cd public/
$ ~/bin/ipv6-httpd.py
Listening on port:8080
Press ^C to quit
2001:db8:ebbd:0:4d18:71cd:b814:9508 - - [09/Jul/2017 11:49:41] "GET / HTTP/1.1" 200 -
^CCaught SIGINT, dying
The webserver log is sent to standard out (stdout), and can be redirected to a file if desired. In the above example, an IPv6 client ..:9508
requests an index, then the server is terminated with a ^C.
Want to server from a different directory? Stop the server, cd
to another directory, and restart the server, it will now serve files from the new current working directory (cwd) location.
Security (or lack there of)
This is a personal webserver, designed to be started and stopped whenever you need it. It is NOT a production quality webserver that you should put on the internet.
It does not server encrypted pages (read: no SSL/TLS), and it is single threaded, which means if a second request comes in while serving the first request, the second request will have to wait.
Demo
Share your Documents direcitory.
$ cd Documents/
$ ~/bin/ipv6-httpd.py
Listening on port:8080
Share your Webcam (internally).
$ cd Pictures/
# start webcam taking photos every 5 seconds
$ fswebcam -r 640x480 --jpeg 85 -S 10 -l 5 -b web-cam-shot.jpg
# start the personal webserver
$ ~/bin/ipv6-httpd.py
Listening on port:8080
Point your browser to fd11::11/cam1.html. The page will reload every 5 seconds, and there will be a new picture every 5 seconds (from the WebCam).
Notes:
- using fswebcam on your Pi or other embedded device
- the ipv6_httpd.py script can be downloaded from github