From 77bc821c38d3a501630d821a04c8c883fea99dc3 Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Thu, 13 Apr 2023 23:23:13 +0000 Subject: [PATCH 1/9] Moved root_path from start and server_forever methods to constructor --- adafruit_httpserver/server.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/adafruit_httpserver/server.py b/adafruit_httpserver/server.py index 84bb256..8f0cf3e 100644 --- a/adafruit_httpserver/server.py +++ b/adafruit_httpserver/server.py @@ -26,18 +26,19 @@ class HTTPServer: """A basic socket-based HTTP server.""" - def __init__(self, socket_source: Protocol) -> None: + def __init__(self, socket_source: Protocol, root_path: str) -> None: """Create a server, and get it ready to run. :param socket: An object that is a source of sockets. This could be a `socketpool` in CircuitPython or the `socket` module in CPython. + :param str root_path: Root directory to serve files from """ self._buffer = bytearray(1024) self._timeout = 1 self.routes = _HTTPRoutes() self._socket_source = socket_source self._sock = None - self.root_path = "/" + self.root_path = root_path def route(self, path: str, method: HTTPMethod = HTTPMethod.GET) -> Callable: """ @@ -63,14 +64,13 @@ def route_decorator(func: Callable) -> Callable: return route_decorator - def serve_forever(self, host: str, port: int = 80, root_path: str = "") -> None: + def serve_forever(self, host: str, port: int = 80) -> None: """Wait for HTTP requests at the given host and port. Does not return. :param str host: host name or IP address :param int port: port - :param str root_path: root directory to serve files from """ - self.start(host, port, root_path) + self.start(host, port) while True: try: @@ -78,17 +78,14 @@ def serve_forever(self, host: str, port: int = 80, root_path: str = "") -> None: except OSError: continue - def start(self, host: str, port: int = 80, root_path: str = "") -> None: + def start(self, host: str, port: int = 80) -> None: """ Start the HTTP server at the given host and port. Requires calling poll() in a while loop to handle incoming requests. :param str host: host name or IP address :param int port: port - :param str root_path: root directory to serve files from """ - self.root_path = root_path - self._sock = self._socket_source.socket( self._socket_source.AF_INET, self._socket_source.SOCK_STREAM ) From e7a2debc670fd23567a109b6507bf4dea1fbb0c6 Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Fri, 14 Apr 2023 02:53:09 +0000 Subject: [PATCH 2/9] Minor changes in comments --- adafruit_httpserver/server.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/adafruit_httpserver/server.py b/adafruit_httpserver/server.py index 8f0cf3e..4ea862d 100644 --- a/adafruit_httpserver/server.py +++ b/adafruit_httpserver/server.py @@ -155,6 +155,7 @@ def poll(self): conn, received_body_bytes, content_length ) + # Find a handler for the route handler = self.routes.find_handler( _HTTPRoute(request.path, request.method) ) @@ -176,13 +177,13 @@ def poll(self): request, status=CommonHTTPStatus.BAD_REQUEST_400 ).send() - except OSError as ex: - # handle EAGAIN and ECONNRESET - if ex.errno == EAGAIN: - # there is no data available right now, try again later. + except OSError as error: + # Handle EAGAIN and ECONNRESET + if error.errno == EAGAIN: + # There is no data available right now, try again later. return - if ex.errno == ECONNRESET: - # connection reset by peer, try again later. + if error.errno == ECONNRESET: + # Connection reset by peer, try again later. return raise From d89e66e78ead6c0d57d948d08b9b2c2047c38169 Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Fri, 14 Apr 2023 02:55:46 +0000 Subject: [PATCH 3/9] Added checking for .. and backslash in path, introduced custom exceptions --- adafruit_httpserver/exceptions.py | 52 ++++++++++++++++++++++++++++++ adafruit_httpserver/response.py | 53 +++++++++++++++++++++++++------ adafruit_httpserver/server.py | 42 +++++++++++++++--------- docs/api.rst | 3 ++ 4 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 adafruit_httpserver/exceptions.py diff --git a/adafruit_httpserver/exceptions.py b/adafruit_httpserver/exceptions.py new file mode 100644 index 0000000..8152719 --- /dev/null +++ b/adafruit_httpserver/exceptions.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries +# +# SPDX-License-Identifier: MIT +""" +`adafruit_httpserver.exceptions` +==================================================== +* Author(s): MichaƂ Pokusa +""" + + +class InvalidPathError(Exception): + """ + Parent class for all path related errors. + """ + + +class ParentDirectoryReferenceError(InvalidPathError): + """ + Path contains ``..``, a reference to the parent directory. + """ + + def __init__(self, path: str) -> None: + """Creates a new ``ParentDirectoryReferenceError`` for the ``path``.""" + super().__init__(f"Parent directory reference in path: {path}") + + +class BackslashInPathError(InvalidPathError): + """ + Backslash ``\\`` in path. + """ + + def __init__(self, path: str) -> None: + """Creates a new ``BackslashInPathError`` for the ``path``.""" + super().__init__(f"Backslash in path: {path}") + + +class ResponseAlreadySentException(Exception): + """ + Another ``HTTPResponse`` has already been sent. There can only be one per ``HTTPRequest``. + """ + + +class FileNotExistsError(Exception): + """ + Raised when a file does not exist. + """ + + def __init__(self, path: str) -> None: + """ + Creates a new ``FileNotExistsError`` for the file at ``path``. + """ + super().__init__(f"File does not exist: {path}") diff --git a/adafruit_httpserver/response.py b/adafruit_httpserver/response.py index fbb2e3f..cf29b97 100644 --- a/adafruit_httpserver/response.py +++ b/adafruit_httpserver/response.py @@ -17,6 +17,12 @@ import os from errno import EAGAIN, ECONNRESET +from .exceptions import ( + BackslashInPathError, + FileNotExistsError, + ParentDirectoryReferenceError, + ResponseAlreadySentException, +) from .mime_type import MIMEType from .request import HTTPRequest from .status import HTTPStatus, CommonHTTPStatus @@ -153,7 +159,7 @@ def send( Should be called **only once** per response. """ if self._response_already_sent: - raise RuntimeError("Response was already sent") + raise ResponseAlreadySentException if getattr(body, "encode", None): encoded_response_message_body = body.encode("utf-8") @@ -167,11 +173,39 @@ def send( self._send_bytes(self.request.connection, encoded_response_message_body) self._response_already_sent = True + @staticmethod + def _check_file_path_is_valid(file_path: str) -> bool: + """ + Checks if ``file_path`` is valid. + If not raises error corresponding to the problem. + """ + + # Check for backslashes + if "\\" in file_path: # pylint: disable=anomalous-backslash-in-string + raise BackslashInPathError(file_path) + + # Check each component of the path for parent directory references + for part in file_path.split("/"): + if part == "..": + raise ParentDirectoryReferenceError(file_path) + + @staticmethod + def _get_file_length(file_path: str) -> int: + """ + Tries to get the length of the file at ``file_path``. + Raises ``FileNotExistsError`` if file does not exist. + """ + try: + return os.stat(file_path)[6] + except OSError: + raise FileNotExistsError(file_path) # pylint: disable=raise-missing-from + def send_file( self, filename: str = "index.html", root_path: str = "./", buffer_size: int = 1024, + safe: bool = True, ) -> None: """ Send response with content of ``filename`` located in ``root_path``. @@ -181,23 +215,24 @@ def send_file( Should be called **only once** per response. """ if self._response_already_sent: - raise RuntimeError("Response was already sent") + raise ResponseAlreadySentException + + if safe: + self._check_file_path_is_valid(filename) if not root_path.endswith("/"): root_path += "/" - try: - file_length = os.stat(root_path + filename)[6] - except OSError: - # If the file doesn't exist, return 404. - HTTPResponse(self.request, status=CommonHTTPStatus.NOT_FOUND_404).send() - return + + full_file_path = root_path + filename + + file_length = self._get_file_length(full_file_path) self._send_headers( content_type=MIMEType.from_file_name(filename), content_length=file_length, ) - with open(root_path + filename, "rb") as file: + with open(full_file_path, "rb") as file: while bytes_read := file.read(buffer_size): self._send_bytes(self.request.connection, bytes_read) self._response_already_sent = True diff --git a/adafruit_httpserver/server.py b/adafruit_httpserver/server.py index 4ea862d..513d193 100644 --- a/adafruit_httpserver/server.py +++ b/adafruit_httpserver/server.py @@ -16,6 +16,7 @@ from errno import EAGAIN, ECONNRESET, ETIMEDOUT +from .exceptions import FileNotExistsError, InvalidPathError from .methods import HTTPMethod from .request import HTTPRequest from .response import HTTPResponse @@ -160,22 +161,33 @@ def poll(self): _HTTPRoute(request.path, request.method) ) - # If a handler for route exists and is callable, call it. - if handler is not None and callable(handler): - handler(request) - - # If no handler exists and request method is GET, try to serve a file. - elif handler is None and request.method == HTTPMethod.GET: - filename = "index.html" if request.path == "/" else request.path - HTTPResponse(request).send_file( - filename=filename, - root_path=self.root_path, - buffer_size=self.request_buffer_size, + try: + # If a handler for route exists and is callable, call it. + if handler is not None and callable(handler): + handler(request) + + # If no handler exists and request method is GET, try to serve a file. + elif handler is None and request.method == HTTPMethod.GET: + filename = "index.html" if request.path == "/" else request.path + HTTPResponse(request).send_file( + filename=filename, + root_path=self.root_path, + buffer_size=self.request_buffer_size, + ) + else: + HTTPResponse( + request, status=CommonHTTPStatus.BAD_REQUEST_400 + ).send() + + except InvalidPathError as error: + HTTPResponse(request, status=CommonHTTPStatus.FORBIDDEN_403).send( + str(error) + ) + + except FileNotExistsError as error: + HTTPResponse(request, status=CommonHTTPStatus.NOT_FOUND_404).send( + str(error) ) - else: - HTTPResponse( - request, status=CommonHTTPStatus.BAD_REQUEST_400 - ).send() except OSError as error: # Handle EAGAIN and ECONNRESET diff --git a/docs/api.rst b/docs/api.rst index 4615507..64bb534 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -27,3 +27,6 @@ .. automodule:: adafruit_httpserver.mime_type :members: + +.. automodule:: adafruit_httpserver.exceptions + :members: From 0467a276e22a23737faba07c733eeff9ccb3a776 Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Fri, 14 Apr 2023 07:08:09 +0000 Subject: [PATCH 4/9] Updated HTTPServer constructors to use "/static" as root path --- adafruit_httpserver/server.py | 4 ++-- examples/httpserver_chunked.py | 2 +- examples/httpserver_cpu_information.py | 2 +- examples/httpserver_mdns.py | 2 +- examples/httpserver_neopixel.py | 2 +- examples/httpserver_simple_poll.py | 2 +- examples/httpserver_simple_serve.py | 2 +- examples/httpserver_url_parameters.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/adafruit_httpserver/server.py b/adafruit_httpserver/server.py index 513d193..9e7af45 100644 --- a/adafruit_httpserver/server.py +++ b/adafruit_httpserver/server.py @@ -210,7 +210,7 @@ def request_buffer_size(self) -> int: Example:: - server = HTTPServer(pool) + server = HTTPServer(pool, "/static") server.request_buffer_size = 2048 server.serve_forever(str(wifi.radio.ipv4_address)) @@ -232,7 +232,7 @@ def socket_timeout(self) -> int: Example:: - server = HTTPServer(pool) + server = HTTPServer(pool, "/static") server.socket_timeout = 3 server.serve_forever(str(wifi.radio.ipv4_address)) diff --git a/examples/httpserver_chunked.py b/examples/httpserver_chunked.py index ae519ec..40781c4 100644 --- a/examples/httpserver_chunked.py +++ b/examples/httpserver_chunked.py @@ -19,7 +19,7 @@ print("Connected to", ssid) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") @server.route("/chunked") diff --git a/examples/httpserver_cpu_information.py b/examples/httpserver_cpu_information.py index cf3d13b..a9360d6 100644 --- a/examples/httpserver_cpu_information.py +++ b/examples/httpserver_cpu_information.py @@ -22,7 +22,7 @@ print("Connected to", ssid) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") @server.route("/cpu-information") diff --git a/examples/httpserver_mdns.py b/examples/httpserver_mdns.py index d2228c9..ea0f2d9 100644 --- a/examples/httpserver_mdns.py +++ b/examples/httpserver_mdns.py @@ -25,7 +25,7 @@ mdns_server.advertise_service(service_type="_http", protocol="_tcp", port=80) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") @server.route("/") diff --git a/examples/httpserver_neopixel.py b/examples/httpserver_neopixel.py index 814a7af..838251f 100644 --- a/examples/httpserver_neopixel.py +++ b/examples/httpserver_neopixel.py @@ -22,7 +22,7 @@ print("Connected to", ssid) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) diff --git a/examples/httpserver_simple_poll.py b/examples/httpserver_simple_poll.py index db876c4..12d62a1 100644 --- a/examples/httpserver_simple_poll.py +++ b/examples/httpserver_simple_poll.py @@ -20,7 +20,7 @@ print("Connected to", ssid) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") @server.route("/") diff --git a/examples/httpserver_simple_serve.py b/examples/httpserver_simple_serve.py index 632c234..506b5f6 100644 --- a/examples/httpserver_simple_serve.py +++ b/examples/httpserver_simple_serve.py @@ -20,7 +20,7 @@ print("Connected to", ssid) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") @server.route("/") diff --git a/examples/httpserver_url_parameters.py b/examples/httpserver_url_parameters.py index 2f95163..abd9541 100644 --- a/examples/httpserver_url_parameters.py +++ b/examples/httpserver_url_parameters.py @@ -20,7 +20,7 @@ print("Connected to", ssid) pool = socketpool.SocketPool(wifi.radio) -server = HTTPServer(pool) +server = HTTPServer(pool, "/static") class Device: From c24466be9d6e9741922c1c826d562f56570be8be Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Fri, 14 Apr 2023 07:10:47 +0000 Subject: [PATCH 5/9] Changed secrets to settings.toml in examples --- examples/httpserver_chunked.py | 5 +++-- examples/httpserver_cpu_information.py | 5 +++-- examples/httpserver_mdns.py | 5 +++-- examples/httpserver_neopixel.py | 5 +++-- examples/httpserver_simple_poll.py | 5 +++-- examples/httpserver_simple_serve.py | 5 +++-- examples/httpserver_url_parameters.py | 5 +++-- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/examples/httpserver_chunked.py b/examples/httpserver_chunked.py index 40781c4..47d00c6 100644 --- a/examples/httpserver_chunked.py +++ b/examples/httpserver_chunked.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import socketpool import wifi @@ -12,7 +12,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_cpu_information.py b/examples/httpserver_cpu_information.py index a9360d6..ac35afc 100644 --- a/examples/httpserver_cpu_information.py +++ b/examples/httpserver_cpu_information.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import json import microcontroller @@ -15,7 +15,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_mdns.py b/examples/httpserver_mdns.py index ea0f2d9..52cdea0 100644 --- a/examples/httpserver_mdns.py +++ b/examples/httpserver_mdns.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import mdns import socketpool @@ -14,7 +14,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_neopixel.py b/examples/httpserver_neopixel.py index 838251f..1837439 100644 --- a/examples/httpserver_neopixel.py +++ b/examples/httpserver_neopixel.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import board import neopixel @@ -15,7 +15,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_simple_poll.py b/examples/httpserver_simple_poll.py index 12d62a1..af29014 100644 --- a/examples/httpserver_simple_poll.py +++ b/examples/httpserver_simple_poll.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import socketpool import wifi @@ -13,7 +13,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_simple_serve.py b/examples/httpserver_simple_serve.py index 506b5f6..74f5479 100644 --- a/examples/httpserver_simple_serve.py +++ b/examples/httpserver_simple_serve.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import socketpool import wifi @@ -13,7 +13,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_url_parameters.py b/examples/httpserver_url_parameters.py index abd9541..e97cac1 100644 --- a/examples/httpserver_url_parameters.py +++ b/examples/httpserver_url_parameters.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Unlicense -import secrets # pylint: disable=no-name-in-module +import os import socketpool import wifi @@ -13,7 +13,8 @@ from adafruit_httpserver.server import HTTPServer -ssid, password = secrets.WIFI_SSID, secrets.WIFI_PASSWORD # pylint: disable=no-member +ssid = os.environ.get("WIFI_SSID") +password = os.environ.get("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) From a545ca73d2e6a9dad75c570646de8efa73f9fa69 Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Fri, 14 Apr 2023 08:42:09 +0000 Subject: [PATCH 6/9] Added missing FORBIDDEN_403 --- adafruit_httpserver/status.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adafruit_httpserver/status.py b/adafruit_httpserver/status.py index d32538c..8a7b198 100644 --- a/adafruit_httpserver/status.py +++ b/adafruit_httpserver/status.py @@ -39,6 +39,9 @@ class CommonHTTPStatus(HTTPStatus): # pylint: disable=too-few-public-methods BAD_REQUEST_400 = HTTPStatus(400, "Bad Request") """400 Bad Request""" + FORBIDDEN_403 = HTTPStatus(403, "Forbidden") + """403 Forbidden""" + NOT_FOUND_404 = HTTPStatus(404, "Not Found") """404 Not Found""" From 8a4f5c4bbe81af085e94284bfe88534c0033b2ac Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Sun, 16 Apr 2023 10:58:23 +0000 Subject: [PATCH 7/9] Extracted multiple send calls logic into decorator --- adafruit_httpserver/exceptions.py | 2 +- adafruit_httpserver/response.py | 33 +++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/adafruit_httpserver/exceptions.py b/adafruit_httpserver/exceptions.py index 8152719..ca70712 100644 --- a/adafruit_httpserver/exceptions.py +++ b/adafruit_httpserver/exceptions.py @@ -34,7 +34,7 @@ def __init__(self, path: str) -> None: super().__init__(f"Backslash in path: {path}") -class ResponseAlreadySentException(Exception): +class ResponseAlreadySentError(Exception): """ Another ``HTTPResponse`` has already been sent. There can only be one per ``HTTPRequest``. """ diff --git a/adafruit_httpserver/response.py b/adafruit_httpserver/response.py index cf29b97..8ea2e7c 100644 --- a/adafruit_httpserver/response.py +++ b/adafruit_httpserver/response.py @@ -8,7 +8,7 @@ """ try: - from typing import Optional, Dict, Union, Tuple + from typing import Optional, Dict, Union, Tuple, Callable from socket import socket from socketpool import SocketPool except ImportError: @@ -21,7 +21,7 @@ BackslashInPathError, FileNotExistsError, ParentDirectoryReferenceError, - ResponseAlreadySentException, + ResponseAlreadySentError, ) from .mime_type import MIMEType from .request import HTTPRequest @@ -29,6 +29,21 @@ from .headers import HTTPHeaders +def _prevent_multiple_send_calls(function: Callable): + """ + Decorator that prevents calling ``send`` or ``send_file`` more than once. + """ + + def wrapper(self: "HTTPResponse", *args, **kwargs): + if self._response_already_sent: # pylint: disable=protected-access + raise ResponseAlreadySentError + + result = function(self, *args, **kwargs) + return result + + return wrapper + + class HTTPResponse: """ Response to a given `HTTPRequest`. Use in `HTTPServer.route` decorator functions. @@ -79,8 +94,8 @@ def route_func(request): """ Defaults to ``text/plain`` if not set. - Can be explicitly provided in the constructor, in `send()` or - implicitly determined from filename in `send_file()`. + Can be explicitly provided in the constructor, in ``send()`` or + implicitly determined from filename in ``send_file()``. Common MIME types are defined in `adafruit_httpserver.mime_type.MIMEType`. """ @@ -100,7 +115,7 @@ def __init__( # pylint: disable=too-many-arguments Sets `status`, ``headers`` and `http_version` and optionally default ``content_type``. - To send the response, call `send` or `send_file`. + To send the response, call ``send`` or ``send_file``. For chunked response use ``with HTTPRequest(request, content_type=..., chunked=True) as r:`` and `send_chunk`. """ @@ -121,7 +136,7 @@ def _send_headers( ) -> None: """ Sends headers. - Implicitly called by `send` and `send_file` and in + Implicitly called by ``send`` and ``send_file`` and in ``with HTTPResponse(request, chunked=True) as response:`` context manager. """ headers = self.headers.copy() @@ -147,6 +162,7 @@ def _send_headers( self.request.connection, response_message_header.encode("utf-8") ) + @_prevent_multiple_send_calls def send( self, body: str = "", @@ -158,8 +174,6 @@ def send( Should be called **only once** per response. """ - if self._response_already_sent: - raise ResponseAlreadySentException if getattr(body, "encode", None): encoded_response_message_body = body.encode("utf-8") @@ -200,6 +214,7 @@ def _get_file_length(file_path: str) -> int: except OSError: raise FileNotExistsError(file_path) # pylint: disable=raise-missing-from + @_prevent_multiple_send_calls def send_file( self, filename: str = "index.html", @@ -214,8 +229,6 @@ def send_file( Should be called **only once** per response. """ - if self._response_already_sent: - raise ResponseAlreadySentException if safe: self._check_file_path_is_valid(filename) From bef9f76720f77a98241221aae009733d7551751c Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:15:41 +0000 Subject: [PATCH 8/9] Removing unnecessary slash in front of filename --- adafruit_httpserver/response.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adafruit_httpserver/response.py b/adafruit_httpserver/response.py index 8ea2e7c..24bb596 100644 --- a/adafruit_httpserver/response.py +++ b/adafruit_httpserver/response.py @@ -235,6 +235,8 @@ def send_file( if not root_path.endswith("/"): root_path += "/" + if filename.startswith("/"): + filename = filename[1:] full_file_path = root_path + filename From 9e173efdbcedb6ca77871a8a587e2e18329d65ef Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Sat, 22 Apr 2023 18:31:34 +0000 Subject: [PATCH 9/9] Replaced os.environ to os.getenv --- examples/httpserver_chunked.py | 4 ++-- examples/httpserver_cpu_information.py | 4 ++-- examples/httpserver_mdns.py | 4 ++-- examples/httpserver_neopixel.py | 4 ++-- examples/httpserver_simple_poll.py | 4 ++-- examples/httpserver_simple_serve.py | 4 ++-- examples/httpserver_url_parameters.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/httpserver_chunked.py b/examples/httpserver_chunked.py index 47d00c6..ed67fc6 100644 --- a/examples/httpserver_chunked.py +++ b/examples/httpserver_chunked.py @@ -12,8 +12,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_cpu_information.py b/examples/httpserver_cpu_information.py index ac35afc..41d7a05 100644 --- a/examples/httpserver_cpu_information.py +++ b/examples/httpserver_cpu_information.py @@ -15,8 +15,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_mdns.py b/examples/httpserver_mdns.py index 52cdea0..bebdc2a 100644 --- a/examples/httpserver_mdns.py +++ b/examples/httpserver_mdns.py @@ -14,8 +14,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_neopixel.py b/examples/httpserver_neopixel.py index 1837439..baff3de 100644 --- a/examples/httpserver_neopixel.py +++ b/examples/httpserver_neopixel.py @@ -15,8 +15,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_simple_poll.py b/examples/httpserver_simple_poll.py index af29014..1ed5027 100644 --- a/examples/httpserver_simple_poll.py +++ b/examples/httpserver_simple_poll.py @@ -13,8 +13,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_simple_serve.py b/examples/httpserver_simple_serve.py index 74f5479..226d8f2 100644 --- a/examples/httpserver_simple_serve.py +++ b/examples/httpserver_simple_serve.py @@ -13,8 +13,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password) diff --git a/examples/httpserver_url_parameters.py b/examples/httpserver_url_parameters.py index e97cac1..22f9f3b 100644 --- a/examples/httpserver_url_parameters.py +++ b/examples/httpserver_url_parameters.py @@ -13,8 +13,8 @@ from adafruit_httpserver.server import HTTPServer -ssid = os.environ.get("WIFI_SSID") -password = os.environ.get("WIFI_PASSWORD") +ssid = os.getenv("WIFI_SSID") +password = os.getenv("WIFI_PASSWORD") print("Connecting to", ssid) wifi.radio.connect(ssid, password)