Implemented control commands and tests. Rewritten error and session/flash handling.
This commit is contained in:
51
cista/app.py
51
cista/app.py
@@ -1,21 +1,54 @@
|
||||
import asyncio
|
||||
from importlib.resources import files
|
||||
|
||||
import msgspec
|
||||
from html5tagger import E
|
||||
from sanic import Sanic, redirect
|
||||
from sanic import Forbidden, Sanic, SanicException, errorpages
|
||||
from sanic.log import logger
|
||||
from sanic.response import html
|
||||
from sanic.response import html, json, redirect
|
||||
|
||||
from . import config, session, watching
|
||||
from .auth import authbp
|
||||
from .fileio import FileServer
|
||||
from .protocol import ErrorMsg, FileRange, StatusMsg
|
||||
from .protocol import ControlBase, ErrorMsg, FileRange, StatusMsg
|
||||
|
||||
|
||||
app = Sanic("cista")
|
||||
fileserver = FileServer()
|
||||
watching.register(app, "/api/watch")
|
||||
app.blueprint(authbp)
|
||||
|
||||
@app.on_request
|
||||
async def use_session(request):
|
||||
request.ctx.session = session.get(request)
|
||||
# CSRF protection
|
||||
if request.method == "GET" and request.headers.upgrade != "websocket":
|
||||
return # Ordinary GET requests are fine
|
||||
if request.headers.origin.split("//", 1)[1] != request.host:
|
||||
raise Forbidden("Invalid origin: Cross-Site requests not permitted")
|
||||
|
||||
@app.exception(Exception)
|
||||
async def handle_sanic_exception(request, e):
|
||||
context, code, message = {}, 500, str(e) or "Internal Server Error"
|
||||
if isinstance(e, SanicException):
|
||||
context = e.context or {}
|
||||
code = e.status_code
|
||||
message = f"⚠️ {message}"
|
||||
# Non-browsers get JSON errors
|
||||
if "text/html" not in request.headers.accept:
|
||||
return json({"error": {"code": code, "message": message, **context}}, status=code)
|
||||
# Redirections flash the error message via cookies
|
||||
if "redirect" in context:
|
||||
res = redirect(context["redirect"])
|
||||
res.cookies.add_cookie("message", message, max_age=5)
|
||||
return res
|
||||
# Otherwise use Sanic's default error page
|
||||
return errorpages.HTMLRenderer(request, e, debug=app.debug).full()
|
||||
|
||||
@app.on_response
|
||||
async def resphandler(request, response):
|
||||
print("In response handler", request.path, response)
|
||||
|
||||
def asend(ws, msg):
|
||||
return ws.send(msg if isinstance(msg, bytes) else msgspec.json.encode(msg).decode())
|
||||
|
||||
@@ -36,11 +69,11 @@ async def index_page(request):
|
||||
if not s:
|
||||
return redirect("/login")
|
||||
index = files("cista").joinpath("static", "index.html").read_text()
|
||||
flash = request.cookies.flash
|
||||
flash = request.cookies.message
|
||||
if flash:
|
||||
index += str(E.div(flash, id="flash"))
|
||||
index += str(E.dialog(flash, id="flash", open=True, style="position: fixed; top: 0; left: 0; width: 100%; opacity: .8"))
|
||||
res = html(index)
|
||||
res.cookies.delete_cookie("flash")
|
||||
session.flash(res, None)
|
||||
return res
|
||||
return html(index)
|
||||
|
||||
@@ -101,3 +134,9 @@ async def download(request, ws):
|
||||
await asend(ws, res)
|
||||
logger.exception(repr(res), e)
|
||||
return
|
||||
|
||||
@app.websocket("/api/control")
|
||||
async def control(request, websocket):
|
||||
cmd = msgspec.json.decode(await websocket.recv(), type=ControlBase)
|
||||
await asyncio.to_thread(cmd)
|
||||
await asend(websocket, StatusMsg(status="ack", req=cmd))
|
||||
|
||||
Reference in New Issue
Block a user