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/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 @@ - -
-- 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 paragraph.
- - - - diff --git a/assignments/week03/athome/assignment.txt b/assignments/week03/athome/assignment.txt deleted file mode 100644 index 42582569..00000000 --- a/assignments/week03/athome/assignment.txt +++ /dev/null @@ -1,25 +0,0 @@ -Using what you've learned this week, create a more complex mashup of some data -that interests you. Map the locations of the breweries near your house. Chart -a multi-axial graph of the popularity of various cities across several -categories. Visualize the most effective legislators in Congress. You have -interests, the Web has tools. Put them together to make something. - -Place the following in the ``assignments/week03/athome`` directory and make a -pull request: - -.. class:: small - -A textual description of your mashup. - What data sources did you scan, what tools did you use, what is the - outcome you wanted to create? - -.. class:: small - -Your source code. - Give me an executable python script that I can run to get output. - -.. class:: small - -Any instructions I need. - If I need instructions beyond 'python myscript.py' to get the right - output, let me know. 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": [ + "{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 = """ +
+
+ +
+