]> code.delx.au - learning/blob - lesson2_dynamic-web.md
Learning HTTP
[learning] / lesson2_dynamic-web.md
1 # Lesson 2 the dynamic web
2
3 ## References:
4 - https://docs.python.org/3
5 - https://docs.python.org/3/library/http.html
6 - https://github.com/python/cpython/blob/3.12/Lib/http/server.py
7 - https://docs.python.org/3/tutorial
8 - https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals
9 - https://developer.mozilla.org/en-US/docs/Glossary/Favicon
10
11 ## Overview
12
13 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.
14
15 ## Learning objective
16
17 The goal is to build a simple dynamic web server.
18 - Understand how the HTTP method and path map to a Python function call.
19 - Build a web server that returns dynamically generated content.
20
21 ## Exercises
22
23 ### Build a simple HTTP server in Python
24
25 Create a file, `ex2.py` and paste the following into it:
26 ```
27 import http.server
28
29 def main():
30 listen_address = ('localhost', 8000)
31 request_handler = http.server.SimpleHTTPRequestHandler
32 server = http.server.HTTPServer(listen_address, request_handler)
33 server.serve_forever()
34
35 if __name__ == '__main__':
36 main()
37 ```
38
39 Run it with `python3 ex2.py`. It should work exactly the same as `python3 -mhttp.server` from the previous exercise.
40
41
42 ### Return HTML from a function instead of a file
43
44 ```
45 import http.server
46
47 def main():
48 listen_address = ('localhost', 8000)
49 request_handler = MyRequestHandler
50 server = http.server.HTTPServer(listen_address, request_handler)
51 server.serve_forever()
52
53 class MyRequestHandler(http.server.BaseHTTPRequestHandler):
54 def write(self, text):
55 self.wfile.write(text.encode('utf-8'))
56
57 def do_GET(self):
58 self.send_response(200)
59 self.send_header('Content-type', 'text/html')
60 self.end_headers()
61 self.write('<html>')
62 self.write('<head><title>My web server!</title></head>')
63 self.write('<body>Hi there!</body>')
64 self.write('</html>')
65
66 if __name__ == '__main__':
67 main()
68 ```
69
70
71 ### Make the HTML dynamic!
72
73 Add `COUNTER = 42` to the top of the file.
74
75 Then modify your `GET` handler to return some dynamic HTML! Something like this...
76
77 ```
78 global COUNTER
79 COUNTER = COUNTER + 1
80 self.write(f'We have had <b>{COUNTER}</b> visitors today')
81 ```
82
83 You can try adding other information to the response too:
84 ```
85 self.write('You requested: ' + self.path + '<br>')
86 self.write('You are using this client: ' + self.headers.get('user-agent') + '<br>')
87 ```
88
89
90 ### A few things to note
91
92 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:
93 - Creates a new instance of the `MyRequestHandler` class.
94 - Sets the HTTP path as: `self.path = '/file.txt'` for this new instance.
95 - Calls the `do_GET()` function on this new instance, because the HTTP method was `GET`.
96
97 ### Fix the double-counting bug
98
99 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.
100
101 Something like this:
102 ```
103 if not self.path.endswith('.html'):
104 self.send_response(404)
105 self.end_headers()
106 self.write('File not found')
107 return
108 ```
109
110 Now try visiting some URL that doesn't end with `.html` and you'll see your 'not found' message.