about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--main.py29
-rw-r--r--src/APIv1.py22
-rw-r--r--src/config.py2
-rw-r--r--src/request.py21
-rw-r--r--www/test.html11
5 files changed, 56 insertions, 29 deletions
diff --git a/main.py b/main.py
index fdb0432..db3a716 100644
--- a/main.py
+++ b/main.py
@@ -4,15 +4,16 @@ The mighty silly webserver written in python for no good reason
 
 
 import ssl
-import gzip
+# import zlib
 import time
 import socket
-import brotli
+# import brotli
 import signal
 import threading
 from src import APIv1
 from src.request import *
 from src.status_code import *
+from src.config import BUFFER_LENGTH
 from src.minimizer import minimize_html
 
 
@@ -44,7 +45,7 @@ class HTTPServer:
     Now uses threading
     """
 
-    def __init__(self, *, port: int, packet_size: int = 2048):
+    def __init__(self, *, port: int, packet_size: int = BUFFER_LENGTH):
         # SSL context
         context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
         context.check_hostname = False
@@ -144,27 +145,27 @@ class HTTPServer:
                 response = Response(data, STATUS_CODE_NOT_FOUND)
 
         # process header data
-        if response.headers.get("Content-Encoding") is None and response.compress:
-            supported_compressions = [x.strip() for x in getattr(request, "Accept-Encoding", "").split(",")]
-            if "br" in supported_compressions:
-                response.headers["Content-Encoding"] = "br"
-                response.data = brotli.compress(response.data)
-            elif "gzip" in supported_compressions:
-                response.headers["Content-Encoding"] = "gzip"
-                response.data = gzip.compress(response.data)
+        # if response.headers.get("Content-Encoding") is None and response.compress:
+        #     supported_compressions = [x.strip() for x in getattr(request, "Accept-Encoding", "").split(",")]
+        #     if "br" in supported_compressions:
+        #         response.headers["Content-Encoding"] = "br"
+        #     elif "gzip" in supported_compressions:
+        #         response.headers["Content-Encoding"] = "gzip"
         if response.headers.get("Content-Length") is None:
             response.headers["Content-Length"] = len(response.data)
         if response.headers.get("Connection") is None:
             response.headers["Connection"] = "close"
 
-            # generate basic message
+        # generate basic message
         message = b'HTTP/1.1 ' + response.status.__bytes__() + b'\r\n'
         for key, value in response.headers.items():
             message += f"{key}: {value}\r\n".encode("ascii")
-        message += b'\r\n' + response.data
+        message += b'\r\n'
 
         # send message
         client.sendall(message)
+        for packet in response.get_data_stream():
+            client.sendall(packet)
 
     def _handle_get(self, client: ssl.SSLSocket, request: Request) -> Response:
         """
@@ -231,7 +232,7 @@ class HTTPServer:
             try:
                 return self.sock.accept()[0]
             except BlockingIOError:
-                time.sleep(0.001)
+                time.sleep(0.005)
         return None
 
 
diff --git a/src/APIv1.py b/src/APIv1.py
index f163c5b..c396825 100644
--- a/src/APIv1.py
+++ b/src/APIv1.py
@@ -1,4 +1,5 @@
 import random
+from ssl import SSLSocket
 from src.request import *
 from src.status_code import *
 
@@ -7,20 +8,19 @@ API_FILE_RANDOM_MIN_SIZE_LIMIT = 1
 API_FILE_RANDOM_MAX_SIZE_LIMIT = 2**30 * 2
 
 
-def random_data_gen(size: int) -> bytes:
+def random_data_gen(size: int, chunk_size: int = 65536) -> bytes:
     """
-    Generates SIZE bytes of random data in 64kib chunks
+    Generates SIZE bytes of random data in CHUNK_SIZE byte chunks
     :param size: bytes to generate
+    :param chunk_size: size of each chunk (bytes)
     :return: random bytes
     """
 
