diff --git a/.gitignore b/.gitignore
index d04dac0b..7b225ae2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,21 @@ svn-commit.tmp
bin
build
include
-lib
\ No newline at end of file
+lib
+share
+cast-offs
+develop-eggs
+development
+*.db
+*.sublime-project
+*.sublime-workspace
+.mr.developer.cfg
+outline_improvements.txt
+src
+html
+slides
+new_mash
+.buildinfo
+pip-selfcheck.json
+.ipynb_checkpoints
+testenvs
diff --git a/Makefile b/Makefile
index 11bcf2d7..84b07bcc 100644
--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,17 @@
#
# You can set these variables from the command line.
+BINDIR = ./bin
SPHINXOPTS =
-SPHINXBUILD = sphinx-build
+SPHINXBUILD = $(BINDIR)/sphinx-build
PAPER =
BUILDDIR = build
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
@@ -29,17 +35,20 @@ help:
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
- -rm -rf $(BUILDDIR)/*
+ rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@@ -77,17 +86,17 @@ qthelp:
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/InternetProgrammingwithPython.qhcp"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonWebProgramming.qhcp"
@echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/InternetProgrammingwithPython.qhc"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonWebProgramming.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/InternetProgrammingwithPython"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/InternetProgrammingwithPython"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonWebProgramming"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonWebProgramming"
@echo "# devhelp"
epub:
@@ -108,6 +117,12 @@ latexpdf:
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+latexpdfja:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@@ -151,3 +166,19 @@ doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+
+
+slides:
+ $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides
+ @echo "Build finished. The HTML slides are in $(BUILDDIR)/slides."
+
diff --git a/README.rst b/README.rst
index 1c7bf8aa..e7769775 100644
--- a/README.rst
+++ b/README.rst
@@ -3,29 +3,85 @@
Introduction
============
+This package provides the source for all lecture materials for a 10-session
+course in Web Development using Python.
+
This package provides the source for all lecture materials used for the
`Internet Programming in Python`_ section of the `Certificate in Python
-Programming`_ offered by the University of Washington Professional & Continuing
-Education program. This version of the documentation is used for the Winter
-2013 instance of the course, taught by `Cris Ewing`_.
+Programming`_ offered by the `University of Washington Professional &
+Continuing Education`_ program. This version of the documentation is used for
+the Winter 2016 instance of the course, Taught by `Cris Ewing`_
-.. _Internet Programming in Python: http://www.pce.uw.edu/courses/internet-programming-python/downtown-seattle-winter-2013/
+.. _Internet Programming in Python: http://www.pce.uw.edu/courses/internet-programming-python/downtown-seattle-winter-2016/
.. _Certificate in Python Programming: http://www.pce.uw.edu/certificates/python-programming.html
+.. _University of Washington Professional & Continuing Education: http://www.pce.uw.edu/
.. _Cris Ewing: http://www.linkedin.com/profile/view?id=19741495
+This course is taught using Python 3.
+
+This documentation builds both an HTML version of the course lectures (for the
+students) and a set of slides (for the instructor). It uses the Python-based
+documentation tool `Sphinx`_ and the `hieroglyph`_ sphinx extension. Shell
+examples use `iPython` and tests are written for `pytest`. The build
+environment is managed using `virtualenv` and `pip`
+
+.. _iPython: http://ipython.org/
+.. _Sphinx: http://sphinx-doc.org/
+.. _hieroglyph: http://docs.hieroglyph.io/en/latest/
+.. _pytest: http://pytest.org/latest/
+.. _virtualenv: https://virtualenv.pypa.io/en/latest/
+.. _pip: https://pip.pypa.io/en/stable
+
Building The Documentation
--------------------------
-After cloning this package from the repository, do the following::
+To build the documentation locally, begin by cloning the project to your
+machine:
+
+.. code-block:: bash
+
+ $ git clone https://github.com/cewing/training.python_web.git
+
+Change directories into the repository, then create a virtualenv using Python
+3:
+
+.. code-block:: bash
+
+ $ cd training.python_web
+ $ virtualenv --python /path/to/bin/python3.5 .
+ Running virtualenv with interpreter /path/to/bin/python3.5
+ New python executable in training.python_web/bin/python3.5
+ Also creating executable in training.python_web/bin/python
+ Installing setuptools, pip...done.
+
+Install the requirements for the documentation using pip:
+
+.. code-block:: bash
+
+ $ bin/pip install -r requirements.pip
+ ...
+
+ Successfully installed Babel-2.0 Jinja2-2.8 MarkupSafe-0.23 Pygments-2.0.2 Sphinx-1.3.1 alabaster-0.7.6 appnope-0.1.0 decorator-4.0.2 docutils-0.12 gnureadline-6.3.3 hieroglyph-0.7.1 ipython-4.0.0 ipython-genutils-0.1.0 path.py-8.1 pexpect-3.3 pickleshare-0.5 py-1.4.30 pytest-2.7.2 pytz-2015.4 simplegeneric-0.8.1 six-1.9.0 snowballstemmer-1.2.0 sphinx-rtd-theme-0.1.8 traitlets-4.0.0
+
+Once that has successfully completed, you should be able to build both the html
+documentation and the slides using the included Makefile.
+
+.. code-block:: bash
+
+ $ make html
+ ...
+
+ Build finished. The HTML pages are in build/html.
+
+ (webdocs)$ make slides
+ ...
+
+ Build finished. The HTML slides are in build/slides.
+
+.. note:: If you prefer to build your virtualenvs in other ways, you will need
+ to adjust the `BINDIR` variable in `Makefile` to fit your reality.
- $ cd training.python_web # the location of your local copy
- $ python bootstrap.py # must be Python 2.6 or 2.7
- $ bin/buildout
- $ bin/sphinx # to build the main documentation and course outline
- $ bin/build_s5 # to build the class session presentations
-At the end of a successful build, you will find a ``build/html`` directory,
-containing the completed documentation and presentations.
Reading The Documentation
-------------------------
diff --git a/assignments/teachers/week01/answers/echo_client.py b/assignments/teachers/week01/answers/echo_client.py
deleted file mode 100644
index 9b48397e..00000000
--- a/assignments/teachers/week01/answers/echo_client.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import socket
-import sys
-
-# Create a TCP/IP socket
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-# Connect the socket to the port where the server is listening
-server_address = ('localhost', 10000)
-print >>sys.stderr, 'connecting to %s port %s' % server_address
-sock.connect(server_address)
-
-try:
-
- # Send data
- message = 'This is the message. It will be repeated.'
- print >>sys.stderr, 'sending "%s"' % message
- sock.sendall(message)
-
- # Look for the response
- amount_received = 0
- amount_expected = len(message)
-
- while amount_received < amount_expected:
- data = sock.recv(16)
- amount_received += len(data)
- print >>sys.stderr, 'received "%s"' % data
-
-finally:
- print >>sys.stderr, 'closing socket'
- sock.close()
diff --git a/assignments/teachers/week01/answers/echo_server.py b/assignments/teachers/week01/answers/echo_server.py
deleted file mode 100644
index 2ef8852d..00000000
--- a/assignments/teachers/week01/answers/echo_server.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import socket
-import sys
-
-# Create a TCP/IP socket
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-# Bind the socket to the port
-server_address = ('localhost', 10000)
-print >>sys.stderr, 'starting up on %s port %s' % server_address
-sock.bind(server_address)
-
-# Listen for incoming connections
-sock.listen(1)
-
-try:
-
- while True:
- # Wait for a connection
- print >>sys.stderr, 'waiting for a connection'
- connection, client_address = sock.accept()
-
- try:
- print >>sys.stderr, 'connection from', client_address
-
- # Receive the data in small chunks and retransmit it
- while True:
- data = connection.recv(16)
- print >>sys.stderr, 'received "%s"' % data
- if data:
- print >>sys.stderr, 'sending data back to the client'
- connection.sendall(data)
- else:
- print >>sys.stderr, 'no more data from', client_address
- break
-
- finally:
- # Clean up the connection
- connection.close()
-
-except KeyboardInterrupt:
- sock.close()
- sys.exit(0)
diff --git a/assignments/teachers/week02/answers/http_serve1.py b/assignments/teachers/week02/answers/http_serve1.py
deleted file mode 100644
index 05f45505..00000000
--- a/assignments/teachers/week02/answers/http_serve1.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- client.send(html)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve2.py b/assignments/teachers/week02/answers/http_serve2.py
deleted file mode 100644
index 37b591e6..00000000
--- a/assignments/teachers/week02/answers/http_serve2.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-
-import httpdate
-
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-def OK_response(entity):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/html')
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:"
- print request
- response = OK_response(html)
- print "sending:"
- print response[:120]
- client.send(response)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve3.py b/assignments/teachers/week02/answers/http_serve3.py
deleted file mode 100644
index 2ee28fd8..00000000
--- a/assignments/teachers/week02/answers/http_serve3.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-
-import httpdate
-
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-def OK_response(entity):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/html')
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-def parse_request(request):
- """
- parse an HTTP request
-
- returns the URI asked for
-
- note: minimal parsing -- only supprt GET
-
- example:
- GET / HTTP/1.1
- Host: localhost:50000
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-us,en;q=0.5
- Accept-Encoding: gzip, deflate
- Connection: keep-alive
- Cache-Control: max-age=0
- """
- # first line should be the method line:
- lines = request.split("\r\n")
- print lines
-
- method, URI, protocol = lines[0].split()
- print method
- print URI
- print protocol
- # a bit of checking:
- if method.strip() != "GET":
- raise ValueError("I can only process a GET request")
- if protocol.split('/')[0] != "HTTP":
- raise ValueError("I can only process an HTTP request")
-
- return URI
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- URI = parse_request(request)
- print "URI requested is:", URI
- response = OK_response(html)
- print "sending:"
- print response[:120]
- client.send(response)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve4.py b/assignments/teachers/week02/answers/http_serve4.py
deleted file mode 100644
index 792f76ec..00000000
--- a/assignments/teachers/week02/answers/http_serve4.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-import os
-
-import httpdate
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-def OK_response(entity):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- resp.append( 'Content-Type: text/plain' )
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-def Error_response(URI):
- """
- returns an HTTP 404 Not Found Error response:
-
- URI is the name of the entity not found
- """
- resp = []
- resp.append('HTTP/1.1 404 Not Found')
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/plain')
-
- msg = "404 Error:\n %s \n not found"%( URI )
-
- resp.append('Content-Length: %i'%( len(msg) ) )
- resp.append('')
- resp.append(msg)
-
- return "\r\n".join(resp)
-
-
-def parse_request(request):
- """
- parse an HTTP request
-
- returns the URI asked for
-
- note: minimal parsing -- only supprt GET
-
- example:
- GET / HTTP/1.1
- Host: localhost:50000
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-us,en;q=0.5
- Accept-Encoding: gzip, deflate
- Connection: keep-alive
- Cache-Control: max-age=0
- """
- # first line should be the method line:
- lines = request.split("\r\n")
- print lines
-
- method, URI, protocol = lines[0].split()
- print method
- print URI
- print protocol
- # a bit of checking:
- if method.strip() != "GET":
- raise ValueError("I can only process a GET request")
- if protocol.split('/')[0] != "HTTP":
- raise ValueError("I can only process an HTTP request")
-
- return URI
-
-def format_dir_list(dir_list):
- msg = ["Directory Listing:"]
- for d in dir_list:
- msg.append(d)
- return "\n".join(msg)
-
-def get_file(URI):
- root_dir = 'web' # must be run from code dir...
- URI = URI.lstrip('/') # os.path.join does not like a leading slash
- filename = os.path.join( root_dir, URI)
- print "path to file:", filename
- if os.path.isfile(filename):
- print "it's a file"
- raise NotImplementedError("I can't handle a file yet")
- elif os.path.isdir(filename):
- print "it's a dir"
- return format_dir_list(os.listdir(filename)), 'txt'
- else:
- raise ValueError("there is nothing by that name")
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- URI = parse_request(request)
- print "URI requested is:", URI
- try:
- file_data, ext = get_file(URI)
- response = OK_response(file_data)
- except ValueError as err:
- print err
- response = Error_response(URI)
- print "sending:"
- print response[:200]
- client.send(response)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve5.py b/assignments/teachers/week02/answers/http_serve5.py
deleted file mode 100644
index 6ce40fd0..00000000
--- a/assignments/teachers/week02/answers/http_serve5.py
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-import os
-
-import httpdate
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-mime_types={}
-mime_types['html'] = "text/html"
-mime_types['htm'] = "text/html"
-mime_types['txt'] = "text/plain"
-mime_types['py'] = "text/plain"
-mime_types['png'] = "image/png"
-mime_types['jpeg'] = "image/jpg"
-mime_types['jpg'] = "image/jpg"
-
-def OK_response(entity, extension='html'):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- type = mime_types.get(extension, 'text/plain')
- resp.append( 'Content-Type: %s'%type )
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-def Error_response(URI):
- """
- returns an HTTP 404 Not Found Error response:
-
- URI is the name of the entity not found
- """
- resp = []
- resp.append('HTTP/1.1 404 Not Found')
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/plain')
-
- msg = "404 Error:\n %s \n not found"%( URI )
-
- resp.append('Content-Length: %i'%( len(msg) ) )
- resp.append('')
- resp.append(msg)
-
- return "\r\n".join(resp)
-
-
-def parse_request(request):
- """
- parse an HTTP request
-
- returns the URI asked for
-
- note: minimal parsing -- only supprt GET
-
- example:
- GET / HTTP/1.1
- Host: localhost:50000
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-us,en;q=0.5
- Accept-Encoding: gzip, deflate
- Connection: keep-alive
- Cache-Control: max-age=0
- """
- # first line should be the method line:
- lines = request.split("\r\n")
- print lines
-
- method, URI, protocol = lines[0].split()
- print method
- print URI
- print protocol
- # a bit of checking:
- if method.strip() != "GET":
- raise ValueError("I can only process a GET request")
- if protocol.split('/')[0] != "HTTP":
- raise ValueError("I can only process an HTTP request")
-
- return URI
-
-def format_dir_list(dir_list):
- """
- format a directory listing as simple text
- """
- msg = ["Directory Listing:"]
- for d in dir_list:
- msg.append(d)
- return "\n".join(msg)
-
-def get_file(URI):
- root_dir = 'web' # must be run from code dir...
- URI = URI.lstrip('/') #weird-- os.path.join does not like a leading slash
- filename = os.path.join( root_dir, URI)
- print "path to file:", filename
- if os.path.isfile(filename):
- print "it's a file"
- contents = open(filename, 'rb').read()
- ext = os.path.splitext(filename)[1].strip('.')
- return contents, ext
- elif os.path.isdir(filename):
- print "it's a dir"
- return format_dir_list(os.listdir(filename)), 'txt'
- else:
- raise ValueError("there is nothing by that name")
-
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- URI = parse_request(request)
- print "URI requested is:", URI
- file_data, ext = get_file(URI)
- response = OK_response(file_data, ext)
- print "sending:"
- print response[:200]
- client.send(response)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve6.py b/assignments/teachers/week02/answers/http_serve6.py
deleted file mode 100644
index c11bb695..00000000
--- a/assignments/teachers/week02/answers/http_serve6.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-import os
-
-import httpdate
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-mime_types={}
-mime_types['html'] = "text/html"
-mime_types['htm'] = "text/html"
-mime_types['txt'] = "text/plain"
-mime_types['png'] = "image/png"
-mime_types['jpeg'] = "image/jpg"
-mime_types['jpg'] = "image/jpg"
-
-def OK_response(entity, extension='html'):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- type = mime_types.get(extension, 'text/plain')
- resp.append( 'Content-Type: %s'%type )
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-def Error_response(URI):
- """
- returns an HTTP 404 Not Found Error response:
-
- URI is the name of the entity not found
- """
- resp = []
- resp.append('HTTP/1.1 404 Not Found')
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/plain')
-
- msg = "404 Error:\n %s \n not found"%( URI )
-
- resp.append('Content-Length: %i'%( len(msg) ) )
- resp.append('')
- resp.append(msg)
-
- return "\r\n".join(resp)
-
-
-def parse_request(request):
- """
- parse an HTTP request
-
- returns the URI asked for
-
- note: minimal parsing -- only supprt GET
-
- example:
- GET / HTTP/1.1
- Host: localhost:50000
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-us,en;q=0.5
- Accept-Encoding: gzip, deflate
- Connection: keep-alive
- Cache-Control: max-age=0
- """
- # first line should be the method line:
- lines = request.split("\r\n")
- print lines
-
- method, URI, protocol = lines[0].split()
- print method
- print URI
- print protocol
- # a bit of checking:
- if method.strip() != "GET":
- raise ValueError("I can only process a GET request")
- if protocol.split('/')[0] != "HTTP":
- raise ValueError("I can only process an HTTP request")
-
- return URI
-
-def format_dir_list(dir_list):
- """
- format the dir list as HTML
- """
- html = ["
"]
- html.append(" Directory Listing
")
-
- for d in dir_list:
- html.append( " %s
"%d )
- html.append( " " )
-
- return "\n".join(html)
-
-def get_time_page():
- """
- returns and html page with the current time in it
- """
- time = httpdate.httpdate_now()
- html = " %s
"%time
- return html
-
-def get_file(URI):
-
- root_dir = 'web' # must be run from code dir...
- URI = URI.strip('/') #weird-- os.path.join does not like a leading slash
- # check if this is the time server option
- if URI.lower() == "get_time":
- return get_time_page(), 'html'
- else:
- filename = os.path.join( root_dir, URI)
- print "path to file:", filename
- if os.path.isfile(filename):
- print "it's a file"
- contents = open(filename, 'rb').read()
- ext = os.path.splitext(filename)[1].strip('.')
- return contents, ext
- elif os.path.isdir(filename):
- print "it's a dir"
- return format_dir_list(os.listdir(filename)), 'htm'
- else:
- raise ValueError("there is nothing by that name")
-
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- URI = parse_request(request)
- print "URI requested is:", URI
- try:
- file_data, ext = get_file(URI)
- response = OK_response(file_data, ext)
- except ValueError:
- response = Error_response(URI)
- print "sending:"
- print response[:200]
- client.send(response)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve7.py b/assignments/teachers/week02/answers/http_serve7.py
deleted file mode 100644
index ae889efc..00000000
--- a/assignments/teachers/week02/answers/http_serve7.py
+++ /dev/null
@@ -1,170 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-import os
-
-import httpdate
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-root_dir = 'web' # must be run from code dir...
-
-print "point your browser to http://localhost:%i"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-mime_types={}
-mime_types['html'] = "text/html"
-mime_types['htm'] = "text/html"
-mime_types['txt'] = "text/plain"
-mime_types['png'] = "image/png"
-mime_types['jpeg'] = "image/jpg"
-mime_types['jpg'] = "image/jpg"
-
-def OK_response(entity, extension='html'):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- type = mime_types.get(extension, 'text/plain')
- resp.append( 'Content-Type: %s'%type )
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-def Error_response(URI):
- """
- returns an HTTP 404 Not Found Error response:
-
- URI is the name of the entity not found
- """
- resp = []
- resp.append('HTTP/1.1 404 Not Found')
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/plain')
-
- msg = "404 Error:\n %s \n not found"%( URI )
-
- resp.append('Content-Length: %i'%( len(msg) ) )
- resp.append('')
- resp.append(msg)
-
- return "\r\n".join(resp)
-
-
-def parse_request(request):
- """
- parse an HTTP request
-
- returns the URI asked for
-
- note: minimal parsing -- only supprt GET
-
- example:
- GET / HTTP/1.1
- Host: localhost:50000
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-us,en;q=0.5
- Accept-Encoding: gzip, deflate
- Connection: keep-alive
- Cache-Control: max-age=0
- """
- # first line should be the method line:
- lines = request.split("\r\n")
-
- method, URI, protocol = lines[0].split()
-
- # a bit of checking:
- if method.strip() != "GET":
- raise ValueError("I can only process a GET request")
- if protocol.split('/')[0] != "HTTP":
- raise ValueError("I can only process an HTTP request")
-
- return URI
-
-def format_dir_list(URI):
- """
- format the contests of dir as HTML with links
- """
- dir = os.path.join(root_dir, URI)
- names = os.listdir(dir)
-
- dirs = [d for d in names if os.path.isdir(os.path.join(dir,d))]
- files = [d for d in names if os.path.isfile(os.path.join(dir,d))]
-
- html =[]
- html.append(" ")
- html.append("%s
"%URI)
- print "URI:", URI
- if URI: # don't need the parent dir at the root
- html.append('Parent' )
- html.append("Directories:
")
- html.append(" ")
- for d in dirs:
- html.append(' - %s
'%(os.path.join(URI,d), d))
- html.append("
")
- html.append("Files:
")
- html.append(" ")
- for f in files:
- html.append(' - %s
'%(os.path.join(URI,f), f) )
- html.append("
")
- html.append(" ")
- return "\n".join(html)
-
-def get_time_page():
- """
- returns and html page with the current time in it
- """
- time = httpdate.httpdate_now()
- html = " %s
"%time
- return html
-
-def get_file(URI):
-
- URI = URI.strip('/') #weird-- os.path.join does not like a leading slash
- # check if this is the time server option
- if URI.lower() == "get_time":
- return get_time_page(), 'html'
- else:
- filename = os.path.join( root_dir, URI)
- if os.path.isfile(filename):
- contents = open(filename, 'rb').read()
- ext = os.path.splitext(filename)[1].strip('.')
- return contents, ext
- elif os.path.isdir(filename):
- return format_dir_list(URI), 'htm'
- else:
- raise ValueError("there is nothing by that name")
-
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- URI = parse_request(request)
- try:
- file_data, ext = get_file(URI)
- response = OK_response(file_data, ext)
- except ValueError:
- response = Error_response(URI)
- client.send(response)
- client.close()
-
diff --git a/assignments/teachers/week02/answers/http_serve8.py b/assignments/teachers/week02/answers/http_serve8.py
deleted file mode 100644
index 53eb5ecf..00000000
--- a/assignments/teachers/week02/answers/http_serve8.py
+++ /dev/null
@@ -1,199 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-import os
-
-import httpdate
-
-import subprocess
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-root_dir = 'web' # must be run from code dir...
-
-print "point your browser to http://localhost:%i/make_time.py"%port
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-html = open("tiny_html.html").read()
-
-mime_types={}
-mime_types['html'] = "text/html"
-mime_types['htm'] = "text/html"
-mime_types['txt'] = "text/plain"
-mime_types['png'] = "image/png"
-mime_types['jpeg'] = "image/jpg"
-mime_types['jpg'] = "image/jpg"
-
-def OK_response(entity, extension='html'):
- """
- returns an HTTP response: header and entity in a string
- """
- resp = []
- resp.append('HTTP/1.1 200 OK')
- resp.append(httpdate.httpdate_now())
- type = mime_types.get(extension, 'text/plain')
- resp.append( 'Content-Type: %s'%type )
- resp.append('Content-Length: %i'%len(entity))
- resp.append('')
- resp.append(entity)
-
- return "\r\n".join(resp)
-
-def Error_response(URI, error_code=404):
- """
- returns an HTTP 404 Not Found Error response:
-
- URI is the name of the entity not found
- """
- errors = {500: "Server Error",
- 404: "Not Found",
- 301: "Moved Permanently",
- 302: "Moved Temporarily",
- 303: "See Other"
- }
-
- resp = []
- resp.append('HTTP/1.1 %i %s'%(error_code, errors[error_code]))
- resp.append(httpdate.httpdate_now())
- resp.append('Content-Type: text/plain')
-
- msg = "%i Error:\n %s \n %s"%( error_code, URI, errors[error_code] )
-
- resp.append('Content-Length: %i'%( len(msg) ) )
- resp.append('')
- resp.append(msg)
-
- return "\r\n".join(resp)
-
-
-def parse_request(request):
- """
- parse an HTTP request
-
- returns the URI asked for
-
- note: minimal parsing -- only supprt GET
-
- example:
- GET / HTTP/1.1
- Host: localhost:50000
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-us,en;q=0.5
- Accept-Encoding: gzip, deflate
- Connection: keep-alive
- Cache-Control: max-age=0
- """
- # first line should be the method line:
- lines = request.split("\r\n")
-
- method, URI, protocol = lines[0].split()
-
- # a bit of checking:
- if method.strip() != "GET":
- raise ValueError("I can only process a GET request")
- if protocol.split('/')[0] != "HTTP":
- raise ValueError("I can only process an HTTP request")
-
- return URI
-
-def format_dir_list(URI):
- """
- format the contests of dir as HTML with links
- """
- dir = os.path.join(root_dir, URI)
- names = os.listdir(dir)
-
- dirs = [d for d in names if os.path.isdir(os.path.join(dir,d))]
- files = [d for d in names if os.path.isfile(os.path.join(dir,d))]
-
- html =[]
- html.append(" ")
- html.append("%s
"%URI)
- print "URI:", URI
- if URI: # don't need the parent dir at the root
- html.append('Parent' )
- html.append("Directories:
")
- html.append(" ")
- for d in dirs:
- html.append(' - %s
'%(os.path.join(URI,d), d))
- html.append("
")
- html.append("Files:
")
- html.append(" ")
- for f in files:
- html.append(' - %s
'%(os.path.join(URI,f), f) )
- html.append("
")
- html.append(" ")
- return "\n".join(html)
-
-def get_time_page():
- """
- returns and html page with the current time in it
- """
- time = httpdate.httpdate_now()
- html = " %s
"%time
- return html
-
-def run_python_script(URI):
- """
- runs the python script in the URI
-
- returns std out from running the script
-
- raises a subprocess.CalledProcessError if something goes wrong
- """
- script = os.path.join(root_dir, URI)
- result = subprocess.check_output(["python", script])
- return result
-
-
-def get_file(URI):
- """
- returns the contents of the file in the URI -- and a file extension for the mime type.
- """
- URI = URI.strip('/') #os.path.join does not like a leading slash
- # check if this is the time server option
- if URI.lower() == "get_time":
- return get_time_page(), 'html'
- # check if it's a python file
- if os.path.splitext(URI)[1] == ".py":
- return run_python_script(URI), 'html'
- else:
- filename = os.path.join( root_dir, URI)
- if os.path.isfile(filename):
- contents = open(filename, 'rb').read()
- ext = os.path.splitext(filename)[1].strip('.')
- return contents, ext
- elif os.path.isdir(filename):
- return format_dir_list(URI), 'htm'
- else:
- raise ValueError("there is nothing by that name")
-
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- request = client.recv(size)
- if request: # if the connection was closed there would be no data
- print "received:", request
- URI = parse_request(request)
- try:
- file_data, ext = get_file(URI)
- response = OK_response(file_data, ext)
- except ValueError: # file not found
- response = Error_response(URI)
- except subprocess.CalledProcessError: # somethign wrong with the python script
- response = Error_response(URI, 500)
- client.send(response)
- client.close()
-
diff --git a/assignments/week01/athome/assignment.txt b/assignments/week01/athome/assignment.txt
deleted file mode 100644
index c23300fd..00000000
--- a/assignments/week01/athome/assignment.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-1. Create a socket server which can take two numbers, add them together, and
-return the result
-
-2. Create a socket client that sends two numbers to the above server, and
-receives and prints the returned result.
-
-Submit your work by forking this repository. Add the server and client scripts
-to your fork and then issue a pull request.
diff --git a/assignments/week01/lab/echo_client.py b/assignments/week01/lab/echo_client.py
deleted file mode 100644
index b8898436..00000000
--- a/assignments/week01/lab/echo_client.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import socket
-import sys
-
-# Create a TCP/IP socket
-
-# Connect the socket to the port where the server is listening
-server_address = ('localhost', 50000)
-
-try:
- # Send data
- message = 'This is the message. It will be repeated.'
-
- # print the response
-
-finally:
- # close the socket to clean up
diff --git a/assignments/week01/lab/echo_server.py b/assignments/week01/lab/echo_server.py
deleted file mode 100644
index e2c52fc6..00000000
--- a/assignments/week01/lab/echo_server.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import socket
-import sys
-
-# Create a TCP/IP socket
-
-# Bind the socket to the port
-server_address = ('localhost', 50000)
-
-# Listen for incoming connections
-
-while True:
- # Wait for a connection
-
- try:
- # Receive the data and send it back
-
-
- finally:
- # Clean up the connection
diff --git a/assignments/week02/athome/assignment.txt b/assignments/week02/athome/assignment.txt
deleted file mode 100644
index 3b0f2ec3..00000000
--- a/assignments/week02/athome/assignment.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Complete your HTTP Web Server. Accomplish as many of the following goals as
-you are able:
-
-* If you were unable to complete the first five steps in class, circle back
- and finish them
-
-* Complete the 'Bonus point' parts from the first five steps, if you haven't
- already done so
-
-* Format your directory listing as HTML
-
-* In the HTML directory listing, make the files clickable links
-
-* Add a new, dynamic endpoint. If the URI /time-page is requested, return an
- HTML page with the current time displayed.
\ No newline at end of file
diff --git a/assignments/week02/lab/echo_server.py b/assignments/week02/lab/echo_server.py
deleted file mode 100644
index 3eb3400f..00000000
--- a/assignments/week02/lab/echo_server.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-
-import socket
-
-host = '' # listen on all connections (WiFi, etc)
-port = 50000
-backlog = 5 # how many connections can we stack up
-size = 1024 # number of bytes to receive at once
-
-## create the socket
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-# set an option to tell the OS to re-use the socket
-s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-# the bind makes it a server
-s.bind( (host,port) )
-s.listen(backlog)
-
-while True: # keep looking for new connections forever
- client, address = s.accept() # look for a connection
- data = client.recv(size)
- if data: # if the connection was closed there would be no data
- print "received: %s, sending it back"%data
- client.send(data)
- client.close()
\ No newline at end of file
diff --git a/assignments/week02/lab/tiny_html.html b/assignments/week02/lab/tiny_html.html
deleted file mode 100755
index 8d4ec08c..00000000
--- a/assignments/week02/lab/tiny_html.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- This is a header
-
- and this is some regular text
-
-
- and some more
-
-
-
diff --git a/assignments/week02/lab/web/a_web_page.html b/assignments/week02/lab/web/a_web_page.html
deleted file mode 100644
index 82e96100..00000000
--- a/assignments/week02/lab/web/a_web_page.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-My First Heading
-
-My first paragraph.
-
-
-
-
diff --git a/bootstrap.py b/bootstrap.py
deleted file mode 100644
index ad6fdc68..00000000
--- a/bootstrap.py
+++ /dev/null
@@ -1,62 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Bootstrap a buildout-based project
-
-Simply run this script in a directory containing a buildout.cfg.
-The script accepts buildout command-line options, so you can
-use the -c option to specify an alternate configuration file.
-
-$Id: bootstrap.py 85041 2008-03-31 15:57:30Z andreasjung $
-"""
-
-import os, shutil, sys, tempfile, urllib2
-
-tmpeggs = tempfile.mkdtemp()
-
-try:
- import pkg_resources
-except ImportError:
- ez = {}
- exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
- ).read() in ez
- ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
-
- import pkg_resources
-
-if sys.platform == 'win32':
- def quote(c):
- if ' ' in c:
- return '"%s"' % c # work around spawn lamosity on windows
- else:
- return c
-else:
- def quote (c):
- return c
-
-cmd = 'from setuptools.command.easy_install import main; main()'
-ws = pkg_resources.working_set
-assert os.spawnle(
- os.P_WAIT, sys.executable, quote (sys.executable),
- '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
- dict(os.environ,
- PYTHONPATH=
- ws.find(pkg_resources.Requirement.parse('setuptools')).location
- ),
- ) == 0
-
-ws.add_entry(tmpeggs)
-ws.require('zc.buildout')
-import zc.buildout.buildout
-zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
-shutil.rmtree(tmpeggs)
diff --git a/buildout.cfg b/buildout.cfg
deleted file mode 100644
index 2e57c3fd..00000000
--- a/buildout.cfg
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# Buildout to set-up Sphinx
-#
-[buildout]
-parts =
- sphinx
- venv
- build_s5
- executable
-
-extensions =
- buildout.dumppickedversions
-
-allow-picked-versions = true
-
-versions = versions
-
-script-in = ${buildout:directory}/commands/build.in
-
-[sphinx]
-recipe = collective.recipe.sphinxbuilder
-#doc-directory = .
-outputs =
- html
-source = ${buildout:directory}/source/main
-build = ${buildout:directory}/build
-eggs =
- Sphinx
- docutils
- roman
- Pygments
-
-[venv]
-recipe = rjm.recipe.venv
-venv_options = --no-site-packages --distribute
-distutils_urls =
- http://pypi.python.org/packages/source/d/docutils/docutils-0.9.1.tar.gz
-
-[build_s5]
-recipe = collective.recipe.template[genshi]:genshi
-input = ${buildout:script-in}
-output = ${buildout:directory}/bin/build_s5
-build-suffix = html
-build-directory = ${buildout:directory}/build/html/presentations
-build-cmd = ${buildout:directory}/bin/rst2s5.py
-
-[executable]
-recipe = collective.recipe.cmd
-on_install = true
-on_update = true
-cmds =
- chmod 744 ${build_s5:output}
-
-[versions]
-# pin versions for continued sanity
-Jinja2 = 2.6
-Pygments = 1.5
-Sphinx = 1.1.3
-collective.recipe.sphinxbuilder = 0.7.1
-roman = 1.4.0
-
-#Required by:
-#collective.recipe.sphinxbuilder 0.7.1
-docutils = 0.9.1
-
-#Required by:
-#collective.recipe.sphinxbuilder 0.7.1
-zc.buildout = 1.5.2
-
-#Required by:
-#collective.recipe.sphinxbuilder 0.7.1
-zc.recipe.egg = 1.3.2
-
-distribute = 0.6.30
-
-Genshi = 0.6
-collective.recipe.cmd = 0.5
-collective.recipe.template = 1.9
-rjm.recipe.venv = 0.8
-
-#Required by:
-#collective.recipe.sphinxbuilder 0.7.1
-#zc.recipe.egg 1.3.2
-#zc.buildout 1.5.2
-setuptools = 0.6c12dev-r88846
-
-#Required by:
-#rjm.recipe.venv 0.8
-virtualenv = 1.8.2
diff --git a/commands/build.in b/commands/build.in
deleted file mode 100644
index f8c5fb33..00000000
--- a/commands/build.in
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-shopt -s nullglob
-SRC=rst
-DEST=${options['build-suffix']}
-
-for RST in ${parts.buildout.directory}/source/presentations/*.rst
-do
- BASE=`basename $$RST`
- OUT=${options['build-directory']}/$${BASE%.$$SRC}.$$DEST
- ${options['build-cmd']} $$RST $$OUT
-done
-
-cp -R ${parts.buildout.directory}/source/ui ${options['build-directory']}/
-cp -R ${parts.buildout.directory}/source/img ${options['build-directory']}/
diff --git a/docutils.conf b/docutils.conf
index c640184a..f36d1c9b 100644
--- a/docutils.conf
+++ b/docutils.conf
@@ -1,8 +1,11 @@
[general]
source_url: http://github.com/cewing/training.python_web
+[restructuredtext parser]
+syntax_highlight = short
+
[s5_html writer]
current_slide: True
embed_stylesheet: false
stylesheet: ui/uw_pce_theme/pretty.css
-theme_url: ui/uw_pce_theme
\ No newline at end of file
+theme_url: ui/uw_pce_theme
diff --git a/downloads/.gitignore b/downloads/.gitignore
deleted file mode 100644
index d6b7ef32..00000000
--- a/downloads/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
diff --git a/notebooks/Networking & Sockets.ipynb b/notebooks/Networking & Sockets.ipynb
new file mode 100644
index 00000000..09d64583
--- /dev/null
+++ b/notebooks/Networking & Sockets.ipynb
@@ -0,0 +1,309 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "import socket"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def get_constants(prefix):\n",
+ " \"\"\"mapping of socket module constants to their names\"\"\"\n",
+ " return {getattr(socket, n): n\n",
+ " for n in dir(socket)\n",
+ " if n.startswith(prefix)\n",
+ " }"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "families = get_constants('AF_')\n",
+ "types = get_constants('SOCK_')\n",
+ "protocols = get_constants('IPPROTO_')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{: 'AF_UNSPEC',\n",
+ " : 'AF_UNIX',\n",
+ " : 'AF_INET',\n",
+ " : 'AF_SNA',\n",
+ " 12: 'AF_DECnet',\n",
+ " : 'AF_APPLETALK',\n",
+ " : 'AF_ROUTE',\n",
+ " : 'AF_LINK',\n",
+ " : 'AF_IPX',\n",
+ " : 'AF_INET6',\n",
+ " : 'AF_SYSTEM'}"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "families"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{: 'SOCK_STREAM',\n",
+ " : 'SOCK_DGRAM',\n",
+ " : 'SOCK_RAW',\n",
+ " : 'SOCK_RDM',\n",
+ " : 'SOCK_SEQPACKET'}"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "types"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{0: 'IPPROTO_IP',\n",
+ " 1: 'IPPROTO_ICMP',\n",
+ " 2: 'IPPROTO_IGMP',\n",
+ " 3: 'IPPROTO_GGP',\n",
+ " 4: 'IPPROTO_IPV4',\n",
+ " 6: 'IPPROTO_TCP',\n",
+ " 8: 'IPPROTO_EGP',\n",
+ " 12: 'IPPROTO_PUP',\n",
+ " 17: 'IPPROTO_UDP',\n",
+ " 22: 'IPPROTO_IDP',\n",
+ " 29: 'IPPROTO_TP',\n",
+ " 36: 'IPPROTO_XTP',\n",
+ " 41: 'IPPROTO_IPV6',\n",
+ " 43: 'IPPROTO_ROUTING',\n",
+ " 44: 'IPPROTO_FRAGMENT',\n",
+ " 46: 'IPPROTO_RSVP',\n",
+ " 47: 'IPPROTO_GRE',\n",
+ " 50: 'IPPROTO_ESP',\n",
+ " 51: 'IPPROTO_AH',\n",
+ " 58: 'IPPROTO_ICMPV6',\n",
+ " 59: 'IPPROTO_NONE',\n",
+ " 60: 'IPPROTO_DSTOPTS',\n",
+ " 63: 'IPPROTO_HELLO',\n",
+ " 77: 'IPPROTO_ND',\n",
+ " 80: 'IPPROTO_EON',\n",
+ " 103: 'IPPROTO_PIM',\n",
+ " 108: 'IPPROTO_IPCOMP',\n",
+ " 132: 'IPPROTO_SCTP',\n",
+ " 255: 'IPPROTO_RAW',\n",
+ " 256: 'IPPROTO_MAX'}"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "protocols"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "default_socket = socket.socket()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'AF_INET'"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "families[default_socket.family]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'SOCK_STREAM'"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "types[default_socket.type]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'IPPROTO_IP'"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "protocols[default_socket.proto]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def get_address_info(host, port):\n",
+ " for response in socket.getaddrinfo(host, port):\n",
+ " fam, typ, pro, nam, add = response\n",
+ " print('family: {}'.format(families[fam]))\n",
+ " print('type: {}'.format(types[typ]))\n",
+ " print('protocol: {}'.format(protocols[pro]))\n",
+ " print('canonical name: {}'.format(nam))\n",
+ " print('socket address: {}'.format(add))\n",
+ " print('')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "family: AF_INET\n",
+ "type: SOCK_DGRAM\n",
+ "protocol: IPPROTO_UDP\n",
+ "canonical name: \n",
+ "socket address: ('127.0.0.1', 80)\n",
+ "\n",
+ "family: AF_INET\n",
+ "type: SOCK_STREAM\n",
+ "protocol: IPPROTO_TCP\n",
+ "canonical name: \n",
+ "socket address: ('127.0.0.1', 80)\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "get_address_info(socket.gethostname(), 'http')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/requirements.pip b/requirements.pip
new file mode 100644
index 00000000..7539549c
--- /dev/null
+++ b/requirements.pip
@@ -0,0 +1,24 @@
+alabaster==0.7.6
+appnope==0.1.0
+Babel==2.0
+decorator==4.0.2
+docutils==0.12
+gnureadline==6.3.3
+hieroglyph==0.7.1
+ipython==4.0.0
+ipython-genutils==0.1.0
+Jinja2==2.8
+MarkupSafe==0.23
+path.py==8.1
+pexpect==3.3
+pickleshare==0.5
+py==1.4.30
+Pygments==2.0.2
+pytest==2.7.2
+pytz==2015.4
+simplegeneric==0.8.1
+six==1.9.0
+snowballstemmer==1.2.0
+Sphinx==1.3.1
+sphinx-rtd-theme==0.1.8
+traitlets==4.0.0
diff --git a/resources/session01/echo_client.py b/resources/session01/echo_client.py
new file mode 100644
index 00000000..6b2f0472
--- /dev/null
+++ b/resources/session01/echo_client.py
@@ -0,0 +1,48 @@
+import socket
+import sys
+
+
+def client(msg, log_buffer=sys.stderr):
+ server_address = ('localhost', 10000)
+ # TODO: Replace the following line with your code which will instantiate
+ # a TCP socket with IPv4 Addressing, call the socket you make 'sock'
+ sock = None
+ print('connecting to {0} port {1}'.format(*server_address), file=log_buffer)
+ # TODO: connect your socket to the server here.
+
+ # you can use this variable to accumulate the entire message received back
+ # from the server
+ received_message = ''
+
+ # this try/finally block exists purely to allow us to close the socket
+ # when we are finished with it
+ try:
+ print('sending "{0}"'.format(msg), file=log_buffer)
+ # TODO: send your message to the server here.
+
+ # TODO: the server should be sending you back your message as a series
+ # of 16-byte chunks. Accumulate the chunks you get to build the
+ # entire reply from the server. Make sure that you have received
+ # the entire message and then you can break the loop.
+ #
+ # Log each chunk you receive. Use the print statement below to
+ # do it. This will help in debugging problems
+ chunk = ''
+ print('received "{0}"'.format(chunk.decode('utf8')), file=log_buffer)
+ finally:
+ # TODO: after you break out of the loop receiving echoed chunks from
+ # the server you will want to close your client socket.
+ print('closing socket', file=log_buffer)
+
+ # TODO: when all is said and done, you should return the entire reply
+ # you received from the server as the return value of this function.
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ usage = '\nusage: python echo_client.py "this is my message"\n'
+ print(usage, file=sys.stderr)
+ sys.exit(1)
+
+ msg = sys.argv[1]
+ client(msg)
diff --git a/resources/session01/echo_server.py b/resources/session01/echo_server.py
new file mode 100644
index 00000000..4103ac6a
--- /dev/null
+++ b/resources/session01/echo_server.py
@@ -0,0 +1,76 @@
+import socket
+import sys
+
+
+def server(log_buffer=sys.stderr):
+ # set an address for our server
+ address = ('127.0.0.1', 10000)
+ # TODO: Replace the following line with your code which will instantiate
+ # a TCP socket with IPv4 Addressing, call the socket you make 'sock'
+ sock = None
+ # TODO: You may find that if you repeatedly run the server script it fails,
+ # claiming that the port is already used. You can set an option on
+ # your socket that will fix this problem. We DID NOT talk about this
+ # in class. Find the correct option by reading the very end of the
+ # socket library documentation:
+ # http://docs.python.org/3/library/socket.html#example
+
+ # log that we are building a server
+ print("making a server on {0}:{1}".format(*address), file=log_buffer)
+
+ # TODO: bind your new sock 'sock' to the address above and begin to listen
+ # for incoming connections
+
+ try:
+ # the outer loop controls the creation of new connection sockets. The
+ # server will handle each incoming connection one at a time.
+ while True:
+ print('waiting for a connection', file=log_buffer)
+
+ # TODO: make a new socket when a client connects, call it 'conn',
+ # at the same time you should be able to get the address of
+ # the client so we can report it below. Replace the
+ # following line with your code. It is only here to prevent
+ # syntax errors
+ addr = ('bar', 'baz')
+ try:
+ print('connection - {0}:{1}'.format(*addr), file=log_buffer)
+
+ # the inner loop will receive messages sent by the client in
+ # buffers. When a complete message has been received, the
+ # loop will exit
+ while True:
+ # TODO: receive 16 bytes of data from the client. Store
+ # the data you receive as 'data'. Replace the
+ # following line with your code. It's only here as
+ # a placeholder to prevent an error in string
+ # formatting
+ data = b''
+ print('received "{0}"'.format(data.decode('utf8')))
+ # TODO: Send the data you received back to the client, log
+ # the fact using the print statement here. It will help in
+ # debugging problems.
+ print('sent "{0}"'.format(data.decode('utf8')))
+ # TODO: Check here to see if the message you've received is
+ # complete. If it is, break out of this inner loop.
+
+ finally:
+ # TODO: When the inner loop exits, this 'finally' clause will
+ # be hit. Use that opportunity to close the socket you
+ # created above when a client connected.
+ print(
+ 'echo complete, client connection closed', file=log_buffer
+ )
+
+ except KeyboardInterrupt:
+ # TODO: Use the python KeyboardInterrupt exception as a signal to
+ # close the server socket and exit from the server function.
+ # Replace the call to `pass` below, which is only there to
+ # prevent syntax problems
+ pass
+ print('quitting echo server', file=log_buffer)
+
+
+if __name__ == '__main__':
+ server()
+ sys.exit(0)
diff --git a/resources/session01/socket_tools.py b/resources/session01/socket_tools.py
new file mode 100644
index 00000000..d2bc1e18
--- /dev/null
+++ b/resources/session01/socket_tools.py
@@ -0,0 +1,21 @@
+import socket
+
+
+def get_constants(prefix):
+ return {getattr(socket, n): n for n in dir(socket) if n.startswith(prefix)}
+
+
+families = get_constants('AF_')
+types = get_constants('SOCK_')
+protocols = get_constants('IPPROTO_')
+
+
+def get_address_info(host, port):
+ for response in socket.getaddrinfo(host, port):
+ fam, typ, pro, nam, add = response
+ print('family: {}'.format(families[fam]))
+ print('type: {}'.format(types[typ]))
+ print('protocol: {}'.format(protocols[pro]))
+ print('canonical name: {}'.format(nam))
+ print('socket address: {}'.format(add))
+ print()
diff --git a/resources/session01/tasks.txt b/resources/session01/tasks.txt
new file mode 100644
index 00000000..8fdab003
--- /dev/null
+++ b/resources/session01/tasks.txt
@@ -0,0 +1,53 @@
+Session 4 Homework
+==================
+
+Required Tasks:
+---------------
+
+* Complete the code in ``echo_server.py`` to create a server that sends back
+ whatever messages it receives from a client
+
+* Complete the code in ``echo_client.py`` to create a client function that
+ can send a message and receive a reply.
+
+* Ensure that the tests in ``tests.py`` pass.
+
+To run the tests:
+
+* Open one terminal while in this folder and execute this command:
+
+ $ python echo_server.py
+
+* Open a second terminal in this same folder and execute this command:
+
+ $ python tests.py
+
+
+
+
+Optional Tasks:
+---------------
+
+Simple:
+
+* Write a python function that lists the services provided by a given range of
+ ports.
+
+ * accept the lower and upper bounds as arguments
+ * provide sensible defaults
+ * Ensure that it only accepts valid port numbers (0-65535)
+
+Challenging:
+
+* The echo server as outlined will only process a connection from one client
+ at a time. If a second client were to attempt a connection, it would have to
+ wait until the first message was fully echoed before it could be dealt with.
+
+ Python provides a module called `select` that allows waiting for I/O events
+ in order to control flow. The `select.select` method can be used to allow
+ our echo server to handle more than one incoming connection in "parallel".
+
+ Read the documentation about the `select` module
+ (http://docs.python.org/3/library/select.html) and attempt to write a second
+ version of the echo server that can handle multiple client connections in
+ "parallel". You do not need to invoke threading of any kind to do this.
diff --git a/resources/session01/tests.py b/resources/session01/tests.py
new file mode 100644
index 00000000..d4c6c791
--- /dev/null
+++ b/resources/session01/tests.py
@@ -0,0 +1,46 @@
+from echo_client import client
+import socket
+import unittest
+
+
+class EchoTestCase(unittest.TestCase):
+ """tests for the echo server and client"""
+
+ def send_message(self, message):
+ """Attempt to send a message using the client
+
+ In case of a socket error, fail and report the problem
+ """
+ try:
+ reply = client(message)
+ except socket.error as e:
+ if e.errno == 61:
+ msg = "Error: {0}, is the server running?"
+ self.fail(msg.format(e.strerror))
+ else:
+ self.fail("Unexpected Error: {0}".format(str(e)))
+ return reply
+
+ def test_short_message_echo(self):
+ """test that a message short than 16 bytes echoes cleanly"""
+ expected = "short message"
+ actual = self.send_message(expected)
+ self.assertEqual(
+ expected,
+ actual,
+ "expected {0}, got {1}".format(expected, actual)
+ )
+
+ def test_long_message_echo(self):
+ """test that a message longer than 16 bytes echoes in 16-byte chunks"""
+ expected = "Four score and seven years ago our fathers did stuff"
+ actual = self.send_message(expected)
+ self.assertEqual(
+ expected,
+ actual,
+ "expected {0}, got {1}".format(expected, actual)
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/resources/session02/homework/http_server.py b/resources/session02/homework/http_server.py
new file mode 100644
index 00000000..84ceffe1
--- /dev/null
+++ b/resources/session02/homework/http_server.py
@@ -0,0 +1,86 @@
+import socket
+import sys
+
+
+def response_ok(body=b"this is a pretty minimal response", mimetype=b"text/plain"):
+ """returns a basic HTTP response"""
+ resp = []
+ resp.append(b"HTTP/1.1 200 OK")
+ resp.append(b"Content-Type: text/plain")
+ resp.append(b"")
+ resp.append(b"this is a pretty minimal response")
+ return b"\r\n".join(resp)
+
+
+def response_method_not_allowed():
+ """returns a 405 Method Not Allowed response"""
+ resp = []
+ resp.append("HTTP/1.1 405 Method Not Allowed")
+ resp.append("")
+ return "\r\n".join(resp).encode('utf8')
+
+
+def response_not_found():
+ """returns a 404 Not Found response"""
+ return b""
+
+
+def parse_request(request):
+ first_line = request.split("\r\n", 1)[0]
+ method, uri, protocol = first_line.split()
+ if method != "GET":
+ raise NotImplementedError("We only accept GET")
+ return uri
+
+
+def resolve_uri(uri):
+ """This method should return appropriate content and a mime type"""
+ return b"still broken", b"text/plain"
+
+
+def server(log_buffer=sys.stderr):
+ address = ('127.0.0.1', 10000)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ print("making a server on {0}:{1}".format(*address), file=log_buffer)
+ sock.bind(address)
+ sock.listen(1)
+
+ try:
+ while True:
+ print('waiting for a connection', file=log_buffer)
+ conn, addr = sock.accept() # blocking
+ try:
+ print('connection - {0}:{1}'.format(*addr), file=log_buffer)
+ request = ''
+ while True:
+ data = conn.recv(1024)
+ request += data.decode('utf8')
+ if len(data) < 1024:
+ break
+
+ try:
+ uri = parse_request(request)
+ except NotImplementedError:
+ response = response_method_not_allowed()
+ else:
+ try:
+ content, mime_type = resolve_uri(uri)
+ except NameError:
+ response = response_not_found()
+ else:
+ response = response_ok(content, mime_type)
+
+ print('sending response', file=log_buffer)
+ conn.sendall(response)
+ finally:
+ conn.close()
+
+ except KeyboardInterrupt:
+ sock.close()
+ return
+
+
+if __name__ == '__main__':
+ server()
+ sys.exit(0)
diff --git a/resources/session02/homework/simple_client.py b/resources/session02/homework/simple_client.py
new file mode 100644
index 00000000..2c9ed4cd
--- /dev/null
+++ b/resources/session02/homework/simple_client.py
@@ -0,0 +1,44 @@
+import socket
+import sys
+
+
+def bytes_client(msg):
+ server_address = ('localhost', 10000)
+ sock = socket.socket(
+ socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP
+ )
+ print(
+ 'connecting to {0} port {1}'.format(*server_address),
+ file=sys.stderr
+ )
+ sock.connect(server_address)
+ response = b''
+ done = False
+ bufsize = 1024
+ try:
+ print('sending "{0}"'.format(msg), file=sys.stderr)
+ sock.sendall(msg.encode('utf8'))
+ while not done:
+ chunk = sock.recv(bufsize)
+ if len(chunk) < bufsize:
+ done = True
+ response += chunk
+ print('received "{0}"'.format(response), file=sys.stderr)
+ finally:
+ print('closing socket', file=sys.stderr)
+ sock.close()
+ return response
+
+
+def client(msg):
+ return bytes_client(msg).decode('utf8')
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ usg = '\nusage: python echo_client.py "this is my message"\n'
+ print(usg, file=sys.stderr)
+ sys.exit(1)
+
+ msg = sys.argv[1]
+ client(msg)
diff --git a/resources/session02/homework/tests.py b/resources/session02/homework/tests.py
new file mode 100644
index 00000000..45007311
--- /dev/null
+++ b/resources/session02/homework/tests.py
@@ -0,0 +1,427 @@
+import mimetypes
+import os
+import pathlib
+import socket
+import unittest
+
+
+CRLF = '\r\n'
+CRLF_BYTES = CRLF.encode('utf8')
+KNOWN_TYPES = set(
+ map(lambda x: x.encode('utf8'), mimetypes.types_map.values())
+)
+
+
+def extract_response_code(response):
+ return response.split(CRLF_BYTES, 1)[0].split(b' ', 1)[1].strip()
+
+
+def extract_response_protocol(response):
+ return response.split(CRLF_BYTES, 1)[0].split(b' ', 1)[0].strip()
+
+
+def extract_headers(response):
+ return response.split(CRLF_BYTES*2, 1)[0].split(CRLF_BYTES)[1:]
+
+
+def extract_body(response):
+ return response.split(CRLF_BYTES*2, 1)[1]
+
+
+class ResponseOkTestCase(unittest.TestCase):
+ """unit tests for the response_ok method in our server
+
+ Becase this is a unit test case, it does not require the server to be
+ running.
+ """
+
+ def call_function_under_test(self, body=b"", mimetype=b"text/plain"):
+ """call the `response_ok` function from our http_server module"""
+ from http_server import response_ok
+ return response_ok(body=body, mimetype=mimetype)
+
+ def test_response_code(self):
+ ok = self.call_function_under_test()
+ expected = "200 OK"
+ actual = extract_response_code(ok)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_protocol(self):
+ ok = self.call_function_under_test()
+ expected = 'HTTP/1.1'
+ actual = extract_response_protocol(ok)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_has_content_type_header(self):
+ ok = self.call_function_under_test()
+ headers = extract_headers(ok)
+ expected_name = 'content-type'.encode('utf8')
+ has_header = False
+ for header in headers:
+ name, value = header.split(b':')
+ actual_name = name.strip().lower()
+ if actual_name == expected_name:
+ has_header = True
+ break
+ self.assertTrue(has_header)
+
+ def test_response_has_legitimate_content_type(self):
+ ok = self.call_function_under_test()
+ headers = extract_headers(ok)
+ expected_name = 'content-type'.encode('utf8')
+ for header in headers:
+ name, value = header.split(b':')
+ actual_name = name.strip().lower()
+ if actual_name == expected_name:
+ self.assertTrue(value.strip() in KNOWN_TYPES)
+ return
+ self.fail('no content type header found')
+
+ def test_passed_mimetype_in_response(self):
+ mimetypes = [
+ b'image/jpeg', b'text/html', b'text/x-python',
+ ]
+ header_name = b'content-type'
+ for expected in mimetypes:
+ ok = self.call_function_under_test(mimetype=expected)
+ headers = extract_headers(ok)
+ for header in headers:
+ name, value = header.split(b':')
+ if header_name == name.strip().lower():
+ actual = value.strip()
+ self.assertEqual(
+ expected,
+ actual,
+ "expected {0}, got {1}".format(expected, actual)
+ )
+
+ def test_passed_body_in_response(self):
+ bodies = [
+ b"a body",
+ b"a longer body\nwith two lines",
+ pathlib.Path("webroot/sample.txt").read_bytes(),
+ ]
+ for expected in bodies:
+ ok = self.call_function_under_test(body=expected)
+ actual = extract_body(ok)
+ self.assertEqual(
+ expected,
+ actual,
+ "expected {0}, got {1}".format(expected, actual))
+
+
+class ResponseMethodNotAllowedTestCase(unittest.TestCase):
+ """unit tests for the response_method_not_allowed function"""
+
+ def call_function_under_test(self):
+ """call the `response_method_not_allowed` function"""
+ from http_server import response_method_not_allowed
+ return response_method_not_allowed()
+
+ def test_response_code(self):
+ resp = self.call_function_under_test()
+ expected = "405 Method Not Allowed"
+ actual = extract_response_code(resp)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_method(self):
+ resp = self.call_function_under_test()
+ expected = 'HTTP/1.1'
+ actual = extract_response_protocol(resp)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+
+class ResponseNotFoundTestCase(unittest.TestCase):
+ """unit tests for the response_not_found function"""
+
+ def call_function_under_test(self):
+ """call the 'response_not_found' function"""
+ from http_server import response_not_found
+ return response_not_found()
+
+ def test_response_code(self):
+ resp = self.call_function_under_test()
+ expected = "404 Not Found"
+ actual = extract_response_code(resp)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_method(self):
+ resp = self.call_function_under_test()
+ expected = 'HTTP/1.1'
+ actual = extract_response_protocol(resp)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+
+class ParseRequestTestCase(unittest.TestCase):
+ """unit tests for the parse_request method"""
+
+ def call_function_under_test(self, request):
+ """call the `parse_request` function"""
+ from http_server import parse_request
+ return parse_request(request)
+
+ def test_get_method(self):
+ """verify that GET HTTP requests do not raise an error"""
+ request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
+ try:
+ self.call_function_under_test(request)
+ except (NotImplementedError, Exception) as e:
+ self.fail('GET method raises an error {0}'.format(str(e)))
+
+ def test_bad_http_methods(self):
+ """verify that non-GET HTTP methods raise a NotImplementedError"""
+ methods = ['POST', 'PUT', 'DELETE', 'HEAD']
+ request_template = "{0} / HTTP/1.1\r\nHost: example.com\r\n\r\n"
+ for method in methods:
+ request = request_template.format(method)
+ self.assertRaises(
+ NotImplementedError, self.call_function_under_test, request
+ )
+
+ def test_uri_returned(self):
+ """verify that the parse_request function returns a URI"""
+ URIs = [
+ '/', '/a_web_page.html', '/sample.txt', '/images/sample_1.png',
+ ]
+ request_tmplt = "GET {0} HTTP/1.1"
+ for expected in URIs:
+ request = request_tmplt.format(expected)
+ actual = self.call_function_under_test(request)
+ self.assertEqual(
+ expected,
+ actual,
+ "expected {0}, got {1}".format(expected, actual)
+ )
+
+
+class ResolveURITestCase(unittest.TestCase):
+ """unit tests for the resolve_uri function"""
+
+ def call_function_under_test(self, uri):
+ """call the resolve_uri function"""
+ from http_server import resolve_uri
+ content, mime_type = resolve_uri(uri)
+ return content, mime_type.decode('utf8')
+
+ def test_directory_resource(self):
+ uri = '/'
+ expected_names = [
+ 'a_web_page.html', 'images', 'make_time.py', 'sample.txt',
+ ]
+ expected_mimetype = "text/plain"
+ actual_body, actual_mimetype = self.call_function_under_test(uri)
+ self.assertEqual(
+ expected_mimetype,
+ actual_mimetype,
+ 'expected {0} got {1}'.format(expected_mimetype, actual_mimetype)
+ )
+ actual_body = actual_body.decode('utf8')
+ for expected in expected_names:
+ self.assertTrue(
+ expected in actual_body,
+ '"{0}" not in "{1}"'.format(expected, actual_body)
+ )
+
+ def test_file_resource(self):
+ uris_types = {
+ '/a_web_page.html': 'text/html',
+ '/make_time.py': 'text/x-python',
+ '/sample.txt': 'text/plain',
+ }
+ for uri, expected_mimetype in uris_types.items():
+ path = pathlib.Path("webroot{0}".format(uri))
+ expected_body = path.read_bytes()
+ actual_body, actual_mimetype = self.call_function_under_test(uri)
+ self.assertEqual(
+ expected_mimetype,
+ actual_mimetype,
+ 'expected {0} got {1}'.format(
+ expected_mimetype, actual_mimetype
+ )
+ )
+ self.assertEqual(
+ expected_body,
+ actual_body,
+ 'expected {0} got {1}'.format(
+ expected_mimetype, actual_mimetype
+ )
+ )
+
+ def test_image_resource(self):
+ names_types = {
+ 'JPEG_example.jpg': 'image/jpeg',
+ 'sample_1.png': 'image/png',
+ }
+ for filename, expected_mimetype in names_types.items():
+ uri = "/images/{0}".format(filename)
+ path = pathlib.Path("webroot{0}".format(uri))
+ expected_body = path.read_bytes()
+ actual_body, actual_mimetype = self.call_function_under_test(uri)
+ self.assertEqual(
+ expected_mimetype,
+ actual_mimetype,
+ 'expected {0} got {1}'.format(
+ expected_mimetype, actual_mimetype
+ )
+ )
+ self.assertEqual(
+ expected_body,
+ actual_body,
+ 'expected {0} got {1}'.format(
+ expected_mimetype, actual_mimetype
+ )
+ )
+
+ def test_missing_resource(self):
+ uri = "/missing.html"
+ self.assertRaises(NameError, self.call_function_under_test, uri)
+
+
+class HTTPServerFunctionalTestCase(unittest.TestCase):
+ """functional tests of the HTTP Server
+
+ This test case interacts with the http server, and as such requires it to
+ be running in order for the tests to pass
+ """
+
+ def send_message(self, message, use_bytes=False):
+ """Attempt to send a message using the client and the test buffer
+
+ In case of a socket error, fail and report the problem
+ """
+ response = ''
+ if not use_bytes:
+ from simple_client import client
+ else:
+ from simple_client import bytes_client as client
+
+ try:
+ response = client(message)
+ except socket.error as e:
+ if e.errno == 61:
+ msg = "Error: {0}, is the server running?"
+ self.fail(msg.format(e.strerror))
+ else:
+ self.fail("Unexpected Error: {0}".format(str(e)))
+ return response
+
+ def test_get_request(self):
+ message = CRLF.join(['GET / HTTP/1.1', 'Host: example.com', ''])
+ expected = '200 OK'
+ actual = self.send_message(message)
+ self.assertTrue(
+ expected in actual, '"{0}" not in "{1}"'.format(expected, actual)
+ )
+
+ def test_post_request(self):
+ message = CRLF.join(['POST / HTTP/1.1', 'Host: example.com', ''])
+ expected = '405 Method Not Allowed'
+ actual = self.send_message(message)
+ self.assertTrue(
+ expected in actual, '"{0}" not in "{1}"'.format(expected, actual)
+ )
+
+ def test_webroot_directory_resources(self):
+ """verify that directory uris are properly served"""
+ message_tmpl = CRLF.join(['GET {0} HTTP/1.1', 'Host: example.com', ''])
+ root = "webroot/"
+ for directory, directories, files in os.walk(root):
+ directory_uri = "/{0}".format(directory[len(root):])
+ message = message_tmpl.format(directory_uri)
+ actual = self.send_message(message)
+ # verify that directory listings are correct
+ self.assertTrue(
+ "200 OK" in actual,
+ "request for {0} did not result in OK".format(directory_uri))
+ for expected in directories + files:
+ self.assertTrue(
+ expected in actual,
+ '"{0}" not in "{1}"'.format(expected, actual)
+ )
+
+ def test_webroot_file_uris(self):
+ """verify that file uris are properly served"""
+ message_tmpl = CRLF.join(['GET {0} HTTP/1.1', 'Host: example.com', ''])
+ root = pathlib.Path("webroot")
+ for file_path in root.iterdir():
+ # set up expectations for this file
+ if file_path.is_dir():
+ continue
+ expected_body = file_path.read_bytes().decode('utf8')
+ expected_mimetype = mimetypes.types_map[
+ os.path.splitext(str(file_path))[1]
+ ]
+ file_uri = str(file_path)[len(str(root)):]
+ message = message_tmpl.format(file_uri)
+ actual = self.send_message(message)
+ self.assertTrue(
+ "200 OK" in actual,
+ "request for {0} did not result in OK".format(
+ file_uri
+ )
+ )
+ self.assertTrue(
+ expected_mimetype in actual,
+ "mimetype {0} not in response for {1}".format(
+ expected_mimetype, file_uri
+ )
+ )
+ self.assertTrue(
+ expected_body in actual,
+ "body of {0} not in response for {1}".format(
+ file_path, file_uri
+ )
+ )
+
+ def test_webroot_image_uris(self):
+ """verify that image uris are properly served
+
+ requires using a client that does not attempt to decode the response
+ body
+ """
+ message_tmpl = CRLF.join(['GET {0} HTTP/1.1', 'Host: example.com', ''])
+ root = pathlib.Path("webroot")
+ images_path = root / 'images'
+ for file_path in images_path.iterdir():
+ # set up expectations for this file
+ if file_path.is_dir():
+ continue
+ expected_body = file_path.read_bytes()
+ expected_mimetype = mimetypes.types_map[
+ os.path.splitext(str(file_path))[1]
+ ]
+ file_uri = str(file_path)[len(str(root)):]
+ message = message_tmpl.format(file_uri)
+ actual = self.send_message(message, use_bytes=True)
+ self.assertTrue(
+ b"200 OK" in actual,
+ "request for {0} did not result in OK".format(
+ file_uri
+ )
+ )
+ self.assertTrue(
+ expected_mimetype.encode('utf8') in actual,
+ "mimetype {0} not in response for {1}".format(
+ expected_mimetype, file_uri
+ )
+ )
+ self.assertTrue(
+ expected_body in actual,
+ "body of {0} not in response for {1}".format(
+ file_path, file_uri
+ )
+ )
+
+ def test_missing_resource(self):
+ message = CRLF.join(
+ ['GET /missing.html HTTP/1.1', 'Host: example.com', '']
+ )
+ expected = '404 Not Found'
+ actual = self.send_message(message)
+ self.assertTrue(
+ expected in actual, '"{0}" not in "{1}"'.format(expected, actual)
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/resources/session02/homework/webroot/a_web_page.html b/resources/session02/homework/webroot/a_web_page.html
new file mode 100644
index 00000000..4635692d
--- /dev/null
+++ b/resources/session02/homework/webroot/a_web_page.html
@@ -0,0 +1,11 @@
+
+
+
+
+North Carolina
+
+A fine place to spend a week learning web programming!
+
+
+
+
diff --git a/assignments/week02/lab/web/images/JPEG_example.jpg b/resources/session02/homework/webroot/images/JPEG_example.jpg
similarity index 100%
rename from assignments/week02/lab/web/images/JPEG_example.jpg
rename to resources/session02/homework/webroot/images/JPEG_example.jpg
diff --git a/assignments/week02/lab/web/images/Sample_Scene_Balls.jpg b/resources/session02/homework/webroot/images/Sample_Scene_Balls.jpg
similarity index 100%
rename from assignments/week02/lab/web/images/Sample_Scene_Balls.jpg
rename to resources/session02/homework/webroot/images/Sample_Scene_Balls.jpg
diff --git a/assignments/week02/lab/web/images/sample_1.png b/resources/session02/homework/webroot/images/sample_1.png
similarity index 100%
rename from assignments/week02/lab/web/images/sample_1.png
rename to resources/session02/homework/webroot/images/sample_1.png
diff --git a/assignments/week02/lab/web/make_time.py b/resources/session02/homework/webroot/make_time.py
similarity index 89%
rename from assignments/week02/lab/web/make_time.py
rename to resources/session02/homework/webroot/make_time.py
index d3064dd2..b69acf38 100644
--- a/assignments/week02/lab/web/make_time.py
+++ b/resources/session02/homework/webroot/make_time.py
@@ -17,9 +17,6 @@
%s
+Hey there, this page has been generated by {software}, running {script}
+Today is {month} {date}, {year}.
+This page was requested by IP Address {client_ip}
+
-"""% time_str
-
-print html
-
-
+""" % time_str
+print(html)
diff --git a/assignments/week02/lab/web/sample.txt b/resources/session02/homework/webroot/sample.txt
similarity index 100%
rename from assignments/week02/lab/web/sample.txt
rename to resources/session02/homework/webroot/sample.txt
diff --git a/resources/session02/http_server.py b/resources/session02/http_server.py
new file mode 100644
index 00000000..d5aaf480
--- /dev/null
+++ b/resources/session02/http_server.py
@@ -0,0 +1,39 @@
+import socket
+import sys
+
+
+def server(log_buffer=sys.stderr):
+ address = ('127.0.0.1', 10000)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ print("making a server on {0}:{1}".format(*address), file=log_buffer)
+ sock.bind(address)
+ sock.listen(1)
+
+ try:
+ while True:
+ print('waiting for a connection', file=log_buffer)
+ conn, addr = sock.accept() # blocking
+ try:
+ print('connection - {0}:{1}'.format(*addr), file=log_buffer)
+ while True:
+ data = conn.recv(16)
+ print('received "{0}"'.format(data), file=log_buffer)
+ if data:
+ print('sending data back to client', file=log_buffer)
+ conn.sendall(data)
+ else:
+ msg = 'no more data from {0}:{1}'.format(*addr)
+ print(msg, log_buffer)
+ break
+ finally:
+ conn.close()
+
+ except KeyboardInterrupt:
+ sock.close()
+ return
+
+
+if __name__ == '__main__':
+ server()
+ sys.exit(0)
diff --git a/resources/session02/simple_client.py b/resources/session02/simple_client.py
new file mode 100644
index 00000000..74523a2a
--- /dev/null
+++ b/resources/session02/simple_client.py
@@ -0,0 +1,40 @@
+import socket
+import sys
+
+
+def client(msg):
+ server_address = ('localhost', 10000)
+ sock = socket.socket(
+ socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP
+ )
+ print(
+ 'connecting to {0} port {1}'.format(*server_address),
+ file=sys.stderr
+ )
+ sock.connect(server_address)
+ response = ''
+ done = False
+ bufsize = 1024
+ try:
+ print('sending "{0}"'.format(msg), file=sys.stderr)
+ sock.sendall(msg.encode('utf8'))
+ while not done:
+ chunk = sock.recv(bufsize)
+ if len(chunk) < bufsize:
+ done = True
+ response += chunk.decode('utf8')
+ print('received "{0}"'.format(response), file=sys.stderr)
+ finally:
+ print('closing socket', file=sys.stderr)
+ sock.close()
+ return response
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ usg = '\nusage: python echo_client.py "this is my message"\n'
+ print(usg, file=sys.stderr)
+ sys.exit(1)
+
+ msg = sys.argv[1]
+ client(msg)
diff --git a/resources/session02/tests.py b/resources/session02/tests.py
new file mode 100644
index 00000000..a4da1793
--- /dev/null
+++ b/resources/session02/tests.py
@@ -0,0 +1,165 @@
+import mimetypes
+import socket
+import unittest
+
+
+CRLF = '\r\n'
+CRLF_BYTES = CRLF.encode('utf8')
+KNOWN_TYPES = set(
+ map(lambda x: x.encode('utf8'), mimetypes.types_map.values())
+)
+
+
+def extract_response_code(response):
+ return response.split(CRLF_BYTES, 1)[0].split(b' ', 1)[1].strip()
+
+
+def extract_response_protocol(response):
+ return response.split(CRLF_BYTES, 1)[0].split(b' ', 1)[0].strip()
+
+
+def extract_headers(response):
+ return response.split(CRLF_BYTES*2, 1)[0].split(CRLF_BYTES)[1:]
+
+
+class ResponseOkTestCase(unittest.TestCase):
+ """unit tests for the response_ok method in our server
+
+ Becase this is a unit test case, it does not require the server to be
+ running.
+ """
+
+ def call_function_under_test(self):
+ """call the `response_ok` function from our http_server module"""
+ from http_server import response_ok
+ return response_ok()
+
+ def test_response_code(self):
+ ok = self.call_function_under_test()
+ expected = "200 OK"
+ actual = extract_response_code(ok)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_protocol(self):
+ ok = self.call_function_under_test()
+ expected = 'HTTP/1.1'
+ actual = extract_response_protocol(ok)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_has_content_type_header(self):
+ ok = self.call_function_under_test()
+ headers = extract_headers(ok)
+ expected_name = 'content-type'.encode('utf8')
+ has_header = False
+ for header in headers:
+ name, value = header.split(b':')
+ actual_name = name.strip().lower()
+ if actual_name == expected_name:
+ has_header = True
+ break
+ self.assertTrue(has_header)
+
+ def test_response_has_legitimate_content_type(self):
+ ok = self.call_function_under_test()
+ headers = extract_headers(ok)
+ expected_name = 'content-type'.encode('utf8')
+ for header in headers:
+ name, value = header.split(b':')
+ actual_name = name.strip().lower()
+ if actual_name == expected_name:
+ self.assertTrue(value.strip() in KNOWN_TYPES)
+ return
+ self.fail('no content type header found')
+
+
+class ResponseMethodNotAllowedTestCase(unittest.TestCase):
+ """unit tests for the response_method_not_allowed function"""
+
+ def call_function_under_test(self):
+ """call the `response_method_not_allowed` function"""
+ from http_server import response_method_not_allowed
+ return response_method_not_allowed()
+
+ def test_response_code(self):
+ resp = self.call_function_under_test()
+ expected = "405 Method Not Allowed"
+ actual = extract_response_code(resp)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+ def test_response_method(self):
+ resp = self.call_function_under_test()
+ expected = 'HTTP/1.1'
+ actual = extract_response_protocol(resp)
+ self.assertEqual(expected.encode('utf8'), actual)
+
+
+class ParseRequestTestCase(unittest.TestCase):
+ """unit tests for the parse_request method"""
+
+ def call_function_under_test(self, request):
+ """call the `parse_request` function"""
+ from http_server import parse_request
+ return parse_request(request)
+
+ def test_get_method(self):
+ """verify that GET HTTP requests do not raise an error"""
+ request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
+ try:
+ self.call_function_under_test(request)
+ except (NotImplementedError, Exception) as e:
+ self.fail('GET method raises an error {0}'.format(str(e)))
+
+ def test_bad_http_methods(self):
+ """verify that non-GET HTTP methods raise a NotImplementedError"""
+ methods = ['POST', 'PUT', 'DELETE', 'HEAD']
+ request_template = "{0} / HTTP/1.1\r\nHost: example.com\r\n\r\n"
+ for method in methods:
+ request = request_template.format(method)
+ self.assertRaises(
+ NotImplementedError, self.call_function_under_test, request
+ )
+
+
+class HTTPServerFunctionalTestCase(unittest.TestCase):
+ """functional tests of the HTTP Server
+
+ This test case interacts with the http server, and as such requires it to
+ be running in order for the tests to pass
+ """
+
+ def send_message(self, message):
+ """Attempt to send a message using the client and the test buffer
+
+ In case of a socket error, fail and report the problem
+ """
+ from simple_client import client
+ response = ''
+ try:
+ response = client(message)
+ except socket.error as e:
+ if e.errno == 61:
+ msg = "Error: {0}, is the server running?"
+ self.fail(msg.format(e.strerror))
+ else:
+ self.fail("Unexpected Error: {0}".format(str(e)))
+ return response
+
+ def test_get_request(self):
+ message = CRLF.join(['GET / HTTP/1.1', 'Host: example.com', ''])
+ expected = '200 OK'
+ actual = self.send_message(message)
+ self.assertTrue(
+ expected in actual, '"{0}" not in "{1}"'.format(expected, actual)
+ )
+
+ def test_post_request(self):
+ message = CRLF.join(['POST / HTTP/1.1', 'Host: example.com', ''])
+ expected = '405 Method Not Allowed'
+ actual = self.send_message(message)
+ self.assertTrue(
+ expected in actual, '"{0}" not in "{1}"'.format(expected, actual)
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/resources/session03/cgi/cgi-bin/cgi_1.py b/resources/session03/cgi/cgi-bin/cgi_1.py
new file mode 100755
index 00000000..baa5c3e9
--- /dev/null
+++ b/resources/session03/cgi/cgi-bin/cgi_1.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+import cgi
+
+
+cgi.test()
diff --git a/resources/session03/cgi/cgi-bin/cgi_2.py b/resources/session03/cgi/cgi-bin/cgi_2.py
new file mode 100755
index 00000000..100ccded
--- /dev/null
+++ b/resources/session03/cgi/cgi-bin/cgi_2.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+import cgi
+import cgitb
+cgitb.enable()
+import os
+import datetime
+
+
+default = "No Value Present"
+
+
+print("Content-Type: text/html")
+print()
+
+body = """
+