Skip to content

Commit 5cd39a5

Browse files
authored
Merge pull request #3 from nipun172006/codex/github-cleanup
Finish fixture cleanup and expand server tests
2 parents 27570d8 + 1352694 commit 5cd39a5

7 files changed

Lines changed: 92 additions & 18 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ curl -i --path-as-is http://127.0.0.1:8080/../etc/passwd
7676
python3 -m unittest discover -s tests -v
7777
```
7878

79+
The streaming test creates a deterministic multi-chunk payload in a temporary
80+
directory. Large generated binaries are not stored in the repository.
81+
7982
## Project structure
8083

8184
```text

resources/about.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
<a href="/logo.png" download>logo.png (PNG)</a>
1919
<a href="/photo.jpg" download>photo.jpg (JPEG)</a>
2020
<a href="/photo2.jpg" download>photo2.jpg (JPEG)</a>
21-
<a href="/large.png" download>large.png (PNG ~27MB)</a>
2221
<a href="/sample.txt" download>sample.txt</a>
2322
<a href="/sample2.txt" download>sample2.txt</a>
2423
</div>
@@ -35,7 +34,7 @@ <h2 style="font-size: 20px; margin: 20px 0 10px; color: #333;">Key Features</h2>
3534
<li><strong>Socket Programming:</strong> Built on raw TCP sockets (bind, listen, accept) for client connections</li>
3635
<li><strong>Multi-threading:</strong> Custom ThreadPool with bounded queue (max 100 tasks) handles concurrent requests</li>
3736
<li><strong>HTTP Methods:</strong> Supports GET for static files and POST for JSON uploads to /upload endpoint</li>
38-
<li><strong>Binary Transfer:</strong> Serves images (PNG, JPEG) and large files (>1MB) with proper Content-Type headers</li>
37+
<li><strong>Binary Transfer:</strong> Streams PNG and JPEG fixtures with explicit download headers</li>
3938
<li><strong>Keep-Alive:</strong> Connection reuse with timeout=30s and max=100 requests per connection</li>
4039
<li><strong>Security:</strong> Path traversal protection (blocks .., ./, ~, absolute paths) and Host header validation</li>
4140
<li><strong>Error Handling:</strong> Returns 503 Service Unavailable when thread pool is saturated</li>
@@ -63,4 +62,4 @@ <h2 style="font-size: 20px; margin: 20px 0 10px; color: #333;">Project Specifica
6362
</main>
6463
<footer class="footer">Server: Multi-threaded HTTP Server</footer>
6564
</body>
66-
</html>
65+
</html>

resources/contact.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
<a href="/logo.png" download>logo.png (PNG)</a>
1919
<a href="/photo.jpg" download>photo.jpg (JPEG)</a>
2020
<a href="/photo2.jpg" download>photo2.jpg (JPEG)</a>
21-
<a href="/large.png" download>large.png (PNG ~27MB)</a>
2221
<a href="/sample.txt" download>sample.txt</a>
2322
<a href="/sample2.txt" download>sample2.txt</a>
2423
</div>
@@ -40,8 +39,7 @@ <h2 style="font-size: 20px; margin: 20px 0 10px; color: #333;">Testing Examples<
4039
<h3 style="font-size: 16px; margin: 15px 0 8px; color: #555;">GET Requests (Static Files)</h3>
4140
<pre class="code">curl -i http://127.0.0.1:8080/
4241
curl -i http://127.0.0.1:8080/sample.txt
43-
curl -i http://127.0.0.1:8080/photo.jpg --output photo.jpg
44-
curl -i http://127.0.0.1:8080/large.png --output large.png</pre>
42+
curl -i http://127.0.0.1:8080/photo.jpg --output photo.jpg</pre>
4543

4644
<h3 style="font-size: 16px; margin: 15px 0 8px; color: #555;">POST Request (Upload JSON)</h3>
4745
<pre class="code">curl -i -X POST http://127.0.0.1:8080/upload \
@@ -59,7 +57,6 @@ <h2 style="font-size: 20px; margin: 20px 0 10px; color: #333;">Available Downloa
5957
<ul style="line-height: 1.8;">
6058
<li><strong>logo.png</strong> - Small PNG image</li>
6159
<li><strong>photo.jpg, photo2.jpg</strong> - JPEG images</li>
62-
<li><strong>large.png</strong> - ~27MB PNG for large file transfer testing</li>
6360
<li><strong>sample.txt, sample2.txt</strong> - Plain text files</li>
6461
</ul>
6562

@@ -82,7 +79,7 @@ <h2 style="font-size: 20px; margin: 20px 0 10px; color: #333;">Assignment Requir
8279
<li>✅ Multi-threaded server using socket programming</li>
8380
<li>✅ ThreadPool with bounded queue (503 on saturation)</li>
8481
<li>✅ GET and POST method support</li>
85-
<li>Binary file transfers (images, large files)</li>
82+
<li>Streamed binary file transfers</li>
8683
<li>✅ Static file serving (HTML, CSS, JS, text)</li>
8784
<li>✅ Path traversal attack prevention</li>
8885
<li>✅ Host header validation</li>

