summaryrefslogtreecommitdiff
path: root/testenv/server/http/http_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'testenv/server/http/http_server.py')
-rw-r--r--testenv/server/http/http_server.py196
1 files changed, 100 insertions, 96 deletions
diff --git a/testenv/server/http/http_server.py b/testenv/server/http/http_server.py
index e554a105..12e04348 100644
--- a/testenv/server/http/http_server.py
+++ b/testenv/server/http/http_server.py
@@ -1,4 +1,5 @@
from http.server import HTTPServer, BaseHTTPRequestHandler
+from exc.server_error import ServerError
from socketserver import BaseServer
from posixpath import basename, splitext
from base64 import b64encode
@@ -11,20 +12,12 @@ import ssl
import os
-class InvalidRangeHeader (Exception):
-
- """ Create an Exception for handling of invalid Range Headers. """
- # TODO: Eliminate this exception and use only ServerError
-
- def __init__ (self, err_message):
- self.err_message = err_message
-
-class ServerError (Exception):
- def __init__ (self, err_message):
- self.err_message = err_message
-
-
class StoppableHTTPServer (HTTPServer):
+ """ This class extends the HTTPServer class from default http.server library
+ in Python 3. The StoppableHTTPServer class is capable of starting an HTTP
+ server that serves a virtual set of files made by the WgetFile class and
+ has most of its properties configurable through the server_conf()
+ method. """
request_headers = list ()
@@ -35,45 +28,45 @@ class StoppableHTTPServer (HTTPServer):
self.server_configs = conf_dict
self.fileSys = filelist
- def server_sett (self, settings):
- for settings_key in settings:
- setattr (self.RequestHandlerClass, settings_key, settings[settings_key])
-
def get_req_headers (self):
return self.request_headers
-class HTTPSServer (StoppableHTTPServer):
- def __init__ (self, address, handler):
- BaseServer.__init__ (self, address, handler)
- print (os.getcwd())
- CERTFILE = os.path.abspath (os.path.join ('..', 'certs', 'wget-cert.pem'))
- print (CERTFILE)
- fop = open (CERTFILE)
- print (fop.readline())
- self.socket = ssl.wrap_socket (
- sock = socket.socket (self.address_family, self.socket_type),
- ssl_version = ssl.PROTOCOL_TLSv1,
- certfile = CERTFILE,
- server_side = True
- )
- self.server_bind ()
- self.server_activate ()
-
-class WgetHTTPRequestHandler (BaseHTTPRequestHandler):
-
- """ Define methods for handling Test Checks. """
+class HTTPSServer (StoppableHTTPServer):
+ """ The HTTPSServer class extends the StoppableHTTPServer class with
+ additional support for secure connections through SSL. """
+
+ def __init__ (self, address, handler):
+ BaseServer.__init__ (self, address, handler)
+ print (os.getcwd())
+ CERTFILE = os.path.abspath(os.path.join('..', 'certs', 'wget-cert.pem'))
+ print (CERTFILE)
+ fop = open (CERTFILE)
+ print (fop.readline())
+ self.socket = ssl.wrap_socket (
+ sock = socket.socket (self.address_family, self.socket_type),
+ ssl_version = ssl.PROTOCOL_TLSv1,
+ certfile = CERTFILE,
+ server_side = True
+ )
+ self.server_bind()
+ self.server_activate()
+
+
+class _Handler (BaseHTTPRequestHandler):
+ """ This is a private class which tells the server *HOW* to handle each
+ request. For each HTTP Request Command that the server should be capable of
+ responding to, there must exist a do_REQUESTNAME() method which details the
+ steps in which such requests should be processed. The rest of the methods
+ in this class are auxilliary methods created to help in processing certain
+ requests. """
def get_rule_list (self, name):
r_list = self.rules.get (name) if name in self.rules else None
return r_list
-
-class _Handler (WgetHTTPRequestHandler):
-
- """ Define Handler Methods for different Requests. """
-
- InvalidRangeHeader = InvalidRangeHeader
+ # The defailt protocol version of the server we run is HTTP/1.1 not
+ # HTTP/1.0 which is the default with the http.server module.
protocol_version = 'HTTP/1.1'
""" Define functions for various HTTP Requests. """
@@ -82,6 +75,11 @@ class _Handler (WgetHTTPRequestHandler):
self.send_head ("HEAD")
def do_GET (self):
+ """ Process HTTP GET requests. This is the same as processing HEAD
+ requests and then actually transmitting the data to the client. If
+ send_head() does not specify any "start" offset, we send the complete
+ data, else transmit only partial data. """
+
content, start = self.send_head ("GET")
if content:
if start is None:
@@ -90,11 +88,26 @@ class _Handler (WgetHTTPRequestHandler):
self.wfile.write (content.encode ('utf-8')[start:])
def do_POST (self):
+ """ According to RFC 7231 sec 4.3.3, if the resource requested in a POST
+ request does not exist on the server, the first POST request should
+ create that resource. PUT requests are otherwise used to create a
+ resource. Hence, we call the handle for processing PUT requests if the
+ resource requested does not already exist.
+
+ Currently, when the server recieves a POST request for a resource, we
+ simply append the body data to the existing file and return the new
+ file to the client. If the file does not exist, a new file is created
+ using the contents of the request body. """
+
path = self.path[1:]
- self.rules = self.server.server_configs.get (path)
- if not self.custom_response ():
- return (None, None)
if path in self.server.fileSys:
+ self.rules = self.server.server_configs.get (path)
+ if not self.rules:
+ self.rules = dict ()
+
+ if not self.custom_response ():
+ return (None, None)
+
body_data = self.get_body_data ()
self.send_response (200)
self.send_header ("Content-type", "text/plain")
@@ -102,6 +115,7 @@ class _Handler (WgetHTTPRequestHandler):
total_length = len (content)
self.server.fileSys[path] = content
self.send_header ("Content-Length", total_length)
+ self.send_header ("Location", self.path)
self.finish_headers ()
try:
self.wfile.write (content.encode ('utf-8'))
@@ -115,7 +129,6 @@ class _Handler (WgetHTTPRequestHandler):
self.rules = self.server.server_configs.get (path)
if not self.custom_response ():
return (None, None)
- self.server.fileSys.pop (path, None)
self.send_put (path)
""" End of HTTP Request Method Handlers. """
@@ -126,12 +139,12 @@ class _Handler (WgetHTTPRequestHandler):
if header_line is None:
return None
if not header_line.startswith ("bytes="):
- raise InvalidRangeHeader ("Cannot parse header Range: %s" %
- (header_line))
+ raise ServerError ("Cannot parse header Range: %s" %
+ (header_line))
regex = re.match (r"^bytes=(\d*)\-$", header_line)
range_start = int (regex.group (1))
if range_start >= length:
- raise InvalidRangeHeader ("Range Overflow")
+ raise ServerError ("Range Overflow")
return range_start
def get_body_data (self):
@@ -141,23 +154,27 @@ class _Handler (WgetHTTPRequestHandler):
return body_data
def send_put (self, path):
+ if path in self.server.fileSys:
+ self.server.fileSys.pop (path, None)
+ self.send_response (204)
+ else:
+ self.rules = dict ()
+ self.send_response (201)
body_data = self.get_body_data ()
- self.send_response (201)
self.server.fileSys[path] = body_data
- self.send_header ("Content-type", "text/plain")
- self.send_header ("Content-Length", len (body_data))
+ self.send_header ("Location", self.path)
self.finish_headers ()
- try:
- self.wfile.write (body_data.encode ('utf-8'))
- except Exception:
- pass
+ """ This empty method is called automatically when all the rules are
+ processed for a given request. However, send_header() should only be called
+ AFTER a response has been sent. But, at the moment of processing the rules,
+ the appropriate response has not yet been identified. As a result, we defer
+ the processing of this rule till later. Each do_* request handler MUST call
+ finish_headers() instead of end_headers(). The finish_headers() method
+ takes care of sending the appropriate headers before completing the
+ response. """
def SendHeader (self, header_obj):
pass
-# headers_list = header_obj.headers
-# for header_line in headers_list:
-# print (header_line + " : " + headers_list[header_line])
-# self.send_header (header_line, headers_list[header_line])
def send_cust_headers (self):
header_obj = self.get_rule_list ('SendHeader')
@@ -195,11 +212,11 @@ class _Handler (WgetHTTPRequestHandler):
if auth_type == "Basic":
challenge_str = 'Basic realm="Wget-Test"'
elif auth_type == "Digest" or auth_type == "Both_inline":
- self.nonce = md5 (str (random ()).encode ('utf-8')).hexdigest ()
- self.opaque = md5 (str (random ()).encode ('utf-8')).hexdigest ()
- challenge_str = 'Digest realm="Test", nonce="%s", opaque="%s"' %(
- self.nonce,
- self.opaque)
+ self.nonce = md5 (str (random ()).encode ('utf-8')).hexdigest()
+ self.opaque = md5 (str (random ()).encode ('utf-8')).hexdigest()
+ challenge_str = 'Digest realm="Test", nonce="%s", opaque="%s"' % (
+ self.nonce,
+ self.opaque)
challenge_str += ', qop="auth"'
if auth_type == "Both_inline":
challenge_str = 'Basic realm="Wget-Test", ' + challenge_str
@@ -218,9 +235,9 @@ class _Handler (WgetHTTPRequestHandler):
n = len("Digest ")
auth_header = auth_header[n:].strip()
items = auth_header.split(", ")
- key_values = [i.split("=", 1) for i in items]
- key_values = [(k.strip(), v.strip().replace('"', '')) for k, v in key_values]
- return dict(key_values)
+ keyvals = [i.split("=", 1) for i in items]
+ keyvals = [(k.strip(), v.strip().replace('"', '')) for k, v in keyvals]
+ return dict(keyvals)
def KD (self, secret, data):
return self.H (secret + ":" + data)
@@ -237,10 +254,10 @@ class _Handler (WgetHTTPRequestHandler):
def check_response (self, params):
if "qop" in params:
data_str = params['nonce'] \
- + ":" + params['nc'] \
- + ":" + params['cnonce'] \
- + ":" + params['qop'] \
- + ":" + self.H (self.A2 (params))
+ + ":" + params['nc'] \
+ + ":" + params['cnonce'] \
+ + ":" + params['qop'] \
+ + ":" + self.H (self.A2 (params))
else:
data_str = params['nonce'] + ":" + self.H (self.A2 (params))
resp = self.KD (self.H (self.A1 ()), data_str)
@@ -256,11 +273,12 @@ class _Handler (WgetHTTPRequestHandler):
params = self.parse_auth_header (auth_header)
pass_auth = True
if self.user != params['username'] or \
- self.nonce != params['nonce'] or self.opaque != params['opaque']:
+ self.nonce != params['nonce'] or \
+ self.opaque != params['opaque']:
pass_auth = False
req_attribs = ['username', 'realm', 'nonce', 'uri', 'response']
for attrib in req_attribs:
- if not attrib in params:
+ if attrib not in params:
pass_auth = False
if not self.check_response (params):
pass_auth = False
@@ -326,19 +344,6 @@ class _Handler (WgetHTTPRequestHandler):
self.finish_headers ()
raise ServerError ("Header " + header_line + " not found")
- def expect_headers (self):
- """ This is modified code to handle a few changes. Should be removed ASAP """
- exp_headers_obj = self.get_rule_list ('ExpectHeader')
- if exp_headers_obj:
- exp_headers = exp_headers_obj.headers
- for header_line in exp_headers:
- header_re = self.headers.get (header_line)
- if header_re is None or header_re != exp_headers[header_line]:
- self.send_error (400, 'Expected Header not Found')
- self.end_headers ()
- return False
- return True
-
def RejectHeader (self, header_obj):
rej_headers = header_obj.headers
for header_line in rej_headers:
@@ -400,7 +405,7 @@ class _Handler (WgetHTTPRequestHandler):
try:
self.range_begin = self.parse_range_header (
self.headers.get ("Range"), content_length)
- except InvalidRangeHeader as ae:
+ except ServerError as ae:
# self.log_error("%s", ae.err_message)
if ae.err_message == "Range Overflow":
self.send_response (416)
@@ -431,9 +436,9 @@ class _Handler (WgetHTTPRequestHandler):
base_name = basename ("/" + path)
name, ext = splitext (base_name)
extension_map = {
- ".txt" : "text/plain",
- ".css" : "text/css",
- ".html" : "text/html"
+ ".txt" : "text/plain",
+ ".css" : "text/css",
+ ".html" : "text/html"
}
if ext in extension_map:
return extension_map[ext]
@@ -444,6 +449,7 @@ class _Handler (WgetHTTPRequestHandler):
class HTTPd (threading.Thread):
server_class = StoppableHTTPServer
handler = _Handler
+
def __init__ (self, addr=None):
threading.Thread.__init__ (self)
if addr is None:
@@ -452,16 +458,14 @@ class HTTPd (threading.Thread):
self.server_address = self.server_inst.socket.getsockname()[:2]
def run (self):
- self.server_inst.serve_forever ()
+ self.server_inst.serve_forever ()
def server_conf (self, file_list, server_rules):
self.server_inst.server_conf (file_list, server_rules)
- def server_sett (self, settings):
- self.server_inst.server_sett (settings)
class HTTPSd (HTTPd):
- server_class = HTTPSServer
+ server_class = HTTPSServer
# vim: set ts=4 sts=4 sw=4 tw=80 et :