patx/micropie
update all examples to work with asgi
Commit 70e7bc3 · patx · 2025-01-28T02:01:05-05:00
Comments
No comments yet.
Diff
diff --git a/MicroPie.py b/MicroPie.py
index 2f92931..ad02b3d 100644
--- a/MicroPie.py
+++ b/MicroPie.py
@@ -288,14 +288,15 @@ class Server:
async def _send_response(
self,
- send: Any,
+ send,
status_code: int,
- body: Union[str, bytes, Any],
- extra_headers: List[Tuple[str, str]] = None
- ) -> None:
+ body,
+ extra_headers=None
+ ):
if extra_headers is None:
extra_headers = []
+ # Common HTTP status text
status_map = {
200: "200 OK",
206: "206 Partial Content",
@@ -304,12 +305,15 @@ class Server:
404: "404 Not Found",
500: "500 Internal Server Error",
}
+ # Fallback if not in map
status_text = status_map.get(status_code, f"{status_code} OK")
+ # Ensure there's a Content-Type unless already provided
has_content_type = any(h[0].lower() == "content-type" for h in extra_headers)
if not has_content_type:
extra_headers.append(("Content-Type", "text/html; charset=utf-8"))
+ # Send the initial response start
await send({
"type": "http.response.start",
"status": status_code,
@@ -318,25 +322,61 @@ class Server:
],
})
- response_body = b""
+ #
+ # -- Begin CHUNKED/STREAMING logic --
+ #
+ # 1) Check if body is an async generator (has __aiter__)
+ if hasattr(body, "__aiter__"):
+ async for chunk in body:
+ if isinstance(chunk, str):
+ chunk = chunk.encode("utf-8")
+ await send({
+ "type": "http.response.body",
+ "body": chunk,
+ "more_body": True
+ })
+ # Send a final empty chunk to mark the end
+ await send({
+ "type": "http.response.body",
+ "body": b"",
+ "more_body": False
+ })
+ return
+
+ # 2) Check if body is a *sync* generator (has __iter__) and
+ # is not a plain string/bytes
if hasattr(body, "__iter__") and not isinstance(body, (bytes, str)):
for chunk in body:
if isinstance(chunk, str):
- response_body += chunk.encode("utf-8")
- else:
- response_body += chunk
+ chunk = chunk.encode("utf-8")
+ await send({
+ "type": "http.response.body",
+ "body": chunk,
+ "more_body": True
+ })
+ # Send a final empty chunk
+ await send({
+ "type": "http.response.body",
+ "body": b"",
+ "more_body": False
+ })
+ return
+
+ #
+ # -- Fallback for normal (non-generator) body --
+ #
+ if isinstance(body, str):
+ response_body = body.encode("utf-8")
+ elif isinstance(body, bytes):
+ response_body = body
else:
- if isinstance(body, str):
- response_body = body.encode("utf-8")
- elif isinstance(body, bytes):
- response_body = body
- else:
- response_body = str(body).encode("utf-8")
+ # Convert anything else to string then to bytes
+ response_body = str(body).encode("utf-8")
await send({
"type": "http.response.body",
"body": response_body,
- "more_body": False,
+ "more_body": False
})
def cleanup_sessions(self) -> None:
diff --git a/examples/websockets/templates/index.html b/examples/streaming/templates/index.html
similarity index 100%
rename from examples/websockets/templates/index.html
rename to examples/streaming/templates/index.html
diff --git a/examples/wsgi_streaming/templates/stream.html b/examples/streaming/templates/stream.html
similarity index 100%
rename from examples/wsgi_streaming/templates/stream.html
rename to examples/streaming/templates/stream.html
diff --git a/examples/wsgi_streaming/templates/watch.html b/examples/streaming/templates/watch.html
similarity index 100%
rename from examples/wsgi_streaming/templates/watch.html
rename to examples/streaming/templates/watch.html
diff --git a/examples/wsgi_streaming/text.py b/examples/streaming/text.py
similarity index 77%
rename from examples/wsgi_streaming/text.py
rename to examples/streaming/text.py
index 1021600..096aad8 100644
--- a/examples/wsgi_streaming/text.py
+++ b/examples/streaming/text.py
@@ -18,6 +18,3 @@ class Root(Server):
app = Root()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 text.py`
diff --git a/examples/streaming/video.mp4 b/examples/streaming/video.mp4
new file mode 100644
index 0000000..9b89f62
Binary files /dev/null and b/examples/streaming/video.mp4 differ
diff --git a/examples/streaming/video1.py b/examples/streaming/video1.py
new file mode 100644
index 0000000..03f8672
--- /dev/null
+++ b/examples/streaming/video1.py
@@ -0,0 +1,83 @@
+import os
+from MicroPie import Server
+
+VIDEO_PATH = "video.mp4"
+
+class VideoStreamer(Server):
+ def index(self):
+ """Serve a simple HTML page with a video player."""
+ return '''
+ <html>
+ <body>
+ <center>
+ <video width="640" height="360" controls>
+ <source src="/stream" type="video/mp4">
+ Your browser does not support the video tag. Use Chrome for best results.
+ </video>
+ </center>
+ </body>
+ </html>
+ '''
+
+ def stream(self):
+ """
+ Return a tuple that MicroPie interprets as:
+ (status_code, body, [extra_headers])
+ We'll parse the Range header from scope and produce partial or full content.
+ """
+ headers = {
+ k.decode('latin-1').lower(): v.decode('latin-1')
+ for k, v in self.scope.get('headers', [])
+ }
+
+ range_header = headers.get('range')
+ file_size = os.path.getsize(VIDEO_PATH)
+
+ def read_bytes(start=0, end=None) -> bytes:
+ """Synchronous read of requested byte range."""
+ if end is None or end > file_size:
+ end = file_size
+ length = end - start
+ with open(VIDEO_PATH, 'rb') as f:
+ f.seek(start)
+ return f.read(length)
+
+ if range_header:
+ # Typical format: "bytes=1234-" or "bytes=1234-5678"
+ try:
+ byte_range = range_header.replace("bytes=", "")
+ start_str, end_str = byte_range.split("-")
+ start = int(start_str) if start_str else 0
+ end = int(end_str) if end_str else file_size - 1
+
+ if start >= file_size or end >= file_size:
+ # Out-of-range request, fallback to full content
+ start, end = 0, file_size - 1
+
+ content_length = (end - start) + 1
+ content = read_bytes(start, end + 1)
+
+ extra_headers = [
+ ("Content-Range", f"bytes {start}-{end}/{file_size}"),
+ ("Accept-Ranges", "bytes"),
+ ("Content-Length", str(content_length)),
+ ("Content-Type", "video/mp4"),
+ ]
+ return (206, content, extra_headers)
+
+ except ValueError:
+ # Malformed Range header; fallback
+ pass
+
+ # No valid Range header; return full content
+ content = read_bytes(0, file_size)
+ extra_headers = [
+ ("Content-Length", str(file_size)),
+ ("Content-Type", "video/mp4"),
+ ("Accept-Ranges", "bytes"),
+ ]
+ return (200, content, extra_headers)
+
+
+app = VideoStreamer()
+
diff --git a/examples/wsgi_streaming/video2.py b/examples/streaming/video2.py
similarity index 87%
rename from examples/wsgi_streaming/video2.py
rename to examples/streaming/video2.py
index e0ba7e6..f1da4bc 100644
--- a/examples/wsgi_streaming/video2.py
+++ b/examples/streaming/video2.py
@@ -38,6 +38,3 @@ class VideoStreamer(Server):
app = VideoStreamer()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 video2.py`
diff --git a/examples/streaming/video3.py b/examples/streaming/video3.py
new file mode 100644
index 0000000..fe4601d
--- /dev/null
+++ b/examples/streaming/video3.py
@@ -0,0 +1,74 @@
+import os
+from MicroPie import Server
+
+VIDEO_PATH = "video.mp4"
+
+class Root(Server):
+ def index(self):
+ return '''
+ <html>
+ <body>
+ <center>
+ <video width="640" height="360" controls>
+ <source src="/stream" type="video/mp4">
+ Your browser does not support the video tag. Use Chrome for best results.
+ </video>
+ </center>
+ </body>
+ </html>
+ '''
+
+ async def stream(self):
+ headers = {
+ k.decode('latin-1').lower(): v.decode('latin-1')
+ for k, v in self.scope.get('headers', [])
+ }
+ range_header = headers.get('range')
+ file_size = os.path.getsize(VIDEO_PATH)
+
+ # Decide on start/end
+ start, end = 0, file_size - 1
+ status_code = 200
+ extra_headers = [
+ ("Accept-Ranges", "bytes"),
+ ("Content-Type", "video/mp4"),
+ ]
+
+ if range_header:
+ # e.g. "bytes=1234-" or "bytes=1234-5678"
+ try:
+ byte_range = range_header.replace("bytes=", "")
+ start_str, end_str = byte_range.split("-")
+ start = int(start_str) if start_str else 0
+ end = int(end_str) if end_str else file_size - 1
+ if start >= file_size or end >= file_size:
+ start, end = 0, file_size - 1
+
+ content_length = end - start + 1
+ extra_headers += [
+ ("Content-Range", f"bytes {start}-{end}/{file_size}"),
+ ("Content-Length", str(content_length)),
+ ]
+ status_code = 206
+ except ValueError:
+ # Malformed range; fallback
+ pass
+ else:
+ # Full content
+ extra_headers.append(("Content-Length", str(file_size)))
+
+ # Make an async generator that yields file chunks
+ async def file_chunk_generator(start_pos, end_pos, chunk_size=1024 * 1024):
+ with open(VIDEO_PATH, "rb") as f:
+ f.seek(start_pos)
+ remaining = (end_pos + 1) - start_pos
+ while remaining > 0:
+ data = f.read(min(chunk_size, remaining))
+ if not data:
+ break
+ yield data
+ remaining -= len(data)
+
+ return (status_code, file_chunk_generator(start, end), extra_headers)
+
+app = Root()
diff --git a/examples/websockets/__pycache__/MicroPie.cpython-310.pyc b/examples/websockets/__pycache__/MicroPie.cpython-310.pyc
new file mode 100644
index 0000000..1a68616
Binary files /dev/null and b/examples/websockets/__pycache__/MicroPie.cpython-310.pyc differ
diff --git a/examples/websockets/__pycache__/app.cpython-310.pyc b/examples/websockets/__pycache__/app.cpython-310.pyc
new file mode 100644
index 0000000..60c7de6
Binary files /dev/null and b/examples/websockets/__pycache__/app.cpython-310.pyc differ
diff --git a/examples/websockets/__pycache__/chatroom.cpython-310.pyc b/examples/websockets/__pycache__/chatroom.cpython-310.pyc
new file mode 100644
index 0000000..5429685
Binary files /dev/null and b/examples/websockets/__pycache__/chatroom.cpython-310.pyc differ
diff --git a/examples/websockets/chatroom.py b/examples/websockets/chatroom.py
new file mode 100644
index 0000000..ed00b9b
--- /dev/null
+++ b/examples/websockets/chatroom.py
@@ -0,0 +1,118 @@
+from typing import Dict, Set, Any
+from MicroPie import Server
+
+# We track who is "active" (registered a username)
+active_users: Set[str] = set()
+
+# We store a dictionary of channels -> sets of websocket send handles
+# For a single chat room, we only need one channel, e.g. "global".
+watchers: Dict[str, Set[Any]] = {}
+
+class ChatApp(Server):
+ async def index(self):
+ """
+ Serve a simple form where the user enters a username.
+ """
+ return self.render_template("index_chat.html")
+
+ async def submit(self, username: str):
+ """
+ Handle the POST from index.html where user enters their name.
+ Then redirect them to /chat/<username>.
+ """
+ username = username.strip()
+ if not username:
+ return self.redirect("/") # Invalid username, go back
+
+ # Mark this user as active
+ active_users.add(username)
+ return self.redirect(f"/chat/{username}")
+
+ async def chat(self, username: str):
+ """
+ Show the chat page if the user is active; otherwise, redirect home.
+ """
+ if username not in active_users:
+ return self.redirect("/")
+ return self.render_template("chat.html", username=username)
+
+ #
+ # ------------- WEBSOCKET HANDLER -------------
+ #
+ async def websocket_chat(self, scope, receive, send):
+ """
+ If the path is /chat/<username>, MicroPie calls this method
+ because 'websocket_{pathParts[0]}' = 'websocket_chat'.
+ """
+ # Extract the <username> from self.path_params
+ username = self.path_params[0] if self.path_params else None
+
+ # 1) Make sure username is valid and active
+ if not username or username not in active_users:
+ return await self._reject_websocket(send)
+
+ # 2) Wait for 'websocket.connect' before accepting
+ msg = await receive()
+ if msg["type"] == "websocket.connect":
+ await send({"type": "websocket.accept"})
+ else:
+ # If we didn’t get the connect message, close
+ return await self._reject_websocket(send)
+
+ # 3) Add this connection to watchers["global"]
+ watchers.setdefault("global", set()).add(send)
+
+ # 4) Handle incoming messages until the client disconnects
+ try:
+ while True:
+ message = await receive()
+ if message["type"] == "websocket.receive":
+ # If there's text, broadcast it
+ if "text" in message:
+ text_msg = message["text"]
+ await self._broadcast("global", f"{username}: {text_msg}", is_binary=False)
+ elif "bytes" in message:
+ byte_data = message["bytes"]
+ await self._broadcast("global", byte_data, is_binary=True)
+ elif message["type"] == "websocket.disconnect":
+ break
+ finally:
+ # Remove the user's send handle from watchers["global"]
+ watchers["global"].discard(send)
+ # If watchers["global"] is empty, remove the channel
+ if not watchers["global"]:
+ del watchers["global"]
+
+ #
+ # ------------- HELPER METHODS -------------
+ #
+ async def _reject_websocket(self, send):
+ """
+ Accept then immediately close with a custom code.
+ This is a simple approach to gracefully reject a WebSocket.
+ """
+ await send({"type": "websocket.accept"})
+ await send({"type": "websocket.close", "code": 4000})
+
+ async def _broadcast(self, channel: str, data, is_binary: bool = False):
+ """
+ Broadcast 'data' to all watchers in the given channel.
+ """
+ for ws_send in list(watchers.get(channel, [])):
+ try:
+ await ws_send({
+ "type": "websocket.send",
+ "bytes" if is_binary else "text": data
+ })
+ except:
+ # If sending fails, remove that watcher's socket
+ watchers[channel].discard(ws_send)
+ if not watchers[channel]:
+ del watchers[channel]
+
+# Create the ASGI app instance
+app = ChatApp()
+
+# If you want to run via uvicorn:
+# uvicorn chat_app:app --host 127.0.0.1 --port 5000
+
diff --git a/examples/websockets/templates/chat.html b/examples/websockets/templates/chat.html
new file mode 100644
index 0000000..4850fe1
--- /dev/null
+++ b/examples/websockets/templates/chat.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>MicroPie Chat - {{ username }}</title>
+ <style>
+ body {
+ font-family: sans-serif;
+ margin: 20px;
+ }
+ #chatBox {
+ border: 1px solid #ccc;
+ width: 500px;
+ height: 300px;
+ overflow-y: auto;
+ padding: 10px;
+ margin-bottom: 10px;
+ }
+ #messageInput {
+ width: 400px;
+ }
+ </style>
+</head>
+<body>
+ <h1>Chat Room (User: {{ username }})</h1>
+
+ <div id="chatBox"></div>
+ <input id="messageInput" type="text" placeholder="Type a message..." />
+ <button id="sendBtn">Send</button>
+
+ <script>
+ const username = "{{ username }}";
+ const chatBox = document.getElementById("chatBox");
+ const messageInput = document.getElementById("messageInput");
+ const sendBtn = document.getElementById("sendBtn");
+
+ // Build the WebSocket URL, using the current host but ws:// or wss://
+ const wsProto = (location.protocol === "https:") ? "wss://" : "ws://";
+ const wsUrl = wsProto + window.location.host + "/chat/" + encodeURIComponent(username);
+
+ const socket = new WebSocket(wsUrl);
+
+ socket.onmessage = (event) => {
+ // Display the new message
+ const msgDiv = document.createElement("div");
+ msgDiv.textContent = event.data;
+ chatBox.appendChild(msgDiv);
+ chatBox.scrollTop = chatBox.scrollHeight;
+ };
+
+ sendBtn.onclick = () => {
+ sendMessage();
+ };
+ messageInput.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") sendMessage();
+ });
+
+ function sendMessage() {
+ const text = messageInput.value.trim();
+ if (text) {
+ socket.send(text);
+ messageInput.value = "";
+ }
+ }
+ </script>
+</body>
+</html>
+
diff --git a/examples/websockets/templates/index_chat.html b/examples/websockets/templates/index_chat.html
new file mode 100644
index 0000000..9422898
--- /dev/null
+++ b/examples/websockets/templates/index_chat.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>MicroPie Chat - Home</title>
+</head>
+<body>
+ <h1>Welcome to MicroPie Chat!</h1>
+ <form method="post" action="/submit">
+ <label for="username">Enter a username:</label>
+ <input id="username" name="username" type="text" />
+ <button type="submit">Enter Chat</button>
+ </form>
+</body>
+</html>
+
diff --git a/examples/wsgi_streaming/templates/index.html b/examples/websockets/templates/index_stream.html
similarity index 100%
rename from examples/wsgi_streaming/templates/index.html
rename to examples/websockets/templates/index_stream.html
diff --git a/examples/websockets/app.py b/examples/websockets/webcam.py
similarity index 94%
rename from examples/websockets/app.py
rename to examples/websockets/webcam.py
index 6aab18d..077c6d2 100644
--- a/examples/websockets/app.py
+++ b/examples/websockets/webcam.py
@@ -9,7 +9,7 @@ watchers: Dict[str, Set[Any]] = {}
class MyApp(Server):
async def index(self):
- return self.render_template("index.html")
+ return self.render_template("index_stream.html")
async def submit(self, username: str, action: str):
if username:
@@ -73,8 +73,3 @@ class MyApp(Server):
# Create the ASGI app
app = MyApp()
-
-if __name__ == "__main__":
- import uvicorn
- uvicorn.run(app, host="0.0.0.0", port=5000, reload=True)
-
diff --git a/examples/wsgi_streaming/chatroom.py b/examples/wsgi_streaming/chatroom.py
deleted file mode 100644
index 5c7b13b..0000000
--- a/examples/wsgi_streaming/chatroom.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import asyncio
-import websockets
-import multiprocessing
-from MicroPie import Server
-
-# Store connected WebSocket clients
-connected_clients = set()
-
-class MyApp(Server):
- def index(self):
- return """
- <html>
- <head>
- <script>
- var ws = new WebSocket("ws://localhost:8765");
- ws.onopen = function() {
- console.log("Connected to WebSocket server");
- };
- ws.onmessage = function(event) {
- document.getElementById("output").innerHTML += event.data + "<br>";
- };
- function sendMessage() {
- var message = document.getElementById("message").value;
- ws.send(message);
- document.getElementById("message").value = ""; // Clear input after sending
- }
- window.onbeforeunload = function() {
- ws.close();
- };
- </script>
- </head>
- <body>
- <h1>WebSocket Chat</h1>
- <input type="text" id="message" placeholder="Type a message">
- <button onclick="sendMessage()">Send</button>
- <div id="output"></div>
- </body>
- </html>
- """
-
-async def websocket_handler(websocket):
- """Handles incoming WebSocket connections and broadcasts messages to all clients."""
- global connected_clients
- connected_clients.add(websocket)
- print("Client connected")
-
- try:
- async for message in websocket:
- print(f"Received: {message}")
- response = f"User: {message}"
- await broadcast(response) # Send to all connected clients
- except websockets.exceptions.ConnectionClosed:
- print("Client disconnected")
- finally:
- connected_clients.remove(websocket)
-
-async def broadcast(message):
- """Send a message to all connected WebSocket clients."""
- if connected_clients:
- await asyncio.wait([client.send(message) for client in connected_clients])
-
-async def websocket_server():
- """Start the WebSocket server within the asyncio event loop."""
- async with websockets.serve(websocket_handler, "localhost", 8765):
- print("WebSocket server started on ws://localhost:8765")
- await asyncio.Future() # Keep the server running indefinitely
-
-def start_websocket_server():
- """Runs the WebSocket server with asyncio.run() in a separate thread."""
- asyncio.run(websocket_server())
-
-app = MyApp()
-
-
-if __name__ == "__main__":
- import threading
-
- # Start the WebSocket server in a separate thread
- ws_thread = threading.Thread(target=start_websocket_server, daemon=True)
- ws_thread.start()
-
- print("WebSocket server running on ws://localhost:8765")
-
- # Start the Gunicorn server in a separate process
- wsgi_process = multiprocessing.Process(target=app.run())
- wsgi_process.start()
-
- wsgi_process.join() # Keep the main process alive
-
diff --git a/examples/wsgi_streaming/livestream.py b/examples/wsgi_streaming/livestream.py
deleted file mode 100644
index f4a4752..0000000
--- a/examples/wsgi_streaming/livestream.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import socketio
-import eventlet
-from MicroPie import Server
-
-sio = socketio.Server(cors_allowed_origins="*")
-app = Server()
-active_users = set()
-
-class MyApp(Server):
- def index(self):
- return self.render_template('index.html')
-
- def submit(self, username, action):
- if username:
- active_users.add(username)
- if action == 'Start Streaming':
- return self.redirect(f'/stream/{username}')
- elif action == 'Watch Stream':
- return self.redirect(f'/watch/{username}')
- return self.redirect('/')
-
- def stream(self, username):
- if username not in active_users:
- return self.redirect('/')
- return self.render_template('stream.html', username=username)
-
- def watch(self, username):
- if username not in active_users:
- return self.redirect('/')
- return self.render_template('watch.html', username=username)
-
[email protected]
-def connect(sid, environ):
- print(f"Client {sid} connected")
-
[email protected]
-def disconnect(sid):
- print(f"Client {sid} disconnected")
-
[email protected]
-def stream(sid, data):
- username = data.get('username')
- frame = data.get('frame')
- sio.emit('broadcast', {'username': username, 'frame': frame}, skip_sid=sid)
-
-def run_server():
- eventlet.wsgi.server(eventlet.listen(('0.0.0.0', 5000)), socketio.WSGIApp(sio, MyApp().wsgi_app))
-
-if __name__ == '__main__':
- run_server()
-
diff --git a/examples/wsgi_streaming/video1.py b/examples/wsgi_streaming/video1.py
deleted file mode 100644
index 9a07db6..0000000
--- a/examples/wsgi_streaming/video1.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import os
-from MicroPie import Server
-
-VIDEO_PATH = "video.mp4"
-
-
-class VideoStreamer(Server):
-
- def index(self):
- """Serve a simple HTML page with a video player."""
- return '''
- <html>
- <body>
- <center>
- <video width="640" height="360" controls>
- <source src="/stream" type="video/mp4">
- Your browser does not support the video tag. Use Chrome for best results.
- </video>
- </center>
- </body>
- </html>
- '''
-
- def stream(self):
- """
- Stream the video file with support for range requests (seeking).
- This will only work in WSGI mode, because the built-in server in
- MicroPie doesn't handle custom headers for partial-content.
- """
- environ = self.environ # Provided by our updated MicroPie
- range_header = environ.get('HTTP_RANGE')
- file_size = os.path.getsize(VIDEO_PATH)
-
- def generator(start=0, end=None):
- chunk_size = 1024 * 1024 # 1MB chunks
- with open(VIDEO_PATH, 'rb') as video:
- video.seek(start)
- remaining = end - start if end else file_size - start
- while remaining > 0:
- data = video.read(min(chunk_size, remaining))
- if not data:
- break
- yield data
- remaining -= len(data)
-
- if range_header:
- try:
- # Example "Range" header: "bytes=1234-"
- # or "bytes=1234-5678"
- range_value = range_header.replace('bytes=', '')
- start_str, end_str = range_value.split('-')
- start = int(start_str)
- end = int(end_str) if end_str else file_size - 1
-
- # Ensure range is within file bounds
- if start >= file_size or end >= file_size:
- start, end = 0, file_size - 1
-
- content_length = end - start + 1
-
- # Return a 3-element tuple with status, body, and custom headers
- extra_headers = [
- ("Content-Range", f"bytes {start}-{end}/{file_size}"),
- ("Accept-Ranges", "bytes"),
- ("Content-Length", str(content_length)),
- ("Content-Type", "video/mp4"),
- ]
- return (206, generator(start, end + 1), extra_headers)
-
- except ValueError:
- # If the Range header was invalid, just fall back to full video
- pass
-
- # Default: return the full video
- extra_headers = [
- ("Content-Length", str(file_size)),
- ("Content-Type", "video/mp4"),
- ("Accept-Ranges", "bytes"),
- ]
- return (200, generator(0, file_size), extra_headers)
-
-
-
-
-app = VideoStreamer()
-wsgi_app = app.wsgi_app # Run with `gunicorn video1:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 video1.py`
diff --git a/setup.py b/setup.py
index c861616..6169841 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ MicroPie is Fun
def index(self):
return 'Hello world!'
- MyApp().run()
+ app = MyApp() # Run with `uvicorn app:app`
Links
@@ -25,7 +25,7 @@ Links
from distutils.core import setup
setup(name="MicroPie",
- version="0.7",
+ version="0.8",
description="A ultra micro web framework w/ Jinja2.",
long_description=__doc__,
author="Harrison Erd",