add app.static for serving static files, and sendfile as official helper

pull/39/head
Ciro 2022-11-05 17:06:32 -03:00
rodzic 5c7d5a8605
commit e6cc934060
5 zmienionych plików z 67 dodań i 31 usunięć

Wyświetl plik

@ -1,35 +1,45 @@
from socketify import App
import aiofiles
from aiofiles import os
import time
import mimetypes
import os
from os import path
mimetypes.init()
async def home(res, req):
#there is also a helper called static with an sendfile method see static_files.py for examples of usage
#here is an sample implementation, a more complete one is in static.sendfile
#this is just an implementation example see static_files.py example for use of sendfile and app.static usage
#there is an static_aiofile.py helper and static.aiofiles helper using async implementation of this
#asyncio with IO is really slow so, we will implement "aiofile" using libuv inside socketify in future
filename = "./public/media/flower.webm"
#read headers before the first await
if_modified_since = req.get_header('if-modified-since')
range_header = req.get_header('range')
bytes_range = None
start = 0
end = -1
#parse range header
if range_header:
bytes_range = range_header.replace("bytes=", '').split('-')
start = int(bytes_range[0])
if bytes_range[1]:
end = int(bytes_range[1])
try:
exists = await os.path.exists(filename)
exists = path.exists(filename)
#not found
if not exists:
return res.write_status(404).end(b'Not Found')
#get size and last modified date
stats = await os.stat(filename)
stats = os.stat(filename)
total_size = stats.st_size
size = total_size
last_modified = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(stats.st_mtime))
#check if modified since is provided
if if_modified_since == last_modified:
res.write_status(304).end_without_body()
return
return res.write_status(304).end_without_body()
#tells the broswer the last modified date
res.write_header(b'Last-Modified', last_modified)
@ -40,24 +50,37 @@ async def home(res, req):
elif content_type:
res.write_header(b'Content-Type', content_type)
async with aiofiles.open(filename, "rb") as fd:
res.write_status(200)
pending_size = total_size
with open(filename, "rb") as fd:
#check range and support it
if start > 0 or not end == -1:
if end < 0 or end >= size:
end = size - 1
size = end - start + 1
fd.seek(start)
if start > total_size or size > total_size or size < 0 or start < 0:
return res.write_status(416).end_without_body()
res.write_status(206)
else:
end = size - 1
res.write_status(200)
#tells the browser that we support range
res.write_header(b'Accept-Ranges', b'bytes')
res.write_header(b'Content-Range', 'bytes %d-%d/%d' % (start, end, total_size))
pending_size = size
#keep sending until abort or done
while not res.aborted:
chunk_size = 16384 #16kb chunks
if chunk_size > pending_size:
chunk_size = pending_size
buffer = await fd.read(chunk_size)
buffer = fd.read(chunk_size)
pending_size = pending_size - chunk_size
(ok, done) = await res.send_chunk(buffer, total_size)
if not ok or done or pending_size <= 0: #if cannot send probably aborted
(ok, done) = await res.send_chunk(buffer, size)
if not ok or done: #if cannot send probably aborted
break
except Exception as error:
res.write_status(500).end("Internal Error")
res.write_status(500).end("Internal Error")
app = App()
app.get("/", home)

Wyświetl plik

@ -7,9 +7,9 @@
# nginx - try_files - 77630.15 req/s
# pypy3 - socketify static - 10245.82 req/s
# python3 - socketify static - 8273.71 req/s
# node.js - @fastify/static - 5437.16 req/s
# node.js - express.static - 4077.49 req/s
# python3 - socketify static - 2438.06 req/s
# python3 - socketify static_aiofile - 2390.96 req/s
# python3 - socketify static_aiofiles - 1615.12 req/s
# python3 - scarlette static uvicorn - 1335.56 req/s
@ -29,9 +29,7 @@
# Express production recommendations: https://expressjs.com/en/advanced/best-practice-performance.html
# Fastify production recommendations: https://www.fastify.io/docs/latest/Guides/Recommendations/
from socketify import App
from helpers.static import static_route
from helpers.static import sendfile
from socketify import App, sendfile
app = App()
@ -44,8 +42,8 @@ async def home(res, req):
app.get("/", home)
#serve all files in public folder under / route (main route but can be like /assets)
static_route(app, "/", "./public")
#serve all files in public folder under /* route (you can use any route like /assets)
app.static("/", "./public")
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
app.run()

Wyświetl plik

@ -1 +1,2 @@
from .socketify import App, AppOptions, AppListenOptions
from .helpers import sendfile

Wyświetl plik

@ -4,6 +4,7 @@ import mimetypes
from os import path
mimetypes.init()
# We have an version of this using aiofile and aiofiles
# This is an sync version without any dependencies is normally much faster in CPython and PyPy3
# In production we highly recomend to use CDN like CloudFlare or/and NGINX or similar for static files
@ -77,6 +78,7 @@ async def sendfile(res, req, filename):
except Exception as error:
res.write_status(500).end("Internal Error")
def in_directory(file, directory):
#make both absolute
directory = path.join(path.realpath(directory), '')

Wyświetl plik

@ -1,16 +1,24 @@
import cffi
from datetime import datetime
from http import cookies
import inspect
import json
import mimetypes
import os
from os import path
import platform
import signal
from threading import Thread, local, Lock
import time
from urllib.parse import parse_qs, quote_plus, unquote_plus
from .loop import Loop
from .status_codes import status_codes
import json
import inspect
import signal
from http import cookies
from datetime import datetime
from urllib.parse import parse_qs, quote_plus, unquote_plus
from threading import Thread, local, Lock
from .helpers import static_route
import platform
mimetypes.init()
is_python = platform.python_implementation() == 'CPython'
ffi = cffi.FFI()
@ -849,6 +857,10 @@ class App:
self.handlers = []
self.error_handler = None
def static(self, route, directory):
static_route(self, route, directory)
return self
def get(self, path, handler):
user_data = ffi.new_handle((handler, self))
self.handlers.append(user_data) #Keep alive handler