summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCamil Staps2016-03-04 18:33:12 +0100
committerCamil Staps2016-03-04 18:33:12 +0100
commit198db6f0003935e5f900719fa0b848924b8921bb (patch)
tree783956c7f4ced78e864a0ea7503bf5a63bef7370
parentAdded framework project 1 (diff)
Project one until step 8 in the readme
-rw-r--r--.gitignore76
-rw-r--r--project1/proj1_s4498062/content/test/no-index/.gitkeep0
-rw-r--r--project1/proj1_s4498062/webhttp/__init__.pycbin499 -> 0 bytes
-rw-r--r--project1/proj1_s4498062/webhttp/composer.py24
-rw-r--r--project1/proj1_s4498062/webhttp/message.py75
-rw-r--r--project1/proj1_s4498062/webhttp/message.pycbin3726 -> 0 bytes
-rw-r--r--project1/proj1_s4498062/webhttp/parser.py20
-rw-r--r--project1/proj1_s4498062/webhttp/parser.pycbin2717 -> 0 bytes
-rw-r--r--project1/proj1_s4498062/webhttp/regexes.py151
-rw-r--r--project1/proj1_s4498062/webhttp/resource.py6
-rw-r--r--project1/proj1_s4498062/webhttp/server.py49
-rw-r--r--project1/proj1_s4498062/webhttp/server.pycbin2794 -> 0 bytes
-rw-r--r--project1/proj1_s4498062/webhttp/weblogging.py9
-rw-r--r--project1/proj1_s4498062/webserver.py33
-rw-r--r--project1/proj1_s4498062/webtests.py39
15 files changed, 427 insertions, 55 deletions
diff --git a/.gitignore b/.gitignore
index 3976e5a..a639e2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+### TeX
+
*.aux
*.glo
*.idx
@@ -27,3 +29,77 @@
*.synctex.gz
_minted-*/
+### Python
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask instance folder
+instance/
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# dotenv
+.env
diff --git a/project1/proj1_s4498062/content/test/no-index/.gitkeep b/project1/proj1_s4498062/content/test/no-index/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/project1/proj1_s4498062/content/test/no-index/.gitkeep
diff --git a/project1/proj1_s4498062/webhttp/__init__.pyc b/project1/proj1_s4498062/webhttp/__init__.pyc
deleted file mode 100644
index d86ce3e..0000000
--- a/project1/proj1_s4498062/webhttp/__init__.pyc
+++ /dev/null
Binary files differ
diff --git a/project1/proj1_s4498062/webhttp/composer.py b/project1/proj1_s4498062/webhttp/composer.py
index dfac7a1..5123d31 100644
--- a/project1/proj1_s4498062/webhttp/composer.py
+++ b/project1/proj1_s4498062/webhttp/composer.py
@@ -7,8 +7,8 @@ HTTP requests from a client.
import time
import webhttp.message
-import webhttp.resource
-
+from webhttp.resource import Resource, FileExistError, FileAccessError
+import webhttp.weblogging as logging
class ResponseComposer:
"""Class that composes a HTTP response to a HTTP request"""
@@ -34,10 +34,22 @@ class ResponseComposer:
response = webhttp.message.Response()
# Stub code
- response.code = 200
- response.set_header("Content-Length", 4)
- response.set_header("Connection", "close")
- response.body = "Test"
+ try:
+ resource = Resource(request.uri)
+ response.code = 200
+ response.set_header('Content-Length', resource.get_content_length())
+ response.set_header('Connection', 'close')
+ response.body = resource.get_content()
+ except FileExistError:
+ response.code = 404
+ response.set_header("Content-Length", 9)
+ response.set_header("Connection", "close")
+ response.body = "Not found"
+ except FileAccessError:
+ response.code = 403
+ response.set_header("Content-Length", 13)
+ response.set_header("Connection", "close")
+ response.body = "Access denied"
return response
diff --git a/project1/proj1_s4498062/webhttp/message.py b/project1/proj1_s4498062/webhttp/message.py
index fb47f88..2dc4240 100644
--- a/project1/proj1_s4498062/webhttp/message.py
+++ b/project1/proj1_s4498062/webhttp/message.py
@@ -3,22 +3,29 @@
This modules contains classes for representing HTTP responses and requests.
"""
+import re
+
+import regexes as r
+import weblogging as logging
+
reasondict = {
# Dictionary for code reasons
# Format: code : "Reason"
- 500 : "Internal Server Error"
+ 200 : 'OK',
+ 403 : 'Forbidden',
+ 404 : 'Not Found',
+ 500 : 'Internal Server Error'
}
class Message(object):
- """Class that stores a HTTP Message"""
+ """Class that stores an HTTP Message"""
def __init__(self):
"""Initialize the Message"""
self.version = "HTTP/1.1"
- self.startline = ""
self.body = ""
- self.headerdict = dict()
+ self.headers = dict()
def set_header(self, name, value):
"""Add a header and its value
@@ -27,7 +34,7 @@ class Message(object):
name (str): name of header
value (str): value of header
"""
- self.headerdict[name] = value
+ self.headers[name] = str(value)
def get_header(self, name):
"""Get the value of a header
@@ -38,29 +45,56 @@ class Message(object):
Returns:
str: value of header, empty if header does not exist
"""
- if name in self.headerdict:
- return self.headerdict[name]
+ if name in self.headers:
+ return self.headers[name]
else:
return ""
-
+
+ def parse_headers(self, msg):
+ for name, value in re.findall(r.MessageHeader, msg):
+ self.set_header(name, value.strip())
+ logging.debug("%s: %s" % (name, value.strip()))
+
+ def startline(self):
+ return ''
+
def __str__(self):
"""Convert the Message to a string
Returns:
str: representation the can be sent over socket
"""
- message = ""
- return message
+ msg = ''
+ msg += '%s\r\n' % self.startline()
+ msg += '\r\n'.join([k + ": " + v for k, v in self.headers.iteritems()])
+ msg += '\r\n\r\n'
+ msg += self.body
+ return msg
+
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
class Request(Message):
- """Class that stores a HTTP request"""
+ """Class that stores an HTTP request"""
def __init__(self):
"""Initialize the Request"""
super(Request, self).__init__()
self.method = ""
self.uri = ""
+
+ def parse(self, msg):
+ [reqline, rest] = msg.split('\r\n', 1)
+ reqline = re.match(r.RequestLine, reqline + '\r\n')
+ self.method, self.uri, self.version = reqline.groups()
+
+ [headers, body] = rest.split('\r\n\r\n', 1)
+ self.parse_headers(headers)
+ self.body = body
+
+ def startline(self):
+ return "%s %s %s" % (self.method, self.uri, self.version)
def __str__(self):
"""Convert the Request to a string
@@ -68,17 +102,28 @@ class Request(Message):
Returns:
str: representation the can be sent over socket
"""
- self.startline = ""
return super(Request, self).__str__()
class Response(Message):
- """Class that stores a HTTP Response"""
+ """Class that stores an HTTP Response"""
def __init__(self):
"""Initialize the Response"""
super(Response, self).__init__()
- self.code = 500
+
+ def parse(self, msg):
+ [respline, rest] = msg.split('\r\n', 1)
+ respline = re.match(r.StatusLine, respline + '\r\n')
+ self.version = respline.group(1)
+ self.code = int(respline.group(2))
+
+ [headers, body] = rest.split('\r\n\r\n', 1)
+ self.parse_headers(headers)
+ self.body = body
+
+ def startline(self):
+ return "%s %d %s" % (self.version, self.code, reasondict[self.code])
def __str__(self):
"""Convert the Response to a string
@@ -86,5 +131,5 @@ class Response(Message):
Returns:
str: representation the can be sent over socket
"""
- self.startline = ""
return super(Response, self).__str__()
+
diff --git a/project1/proj1_s4498062/webhttp/message.pyc b/project1/proj1_s4498062/webhttp/message.pyc
deleted file mode 100644
index 432142c..0000000
--- a/project1/proj1_s4498062/webhttp/message.pyc
+++ /dev/null
Binary files differ
diff --git a/project1/proj1_s4498062/webhttp/parser.py b/project1/proj1_s4498062/webhttp/parser.py
index f3809c3..58e83dc 100644
--- a/project1/proj1_s4498062/webhttp/parser.py
+++ b/project1/proj1_s4498062/webhttp/parser.py
@@ -11,7 +11,7 @@ class RequestParser:
def __init__(self):
"""Initialize the RequestParser"""
- pass
+ self.buff = ''
def parse_requests(self, buff):
"""Parse requests in a buffer
@@ -22,28 +22,27 @@ class RequestParser:
Returns:
list of webhttp.Request
"""
- requests = split_requests(buff)
+ self.buff += buff
+ requests = self.get_requests()
http_requests = []
for request in requests:
http_request = webhttp.message.Request()
+ http_request.parse(request)
http_requests.append(http_request)
return http_requests
- def split_requests(self, buff):
- """Split multiple requests
-
- Arguments:
- buff (str): the buffer contents received from socket
+ def get_requests(self):
+ """Split multiple requests from buffer
Returns:
list of str
"""
- requests = buff.split('\r\n\r\n')
+ requests = self.buff.split('\r\n\r\n')
requests = filter(None, requests)
- requests = [r + '\r\n\r\n' for r in requests]
- requests = [r.lstrip() for r in requests]
+ requests = [r.lstrip() + '\r\n\r\n' for r in requests]
+ self.buff = ''
return requests
@@ -63,4 +62,5 @@ class ResponseParser:
webhttp.Response
"""
response = webhttp.message.Response()
+ response.parse(buff)
return response
diff --git a/project1/proj1_s4498062/webhttp/parser.pyc b/project1/proj1_s4498062/webhttp/parser.pyc
deleted file mode 100644
index c5d9ced..0000000
--- a/project1/proj1_s4498062/webhttp/parser.pyc
+++ /dev/null
Binary files differ
diff --git a/project1/proj1_s4498062/webhttp/regexes.py b/project1/proj1_s4498062/webhttp/regexes.py
new file mode 100644
index 0000000..755bb9f
--- /dev/null
+++ b/project1/proj1_s4498062/webhttp/regexes.py
@@ -0,0 +1,151 @@
+from os.path import commonprefix
+from itertools import groupby
+import re
+
+def grpm(regex):
+ return grp(regex, matching=True)
+
+def grp(regex, matching=False):
+ return r'(' + (r'' if matching else r'?:') + regex + r')'
+
+def opt(regex):
+ return grp(grp(regex) + r'?')
+
+def regex_opt_r(regexes):
+ return grp(r'|'.join(regexes))
+
+# The below functions were taken from the pygments package
+# (http://pygmenst.org), in particular pygments.regexopt and pygments.lexer
+# Some small modifications have been made.
+def regex_opt_inner(strings, open_paren):
+ """Return a regex that matches any string in the sorted list of strings."""
+ close_paren = open_paren and ')' or ''
+ # print strings, repr(open_paren)
+ if not strings:
+ # print '-> nothing left'
+ return ''
+ first = strings[0]
+ if len(strings) == 1:
+ # print '-> only 1 string'
+ return open_paren + re.escape(first) + close_paren
+ if not first:
+ # print '-> first string empty'
+ return open_paren + regex_opt_inner(strings[1:], '(?:') \
+ + '?' + close_paren
+ if len(first) == 1:
+ # multiple one-char strings? make a charset
+ oneletter = []
+ rest = []
+ for s in strings:
+ if len(s) == 1:
+ oneletter.append(s)
+ else:
+ rest.append(s)
+ if len(oneletter) > 1: # do we have more than one oneletter string?
+ if rest:
+ # print '-> 1-character + rest'
+ return open_paren + regex_opt_inner(rest, '') + '|' \
+ + make_charset(oneletter) + close_paren
+ # print '-> only 1-character'
+ return make_charset(oneletter)
+ prefix = commonprefix(strings)
+ if prefix:
+ plen = len(prefix)
+ # we have a prefix for all strings
+ # print '-> prefix:', prefix
+ return open_paren + re.escape(prefix) \
+ + regex_opt_inner([s[plen:] for s in strings], '(?:') \
+ + close_paren
+ # is there a suffix?
+ strings_rev = [s[::-1] for s in strings]
+ suffix = commonprefix(strings_rev)
+ if suffix:
+ slen = len(suffix)
+ # print '-> suffix:', suffix[::-1]
+ return open_paren \
+ + regex_opt_inner(sorted(s[:-slen] for s in strings), '(?:') \
+ + re.escape(suffix[::-1]) + close_paren
+ # recurse on common 1-string prefixes
+ # print '-> last resort'
+ return open_paren + \
+ '|'.join(regex_opt_inner(list(group[1]), '')
+ for group in groupby(strings, lambda s: s[0] == first[0])) \
+ + close_paren
+
+def regex_opt(strings, prefix='', suffix=''):
+ """Return a compiled regex that matches any string in the given list.
+
+ The strings to match must be literal strings, not regexes. They will be
+ regex-escaped.
+
+ *prefix* and *suffix* are pre- and appended to the final regex.
+ """
+ strings = sorted(strings)
+ return prefix + regex_opt_inner(strings, '(?:') + suffix
+
+## From here it is own work again
+# RFC 2396
+IPv4address = grp(r'(?:\d{1,3}\.){3}\d{1,3}')
+reserved = grp(r'[;\/?:@&=+$,]')
+alphanum = grp(r'[\da-zA-Z]')
+mark = grp(r'[\-_\.!~\*\'\(\)]')
+hex = grp(r'[\da-fA-F]')
+unreserved = regex_opt_r([alphanum, mark])
+escaped = grp(r'%' + hex + hex)
+pchar = regex_opt_r([unreserved, escaped, r'[:@&=+$,]'])
+param = grp(pchar + r'*')
+segment = grp(pchar + r'*' + grp(r';' + param) + r'*')
+path_segments = grp(segment + grp(r'\/' + segment) + r'*')
+abs_path = grp(r'\/' + path_segments)
+scheme = grp(r'[a-zA-Z](?:[a-zA-Z\d+\-\.]*)')
+userinfo = grp(regex_opt_r([unreserved, escaped, r'[;:&=+$,]']) + r'*')
+domainlabel = grp(r'[a-zA-Z\d]|(?:[a-zA-Z\d](?:[a-zA-Z\d\-])*[a-zA-Z\d])')
+toplabel = grp(r'[a-zA-Z]|(?:[a-zA-Z](?:[a-zA-Z\d\-])*[a-zA-Z\d])')
+hostname = grp(opt(domainlabel + r'\.') + r'*' + toplabel + opt(r'\.'))
+host = regex_opt_r([hostname, IPv4address])
+port = r'\d+'
+hostport = grp(host + opt(r':' + port))
+server = opt(opt(userinfo + r'@') + hostport)
+reg_name = grp(regex_opt_r([unreserved, escaped, r'[;:&=+]']) + r'*')
+authority = regex_opt_r([server, reg_name])
+net_path = grp(r'\/\/' + authority + opt(abs_path))
+hier_part = regex_opt_r([net_path, abs_path])
+uric = regex_opt_r([reserved, unreserved, escaped])
+uric_no_slash = regex_opt_r([unreserved, escaped, r'[;?:@&=+$,]'])
+opaque_part = grp(uric_no_slash + grp(uric) + r'*')
+absoluteURI = grp(scheme + r':' + regex_opt_r([hier_part, opaque_part]))
+
+# RFC 2616
+CTL = r'[\x00-\x1f\x7f]'
+CR = r'\r'
+LF = r'\n'
+CRLF = CR + LF
+HT = r'\t'
+SP = r' '
+LWS = grp(opt(CRLF) + regex_opt_r([SP, HT]))
+TEXT = grp(r'[^\x00-\x1f\x7f]|' + LWS)
+TEXT_NO_LWS = r'[^\x00-\x1f\x7f \t]'
+
+separator = r'[\(\)<>@,;:\\"\/\[\]?=\{\} \t]'
+token = r'[^\x00-\x1f\(\)<>@,;:\\"\/\[\]?=\{\} \t]+'
+qdtext = r'^\x00-\x08\x0b-\x0c\x0e-\x1f\x7f"]'
+quotedPair = r'\\[\x00-\x7f]'
+quotedString = grp(r'"' + regex_opt_r([qdtext, quotedPair]) + r'*"')
+
+HTTPVersion = r'HTTP\/\d\.\d'
+Method = regex_opt(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE',
+ 'CONNECT'])
+RequestURI = regex_opt_r([r'\*', absoluteURI, abs_path, authority])
+RequestLine = grp(grpm(Method) + SP + grpm(RequestURI) + SP +
+ grpm(HTTPVersion) + CRLF)
+
+StatusCode = r'\d{3}'
+ReasonPhrase = r'[^\r\n]*'
+StatusLine = grp(grpm(HTTPVersion) + SP + grpm(StatusCode) + SP +
+ grpm(ReasonPhrase) + CRLF)
+
+FieldName = token
+FieldContent = regex_opt_r([TEXT_NO_LWS + TEXT + r'*(?!' + LWS + r')'])
+FieldValue = grp(regex_opt_r([grp(FieldContent), LWS]) + r'*')
+MessageHeader = grp(grpm(FieldName) + r':' + grpm(FieldValue))
+
diff --git a/project1/proj1_s4498062/webhttp/resource.py b/project1/proj1_s4498062/webhttp/resource.py
index dc20067..cf2b725 100644
--- a/project1/proj1_s4498062/webhttp/resource.py
+++ b/project1/proj1_s4498062/webhttp/resource.py
@@ -4,7 +4,7 @@ This module contains a handler class for resources.
"""
import os
-import mimetype
+import mimetypes
import urlparse
@@ -34,8 +34,12 @@ class Resource:
self.uri = uri
out = urlparse.urlparse(uri)
self.path = os.path.join("content", out.path.lstrip("/"))
+ if not os.path.exists(self.path):
+ raise FileExistError
if os.path.isdir(self.path):
self.path = os.path.join(self.path, "index.html")
+ if not os.path.exists(self.path):
+ raise FileAccessError
if not os.path.isfile(self.path):
raise FileExistError
if not os.access(self.path, os.R_OK):
diff --git a/project1/proj1_s4498062/webhttp/server.py b/project1/proj1_s4498062/webhttp/server.py
index b540ac4..b385636 100644
--- a/project1/proj1_s4498062/webhttp/server.py
+++ b/project1/proj1_s4498062/webhttp/server.py
@@ -3,9 +3,13 @@
This module contains a HTTP server
"""
+from configparser import SafeConfigParser
import threading
import socket
+from composer import ResponseComposer
+from parser import RequestParser
+import weblogging as logging
class ConnectionHandler(threading.Thread):
"""Connection Handler for HTTP Server"""
@@ -18,7 +22,7 @@ class ConnectionHandler(threading.Thread):
addr (str): ip address of client
timeout (int): seconds until timeout
"""
- super(HTTPConnectionHandler, self).__init__()
+ super(ConnectionHandler, self).__init__()
self.daemon = True
self.conn_socket = conn_socket
self.addr = addr
@@ -26,17 +30,35 @@ class ConnectionHandler(threading.Thread):
def handle_connection(self):
"""Handle a new connection"""
- pass
+ rp = RequestParser()
+ rc = ResponseComposer(timeout=self.timeout)
+
+ while True:
+ data = self.conn_socket.recv(4096)
+ if len(data) == 0:
+ break
+ for req in rp.parse_requests(data):
+ logging.info("<-- %s" % req.startline())
+ resp = rc.compose_response(req)
+ logging.info("--> %s" % resp.startline())
+ sent = self.conn_socket.send(str(resp))
+ if sent == 0:
+ raise RuntimeError('Socket broken')
+
+ self.conn_socket.close()
def run(self):
"""Run the thread of the connection handler"""
- self.handle_connection()
+ try:
+ self.handle_connection()
+ except socket.error, e:
+ print('ERR ' + str(e))
class Server:
"""HTTP Server"""
- def __init__(self, hostname, server_port, timeout):
+ def __init__(self, config, **kwargs):
"""Initialize the HTTP server
Args:
@@ -44,15 +66,26 @@ class Server:
server_port (int): port that the server is listening on
timeout (int): seconds until timeout
"""
- self.hostname = hostname
- self.server_port = server_port
- self.timeout = timeout
+ self.read_config(config, **kwargs)
self.done = False
+
+ def read_config(self, config, **kwargs):
+ self.cp = SafeConfigParser()
+ self.cp.read(config)
+ if not self.cp.has_section('webhttp'):
+ self.cp.add_section('webhttp')
+ for (name, val) in self.cp.items('webhttp') + kwargs.items():
+ setattr(self, name, val)
def run(self):
"""Run the HTTP Server and start listening"""
+ serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ serversocket.bind((self.hostname, self.port))
+ serversocket.listen(5)
while not self.done:
- pass
+ (clientsocket, addr) = serversocket.accept()
+ ch = ConnectionHandler(clientsocket, addr, self.timeout)
+ ch.run()
def shutdown(self):
"""Safely shut down the HTTP server"""
diff --git a/project1/proj1_s4498062/webhttp/server.pyc b/project1/proj1_s4498062/webhttp/server.pyc
deleted file mode 100644
index 7981ff7..0000000
--- a/project1/proj1_s4498062/webhttp/server.pyc
+++ /dev/null
Binary files differ
diff --git a/project1/proj1_s4498062/webhttp/weblogging.py b/project1/proj1_s4498062/webhttp/weblogging.py
new file mode 100644
index 0000000..d1792a3
--- /dev/null
+++ b/project1/proj1_s4498062/webhttp/weblogging.py
@@ -0,0 +1,9 @@
+import logging
+
+name = 'webhttp'
+
+def debug(msg, *args, **kwargs):
+ logging.getLogger(name).debug(str(msg), *args, **kwargs)
+
+def info(msg, *args, **kwargs):
+ logging.getLogger(name).info(str(msg), *args, **kwargs)
diff --git a/project1/proj1_s4498062/webserver.py b/project1/proj1_s4498062/webserver.py
index e518810..a361d5b 100644
--- a/project1/proj1_s4498062/webserver.py
+++ b/project1/proj1_s4498062/webserver.py
@@ -1,18 +1,43 @@
import argparse
+import logging
+import os.path
+import sys
+
import webhttp.server
+import webhttp.weblogging
# Create and start the HTTP Server
# Use `python webserver.py --help` to display command line options
if __name__ == '__main__':
# Parse command line arguments
parser = argparse.ArgumentParser(description="HTTP Server")
- parser.add_argument("-a", "--address", type=str, default="localhost")
- parser.add_argument("-p", "--port", type=int, default=8001)
- parser.add_argument("-t", "--timeout", type=int, default=15)
+ parser.add_argument('-c', '--config', type=str, default='~/.webpy.ini',
+ help='configuration file')
+ parser.add_argument('-a', '--address', type=str, default='localhost',
+ help='address to listen on (default localhost)')
+ parser.add_argument('-p', '--port', type=int, default=80,
+ help='port to listen on (default 80)')
+ parser.add_argument('-t', '--timeout', type=int, default=15,
+ help='timeout for incoming connections (default 15)')
+ parser.add_argument('-l', '--log', type=str, default='info',
+ help='log level (debug, info, warning, error, critical)')
+ parser.add_argument('-lf', '--log-file', type=str, default=None,
+ help='log file (default stdout)')
args = parser.parse_args()
+ # Logging
+ fmt = '[%(asctime)s] %(process)d %(levelname)s %(message)s'
+ logging.basicConfig(format=fmt, level=getattr(logging, args.log.upper()))
+ if args.log_file != None:
+ logger = logging.getLogger(webhttp.weblogging.name)
+ handler = logging.FileHandler(args.log_file)
+ handler.setLevel(getattr(logging, args.log.upper()))
+ logger.addHandler(handler)
+
# Start server
- server = webhttp.server.Server(args.address, args.port, args.timeout)
+ config = os.path.expanduser(os.path.expandvars(args.config))
+ server = webhttp.server.Server(config=config,
+ address=args.address, port=args.port, timeout=args.timeout)
try:
server.run()
except KeyboardInterrupt:
diff --git a/project1/proj1_s4498062/webtests.py b/project1/proj1_s4498062/webtests.py
index 9b0cdbf..a869de1 100644
--- a/project1/proj1_s4498062/webtests.py
+++ b/project1/proj1_s4498062/webtests.py
@@ -17,31 +17,43 @@ class TestGetRequests(unittest.TestCase):
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.connect(("localhost", portnr))
self.parser = webhttp.parser.ResponseParser()
+ self.default_headers = [
+ ('Host', 'localhost:%d' % portnr),
+ ('Connection', 'close')
+ ]
def tearDown(self):
"""Clean up after testing"""
self.client_socket.shutdown(socket.SHUT_RDWR)
self.client_socket.close()
- def test_existing_file(self):
- """GET for a single resource that exists"""
- # Send the request
+ def request(self, method, uri, headers):
request = webhttp.message.Request()
- request.method = "GET"
- request.uri = "/test/index.html"
- request.set_header("Host", "localhost:{}".format(portnr))
- request.set_header("Connection", "close")
+ request.method = method
+ request.uri = uri
+ for name, value in headers:
+ request.set_header(name, value)
+
self.client_socket.send(str(request))
- # Test response
message = self.client_socket.recv(1024)
response = self.parser.parse_response(message)
+
+ return response
+
+ def test_existing_file(self):
+ """GET for a single resource that exists"""
+ response = self.request(
+ 'GET', '/test/index.html', self.default_headers)
self.assertEqual(response.code, 200)
self.assertTrue(response.body)
def test_nonexistant_file(self):
"""GET for a single resource that does not exist"""
- pass
+ response = self.request(
+ 'GET', '/test/nonexistant.html', self.default_headers)
+ self.assertEqual(response.code, 404)
+ self.assertTrue(response.body)
def test_caching(self):
"""GET for an existing single resource followed by a GET for that same
@@ -51,11 +63,14 @@ class TestGetRequests(unittest.TestCase):
def test_extisting_index_file(self):
"""GET for a directory with an existing index.html file"""
- pass
+ self.assertEqual(self.request('GET', '/test', self.default_headers),
+ self.request('GET', '/test/index.html', self.default_headers))
def test_nonexistant_index_file(self):
"""GET for a directory with a non-existant index.html file"""
- pass
+ response = self.request('GET', '/test/no-index', self.default_headers)
+ self.assertEqual(response.code, 403)
+ self.assertTrue(response.body)
def test_persistent_close(self):
"""Multiple GETs over the same (persistent) connection with the last
@@ -86,6 +101,8 @@ if __name__ == "__main__":
# Arguments for the unittest framework
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
+
+ portnr = args.port
# Only pass the unittest arguments to unittest
sys.argv[1:] = args.unittest_args