summaryrefslogtreecommitdiff
path: root/project1
diff options
context:
space:
mode:
authorCamil Staps2016-04-07 17:12:57 +0200
committerCamil Staps2016-04-07 17:12:57 +0200
commit6fe6c6c7a972753d894d3107cdeb4cdd28b22125 (patch)
treef369761e5ab1b34a50a7f92160b69037e74fcdee /project1
parentPersistent connections tests project 1 (diff)
Project 1: fix line endings
Diffstat (limited to 'project1')
-rw-r--r--project1/proj1_s4498062/README.md126
-rw-r--r--project1/proj1_s4498062/content/test/index.html18
-rw-r--r--project1/proj1_s4498062/webhttp/__init__.py18
-rw-r--r--project1/proj1_s4498062/webhttp/message.py418
-rw-r--r--project1/proj1_s4498062/webhttp/parser.py124
-rw-r--r--project1/proj1_s4498062/webhttp/resource.py226
-rw-r--r--project1/proj1_s4498062/webhttp/server.py236
-rw-r--r--project1/proj1_s4498062/webserver.py102
-rw-r--r--project1/proj1_s4498062/webtests.py374
9 files changed, 821 insertions, 821 deletions
diff --git a/project1/proj1_s4498062/README.md b/project1/proj1_s4498062/README.md
index adc2a2d..13b38e0 100644
--- a/project1/proj1_s4498062/README.md
+++ b/project1/proj1_s4498062/README.md
@@ -1,63 +1,63 @@
-# Project 1 HTTP Server Framework
-
-## Description
-
-This repo contains a framework for the first project.
-It provides a class structure and several utility wrappers for the Python Standard Library (so that you don't have to dig in the Python Docs).
-The project can be completed by filling in the empty methods
-
-## File Structure
-
-* proj_sn1_sn2
- * content
- * test
- * index.html
- * webhttp
- * composer.py
- * \_\_init\_\_.py
- * message.py
- * resource.py
- * parser.py
- * server.py
- * webserver.py
- * webtests.py
-
-The content directory contains the content for the website.
-The test subfolder is meant for resources that are used by the tests which are defined in webtests.py.
-
-The webhttp directory contains a package for HTTP.
-Most methods are unimplemented.
-The file message.py contains classes for HTTP messages.
-The base class Message implements a basic HTTP message, which is specialized by Request and Response, which are for HTTP requests and HTTP responses respectively.
-The file resource.py contains a handler class for resources.
-The file parser.py contains classes for parsing HTTP requests and responses.
-The file composer.py contains a class for composing responses to request.
-The file server.py contains the main HTTP server.
-
-The webserver.py file is Python file for starting the HTTP server, this file is fully implemented.
-
-Finally webtests.py contains the tests for your HTTP server.
-You manually have to start the HTTP server before running the tests.
-Currently only one of the tests is implemented, you will have to implement the other tests.
-
-## Suggested Implementation Order
-
-1. Implement "run" in the class "Server" in "webhttp/server.py". This method should listen for incoming connections and create to a "ConnectionHandler" to handle the connection.
-2. Implement "handle_connection" in the class "ConnectionHandler" in "webhttp/server.py". For now this method should receive a response from a client, send "Hello World" back and close the connection. Test that this works using the Python shell.
-3. Implement "parse_requests" in the class "RequestParser" in "webhttp/parser.py". You can test your implementation by using to parse a few Requests in the Python shell.
-4. Implement "\_\_str\_\_" for "Message", "Response" and "Request" in "webhttp/message.py". This function should return a string representation of the message according to the RFC. In order to test this you should create a few Responses and Requests in the Python shell and test if they comply with the RFC.
-5. Reimplement "handle_connection" using "Response", "Request", "RequestParser" and "ResponseComposer".
-6. Implement "parse_response" in "ResponseParser" in "webhttp/parser.py". At this point you should be able to pass the test in "webtests.py".
-7. Replace the stub code in "compose_response" in "ResponseComposer" in "webhttp/composer.py". The composer should now be able to create the correct response to a request. You can ignore persistent connections and client side caching for now, but the response should have the right code and body.
-8. Write additional tests in "webtests.py" for the following scenarios:
- * GET for a single resource that does not exist
- * GET for a directory with an existing index.html file
- * GET for a directory with non-existing index.html file
-Your code should be able to pass these tests at this point.
-9. Implement client side caching using ETags in "compose_response" and "generate_etag" in the class "Resource" in "resource.py". The "os.stat" module might be useful for generating an ETag. You should also implement the following test (which your server should pass at this point):
- * GET for an existing resource followed by a GET for that same resource, with caching utilized on the client/tester side.
-10. Implement persistent connections in "compose_response" and "handle_connection", and implement the following tests:
- * multiple GETs over the same (persistent) connection with the last GET prompting closing the connection, the connection should be closed.
- * multiple GETs over the same (persistent) connection, followed by a wait during which the connection times out, the connection should be closed.
-11. Implement content encoding. You may need to add extra methods to class "Resource" for this. You should also implement the following test:
- * GET which requests an existing resource gzip encoding, which is accepted by the server.
+# Project 1 HTTP Server Framework
+
+## Description
+
+This repo contains a framework for the first project.
+It provides a class structure and several utility wrappers for the Python Standard Library (so that you don't have to dig in the Python Docs).
+The project can be completed by filling in the empty methods
+
+## File Structure
+
+* proj_sn1_sn2
+ * content
+ * test
+ * index.html
+ * webhttp
+ * composer.py
+ * \_\_init\_\_.py
+ * message.py
+ * resource.py
+ * parser.py
+ * server.py
+ * webserver.py
+ * webtests.py
+
+The content directory contains the content for the website.
+The test subfolder is meant for resources that are used by the tests which are defined in webtests.py.
+
+The webhttp directory contains a package for HTTP.
+Most methods are unimplemented.
+The file message.py contains classes for HTTP messages.
+The base class Message implements a basic HTTP message, which is specialized by Request and Response, which are for HTTP requests and HTTP responses respectively.
+The file resource.py contains a handler class for resources.
+The file parser.py contains classes for parsing HTTP requests and responses.
+The file composer.py contains a class for composing responses to request.
+The file server.py contains the main HTTP server.
+
+The webserver.py file is Python file for starting the HTTP server, this file is fully implemented.
+
+Finally webtests.py contains the tests for your HTTP server.
+You manually have to start the HTTP server before running the tests.
+Currently only one of the tests is implemented, you will have to implement the other tests.
+
+## Suggested Implementation Order
+
+1. Implement "run" in the class "Server" in "webhttp/server.py". This method should listen for incoming connections and create to a "ConnectionHandler" to handle the connection.
+2. Implement "handle_connection" in the class "ConnectionHandler" in "webhttp/server.py". For now this method should receive a response from a client, send "Hello World" back and close the connection. Test that this works using the Python shell.
+3. Implement "parse_requests" in the class "RequestParser" in "webhttp/parser.py". You can test your implementation by using to parse a few Requests in the Python shell.
+4. Implement "\_\_str\_\_" for "Message", "Response" and "Request" in "webhttp/message.py". This function should return a string representation of the message according to the RFC. In order to test this you should create a few Responses and Requests in the Python shell and test if they comply with the RFC.
+5. Reimplement "handle_connection" using "Response", "Request", "RequestParser" and "ResponseComposer".
+6. Implement "parse_response" in "ResponseParser" in "webhttp/parser.py". At this point you should be able to pass the test in "webtests.py".
+7. Replace the stub code in "compose_response" in "ResponseComposer" in "webhttp/composer.py". The composer should now be able to create the correct response to a request. You can ignore persistent connections and client side caching for now, but the response should have the right code and body.
+8. Write additional tests in "webtests.py" for the following scenarios:
+ * GET for a single resource that does not exist
+ * GET for a directory with an existing index.html file
+ * GET for a directory with non-existing index.html file
+Your code should be able to pass these tests at this point.
+9. Implement client side caching using ETags in "compose_response" and "generate_etag" in the class "Resource" in "resource.py". The "os.stat" module might be useful for generating an ETag. You should also implement the following test (which your server should pass at this point):
+ * GET for an existing resource followed by a GET for that same resource, with caching utilized on the client/tester side.
+10. Implement persistent connections in "compose_response" and "handle_connection", and implement the following tests:
+ * multiple GETs over the same (persistent) connection with the last GET prompting closing the connection, the connection should be closed.
+ * multiple GETs over the same (persistent) connection, followed by a wait during which the connection times out, the connection should be closed.
+11. Implement content encoding. You may need to add extra methods to class "Resource" for this. You should also implement the following test:
+ * GET which requests an existing resource gzip encoding, which is accepted by the server.
diff --git a/project1/proj1_s4498062/content/test/index.html b/project1/proj1_s4498062/content/test/index.html
index a5d8c3d..8f5923f 100644
--- a/project1/proj1_s4498062/content/test/index.html
+++ b/project1/proj1_s4498062/content/test/index.html
@@ -1,9 +1,9 @@
-<html>
- <head>
- <title> Index Page </title>
- </head>
- <body>
- <h1> Index Page </h1>
- <p> Test Text </p>
- </body>
-</html>
+<html>
+ <head>
+ <title> Index Page </title>
+ </head>
+ <body>
+ <h1> Index Page </h1>
+ <p> Test Text </p>
+ </body>
+</html>
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
+
diff --git a/project1/proj1_s4498062/webserver.py b/project1/proj1_s4498062/webserver.py
index c01df4e..44152a0 100644
--- a/project1/proj1_s4498062/webserver.py
+++ b/project1/proj1_s4498062/webserver.py
@@ -1,51 +1,51 @@
-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('-c', '--config', type=str, default='~/.webpy.ini',
- help='configuration file')
-
- parser.add_argument('-a', '--address', type=str, default=None,
- help='address to listen on (default localhost)')
- parser.add_argument('-p', '--port', type=int, default=None,
- help='port to listen on (no default)')
- parser.add_argument('-t', '--timeout', type=int, default=None,
- help='timeout for incoming connections (no default)')
-
- 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] %(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
- config = os.path.expanduser(os.path.expandvars(args.config))
- server = webhttp.server.Server(configfile=config,
- hostname=args.address, port=args.port, timeout=args.timeout)
- try:
- server.run()
- except KeyboardInterrupt:
- server.shutdown()
- print ("")
-
-
-
+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('-c', '--config', type=str, default='~/.webpy.ini',
+ help='configuration file')
+
+ parser.add_argument('-a', '--address', type=str, default=None,
+ help='address to listen on (default localhost)')
+ parser.add_argument('-p', '--port', type=int, default=None,
+ help='port to listen on (no default)')
+ parser.add_argument('-t', '--timeout', type=int, default=None,
+ help='timeout for incoming connections (no default)')
+
+ 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] %(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
+ config = os.path.expanduser(os.path.expandvars(args.config))
+ server = webhttp.server.Server(configfile=config,
+ hostname=args.address, port=args.port, timeout=args.timeout)
+ try:
+ server.run()
+ except KeyboardInterrupt:
+ server.shutdown()
+ print ("")
+
+
+
diff --git a/project1/proj1_s4498062/webtests.py b/project1/proj1_s4498062/webtests.py
index d047526..f2f6fbd 100644
--- a/project1/proj1_s4498062/webtests.py
+++ b/project1/proj1_s4498062/webtests.py
@@ -1,187 +1,187 @@
-import os.path
-import unittest
-import socket
-import sys
-import time
-
-from webhttp.config import config
-import webhttp.message
-import webhttp.parser
-
-
-portnr = 8001
-
-
-class TestGetRequests(unittest.TestCase):
- """Test cases for GET requests"""
-
- def setUp(self):
- """Prepare for testing"""
- self.client_skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.client_skt.connect(("localhost", portnr))
- self.parser = webhttp.parser.ResponseParser()
- self.default_headers = [
- ('Host', 'localhost:%d' % portnr),
- ('Connection', 'close')
- ]
- self.default_headers_pers = [
- ('Host', 'localhost:%d' % portnr),
- ('Connection', 'keep-alive')
- ]
-
- config().read(os.path.expanduser('~/.webpy.ini'))
-
- def tearDown(self):
- """Clean up after testing"""
- try:
- self.client_skt.shutdown(socket.SHUT_RDWR)
- self.client_skt.close()
- except:
- pass
-
- def refresh_socket(self):
- try:
- self.client_skt.shutdown(socket.SHUT_RDWR)
- self.client_skt.close()
- except:
- pass
- self.client_skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.client_skt.connect(("localhost", portnr))
-
- def request(self, method, uri, headers, persistent=False):
- request = webhttp.message.Request()
- request.method = method
- request.uri = uri
- for name, value in headers:
- request.set_header(name, value)
-
- sent = self.client_skt.send(str(request))
- if sent == 0:
- return None
-
- message = self.client_skt.recv(1024)
-
- if not persistent:
- self.refresh_socket()
-
- if message == '':
- return None
- else:
- 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"""
- 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
- resource with caching utilized on the client/tester side
- """
- response = self.request('GET', '/test', self.default_headers)
-
- response = self.request('GET', '/test', self.default_headers + \
- [('If-None-Match', 'invalid-etag')])
- self.assertEqual(response.code, 200, 'If-None-Match returns 200')
-
- response = self.request('GET', '/test', self.default_headers + \
- [('If-None-Match', response.get_header('ETag'))])
- self.assertEqual(response.code, 304, 'If-None-Match returns 304')
-
- response = self.request('GET', '/test', self.default_headers + \
- [('If-Match', response.get_header('ETag'))])
- self.assertEqual(response.code, 200, 'If-Match returns 200')
-
- response = self.request('GET', '/test', self.default_headers + \
- [('If-Match', 'invalid-etag')])
- self.assertEqual(response.code, 304, 'If-Match returns 304')
-
- def test_extisting_index_file(self):
- """GET for a directory with an existing index.html file"""
- 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"""
- response = self.request('GET', '/test/no-index', self.default_headers)
- self.assertEqual(response.code, 403)
- self.assertTrue(response.body)
-
- def test_error_page(self):
- r1 = self.request('GET', '/test/nonexistant', self.default_headers)
- r2 = self.request('GET', config('error404'), self.default_headers)
- self.assertEqual(r1.body, r2.body)
-
- def test_persistent_close(self):
- """Multiple GETs over the same (persistent) connection with the last
- GET prompting closing the connection, the connection should be closed.
- """
- try:
- self.request('GET', '/test', self.default_headers_pers, True)
- self.request('GET', '/test', self.default_headers_pers, True)
- self.request('GET', '/test', self.default_headers_pers, True)
- except socket.error:
- self.fail('Persistent connection closed prematurely')
-
- self.request('GET', '/test', self.default_headers, True)
- self.assertIsNone(self.request('GET', '/test',
- self.default_headers_pers, True))
-
- self.refresh_socket()
-
- def test_persistent_timeout(self):
- """Multiple GETs over the same (persistent) connection, followed by a
- wait during which the connection times out, the connection should be
- closed.
- """
- self.request('GET', '/test', self.default_headers_pers, True)
- time.sleep(20)
- self.assertIsNone(self.request('GET', '/test',
- self.default_headers_pers, True))
-
- self.refresh_socket()
-
- def test_encoding(self):
- """GET which requests an existing resource using gzip encodign, which
- is accepted by the server.
- """
- r1 = self.request('GET', '/test', self.default_headers + \
- [('Accept-Encoding', 'gzip;q=1, identity;q=0')])
- self.assertEqual(r1.get_header('Content-Encoding'), 'gzip')
- r1.decompress()
- r2 = self.request('GET', '/test', self.default_headers + \
- [('Accept-Encoding', '')])
- self.assertEqual(r1.body, r2.body)
-
- def test_doubledot(self):
- response = self.request('GET', '/../test', self.default_headers)
- self.assertEquals(response.code, 403)
-
-
-if __name__ == "__main__":
- # Parse command line arguments
- import argparse
- parser = argparse.ArgumentParser(description="HTTP Tests")
- parser.add_argument("-p", "--port", type=int, default=8001)
-
- # 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
-
- # Start test suite
- unittest.main()
+import os.path
+import unittest
+import socket
+import sys
+import time
+
+from webhttp.config import config
+import webhttp.message
+import webhttp.parser
+
+
+portnr = 8001
+
+
+class TestGetRequests(unittest.TestCase):
+ """Test cases for GET requests"""
+
+ def setUp(self):
+ """Prepare for testing"""
+ self.client_skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.client_skt.connect(("localhost", portnr))
+ self.parser = webhttp.parser.ResponseParser()
+ self.default_headers = [
+ ('Host', 'localhost:%d' % portnr),
+ ('Connection', 'close')
+ ]
+ self.default_headers_pers = [
+ ('Host', 'localhost:%d' % portnr),
+ ('Connection', 'keep-alive')
+ ]
+
+ config().read(os.path.expanduser('~/.webpy.ini'))
+
+ def tearDown(self):
+ """Clean up after testing"""
+ try:
+ self.client_skt.shutdown(socket.SHUT_RDWR)
+ self.client_skt.close()
+ except:
+ pass
+
+ def refresh_socket(self):
+ try:
+ self.client_skt.shutdown(socket.SHUT_RDWR)
+ self.client_skt.close()
+ except:
+ pass
+ self.client_skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.client_skt.connect(("localhost", portnr))
+
+ def request(self, method, uri, headers, persistent=False):
+ request = webhttp.message.Request()
+ request.method = method
+ request.uri = uri
+ for name, value in headers:
+ request.set_header(name, value)
+
+ sent = self.client_skt.send(str(request))
+ if sent == 0:
+ return None
+
+ message = self.client_skt.recv(1024)
+
+ if not persistent:
+ self.refresh_socket()
+
+ if message == '':
+ return None
+ else:
+ 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"""
+ 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
+ resource with caching utilized on the client/tester side
+ """
+ response = self.request('GET', '/test', self.default_headers)
+
+ response = self.request('GET', '/test', self.default_headers + \
+ [('If-None-Match', 'invalid-etag')])
+ self.assertEqual(response.code, 200, 'If-None-Match returns 200')
+
+ response = self.request('GET', '/test', self.default_headers + \
+ [('If-None-Match', response.get_header('ETag'))])
+ self.assertEqual(response.code, 304, 'If-None-Match returns 304')
+
+ response = self.request('GET', '/test', self.default_headers + \
+ [('If-Match', response.get_header('ETag'))])
+ self.assertEqual(response.code, 200, 'If-Match returns 200')
+
+ response = self.request('GET', '/test', self.default_headers + \
+ [('If-Match', 'invalid-etag')])
+ self.assertEqual(response.code, 304, 'If-Match returns 304')
+
+ def test_extisting_index_file(self):
+ """GET for a directory with an existing index.html file"""
+ 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"""
+ response = self.request('GET', '/test/no-index', self.default_headers)
+ self.assertEqual(response.code, 403)
+ self.assertTrue(response.body)
+
+ def test_error_page(self):
+ r1 = self.request('GET', '/test/nonexistant', self.default_headers)
+ r2 = self.request('GET', config('error404'), self.default_headers)
+ self.assertEqual(r1.body, r2.body)
+
+ def test_persistent_close(self):
+ """Multiple GETs over the same (persistent) connection with the last
+ GET prompting closing the connection, the connection should be closed.
+ """
+ try:
+ self.request('GET', '/test', self.default_headers_pers, True)
+ self.request('GET', '/test', self.default_headers_pers, True)
+ self.request('GET', '/test', self.default_headers_pers, True)
+ except socket.error:
+ self.fail('Persistent connection closed prematurely')
+
+ self.request('GET', '/test', self.default_headers, True)
+ self.assertIsNone(self.request('GET', '/test',
+ self.default_headers_pers, True))
+
+ self.refresh_socket()
+
+ def test_persistent_timeout(self):
+ """Multiple GETs over the same (persistent) connection, followed by a
+ wait during which the connection times out, the connection should be
+ closed.
+ """
+ self.request('GET', '/test', self.default_headers_pers, True)
+ time.sleep(20)
+ self.assertIsNone(self.request('GET', '/test',
+ self.default_headers_pers, True))
+
+ self.refresh_socket()
+
+ def test_encoding(self):
+ """GET which requests an existing resource using gzip encodign, which
+ is accepted by the server.
+ """
+ r1 = self.request('GET', '/test', self.default_headers + \
+ [('Accept-Encoding', 'gzip;q=1, identity;q=0')])
+ self.assertEqual(r1.get_header('Content-Encoding'), 'gzip')
+ r1.decompress()
+ r2 = self.request('GET', '/test', self.default_headers + \
+ [('Accept-Encoding', '')])
+ self.assertEqual(r1.body, r2.body)
+
+ def test_doubledot(self):
+ response = self.request('GET', '/../test', self.default_headers)
+ self.assertEquals(response.code, 403)
+
+
+if __name__ == "__main__":
+ # Parse command line arguments
+ import argparse
+ parser = argparse.ArgumentParser(description="HTTP Tests")
+ parser.add_argument("-p", "--port", type=int, default=8001)
+
+ # 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
+
+ # Start test suite
+ unittest.main()