diff --git a/http_server.py b/http_server.py index 58d7386..c75307e 100644 --- a/http_server.py +++ b/http_server.py @@ -1,6 +1,10 @@ import socket import sys import traceback +import os +import mimetypes +import subprocess + def response_ok(body=b"This is a minimal response", mimetype=b"text/plain"): """ @@ -18,34 +22,45 @@ def response_ok(body=b"This is a minimal response", mimetype=b"text/plain"):

Welcome:

\r\n ''' """ - # TODO: Implement response_ok - return b"" + return b"\r\n".join([ + b"HTTP/1.1 200 OK", + b"Content-Type: " + mimetype, + b"", # indicate the begining of body part of the HTTP response. + body, + ]) def response_method_not_allowed(): """Returns a 405 Method Not Allowed response""" - # TODO: Implement response_method_not_allowed - return b"" + return b"\r\n".join([ + b"HTTP/1.1 405 Method Not Allowed", + b"", + b"You can't do that on this server!", + ]) def response_not_found(): """Returns a 404 Not Found response""" - # TODO: Implement response_not_found - return b"" + return b"\r\n".join([ + b"HTTP/1.1 404 Not Found", + b"", + b"Not Found response!", + ]) def parse_request(request): """ Given the content of an HTTP request, returns the path of that request. - This server only handles GET requests, so this method shall raise a NotImplementedError if the method of the request is not GET. """ - # TODO: implement parse_request - return "" + method, path, version = request.split('\r\n')[0].split(' ') + if method != 'GET': + raise NotImplementedError + return path def response_path(path): """ @@ -74,22 +89,51 @@ def response_path(path): response_path('/a_page_that_doesnt_exist.html') -> Raises a NameError """ - - # TODO: Raise a NameError if the requested content is not present - # under webroot. - - # TODO: Fill in the appropriate content and mime_type give the path. - # See the assignment guidelines for help on "mapping mime-types", though - # you might need to create a special case for handling make_time.py # - # If the path is "make_time.py", then you may OPTIONALLY return the - # result of executing `make_time.py`. But you need only return the - # CONTENTS of `make_time.py`. - - content = b"not implemented" - mime_type = b"not implemented" - return content, mime_type + file_dir = os.path.join(os.getcwd(), 'webroot') + file_name = os.path.join(file_dir, *path.split('/')) + #print('--- filename:{}'.format(file_name)) + + content = '' + mime_type = '' + + if os.path.isdir(file_name): + for file in os.listdir(file_name): + content += '{}\n'.format(file) + mime_type = "text/plain" + return content.encode(), mime_type.encode() + + if os.path.isfile(file_name): + # TODO: Fill in the appropriate content and mime_type give the path. + # See the assignment guidelines for help on "mapping mime-types", though + # you might need to create a special case for handling make_time.py + root, extension = os.path.splitext(file_name) + try: + # mime_type = mimetypes.guess_type(file_name)[0] + mime_type = mimetypes.types_map[extension] + except Exception: + print(f"Can't find the mimetype for file extension:{extension}") + sys.exit() + # If the path is "make_time.py", then you may OPTIONALLY return the + # result of executing `make_time.py`. But you need only return the + # CONTENTS of `make_time.py`. + if path == '/make_time.py': + result = subprocess.run(["python", file_name], + stdout=subprocess.PIPE, + # text=True for regular string, default + # stdout is bytestring. + #text=True, + ) + content = result.stdout + # If the file is not 'make_time.py', then return the file content + else: + with open(file_name, 'rb') as f: + content = f.read() + return content, mime_type.encode() + + # Raise a NameError if the requested content is not present under webroot. + raise NameError def server(log_buffer=sys.stderr): @@ -114,40 +158,44 @@ def server(log_buffer=sys.stderr): if '\r\n\r\n' in request: break - print("Request received:\n{}\n\n".format(request)) - # TODO: Use parse_request to retrieve the path from the request. - - # TODO: Use response_path to retrieve the content and the mimetype, - # based on the request path. - - # TODO; If parse_request raised a NotImplementedError, then let - # response be a method_not_allowed response. If response_path raised - # a NameError, then let response be a not_found response. Else, - # use the content and mimetype from response_path to build a - # response_ok. - response = response_ok( - body=b"Welcome to my web server", - mimetype=b"text/plain" - ) + try: + # TODO: Use parse_request to retrieve the path from the request. + # If parse_request raised a NotImplementedError, then let + # response be a method_not_allowed response. + path = parse_request(request) + # TODO: Use response_path to retrieve the content and the mimetype, + # based on the request path. + content, mimetype = response_path(path) + # If response_path raised a NameError, then let response be + # a not_found response. Else, use the content and mimetype + # from response_path to build a response_ok. + + response = response_ok( + body=content, + mimetype=mimetype + ) + except NotImplementedError: + response = response_method_not_allowed() + + except NameError: + response = response_not_found() conn.sendall(response) - except: + except Exception as e: traceback.print_exc() finally: - conn.close() + conn.close() except KeyboardInterrupt: sock.close() return - except: + except Exception as e: traceback.print_exc() if __name__ == '__main__': server() sys.exit(0) - - diff --git a/tests.py b/tests.py index 21da57e..d984ec1 100644 --- a/tests.py +++ b/tests.py @@ -47,8 +47,9 @@ def test_post_yields_method_not_allowed(self): conn.close() - self.assertEqual(response.getcode(), 405) - + # both .getcode() and .status can be used to check the response code. + self.assertEqual(response.status, 405) + #self.assertEqual(response.getcode(), 405) def test_get_sample_text_content(self): """ @@ -64,6 +65,7 @@ def test_get_sample_text_content(self): self.assertEqual(response.getcode(), 200, error_comment) + # response.read() will read the http response body. with open(local_path, 'rb') as f: self.assertEqual(f.read(), response.read(), error_comment) @@ -79,7 +81,9 @@ def test_get_sample_text_mime_type(self): response = self.get_response(web_path) self.assertEqual(response.getcode(), 200, error_comment) - self.assertEqual(response.getheader('Content-Type'), 'text/plain', error_comment) + # response.getheader(header_name) will return the value of the header name + self.assertEqual(response.getheader('Content-Type'), + 'text/plain', error_comment) def test_get_sample_scene_balls_jpeg(self): """ @@ -110,7 +114,8 @@ def test_get_sample_scene_balls_jpeg_mime_type(self): response = self.get_response(web_path) self.assertEqual(response.getcode(), 200, error_comment) - self.assertEqual(response.getheader('Content-Type'), 'image/jpeg', error_comment) + self.assertEqual(response.getheader('Content-Type'), + 'image/jpeg', error_comment) def test_get_sample_1_png(self): """ @@ -190,7 +195,7 @@ def test_root_index(self): def test_ok_response_at_root_index(self): """ - A call to / at least yields a 200 OK response + A call to / at least yields a 200 OK response """ directory = '' @@ -200,6 +205,24 @@ def test_ok_response_at_root_index(self): self.assertEqual(response.getcode(), 200) + def test_make_time_python_file(self): + """ + A call to wobroot/make_time.py and execute the file and serve up the + result of performing that execution. + """ + file = 'make_time.py' + + web_path = '/' + file + error_comment = "Error encountered while visiting " + web_path + + response = self.get_response(web_path) + + self.assertEqual(response.getcode(), 200, error_comment) + self.assertEqual(response.getheader('Content-Type'), + 'text/x-python', error_comment) + # HTTPResponse.read() will read the response body in bytestring. + self.assertIn('', response.read().decode(), error_comment) + if __name__ == '__main__': unittest.main() diff --git a/unit-tests.py b/unit-tests.py index a0c657a..9e823fb 100644 --- a/unit-tests.py +++ b/unit-tests.py @@ -53,7 +53,7 @@ def test_response_path_file(self): content, mime_type = http_server.response_path(path) self.assertEqual(b"text/html", mime_type) - + with open(os.path.join("webroot", "a_web_page.html"), "rb") as f: self.assertEqual(f.read(), content)