diff --git a/docs/source/simulation_components/system/web_browser_and_web_server_service.rst b/docs/source/simulation_components/system/web_browser_and_web_server_service.rst index a02ac621..d2bde80e 100644 --- a/docs/source/simulation_components/system/web_browser_and_web_server_service.rst +++ b/docs/source/simulation_components/system/web_browser_and_web_server_service.rst @@ -25,8 +25,8 @@ Usage Implementation ^^^^^^^^^^^^^^ -- HTTP request uses a ``HTTPRequestPacket`` object -- HTTP reaponse uses a ``HTTPResponsePacket`` object +- HTTP request uses a ``HttpRequestPacket`` object +- HTTP response uses a ``HttpResponsePacket`` object - Extends Service class for integration with ``SoftwareManager``. Web Browser (Web Client) diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py index 23a7ab1a..e465e08a 100644 --- a/src/primaite/simulator/network/networks.py +++ b/src/primaite/simulator/network/networks.py @@ -12,7 +12,7 @@ from primaite.simulator.system.applications.database_client import DatabaseClien from primaite.simulator.system.services.database.database_service import DatabaseService from primaite.simulator.system.services.dns.dns_server import DNSServer from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot -from primaite.simulator.system.services.web_server.web_server_service import WebServer +from primaite.simulator.system.services.web_server.web_server import WebServer def client_server_routed() -> Network: diff --git a/src/primaite/simulator/network/protocols/http.py b/src/primaite/simulator/network/protocols/http.py index 4be0ed88..2dba2614 100644 --- a/src/primaite/simulator/network/protocols/http.py +++ b/src/primaite/simulator/network/protocols/http.py @@ -3,7 +3,7 @@ from enum import Enum from primaite.simulator.network.protocols.packet import DataPacket -class HTTPRequestMethod(Enum): +class HttpRequestMethod(Enum): """Enum list of HTTP Request methods that can be handled by the simulation.""" GET = "GET" @@ -25,7 +25,7 @@ class HTTPRequestMethod(Enum): """Apply partial modifications to a resource.""" -class HTTPStatusCode(Enum): +class HttpStatusCode(Enum): """List of available HTTP Statuses.""" OK = 200 @@ -37,6 +37,9 @@ class HTTPStatusCode(Enum): UNAUTHORIZED = 401 """Auth required.""" + NOT_FOUND = 404 + """Item not found in server.""" + METHOD_NOT_ALLOWED = 405 """Method is not supported by server.""" @@ -44,18 +47,18 @@ class HTTPStatusCode(Enum): """Error on the server side.""" -class HTTPRequestPacket(DataPacket): +class HttpRequestPacket(DataPacket): """Class that represents an HTTP Request Packet.""" - request_method: HTTPRequestMethod + request_method: HttpRequestMethod """The HTTP Request method.""" request_url: str """URL of request.""" -class HTTPResponsePacket(DataPacket): +class HttpResponsePacket(DataPacket): """Class that reprensents an HTTP Response Packet.""" - status_code: HTTPStatusCode = None + status_code: HttpStatusCode = None """Status code of the HTTP response.""" diff --git a/src/primaite/simulator/system/applications/web_browser.py b/src/primaite/simulator/system/applications/web_browser.py index 69de333e..4f6b81c1 100644 --- a/src/primaite/simulator/system/applications/web_browser.py +++ b/src/primaite/simulator/system/applications/web_browser.py @@ -2,7 +2,7 @@ from ipaddress import IPv4Address from typing import Dict, Optional from urllib.parse import urlparse -from primaite.simulator.network.protocols.http import HTTPRequestMethod, HTTPRequestPacket, HTTPResponsePacket +from primaite.simulator.network.protocols.http import HttpRequestMethod, HttpRequestPacket, HttpResponsePacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.applications.application import Application @@ -19,7 +19,7 @@ class WebBrowser(Application): domain_name_ip_address: Optional[IPv4Address] = None "The IP address of the domain name for the webpage." - latest_response: HTTPResponsePacket = None + latest_response: HttpResponsePacket = None """Keeps track of the latest HTTP response.""" def __init__(self, **kwargs): @@ -87,7 +87,7 @@ class WebBrowser(Application): return False # create HTTPRequest payload - payload = HTTPRequestPacket(request_method=HTTPRequestMethod.GET, request_url=url) + payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url=url) # send request return self.send( @@ -98,7 +98,7 @@ class WebBrowser(Application): def send( self, - payload: HTTPRequestPacket, + payload: HttpRequestPacket, dest_ip_address: Optional[IPv4Address] = None, dest_port: Optional[Port] = Port.HTTP, session_id: Optional[str] = None, @@ -120,7 +120,7 @@ class WebBrowser(Application): payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs ) - def receive(self, payload: HTTPResponsePacket, session_id: Optional[str] = None, **kwargs) -> bool: + def receive(self, payload: HttpResponsePacket, session_id: Optional[str] = None, **kwargs) -> bool: """ Receives a payload from the SessionManager. @@ -128,8 +128,8 @@ class WebBrowser(Application): :param session_id: The Session ID the payload is to originate from. Optional. :return: True if successful, False otherwise. """ - if not isinstance(payload, HTTPResponsePacket): - self.sys_log.error(f"{self.name} received a packet that is not an HTTPResponsePacket") + if not isinstance(payload, HttpResponsePacket): + self.sys_log.error(f"{self.name} received a packet that is not an HttpResponsePacket") return False self.sys_log.info(f"{self.name}: Received HTTP {payload.status_code.value}") self.latest_response = payload diff --git a/src/primaite/simulator/system/services/web_server/web_server_service.py b/src/primaite/simulator/system/services/web_server/web_server.py similarity index 78% rename from src/primaite/simulator/system/services/web_server/web_server_service.py rename to src/primaite/simulator/system/services/web_server/web_server.py index 59686388..4566f3b3 100644 --- a/src/primaite/simulator/system/services/web_server/web_server_service.py +++ b/src/primaite/simulator/system/services/web_server/web_server.py @@ -3,10 +3,10 @@ from typing import Any, Optional from urllib.parse import urlparse from primaite.simulator.network.protocols.http import ( - HTTPRequestMethod, - HTTPRequestPacket, - HTTPResponsePacket, - HTTPStatusCode, + HttpRequestMethod, + HttpRequestPacket, + HttpResponsePacket, + HttpStatusCode, ) from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port @@ -37,52 +37,52 @@ class WebServer(Service): # index HTML main file self.file_system.create_file(file_name="index.html", folder_name="primaite", real=True) - def _process_http_request(self, payload: HTTPRequestPacket, session_id: Optional[str] = None) -> bool: + def _process_http_request(self, payload: HttpRequestPacket, session_id: Optional[str] = None) -> bool: """ - Parse the HTTPRequestPacket. + Parse the HttpRequestPacket. - :param: payload: Payload containing th HTTPRequestPacket - :type: payload: HTTPRequestPacket + :param: payload: Payload containing th HttpRequestPacket + :type: payload: HttpRequestPacket :param: session_id: Session id of the http request :type: session_id: Optional[str] """ - response = HTTPResponsePacket() + response = HttpResponsePacket() self.sys_log.info(f"{self.name}: Received HTTP {payload.request_method.name} {payload.request_url}") # check the type of HTTP request - if payload.request_method == HTTPRequestMethod.GET: + if payload.request_method == HttpRequestMethod.GET: response = self._handle_get_request(payload=payload) - elif payload.request_method == HTTPRequestMethod.POST: + elif payload.request_method == HttpRequestMethod.POST: pass else: # send a method not allowed response - response.status_code = HTTPStatusCode.METHOD_NOT_ALLOWED + response.status_code = HttpStatusCode.METHOD_NOT_ALLOWED # send response to web client self.send(payload=response, session_id=session_id) # return true if response is OK - return response.status_code == HTTPStatusCode.OK + return response.status_code == HttpStatusCode.OK - def _handle_get_request(self, payload: HTTPRequestPacket) -> HTTPResponsePacket: + def _handle_get_request(self, payload: HttpRequestPacket) -> HttpResponsePacket: """ Handle a GET HTTP request. :param: payload: HTTP request payload - :type: payload: HTTPRequestPacket + :type: payload: HttpRequestPacket """ - response = HTTPResponsePacket(status_code=HTTPStatusCode.BAD_REQUEST, payload=payload) + response = HttpResponsePacket(status_code=HttpStatusCode.NOT_FOUND, payload=payload) try: parsed_url = urlparse(payload.request_url) path = parsed_url.path.strip("/") if len(path) < 1: # query succeeded - response.status_code = HTTPStatusCode.OK + response.status_code = HttpStatusCode.OK if path.startswith("users"): # get data from DatabaseServer @@ -90,17 +90,17 @@ class WebServer(Service): # get all users if db_client.query("SELECT * FROM user;"): # query succeeded - response.status_code = HTTPStatusCode.OK + response.status_code = HttpStatusCode.OK return response except Exception: # something went wrong on the server - response.status_code = HTTPStatusCode.INTERNAL_SERVER_ERROR + response.status_code = HttpStatusCode.INTERNAL_SERVER_ERROR return response def send( self, - payload: HTTPResponsePacket, + payload: HttpResponsePacket, session_id: Optional[str] = None, dest_ip_address: Optional[IPv4Address] = None, dest_port: Optional[Port] = None, @@ -138,7 +138,7 @@ class WebServer(Service): :param: session_id: The id of the session. Optional. """ # check if the payload is an HTTPPacket - if not isinstance(payload, HTTPRequestPacket): + if not isinstance(payload, HttpRequestPacket): self.sys_log.error("Payload is not an HTTPPacket") return False diff --git a/tests/integration_tests/system/test_web_client_server.py b/tests/integration_tests/system/test_web_client_server.py index 8b6f4072..f4546cbf 100644 --- a/tests/integration_tests/system/test_web_client_server.py +++ b/tests/integration_tests/system/test_web_client_server.py @@ -1,6 +1,6 @@ from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.server import Server -from primaite.simulator.network.protocols.http import HTTPStatusCode +from primaite.simulator.network.protocols.http import HttpStatusCode from primaite.simulator.system.applications.application import ApplicationOperatingState from primaite.simulator.system.applications.web_browser import WebBrowser from primaite.simulator.system.services.service import ServiceOperatingState @@ -17,7 +17,7 @@ def test_web_page_home_page(uc2_network): # latest reponse should have status code 200 assert web_client.latest_response is not None - assert web_client.latest_response.status_code == HTTPStatusCode.OK + assert web_client.latest_response.status_code == HttpStatusCode.OK def test_web_page_get_users_page_request_with_domain_name(uc2_network): @@ -31,7 +31,7 @@ def test_web_page_get_users_page_request_with_domain_name(uc2_network): # latest reponse should have status code 200 assert web_client.latest_response is not None - assert web_client.latest_response.status_code == HTTPStatusCode.OK + assert web_client.latest_response.status_code == HttpStatusCode.OK def test_web_page_get_users_page_request_with_ip_address(uc2_network): @@ -49,4 +49,4 @@ def test_web_page_get_users_page_request_with_ip_address(uc2_network): # latest reponse should have status code 200 assert web_client.latest_response is not None - assert web_client.latest_response.status_code == HTTPStatusCode.OK + assert web_client.latest_response.status_code == HttpStatusCode.OK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py new file mode 100644 index 00000000..b2724369 --- /dev/null +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py @@ -0,0 +1,39 @@ +import pytest + +from primaite.simulator.network.hardware.nodes.computer import Computer +from primaite.simulator.network.protocols.http import HttpResponsePacket, HttpStatusCode +from primaite.simulator.network.transmission.network_layer import IPProtocol +from primaite.simulator.network.transmission.transport_layer import Port +from primaite.simulator.system.applications.web_browser import WebBrowser + + +@pytest.fixture(scope="function") +def web_client() -> Computer: + node = Computer( + hostname="web_client", ip_address="192.168.1.11", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" + ) + return node + + +def test_create_web_client(web_client): + assert web_client is not None + web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"] + assert web_browser.name is "WebBrowser" + assert web_browser.port is Port.HTTP + assert web_browser.protocol is IPProtocol.TCP + + +def test_receive_invalid_payload(web_client): + web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"] + + assert web_browser.receive(payload={}) is False + + +def test_receive_payload(web_client): + payload = HttpResponsePacket(status_code=HttpStatusCode.OK) + web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"] + assert web_browser.latest_response is None + + web_browser.receive(payload=payload) + + assert web_browser.latest_response is not None diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py new file mode 100644 index 00000000..e6f0b9d9 --- /dev/null +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py @@ -0,0 +1,64 @@ +import pytest + +from primaite.simulator.network.hardware.nodes.server import Server +from primaite.simulator.network.protocols.http import ( + HttpRequestMethod, + HttpRequestPacket, + HttpResponsePacket, + HttpStatusCode, +) +from primaite.simulator.network.transmission.network_layer import IPProtocol +from primaite.simulator.network.transmission.transport_layer import Port +from primaite.simulator.system.services.web_server.web_server import WebServer + + +@pytest.fixture(scope="function") +def web_server() -> Server: + node = Server( + hostname="web_server", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.1" + ) + node.software_manager.install(software_class=WebServer) + node.software_manager.software["WebServer"].start() + return node + + +def test_create_web_server(web_server): + assert web_server is not None + web_server_service: WebServer = web_server.software_manager.software["WebServer"] + assert web_server_service.name is "WebServer" + assert web_server_service.port is Port.HTTP + assert web_server_service.protocol is IPProtocol.TCP + + +def test_handling_get_request_not_found_path(web_server): + payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/fake-path") + + web_server_service: WebServer = web_server.software_manager.software["WebServer"] + + response: HttpResponsePacket = web_server_service._handle_get_request(payload=payload) + assert response.status_code == HttpStatusCode.NOT_FOUND + + +def test_handling_get_request_home_page(web_server): + payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/") + + web_server_service: WebServer = web_server.software_manager.software["WebServer"] + + response: HttpResponsePacket = web_server_service._handle_get_request(payload=payload) + assert response.status_code == HttpStatusCode.OK + + +def test_process_http_request_get(web_server): + payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/") + + web_server_service: WebServer = web_server.software_manager.software["WebServer"] + + assert web_server_service._process_http_request(payload=payload) is True + + +def test_process_http_request_method_not_allowed(web_server): + payload = HttpRequestPacket(request_method=HttpRequestMethod.DELETE, request_url="http://domain.com/") + + web_server_service: WebServer = web_server.software_manager.software["WebServer"] + + assert web_server_service._process_http_request(payload=payload) is False