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