From 9ee90dd6e937ca358cd9deb0d2dd9ec2ac83b1bc Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Thu, 7 Apr 2016 17:10:16 +0200 Subject: Final fixes project 1 --- project1/proj1_s4498062/webhttp/composer.py | 263 ++++++++++++++-------------- project1/proj1_s4498062/webhttp/parser.py | 12 +- project1/proj1_s4498062/webhttp/resource.py | 6 +- project1/proj1_s4498062/webhttp/server.py | 14 +- 4 files changed, 148 insertions(+), 147 deletions(-) (limited to 'project1') diff --git a/project1/proj1_s4498062/webhttp/composer.py b/project1/proj1_s4498062/webhttp/composer.py index bce6208..ddee0ad 100644 --- a/project1/proj1_s4498062/webhttp/composer.py +++ b/project1/proj1_s4498062/webhttp/composer.py @@ -1,132 +1,131 @@ -""" Composer for HTTP responses - -This module contains a composer, which can compose responses to -HTTP requests from a client. -""" - -from multiprocessing import Process, Queue -import re -import time - -from config import config -import encodings -from message import Response -from resource import Resource, FileExistError, FileAccessError -import weblogging as logging - -class ResponseComposer: - """Class that composes a HTTP response to a HTTP request""" - - def __init__(self, timeout): - """Initialize the ResponseComposer - - Args: - timeout (int): connection timeout - """ - self.timeout = timeout - - def compose_response(self, request): - """Compose a response to a request - - Args: - request (webhttp.Request): request from client - - Returns: - webhttp.Response: response to request - - """ - response = Response() - - q = Queue() - - if re.search(r'\/\.\.?(\/|$)', request.uri): - p = Process(target=self.serve_error, - args=(403,), kwargs=dict(queue=q)) - else: - p = Process(target=self.serve, - args=(request.uri,), kwargs=dict(queue=q, request=request)) - - p.start() - p.join(self.timeout) - - if p.is_alive(): - p.terminate() - p.join() - - logging.warning('Had to kill Composer.serve') - - resp = Response() - resp.code = 504 - resp.set_header('Connection', 'close') - resp.body = 'Internal timeout' - resp.set_content_length() - else: - try: - resp = q.get(False) - except: - # After a timeout, we just make the most minimalistic response - # here (that is, no special error files are considered). - resp = Response() - resp.code = 500 - resp.set_header('Connection', 'close') - resp.body = 'This should never happen' - resp.set_content_length() - - return resp - - - def serve(self, uri, code=200, etag=None, request=None, queue=None): - resp = Response() - req = request - - try: - resource = Resource(uri) - if req != None and ( - resource.etag_match(req.get_header('If-None-Match')) or \ - req.get_header('If-Match') != None and \ - not resource.etag_match(req.get_header('If-Match'))): - resp.code = 304 - else: - resp.code = code - resp.body = None - encs = req.encodings() if req != None else [encodings.IDENTITY] - for enc in encs: - try: - resp.body = resource.get_content(enc) - resp.set_header('Content-Encoding', encodings.str(enc)) - break - except UnknownEncodingError: - pass - if resp.body == None: - return self.serve_error(406) - resp.set_header('ETag', resource.generate_etag()) - resp.set_header('Content-Type', resource.get_content_type()) - resp.set_content_length() - except FileExistError: - if code < 400: - self.serve_error(404, queue=queue) - else: - resp.code = code - resp.body = 'Error %d' % code - resp.set_content_length() - except FileAccessError: - self.serve_error(403, queue=queue) - - conn = 'keep-alive' - if req != None and req.get_header('Connection') == 'close': - conn = 'close' - resp.set_header('Connection', conn) - - queue.put(resp) - - def serve_error(self, code, queue=None): - return self.serve(config('error%d' % code, default=None), - code=code, queue=queue) - - def make_date_string(self): - """Make string of date and time - - Returns: - str: formatted string of date and time - """ - return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime()) +""" Composer for HTTP responses + +This module contains a composer, which can compose responses to +HTTP requests from a client. +""" + +from multiprocessing import Process, Queue +import re +import time + +from config import config +import encodings +from message import Response +from resource import Resource, FileExistError, FileAccessError +import weblogging as logging + +class ResponseComposer: + """Class that composes a HTTP response to a HTTP request""" + + def __init__(self, timeout): + """Initialize the ResponseComposer + + Args: + timeout (int): connection timeout + """ + self.timeout = timeout + + def compose_response(self, request): + """Compose a response to a request + + Args: + request (webhttp.Request): request from client + + Returns: + webhttp.Response: response to request + + """ + response = Response() + + q = Queue() + + if re.search(r'\/\.\.?(\/|$)', request.uri): + p = Process(target=self.serve_error, + args=(403,), kwargs=dict(queue=q, request=request)) + else: + p = Process(target=self.serve, + args=(request.uri,), kwargs=dict(queue=q, request=request)) + + p.start() + p.join(self.timeout) + + if p.is_alive(): + p.terminate() + p.join() + + logging.warning('Had to kill Composer.serve') + + resp = Response() + resp.code = 504 + resp.set_header('Connection', 'close') + resp.body = 'Internal timeout' + resp.set_content_length() + else: + try: + resp = q.get(False) + except: + # After a timeout, we just make the most minimalistic response + # here (that is, no special error files are considered). + resp = Response() + resp.code = 500 + resp.set_header('Connection', 'close') + resp.body = 'This should never happen' + resp.set_content_length() + + return resp + + def serve(self, uri, code=200, etag=None, request=None, queue=None): + resp = Response() + req = request + + try: + resource = Resource(uri) + if req != None and ( + resource.etag_match(req.get_header('If-None-Match')) or \ + req.get_header('If-Match') != None and \ + not resource.etag_match(req.get_header('If-Match'))): + resp.code = 304 + else: + resp.code = code + resp.body = None + encs = req.encodings() if req != None else [encodings.IDENTITY] + for enc in encs: + try: + resp.body = resource.get_content(enc) + resp.set_header('Content-Encoding', encodings.str(enc)) + break + except UnknownEncodingError: + pass + if resp.body == None: + return self.serve_error(406, req, queue=queue) + resp.set_header('ETag', resource.generate_etag()) + resp.set_header('Content-Type', resource.get_content_type()) + resp.set_content_length() + except FileExistError: + if code < 400: + self.serve_error(404, req, queue=queue) + else: + resp.code = code + resp.body = 'Error %d' % code + resp.set_content_length() + except FileAccessError: + self.serve_error(403, req, queue=queue) + + conn = 'close' + if req != None and req.get_header('Connection') == 'keep-alive': + conn = 'keep-alive' + resp.set_header('Connection', conn) + + queue.put(resp) + + def serve_error(self, code, request, queue=None): + return self.serve(config('error%d' % code, default=None), + code=code, request=request, queue=queue) + + def make_date_string(self): + """Make string of date and time + + Returns: + str: formatted string of date and time + """ + return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime()) diff --git a/project1/proj1_s4498062/webhttp/parser.py b/project1/proj1_s4498062/webhttp/parser.py index 58e83dc..7dbb3b5 100644 --- a/project1/proj1_s4498062/webhttp/parser.py +++ b/project1/proj1_s4498062/webhttp/parser.py @@ -25,13 +25,10 @@ class RequestParser: 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 + yield http_request def get_requests(self): """Split multiple requests from buffer @@ -40,10 +37,9 @@ class RequestParser: list of str """ requests = self.buff.split('\r\n\r\n') - requests = filter(None, requests) - requests = [r.lstrip() + '\r\n\r\n' for r in requests] - self.buff = '' - return requests + self.buff = requests[-1] + for req in requests[:-1]: + yield req.lstrip() + '\r\n\r\n' class ResponseParser: diff --git a/project1/proj1_s4498062/webhttp/resource.py b/project1/proj1_s4498062/webhttp/resource.py index 9dd948c..9509f82 100644 --- a/project1/proj1_s4498062/webhttp/resource.py +++ b/project1/proj1_s4498062/webhttp/resource.py @@ -44,11 +44,13 @@ class Resource: raise FileExistError self.uri = uri out = urlparse.urlparse(uri) - self.path = os.path.join("content", out.path.lstrip("/")) + self.path = os.path.join(config('root', default='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, config('index')) + self.path = os.path.join(self.path, + config('index', default='index.html')) if not os.path.exists(self.path): raise FileAccessError if not os.path.isfile(self.path): diff --git a/project1/proj1_s4498062/webhttp/server.py b/project1/proj1_s4498062/webhttp/server.py index 62f1bdb..5db8620 100644 --- a/project1/proj1_s4498062/webhttp/server.py +++ b/project1/proj1_s4498062/webhttp/server.py @@ -38,15 +38,16 @@ class ConnectionHandler(threading.Thread): try: while not self.done: - data = self.conn_socket.recv(4096) + data = self.conn_socket.recv(32) if len(data) == 0: break self.handle_data(data) except socket.timeout: - logging.debug('Connection to %s timed out.' % self.addr[0]) + logging.debug('%s connection timed out.' % self.addr[0]) finally: + self.conn_socket.shutdown(socket.SHUT_RDWR) self.conn_socket.close() - logging.debug('Connection to %s closed.' % self.addr[0]) + logging.debug('%s connection closed.' % self.addr[0]) def handle_data(self, data): for req in self.rp.parse_requests(data): @@ -57,6 +58,7 @@ class ConnectionHandler(threading.Thread): if resp.get_header('Connection') == 'close': self.done = True + return def send(self, data): sent = self.conn_socket.send(str(data)) @@ -99,10 +101,12 @@ class Server: def run(self): """Run the HTTP Server and start listening""" self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.bind((config('hostname'), config('port', type=int))) - self.socket.listen(config('max_connections', type=int)) + self.socket.bind((config('hostname', default='localhost'), + config('port', type=int, default=8001))) + self.socket.listen(config('max_connections', type=int, default=1000)) while not self.done: (csocket, addr) = self.socket.accept() + logging.debug('%s connection accepted.' % addr[0]) ch = ConnectionHandler(csocket, addr, config('timeout', type=int)) ch.start() -- cgit v1.2.3