diff options
author | Camil Staps | 2016-03-04 18:33:12 +0100 |
---|---|---|
committer | Camil Staps | 2016-03-04 18:33:12 +0100 |
commit | 198db6f0003935e5f900719fa0b848924b8921bb (patch) | |
tree | 783956c7f4ced78e864a0ea7503bf5a63bef7370 | |
parent | Added framework project 1 (diff) |
Project one until step 8 in the readme
-rw-r--r-- | .gitignore | 76 | ||||
-rw-r--r-- | project1/proj1_s4498062/content/test/no-index/.gitkeep | 0 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/__init__.pyc | bin | 499 -> 0 bytes | |||
-rw-r--r-- | project1/proj1_s4498062/webhttp/composer.py | 24 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/message.py | 75 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/message.pyc | bin | 3726 -> 0 bytes | |||
-rw-r--r-- | project1/proj1_s4498062/webhttp/parser.py | 20 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/parser.pyc | bin | 2717 -> 0 bytes | |||
-rw-r--r-- | project1/proj1_s4498062/webhttp/regexes.py | 151 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/resource.py | 6 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/server.py | 49 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/server.pyc | bin | 2794 -> 0 bytes | |||
-rw-r--r-- | project1/proj1_s4498062/webhttp/weblogging.py | 9 | ||||
-rw-r--r-- | project1/proj1_s4498062/webserver.py | 33 | ||||
-rw-r--r-- | project1/proj1_s4498062/webtests.py | 39 |
15 files changed, 427 insertions, 55 deletions
@@ -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 Binary files differdeleted file mode 100644 index d86ce3e..0000000 --- a/project1/proj1_s4498062/webhttp/__init__.pyc +++ /dev/null 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 Binary files differdeleted file mode 100644 index 432142c..0000000 --- a/project1/proj1_s4498062/webhttp/message.pyc +++ /dev/null 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 Binary files differdeleted file mode 100644 index c5d9ced..0000000 --- a/project1/proj1_s4498062/webhttp/parser.pyc +++ /dev/null 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 Binary files differdeleted file mode 100644 index 7981ff7..0000000 --- a/project1/proj1_s4498062/webhttp/server.pyc +++ /dev/null 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
|