-    data = bytearray()
-    int_size = size // 65536
+    int_size = size // chunk_size
     for _ in range(int_size):
-        data += random.randbytes(65536)
-    data += random.randbytes((int_size * 65536) - size)
-
-    return data
+        yield random.randbytes(chunk_size)
+    if (final_size := (int_size * chunk_size) - size) > 0:
+        yield random.randbytes(final_size)
 
 
 def decode_size(size: str) -> int:
@@ -78,7 +78,11 @@ def api_call(client: SSLSocket, request: Request) -> Response:
             if size < API_FILE_RANDOM_MIN_SIZE_LIMIT or size > API_FILE_RANDOM_MAX_SIZE_LIMIT:
                 return Response(b'', STATUS_CODE_BAD_REQUEST)
 
-            return Response(random_data_gen(size), STATUS_CODE_OK, compress=False)
+            return Response(
+                b'',
+                STATUS_CODE_OK,
+                headers={"Content-Length": size},
+                data_stream=random_data_gen(size))
         else:
             return Response(b'', STATUS_CODE_BAD_REQUEST)
     else:
diff --git a/src/config.py b/src/config.py
new file mode 100644
index 0000000..e39d18c
--- /dev/null
+++ b/src/config.py
@@ -0,0 +1,2 @@
+BUFFER_LENGTH = 65536
+
diff --git a/src/request.py b/src/request.py
index 003783a..a197649 100644
--- a/src/request.py
+++ b/src/request.py
@@ -1,5 +1,5 @@
-from typing import Any
-from ssl import SSLSocket
+from typing import Any, Generator
+from src.config import BUFFER_LENGTH
 from src.status_code import StatusCode
 
 
@@ -72,10 +72,25 @@ class Response:
         :param data: response data
         :param status: response status code
         :param headers: headers to include
-        :param kwarg: compress - whether to compress data or not
+        :key compress: compress data or not
+        :key data_stream: stream of data
         """
 
         self.data: bytes = data
+        self.data_stream: Generator[bytes, None, None] | None = kwargs.get("data_stream")
         self.status: StatusCode = status
         self.headers: dict[str, Any] = headers if headers is not None else dict()
         self.compress: bool = kwargs.get("compress", True)
+
+        # check for content-length when using data_stream
+        if self.data_stream is not None and self.headers.get("Content-Length") is None:
+            raise Exception("Undefined length for data stream")
+
+    def get_data_stream(self):
+        if self.data_stream is None:
+            def generator() -> bytes:
+                for i in range(0, len(self.data), BUFFER_LENGTH):
+                    yield self.data[i:i+BUFFER_LENGTH]
+            return generator()
+        else:
+            return self.data_stream
diff --git a/www/test.html b/www/test.html
index f7b7b27..77d376d 100644
--- a/www/test.html
+++ b/www/test.html
@@ -11,10 +11,15 @@
     </header>
     <div id="section-div">
         <section>
+            <h1> APIv1 Testing page </h1>
+            <p> You are supposed to see that, it's not a bug </p>
+            <p> This is a small API / webserver testing page, so feel free to click on downloads to see if they work </p>x
+        </section>
+        <section>
             <h1> File download test </h1>
-            <p> <a href="/APIv1/file/random?size=128mib" target="_blank">Download 128 MiB file</a> </p>
-            <p> <a href="/APIv1/file/random?size=512mib" target="_blank">Download 512 MiB file</a> </p>
-            <p> <a href="/APIv1/file/random?size=1gib" target="_blank">Download 1 GiB file</a> </p>
+            <p> <a href="/APIv1/file/random?size=128mib" target="_blank">Download random 128 MiB file</a> </p>
+            <p> <a href="/APIv1/file/random?size=512mib" target="_blank">Download random 512 MiB file</a> </p>
+            <p> <a href="/APIv1/file/random?size=1gib" target="_blank">Download random 1 GiB file</a> </p>
         </section>
     </div>
     <footer>