summaryrefslogtreecommitdiff
path: root/project1/proj1_s4498062/webhttp
diff options
context:
space:
mode:
Diffstat (limited to 'project1/proj1_s4498062/webhttp')
-rw-r--r--project1/proj1_s4498062/webhttp/composer.py54
-rw-r--r--project1/proj1_s4498062/webhttp/config.py12
-rw-r--r--project1/proj1_s4498062/webhttp/message.py45
-rw-r--r--project1/proj1_s4498062/webhttp/regexes.py2
-rw-r--r--project1/proj1_s4498062/webhttp/resource.py20
-rw-r--r--project1/proj1_s4498062/webhttp/server.py63
6 files changed, 146 insertions, 50 deletions
diff --git a/project1/proj1_s4498062/webhttp/composer.py b/project1/proj1_s4498062/webhttp/composer.py
index 5123d31..f9bc0e0 100644
--- a/project1/proj1_s4498062/webhttp/composer.py
+++ b/project1/proj1_s4498062/webhttp/composer.py
@@ -4,11 +4,13 @@ This module contains a composer, which can compose responses to
HTTP requests from a client.
"""
+import re
import time
-import webhttp.message
-from webhttp.resource import Resource, FileExistError, FileAccessError
-import webhttp.weblogging as logging
+from config import config
+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"""
@@ -31,25 +33,43 @@ class ResponseComposer:
webhttp.Response: response to request
"""
- response = webhttp.message.Response()
+ response = Response()
- # Stub code
+ if re.search(r'\/\.\.?(\/|$)', request.uri):
+ response.code = 403
+ response.set_header('Connection', 'close')
+ response.body = 'Don\'t even think about it.'
+ response.set_content_length()
+ return response
+
+ return self.serve(request.uri, request=request)
+
+ def serve(self, uri, code=200, etag=None, request=None, error_page=False):
+ response = Response()
try:
- resource = Resource(request.uri)
- response.code = 200
- response.set_header('Content-Length', resource.get_content_length())
+ resource = Resource(uri)
+ req = request
+ if req != None and (
+ resource.etag_match(req.get_header('If-None-Match')) or \
+ not resource.etag_match(req.get_header('If-Match') or '*')):
+ response.code = 304
+ else:
+ response.code = code
+ response.body = resource.get_content()
+ response.set_header('ETag', resource.generate_etag())
response.set_header('Connection', 'close')
- response.body = resource.get_content()
+ response.set_header('Content-Type', resource.get_content_type())
+ response.set_content_length()
except FileExistError:
- response.code = 404
- response.set_header("Content-Length", 9)
- response.set_header("Connection", "close")
- response.body = "Not found"
+ if not error_page:
+ return self.serve(config('error404'), code=404, error_page=True)
+ else:
+ response.code = code
+ response.set_header('Connection', 'close')
+ response.body = 'Error %d' % code
+ response.set_content_length()
except FileAccessError:
- response.code = 403
- response.set_header("Content-Length", 13)
- response.set_header("Connection", "close")
- response.body = "Access denied"
+ return self.serve(config('error403'), code=403, error_page=True)
return response
diff --git a/project1/proj1_s4498062/webhttp/config.py b/project1/proj1_s4498062/webhttp/config.py
new file mode 100644
index 0000000..35d73cb
--- /dev/null
+++ b/project1/proj1_s4498062/webhttp/config.py
@@ -0,0 +1,12 @@
+from configparser import SafeConfigParser
+
+__all__ = ['config']
+
+scp = SafeConfigParser()
+
+def config(option=None, section='webhttp', type=lambda x:x):
+ if option == None:
+ return scp
+ else:
+ return type(scp.get(section, option))
+
diff --git a/project1/proj1_s4498062/webhttp/message.py b/project1/proj1_s4498062/webhttp/message.py
index 2dc4240..059c765 100644
--- a/project1/proj1_s4498062/webhttp/message.py
+++ b/project1/proj1_s4498062/webhttp/message.py
@@ -9,12 +9,46 @@ import regexes as r
import weblogging as logging
reasondict = {
- # Dictionary for code reasons
- # Format: code : "Reason"
+ 100 : 'Continue',
+ 101 : 'Switching Protocols',
200 : 'OK',
+ 201 : 'Created',
+ 202 : 'Accepted',
+ 203 : 'Non-Authoritative Information',
+ 204 : 'No Content',
+ 205 : 'Reset Content',
+ 206 : 'Partial Content',
+ 300 : 'Multiple Choices',
+ 301 : 'Moved Permanently',
+ 302 : 'Found',
+ 303 : 'See Other',
+ 304 : 'Not Modified',
+ 305 : 'Use Proxy',
+ 307 : 'Temporary Redirect',
+ 400 : 'Bad Request',
+ 401 : 'Unauthorized',
+ 402 : 'Payment Required',
403 : 'Forbidden',
404 : 'Not Found',
- 500 : 'Internal Server Error'
+ 405 : 'Method Not Allowed',
+ 406 : 'Not Acceptable',
+ 407 : 'Proxy Authentication Required',
+ 408 : 'Request Time-out',
+ 409 : 'Conflict',
+ 410 : 'Gone',
+ 411 : 'Length Required',
+ 412 : 'Precondition Failed',
+ 413 : 'Request Entity Too Large',
+ 414 : 'Request-URI Too Large',
+ 415 : 'Unsupported Media Type',
+ 416 : 'Requested range not satisfiable',
+ 417 : 'Expectation Failed',
+ 500 : 'Internal Server Error',
+ 501 : 'Not Implemented',
+ 502 : 'Bad Gateway',
+ 503 : 'Service Unavailable',
+ 504 : 'Gateway Time-out',
+ 505 : 'HTTP Version not supported',
}
@@ -48,7 +82,7 @@ class Message(object):
if name in self.headers:
return self.headers[name]
else:
- return ""
+ return None
def parse_headers(self, msg):
for name, value in re.findall(r.MessageHeader, msg):
@@ -124,6 +158,9 @@ class Response(Message):
def startline(self):
return "%s %d %s" % (self.version, self.code, reasondict[self.code])
+
+ def set_content_length(self):
+ self.set_header('Content-Length', len(self.body))
def __str__(self):
"""Convert the Response to a string
diff --git a/project1/proj1_s4498062/webhttp/regexes.py b/project1/proj1_s4498062/webhttp/regexes.py
index 755bb9f..b1cacc9 100644
--- a/project1/proj1_s4498062/webhttp/regexes.py
+++ b/project1/proj1_s4498062/webhttp/regexes.py
@@ -149,3 +149,5 @@ 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))
+ETagSplit = grp(r',' + LWS + r'*')
+
diff --git a/project1/proj1_s4498062/webhttp/resource.py b/project1/proj1_s4498062/webhttp/resource.py
index cf2b725..8fefc9c 100644
--- a/project1/proj1_s4498062/webhttp/resource.py
+++ b/project1/proj1_s4498062/webhttp/resource.py
@@ -3,10 +3,15 @@
This module contains a handler class for resources.
"""
-import os
+import binascii
+import hashlib
import mimetypes
+import os
+import re
import urlparse
+from config import config
+import regexes as r
class FileExistError(Exception):
"""Exception which is raised when file does not exist"""
@@ -37,7 +42,7 @@ class Resource:
if not os.path.exists(self.path):
raise FileExistError
if os.path.isdir(self.path):
- self.path = os.path.join(self.path, "index.html")
+ self.path = os.path.join(self.path, config('index'))
if not os.path.exists(self.path):
raise FileAccessError
if not os.path.isfile(self.path):
@@ -52,9 +57,18 @@ class Resource:
str: ETag for the resource
"""
stat = os.stat(self.path)
- etag = ""
+ m = hashlib.md5()
+ m.update(str(stat))
+ etag = binascii.hexlify(m.digest())
return etag
+ def etag_match(self, etag):
+ if etag == None:
+ return False
+ my_etag = self.generate_etag()
+ return etag == '*' or \
+ any([tag == my_etag for tag in re.split(r.ETagSplit, etag)])
+
def get_content(self):
"""Get the contents of the resource
diff --git a/project1/proj1_s4498062/webhttp/server.py b/project1/proj1_s4498062/webhttp/server.py
index b385636..7eeec27 100644
--- a/project1/proj1_s4498062/webhttp/server.py
+++ b/project1/proj1_s4498062/webhttp/server.py
@@ -3,13 +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
+from config import config
class ConnectionHandler(threading.Thread):
"""Connection Handler for HTTP Server"""
@@ -30,22 +30,28 @@ class ConnectionHandler(threading.Thread):
def handle_connection(self):
"""Handle a new connection"""
- rp = RequestParser()
- rc = ResponseComposer(timeout=self.timeout)
+ self.rp = RequestParser()
+ self.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.handle_data(data)
self.conn_socket.close()
+
+ def handle_data(self, data):
+ for req in self.rp.parse_requests(data):
+ logging.info("<-- (%s) %s" % (self.addr[0], req.startline()))
+ resp = self.rc.compose_response(req)
+ logging.info("--> (%s) %s" % (self.addr[0], resp.startline()))
+ self.send(resp)
+
+ def send(self, data):
+ sent = self.conn_socket.send(str(data))
+ if sent == 0:
+ raise RuntimeError('Socket broken')
def run(self):
"""Run the thread of the connection handler"""
@@ -53,12 +59,12 @@ class ConnectionHandler(threading.Thread):
self.handle_connection()
except socket.error, e:
print('ERR ' + str(e))
-
+
class Server:
"""HTTP Server"""
- def __init__(self, config, **kwargs):
+ def __init__(self, configfile, **kwargs):
"""Initialize the HTTP server
Args:
@@ -66,27 +72,32 @@ class Server:
server_port (int): port that the server is listening on
timeout (int): seconds until timeout
"""
- self.read_config(config, **kwargs)
+ self.read_config(configfile, **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 read_config(self, configfile, **kwargs):
+ config().read(configfile)
+
+ if not config().has_section('webhttp'):
+ config().add_section('webhttp')
+
+ for name, val in kwargs.items():
+ if val != None:
+ config().set('webhttp', name, str(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)
+ 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))
while not self.done:
- (clientsocket, addr) = serversocket.accept()
- ch = ConnectionHandler(clientsocket, addr, self.timeout)
- ch.run()
+ (csocket, addr) = self.socket.accept()
+ ch = ConnectionHandler(csocket, addr, config('timeout', type=int))
+ ch.start()
def shutdown(self):
"""Safely shut down the HTTP server"""
+ self.socket.shutdown(socket.SHUT_RDWR)
+ self.socket.close()
self.done = True
+