diff options
author | Camil Staps | 2016-04-07 17:12:57 +0200 |
---|---|---|
committer | Camil Staps | 2016-04-07 17:12:57 +0200 |
commit | 6fe6c6c7a972753d894d3107cdeb4cdd28b22125 (patch) | |
tree | f369761e5ab1b34a50a7f92160b69037e74fcdee /project1/proj1_s4498062/webhttp | |
parent | Persistent connections tests project 1 (diff) |
Project 1: fix line endings
Diffstat (limited to 'project1/proj1_s4498062/webhttp')
-rw-r--r-- | project1/proj1_s4498062/webhttp/__init__.py | 18 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/message.py | 418 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/parser.py | 124 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/resource.py | 226 | ||||
-rw-r--r-- | project1/proj1_s4498062/webhttp/server.py | 236 |
5 files changed, 511 insertions, 511 deletions
diff --git a/project1/proj1_s4498062/webhttp/__init__.py b/project1/proj1_s4498062/webhttp/__init__.py index d501a0e..0d5a9ab 100644 --- a/project1/proj1_s4498062/webhttp/__init__.py +++ b/project1/proj1_s4498062/webhttp/__init__.py @@ -1,9 +1,9 @@ -"""HTTP Package
-
-This package contains the following modules:
- * message: Module for HTTP responses/requests
- * composer: Module for composing responses to requests
- * parser: Module for parsing HTTP responses/requests
- * util: Module with utility functions
- * server: Module which contains a HTTP server
-"""
+"""HTTP Package + +This package contains the following modules: + * message: Module for HTTP responses/requests + * composer: Module for composing responses to requests + * parser: Module for parsing HTTP responses/requests + * util: Module with utility functions + * server: Module which contains a HTTP server +""" diff --git a/project1/proj1_s4498062/webhttp/message.py b/project1/proj1_s4498062/webhttp/message.py index 2e1a962..393236e 100644 --- a/project1/proj1_s4498062/webhttp/message.py +++ b/project1/proj1_s4498062/webhttp/message.py @@ -1,209 +1,209 @@ -"""HTTP Messages
-
-This modules contains classes for representing HTTP responses and requests.
-"""
-
-import re
-
-import encodings
-import regexes as r
-import weblogging as logging
-
-reasondict = {
- 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',
- 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',
-}
-
-
-class Message(object):
- """Class that stores an HTTP Message"""
-
- def __init__(self):
- """Initialize the Message"""
- self.version = "HTTP/1.1"
- self.body = ""
- self.headers = dict()
-
- def set_header(self, name, value):
- """Add a header and its value
-
- Args:
- name (str): name of header
- value (str): value of header
- """
- self.headers[name] = str(value)
-
- def get_header(self, name):
- """Get the value of a header
-
- Args:
- name (str): name of header
-
- Returns:
- str: value of header, empty if header does not exist
- """
- if name in self.headers:
- return self.headers[name]
- else:
- return None
-
- 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
- """
- 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' + self.body
- return msg
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
-
-class Request(Message):
- """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 encodings(self):
- requested = self.get_header('Accept-Encoding')
- if requested == None:
- return encodings.all
-
- encs = []
- requested = re.split(r.EncodingSplit, requested)
- for value in requested:
- try:
- match = re.match(r.AcceptEncodingValue, value)
- if match == None:
- return [encodings.IDENTITY]
- enc, q = match.groups()
- # Unclear what should happen when some qvalues are omitted
- q = q or '0.001'
- if enc == '*':
- for enc in encodings.all:
- encs.append((encodings.get(enc), float(q)))
- else:
- encs.append((encodings.get(enc), float(q)))
- except encodings.UnknownEncodingError:
- pass
-
- rejected = [e[0] for e in encs if e[1] == 0]
- accepted = [e for e in encs if e[1] != 0]
-
- if not encodings.IDENTITY in rejected + [a[0] for a in accepted]:
- accepted.append((encodings.IDENTITY, 0.001))
-
- accepted.sort(key=lambda x:x[1], reverse=True)
- return [a[0] for a in accepted]
-
- def startline(self):
- return "%s %s %s" % (self.method, self.uri, self.version)
-
- def __str__(self):
- """Convert the Request to a string
-
- Returns:
- str: representation the can be sent over socket
- """
- return super(Request, self).__str__()
-
-
-class Response(Message):
- """Class that stores an HTTP Response"""
-
- def __init__(self):
- """Initialize the Response"""
- super(Response, self).__init__()
- self.set_header('Server', 'WebPy')
-
- 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 set_content_length(self):
- self.set_header('Content-Length', len(self.body))
-
- def decompress(self):
- self.body = encodings.decode(
- self.get_header('Content-Encoding'), self.body)
-
- def __str__(self):
- """Convert the Response to a string
-
- Returns:
- str: representation the can be sent over socket
- """
- return super(Response, self).__str__()
-
+"""HTTP Messages + +This modules contains classes for representing HTTP responses and requests. +""" + +import re + +import encodings +import regexes as r +import weblogging as logging + +reasondict = { + 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', + 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', +} + + +class Message(object): + """Class that stores an HTTP Message""" + + def __init__(self): + """Initialize the Message""" + self.version = "HTTP/1.1" + self.body = "" + self.headers = dict() + + def set_header(self, name, value): + """Add a header and its value + + Args: + name (str): name of header + value (str): value of header + """ + self.headers[name] = str(value) + + def get_header(self, name): + """Get the value of a header + + Args: + name (str): name of header + + Returns: + str: value of header, empty if header does not exist + """ + if name in self.headers: + return self.headers[name] + else: + return None + + 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 + """ + 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' + self.body + return msg + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + +class Request(Message): + """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 encodings(self): + requested = self.get_header('Accept-Encoding') + if requested == None: + return encodings.all + + encs = [] + requested = re.split(r.EncodingSplit, requested) + for value in requested: + try: + match = re.match(r.AcceptEncodingValue, value) + if match == None: + return [encodings.IDENTITY] + enc, q = match.groups() + # Unclear what should happen when some qvalues are omitted + q = q or '0.001' + if enc == '*': + for enc in encodings.all: + encs.append((encodings.get(enc), float(q))) + else: + encs.append((encodings.get(enc), float(q))) + except encodings.UnknownEncodingError: + pass + + rejected = [e[0] for e in encs if e[1] == 0] + accepted = [e for e in encs if e[1] != 0] + + if not encodings.IDENTITY in rejected + [a[0] for a in accepted]: + accepted.append((encodings.IDENTITY, 0.001)) + + accepted.sort(key=lambda x:x[1], reverse=True) + return [a[0] for a in accepted] + + def startline(self): + return "%s %s %s" % (self.method, self.uri, self.version) + + def __str__(self): + """Convert the Request to a string + + Returns: + str: representation the can be sent over socket + """ + return super(Request, self).__str__() + + +class Response(Message): + """Class that stores an HTTP Response""" + + def __init__(self): + """Initialize the Response""" + super(Response, self).__init__() + self.set_header('Server', 'WebPy') + + 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 set_content_length(self): + self.set_header('Content-Length', len(self.body)) + + def decompress(self): + self.body = encodings.decode( + self.get_header('Content-Encoding'), self.body) + + def __str__(self): + """Convert the Response to a string + + Returns: + str: representation the can be sent over socket + """ + return super(Response, self).__str__() + diff --git a/project1/proj1_s4498062/webhttp/parser.py b/project1/proj1_s4498062/webhttp/parser.py index 7dbb3b5..35f01e8 100644 --- a/project1/proj1_s4498062/webhttp/parser.py +++ b/project1/proj1_s4498062/webhttp/parser.py @@ -1,62 +1,62 @@ -"""HTTP response and request parsers
-
-This module contains parses for HTTP response and HTTP requests.
-"""
-
-import webhttp.message
-
-
-class RequestParser:
- """Class that parses a HTTP request"""
-
- def __init__(self):
- """Initialize the RequestParser"""
- self.buff = ''
-
- def parse_requests(self, buff):
- """Parse requests in a buffer
-
- Args:
- buff (str): the buffer contents received from socket
-
- Returns:
- list of webhttp.Request
- """
- self.buff += buff
- requests = self.get_requests()
-
- for request in requests:
- http_request = webhttp.message.Request()
- http_request.parse(request)
- yield http_request
-
- def get_requests(self):
- """Split multiple requests from buffer
-
- Returns:
- list of str
- """
- requests = self.buff.split('\r\n\r\n')
- self.buff = requests[-1]
- for req in requests[:-1]:
- yield req.lstrip() + '\r\n\r\n'
-
-
-class ResponseParser:
- """Class that parses a HTTP response"""
- def __init__(self):
- """Initialize the ResponseParser"""
- pass
-
- def parse_response(self, buff):
- """Parse responses in buffer
-
- Args:
- buff (str): the buffer contents received from socket
-
- Returns:
- webhttp.Response
- """
- response = webhttp.message.Response()
- response.parse(buff)
- return response
+"""HTTP response and request parsers + +This module contains parses for HTTP response and HTTP requests. +""" + +import webhttp.message + + +class RequestParser: + """Class that parses a HTTP request""" + + def __init__(self): + """Initialize the RequestParser""" + self.buff = '' + + def parse_requests(self, buff): + """Parse requests in a buffer + + Args: + buff (str): the buffer contents received from socket + + Returns: + list of webhttp.Request + """ + self.buff += buff + requests = self.get_requests() + + for request in requests: + http_request = webhttp.message.Request() + http_request.parse(request) + yield http_request + + def get_requests(self): + """Split multiple requests from buffer + + Returns: + list of str + """ + requests = self.buff.split('\r\n\r\n') + self.buff = requests[-1] + for req in requests[:-1]: + yield req.lstrip() + '\r\n\r\n' + + +class ResponseParser: + """Class that parses a HTTP response""" + def __init__(self): + """Initialize the ResponseParser""" + pass + + def parse_response(self, buff): + """Parse responses in buffer + + Args: + buff (str): the buffer contents received from socket + + Returns: + webhttp.Response + """ + response = webhttp.message.Response() + response.parse(buff) + return response diff --git a/project1/proj1_s4498062/webhttp/resource.py b/project1/proj1_s4498062/webhttp/resource.py index 9509f82..ee05fa7 100644 --- a/project1/proj1_s4498062/webhttp/resource.py +++ b/project1/proj1_s4498062/webhttp/resource.py @@ -1,113 +1,113 @@ -"""Resources
-
-This module contains a handler class for resources.
-"""
-
-import binascii
-import gzip
-import hashlib
-import mimetypes
-import os
-import re
-import StringIO
-import urlparse
-
-from config import config
-import encodings
-import regexes as r
-
-
-class FileExistError(Exception):
- """Exception which is raised when file does not exist"""
- pass
-
-
-class FileAccessError(Exception):
- """Exception which is raised when file exists, but cannot be accessed"""
- pass
-
-
-class Resource:
- """Class for representing a Resource (file)"""
-
- def __init__(self, uri):
- """Initialize the resource"
-
- Raises:
- FileExistError: if resource does not exist
- FileAccessError: if resource exists, but cannot be accessed
-
- Args:
- uri (str): Uniform Resource Identifier
- """
- if uri == None:
- raise FileExistError
- self.uri = uri
- out = urlparse.urlparse(uri)
- 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', default='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):
- raise FileAccessError
-
- def generate_etag(self):
- """Generate the ETag for the resource
-
- Returns:
- str: ETag for the resource
- """
- stat = os.stat(self.path)
- 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, encoding=encodings.IDENTITY):
- """Get the contents of the resource
-
- Returns:
- str: Contents of the resource
- """
- content = open(self.path).read()
- return encodings.encode(encoding, content)
-
- def get_content_type(self):
- """Get the content type, i.e "text/html"
-
- Returns:
- str: type of content in the resource
- """
- mimetype = mimetypes.guess_type(self.path)
- return mimetype[0]
-
- def get_content_encoding(self):
- """Get the content encoding, i.e "gzip"
-
- Returns:
- str: encoding used for the resource
- """
- mimetype = mimetypes.guess_type(self.path)
- return mimetype[1]
-
- def get_content_length(self):
- """Get the length of the resource
-
- Returns:
- int: length of resource in bytes
- """
- return os.path.getsize(self.path)
+"""Resources + +This module contains a handler class for resources. +""" + +import binascii +import gzip +import hashlib +import mimetypes +import os +import re +import StringIO +import urlparse + +from config import config +import encodings +import regexes as r + + +class FileExistError(Exception): + """Exception which is raised when file does not exist""" + pass + + +class FileAccessError(Exception): + """Exception which is raised when file exists, but cannot be accessed""" + pass + + +class Resource: + """Class for representing a Resource (file)""" + + def __init__(self, uri): + """Initialize the resource" + + Raises: + FileExistError: if resource does not exist + FileAccessError: if resource exists, but cannot be accessed + + Args: + uri (str): Uniform Resource Identifier + """ + if uri == None: + raise FileExistError + self.uri = uri + out = urlparse.urlparse(uri) + 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', default='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): + raise FileAccessError + + def generate_etag(self): + """Generate the ETag for the resource + + Returns: + str: ETag for the resource + """ + stat = os.stat(self.path) + 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, encoding=encodings.IDENTITY): + """Get the contents of the resource + + Returns: + str: Contents of the resource + """ + content = open(self.path).read() + return encodings.encode(encoding, content) + + def get_content_type(self): + """Get the content type, i.e "text/html" + + Returns: + str: type of content in the resource + """ + mimetype = mimetypes.guess_type(self.path) + return mimetype[0] + + def get_content_encoding(self): + """Get the content encoding, i.e "gzip" + + Returns: + str: encoding used for the resource + """ + mimetype = mimetypes.guess_type(self.path) + return mimetype[1] + + def get_content_length(self): + """Get the length of the resource + + Returns: + int: length of resource in bytes + """ + return os.path.getsize(self.path) diff --git a/project1/proj1_s4498062/webhttp/server.py b/project1/proj1_s4498062/webhttp/server.py index 5db8620..70d1dd6 100644 --- a/project1/proj1_s4498062/webhttp/server.py +++ b/project1/proj1_s4498062/webhttp/server.py @@ -1,118 +1,118 @@ -"""HTTP Server
-
-This module contains a HTTP server
-"""
-
-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"""
-
- def __init__(self, conn_socket, addr, timeout):
- """Initialize the HTTP Connection Handler
-
- Args:
- conn_socket (socket): socket used for connection with client
- addr (str): ip address of client
- timeout (int): seconds until timeout
- """
- super(ConnectionHandler, self).__init__()
- self.daemon = True
- self.conn_socket = conn_socket
- self.addr = addr
- self.timeout = timeout
- self.done = False
-
- def handle_connection(self):
- """Handle a new connection"""
- self.rp = RequestParser()
- self.rc = ResponseComposer(timeout=self.timeout)
-
- self.conn_socket.settimeout(self.timeout)
-
- try:
- while not self.done:
- data = self.conn_socket.recv(32)
- if len(data) == 0:
- break
- self.handle_data(data)
- except socket.timeout:
- logging.debug('%s connection timed out.' % self.addr[0])
- finally:
- self.conn_socket.shutdown(socket.SHUT_RDWR)
- self.conn_socket.close()
- logging.debug('%s connection closed.' % self.addr[0])
-
- 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)
-
- if resp.get_header('Connection') == 'close':
- self.done = True
- return
-
- 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"""
- try:
- self.handle_connection()
- except socket.error, e:
- logging.error('Error in handling connection with %s: %s' %
- (self.addr[0], str(e)))
-
-
-class Server:
- """HTTP Server"""
-
- def __init__(self, configfile, **kwargs):
- """Initialize the HTTP server
-
- Args:
- hostname (str): hostname of the server
- server_port (int): port that the server is listening on
- timeout (int): seconds until timeout
- """
- self.read_config(configfile, **kwargs)
- self.done = False
-
- 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"""
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- 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()
-
- def shutdown(self):
- """Safely shut down the HTTP server"""
- self.socket.shutdown(socket.SHUT_RDWR)
- self.socket.close()
- self.done = True
-
+"""HTTP Server + +This module contains a HTTP server +""" + +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""" + + def __init__(self, conn_socket, addr, timeout): + """Initialize the HTTP Connection Handler + + Args: + conn_socket (socket): socket used for connection with client + addr (str): ip address of client + timeout (int): seconds until timeout + """ + super(ConnectionHandler, self).__init__() + self.daemon = True + self.conn_socket = conn_socket + self.addr = addr + self.timeout = timeout + self.done = False + + def handle_connection(self): + """Handle a new connection""" + self.rp = RequestParser() + self.rc = ResponseComposer(timeout=self.timeout) + + self.conn_socket.settimeout(self.timeout) + + try: + while not self.done: + data = self.conn_socket.recv(32) + if len(data) == 0: + break + self.handle_data(data) + except socket.timeout: + logging.debug('%s connection timed out.' % self.addr[0]) + finally: + self.conn_socket.shutdown(socket.SHUT_RDWR) + self.conn_socket.close() + logging.debug('%s connection closed.' % self.addr[0]) + + 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) + + if resp.get_header('Connection') == 'close': + self.done = True + return + + 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""" + try: + self.handle_connection() + except socket.error, e: + logging.error('Error in handling connection with %s: %s' % + (self.addr[0], str(e))) + + +class Server: + """HTTP Server""" + + def __init__(self, configfile, **kwargs): + """Initialize the HTTP server + + Args: + hostname (str): hostname of the server + server_port (int): port that the server is listening on + timeout (int): seconds until timeout + """ + self.read_config(configfile, **kwargs) + self.done = False + + 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""" + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + 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() + + def shutdown(self): + """Safely shut down the HTTP server""" + self.socket.shutdown(socket.SHUT_RDWR) + self.socket.close() + self.done = True + |