resources/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
<a href="/logo.png" download>logo.png (PNG)</a>
1919
<a href="/photo.jpg" download>photo.jpg (JPEG)</a>
2020
<a href="/photo2.jpg" download>photo2.jpg (JPEG)</a>
21-
<a href="/large.png" download>large.png (PNG ~27MB)</a>
2221
<a href="/sample.txt" download>sample.txt</a>
2322
<a href="/sample2.txt" download>sample2.txt</a>
2423
</div>
@@ -59,4 +58,4 @@ <h2 class="h1" style="font-size:24px;">Try POST /upload (JSON)</h2>
5958
<footer class="footer">Server: Multi-threaded HTTP Server</footer>
6059
<script src="/app.js"></script>
6160
</body>
62-
</html>
61+
</html>

resources/large.png

-25.8 MB
Binary file not shown.

server.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
"""
2-
Multi-threaded HTTP/1.1 server built from sockets.
3-
4-
HELLO SIR , I am Nipun Patel Thumu . Roll no - 10207 . I wrote the starting basic coding on my own and later generated bit of it . Tried to go through all the code and understand most of it.
5-
6-
"""
1+
"""Educational HTTP/1.1 server built directly on Python sockets."""
72
import socket, sys, threading, os, json, random, string, queue
83
from datetime import datetime, timezone
94
from typing import Optional, Tuple, Dict
@@ -111,11 +106,11 @@ def main():
111106

112107
class ThreadPool:
113108
"""Very small fixed-size thread pool with a bounded task queue."""
114-
def __init__(self, max_workers: int, on_dequeued=None):
109+
def __init__(self, max_workers: int, on_dequeued=None, queue_size: int = MAX_QUEUE_SIZE):
115110
# I'm prestarting worker threads so incoming connections get handled immediately.
116111
self.max_workers = max_workers
117112
self.on_dequeued = on_dequeued # callback(thread_name, client_address)
118-
self.tasks = queue.Queue(maxsize=MAX_QUEUE_SIZE)
113+
self.tasks = queue.Queue(maxsize=queue_size)
119114
self.threads = []
120115
self._active = 0
121116
self._lock = threading.Lock()

tests/test_server.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
22
import socket
3+
import tempfile
4+
import threading
35
import unittest
6+
from unittest.mock import patch
47

58
import server
69

@@ -99,5 +102,83 @@ def test_response_headers_include_length_and_status(self):
99102
self.assertIn("Connection: close", response)
100103

101104

105+
class StreamingAndConcurrencyTests(unittest.TestCase):
106+
def test_streams_a_deterministic_multi_chunk_fixture(self):
107+
payload = b"0123456789abcdef" * 700
108+
sender, receiver = socket.socketpair()
109+
result = {}
110+
111+
try:
112+
with tempfile.NamedTemporaryFile(suffix=".txt") as fixture:
113+
fixture.write(payload)
114+
fixture.flush()
115+
headers, error = server.get_content_headers_for_path(fixture.name)
116+
117+
self.assertIsNone(error)
118+
119+
def stream_fixture():
120+
try:
121+
result["bytes_sent"] = server.send_file(sender, fixture.name, headers)
122+
sender.shutdown(socket.SHUT_WR)
123+
except Exception as exc: # Propagate worker failures below.
124+
result["error"] = exc
125+
126+
stream_thread = threading.Thread(target=stream_fixture)
127+
stream_thread.start()
128+
129+
response = bytearray()
130+
while chunk := receiver.recv(4096):
131+
response.extend(chunk)
132+
133+
stream_thread.join(timeout=2)
134+
self.assertFalse(stream_thread.is_alive())
135+
finally:
136+
sender.close()
137+
receiver.close()
138+
139+
if "error" in result:
140+
raise result["error"]
141+
142+
_, response_body = bytes(response).split(b"\r\n\r\n", 1)
143+
self.assertEqual(result["bytes_sent"], len(payload))
144+
self.assertEqual(response_body, payload)
145+
146+
def test_bounded_pool_rejects_work_when_its_queue_is_full(self):
147+
started = threading.Event()
148+
release = threading.Event()
149+
server_sockets = []
150+
peer_sockets = []
151+
152+
def blocking_handler(client_socket, _client_address):
153+
started.set()
154+
release.wait(timeout=2)
155+
client_socket.close()
156+
157+
try:
158+
with patch.object(server, "handle_client", side_effect=blocking_handler):
159+
pool = server.ThreadPool(max_workers=1, queue_size=1)
160+
161+
for _ in range(3):
162+
server_socket, peer_socket = socket.socketpair()
163+
server_sockets.append(server_socket)
164+
peer_sockets.append(peer_socket)
165+
166+
self.assertTrue(pool.submit(server_sockets[0], ("local", 1)))
167+
self.assertTrue(started.wait(timeout=1))
168+
self.assertTrue(pool.submit(server_sockets[1], ("local", 2)))
169+
self.assertFalse(pool.submit(server_sockets[2], ("local", 3)))
170+
171+
server_sockets[2].close()
172+
release.set()
173+
pool.tasks.join()
174+
finally:
175+
release.set()
176+
for sock in server_sockets + peer_sockets:
177+
try:
178+
sock.close()
179+
except OSError:
180+
pass
181+
182+
102183
if __name__ == "__main__":
103184
unittest.main()

0 commit comments

Comments
 (0)