kopia lustrzana https://github.com/cirospaciari/socketify.py
add app.static for serving static files, and sendfile as official helper
rodzic
5c7d5a8605
commit
e6cc934060
|
@ -1,35 +1,45 @@
|
||||||
from socketify import App
|
from socketify import App
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from aiofiles import os
|
|
||||||
import time
|
import time
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
|
||||||
async def home(res, req):
|
async def home(res, req):
|
||||||
#there is also a helper called static with an sendfile method see static_files.py for examples of usage
|
#this is just an implementation example see static_files.py example for use of sendfile and app.static usage
|
||||||
#here is an sample implementation, a more complete one is in static.sendfile
|
#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"
|
filename = "./public/media/flower.webm"
|
||||||
#read headers before the first await
|
#read headers before the first await
|
||||||
if_modified_since = req.get_header('if-modified-since')
|
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:
|
try:
|
||||||
exists = await os.path.exists(filename)
|
exists = path.exists(filename)
|
||||||
#not found
|
#not found
|
||||||
if not exists:
|
if not exists:
|
||||||
return res.write_status(404).end(b'Not Found')
|
return res.write_status(404).end(b'Not Found')
|
||||||
|
|
||||||
#get size and last modified date
|
#get size and last modified date
|
||||||
stats = await os.stat(filename)
|
stats = os.stat(filename)
|
||||||
total_size = stats.st_size
|
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))
|
last_modified = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(stats.st_mtime))
|
||||||
|
|
||||||
#check if modified since is provided
|
#check if modified since is provided
|
||||||
if if_modified_since == last_modified:
|
if if_modified_since == last_modified:
|
||||||
res.write_status(304).end_without_body()
|
return res.write_status(304).end_without_body()
|
||||||
return
|
|
||||||
#tells the broswer the last modified date
|
#tells the broswer the last modified date
|
||||||
res.write_header(b'Last-Modified', last_modified)
|
res.write_header(b'Last-Modified', last_modified)
|
||||||
|
|
||||||
|
@ -40,24 +50,37 @@ async def home(res, req):
|
||||||
elif content_type:
|
elif content_type:
|
||||||
res.write_header(b'Content-Type', content_type)
|
res.write_header(b'Content-Type', content_type)
|
||||||
|
|
||||||
async with aiofiles.open(filename, "rb") as fd:
|
with open(filename, "rb") as fd:
|
||||||
res.write_status(200)
|
#check range and support it
|
||||||
|
if start > 0 or not end == -1:
|
||||||
pending_size = total_size
|
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
|
#keep sending until abort or done
|
||||||
while not res.aborted:
|
while not res.aborted:
|
||||||
chunk_size = 16384 #16kb chunks
|
chunk_size = 16384 #16kb chunks
|
||||||
if chunk_size > pending_size:
|
if chunk_size > pending_size:
|
||||||
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
|
pending_size = pending_size - chunk_size
|
||||||
(ok, done) = await res.send_chunk(buffer, total_size)
|
(ok, done) = await res.send_chunk(buffer, size)
|
||||||
if not ok or done or pending_size <= 0: #if cannot send probably aborted
|
if not ok or done: #if cannot send probably aborted
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
res.write_status(500).end("Internal Error")
|
res.write_status(500).end("Internal Error")
|
||||||
|
|
||||||
app = App()
|
app = App()
|
||||||
app.get("/", home)
|
app.get("/", home)
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
# nginx - try_files - 77630.15 req/s
|
# nginx - try_files - 77630.15 req/s
|
||||||
# pypy3 - socketify static - 10245.82 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 - @fastify/static - 5437.16 req/s
|
||||||
# node.js - express.static - 4077.49 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_aiofile - 2390.96 req/s
|
||||||
# python3 - socketify static_aiofiles - 1615.12 req/s
|
# python3 - socketify static_aiofiles - 1615.12 req/s
|
||||||
# python3 - scarlette static uvicorn - 1335.56 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
|
# Express production recommendations: https://expressjs.com/en/advanced/best-practice-performance.html
|
||||||
# Fastify production recommendations: https://www.fastify.io/docs/latest/Guides/Recommendations/
|
# Fastify production recommendations: https://www.fastify.io/docs/latest/Guides/Recommendations/
|
||||||
|
|
||||||
from socketify import App
|
from socketify import App, sendfile
|
||||||
from helpers.static import static_route
|
|
||||||
from helpers.static import sendfile
|
|
||||||
|
|
||||||
|
|
||||||
app = App()
|
app = App()
|
||||||
|
@ -44,8 +42,8 @@ async def home(res, req):
|
||||||
|
|
||||||
app.get("/", home)
|
app.get("/", home)
|
||||||
|
|
||||||
#serve all files in public folder under / route (main route but can be like /assets)
|
#serve all files in public folder under /* route (you can use any route like /assets)
|
||||||
static_route(app, "/", "./public")
|
app.static("/", "./public")
|
||||||
|
|
||||||
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
|
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
|
||||||
app.run()
|
app.run()
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
from .socketify import App, AppOptions, AppListenOptions
|
from .socketify import App, AppOptions, AppListenOptions
|
||||||
|
from .helpers import sendfile
|
|
@ -4,6 +4,7 @@ import mimetypes
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
|
||||||
# We have an version of this using aiofile and aiofiles
|
# 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
|
# 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
|
# 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:
|
except Exception as error:
|
||||||
res.write_status(500).end("Internal Error")
|
res.write_status(500).end("Internal Error")
|
||||||
|
|
||||||
|
|
||||||
def in_directory(file, directory):
|
def in_directory(file, directory):
|
||||||
#make both absolute
|
#make both absolute
|
||||||
directory = path.join(path.realpath(directory), '')
|
directory = path.join(path.realpath(directory), '')
|
|
@ -1,16 +1,24 @@
|
||||||
import cffi
|
import cffi
|
||||||
|
from datetime import datetime
|
||||||
|
from http import cookies
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import mimetypes
|
||||||
import os
|
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 .loop import Loop
|
||||||
from .status_codes import status_codes
|
from .status_codes import status_codes
|
||||||
import json
|
from .helpers import static_route
|
||||||
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
|
|
||||||
|
|
||||||
import platform
|
mimetypes.init()
|
||||||
|
|
||||||
|
|
||||||
is_python = platform.python_implementation() == 'CPython'
|
is_python = platform.python_implementation() == 'CPython'
|
||||||
|
|
||||||
ffi = cffi.FFI()
|
ffi = cffi.FFI()
|
||||||
|
@ -849,6 +857,10 @@ class App:
|
||||||
self.handlers = []
|
self.handlers = []
|
||||||
self.error_handler = None
|
self.error_handler = None
|
||||||
|
|
||||||
|
def static(self, route, directory):
|
||||||
|
static_route(self, route, directory)
|
||||||
|
return self
|
||||||
|
|
||||||
def get(self, path, handler):
|
def get(self, path, handler):
|
||||||
user_data = ffi.new_handle((handler, self))
|
user_data = ffi.new_handle((handler, self))
|
||||||
self.handlers.append(user_data) #Keep alive handler
|
self.handlers.append(user_data) #Keep alive handler
|
||||||
|
|
Ładowanie…
Reference in New Issue