Lesson 2 the dynamic web

References:

Overview

In the mid to late 1990s the web started becoming dynamic. Web servers might return different content to different users who requested the same URL. A common example of this was a hit counter which would increment each time any user visited the website, or a guest book to allow users to leave messages.

Learning objective

The goal is to build a simple dynamic web server.
- Understand how the HTTP method and path map to a Python function call.
- Build a web server that returns dynamically generated content.

Exercises

Build a simple HTTP server in Python

Create a file, ex2.py and paste the following into it:

import http.server

def main():
    listen_address = ('localhost', 8000)
    request_handler = http.server.SimpleHTTPRequestHandler
    server = http.server.HTTPServer(listen_address, request_handler)
    server.serve_forever()

if __name__ == '__main__':
    main()

Run it with python3 ex2.py. It should work exactly the same as python3 -mhttp.server from the previous exercise.

Return HTML from a function instead of a file

import http.server

def main():
    listen_address = ('localhost', 8000)
    request_handler = MyRequestHandler
    server = http.server.HTTPServer(listen_address, request_handler)
    server.serve_forever()

class MyRequestHandler(http.server.BaseHTTPRequestHandler):
    def write(self, text):
        self.wfile.write(text.encode('utf-8'))

    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.write('<html>')
        self.write('<head><title>My web server!</title></head>')
        self.write('<body>Hi there!</body>')
        self.write('</html>')

if __name__ == '__main__':
    main()

Make the HTML dynamic!

Add COUNTER = 42 to the top of the file.

Then modify your GET handler to return some dynamic HTML! Something like this...

global COUNTER
COUNTER = COUNTER + 1
self.write(f'We have had <b>{COUNTER}</b> visitors today')

You can try adding other information to the response too:

self.write('You requested: ' + self.path + '<br>')
self.write('You are using this client: ' + self.headers.get('user-agent') + '<br>')

A few things to note

Remember doing raw HTTP requests in the previous exercise? With the code above if a client does GET /file.txt then Python's http.server library parses the HTTP request and does something like the following:
- Creates a new instance of the MyRequestHandler class.
- Sets the HTTP path as: self.path = '/file.txt' for this new instance.
- Calls the do_GET() function on this new instance, because the HTTP method was GET.

Fix the double-counting bug

Notice that if you press ctrl-shift-R to reload your counter is going up by two at a time? If you look at the log you can see this is because Firefox is requesting /favicon.ico. This is the little icon next to the URL in the address bar. Our site isn't fancy enough for this, so we should modify do_GET() to return a 404 not found for these requests.

Something like this:

if not self.path.endswith('.html'):
    self.send_response(404)
    self.end_headers()
    self.write('File not found')
    return

Now try visiting some URL that doesn't end with .html and you'll see your 'not found' message.