From 1601bed77956b4c635723180d328a485394af552 Mon Sep 17 00:00:00 2001 From: James Bunton Date: Mon, 1 Jan 2024 18:52:04 +1100 Subject: [PATCH 1/1] Learning HTTP --- Makefile | 11 ++++ exercise2.py | 40 ++++++++++++++ lesson1_HTTP.html | 47 +++++++++++++++++ lesson1_HTTP.md | 49 +++++++++++++++++ lesson2_dynamic-web.html | 82 +++++++++++++++++++++++++++++ lesson2_dynamic-web.md | 110 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 339 insertions(+) create mode 100644 Makefile create mode 100644 exercise2.py create mode 100644 lesson1_HTTP.html create mode 100644 lesson1_HTTP.md create mode 100644 lesson2_dynamic-web.html create mode 100644 lesson2_dynamic-web.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..29e0321 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +MARKDOWN := python3 -mmarkdown -x markdown.extensions.fenced_code -x markdown.extensions.nl2br + +all: $(addsuffix .html, $(basename $(wildcard *.md))) + +%.html: %.md + $(MARKDOWN) < $< > $@ + +clean: + rm -f *.html + +.PHONY: clean diff --git a/exercise2.py b/exercise2.py new file mode 100644 index 0000000..f77f610 --- /dev/null +++ b/exercise2.py @@ -0,0 +1,40 @@ +#!/usr/bin/python + +import http.server + +COUNTER = 0 + +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 do_GET(self): + if not self.path.endswith('.html'): + self.send_response(404) + self.end_headers() + self.write('File not found') + return + + global COUNTER + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.write('') + self.write('My web server!') + self.write('') + self.write('Hi there!
') + self.write('You requested: ' + self.path + '
') + self.write('You are using this client: ' + self.headers.get('user-agent') + '
') + self.write(f'We have had {COUNTER} visitors today') + COUNTER = COUNTER + 1 + self.write('') + self.write('') + + def write(self, text): + self.wfile.write(text.encode('utf-8')) + +if __name__ == '__main__': + main() diff --git a/lesson1_HTTP.html b/lesson1_HTTP.html new file mode 100644 index 0000000..5804e6d --- /dev/null +++ b/lesson1_HTTP.html @@ -0,0 +1,47 @@ +

Lesson 1 HTTP

+

References:

+ +

Overview

+

In the 1991 HTTP was created. Web browsers used HTTP to fetch static HTML, text and images from web servers. These static files were sitting in a directory on a server. The only way to change the website was to update these files.

+

You could type a URL like http://acme.com/products.html into a graphical web browser, the browser would then speak HTTP to the acme.com server to fetch the /products.html file. The user may then click a <a href="http://contoso.net/about.html"> to cause the browser to visit a different site, speaking HTTP to the contoso.net server to fetch the /about.html file.

+

Learning objective

+

The goal is to learn about HTTP, specifically what it does and what it looks like.
+- What is an HTTP method?
+- What is an HTTP status code?
+- Identify the components of a URL, server, path.
+- How does an HTTP server use the server and path from the URL?

+

Exercises

+

Run a simple web server using Python

+ +

Access the web server with a browser like Firefox

+ +

Access the web server with a command line client, curl

+ +

Access the web server by typing raw HTTP

+ +

HTTP is just text! When you click a link, all that Firefox does is send this text to the server and display the result on the screen.

\ No newline at end of file diff --git a/lesson1_HTTP.md b/lesson1_HTTP.md new file mode 100644 index 0000000..9334e2d --- /dev/null +++ b/lesson1_HTTP.md @@ -0,0 +1,49 @@ +# Lesson 1 HTTP + +## References: +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview +- https://www.rfc-editor.org/rfc/rfc2616 +- https://curl.se/docs/manpage.html + +## Overview + +In the 1991 HTTP was created. Web browsers used HTTP to fetch static HTML, text and images from web servers. These static files were sitting in a directory on a server. The only way to change the website was to update these files. + +You could type a URL like `http://acme.com/products.html` into a graphical web browser, the browser would then speak HTTP to the `acme.com` server to fetch the `/products.html` file. The user may then click a `` to cause the browser to visit a different site, speaking HTTP to the `contoso.net` server to fetch the `/about.html` file. + +## Learning objective + +The goal is to learn about HTTP, specifically what it does and what it looks like. +- What is an HTTP method? +- What is an HTTP status code? +- Identify the components of a URL, server, path. +- How does an HTTP server use the server and path from the URL? + +## Exercises + +### Run a simple web server using Python +- Make new directory with some text or html files, at least two. Eg demo.html and hello.txt. +- Open terminal in that directory. Right click, open in terminal. +- Start the web server: `python -mhttp.server` +- Keep this terminal window open for later. + +### Access the web server with a browser like Firefox +- Open web browser and visit: http://localhost:8000 +- See what is happening in the web server terminal window. +- You can click on the files to view them. +- The browser is acting as an HTTP client to view the files being served by the Python HTTP server. + +### Access the web server with a command line client, curl +- Open a new terminal window in addition to the Python web server one. +- Run `curl http://localhost:8000` to see the directory index generated by the web server. +- Run `curl http://localhost:8000/demo.html` to see a file you created in the web server directory. +- Try variations of this like: `/hello.txt`, or `/missing.txt` +- You can also try this on other web servers, like `https://www.google.com` +- Try in verbose mode to see the raw HTTP: `curl -v http://localhost:8000` + +### Access the web server by typing raw HTTP +- Run command `nc localhost 8000` +- Enter `GET / HTTP/1.0`, then press return twice +- Try with `GET /hello.txt` or other variations to see how the server responds. + +HTTP is just text! When you click a link, all that Firefox does is send this text to the server and display the result on the screen. diff --git a/lesson2_dynamic-web.html b/lesson2_dynamic-web.html new file mode 100644 index 0000000..6e0735a --- /dev/null +++ b/lesson2_dynamic-web.html @@ -0,0 +1,82 @@ +

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.

\ No newline at end of file diff --git a/lesson2_dynamic-web.md b/lesson2_dynamic-web.md new file mode 100644 index 0000000..cabb972 --- /dev/null +++ b/lesson2_dynamic-web.md @@ -0,0 +1,110 @@ +# Lesson 2 the dynamic web + +## References: +- https://docs.python.org/3 +- https://docs.python.org/3/library/http.html +- https://github.com/python/cpython/blob/3.12/Lib/http/server.py +- https://docs.python.org/3/tutorial +- https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals +- https://developer.mozilla.org/en-US/docs/Glossary/Favicon + +## 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('') + self.write('My web server!') + self.write('Hi there!') + self.write('') + +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 {COUNTER} visitors today') +``` + +You can try adding other information to the response too: +``` +self.write('You requested: ' + self.path + '
') +self.write('You are using this client: ' + self.headers.get('user-agent') + '
') +``` + + +### 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. -- 2.39.2