justpy-org / justpy

An object oriented high-level Python Web Framework that requires no frontend programming
https://justpy.io
Apache License 2.0
1.22k stars 96 forks source link

Returning a PDF #587

Closed aalexei closed 1 year ago

aalexei commented 2 years ago

Hi, the following code used to work in juspy but now gives a "AssertionError: Function did not return a web page but a Response"

@jp.SetRoute('/file/{uid}')
async def servePdf(request):
 ...
    return starlette.responses.Response(
        content=data,
        #headers={'Content-Disposition':'attachment; filename="name.pdf"'},
        media_type='application/pdf'
    )

What is the correct way of returning a binary file like a PDF?

aalexei commented 2 years ago

Ok, one workaround is to insert a starlette route instead of using SetRoute:

async def servePdf(request):
 ...
    return starlette.responses.Response(
        content=data,
        #headers={'Content-Disposition':'attachment; filename="name.pdf"'},
        media_type='application/pdf'
    )
jp.app.routes.insert(0, starlette.routing.Route('/file/{uid}', endpoint=servePdf))

The problem I now have is that session_id is not available on the request object. Any way of getting that independent of request (I'm using it to track authentication)?

aalexei commented 2 years ago

Could replicate some code from justpy:

from jpcore.justpy_config import SECRET_KEY, SESSION_COOKIE_NAME
from itsdangerous import Signer

def setSessionId(request):
    '''
    Set session_id when bypassing JP routing
    '''
    cookie_signer = Signer(str(SECRET_KEY))
    session_cookie = request.cookies.get(SESSION_COOKIE_NAME)
    session_id = cookie_signer.unsign(session_cookie).decode("utf-8")
    request.session_id = session_id

Seems fragile though, if justpy changes its cookie handling then it will break. Is there a better way of handling responses that are not WebPage's?

WolfgangFahl commented 2 years ago

@aalexei the current state of refactoring has moved the cookie handling to justpy_app in jpcore see https://github.com/justpy-org/justpy/blob/71028167e395b036e379527ed93c74ef9f86eeb8/jpcore/justpy_app.py#L379 and the code excerpt below

i consider this as a duplicate/addition to #535

current state of justpy cookie handling

def handle_session_cookie(self,request) -> typing.Union[bool, Response]:
        """
        handle the session cookie for this request

        Returns:
            True if a new cookie and session has been created
        """
        # Handle web requests
        session_cookie = request.cookies.get(SESSION_COOKIE_NAME)
        new_cookie=None
        if SESSIONS:
            new_cookie = False
            if session_cookie:
                try:
                    session_id = cookie_signer.unsign(session_cookie).decode("utf-8")
                except:
                    return PlainTextResponse("Bad Session")
                request.state.session_id = session_id
                request.session_id = session_id
            else:
                # Create new session_id
                request.state.session_id = str(uuid.uuid4().hex)
                request.session_id = request.state.session_id
                new_cookie = True
                logging.debug(f"New session_id created: {request.session_id}")
        return new_cookie

    def set_cookie(self, request, response, load_page, new_cookie: typing.Union[bool, Response]):
        """
        set the cookie_value

        Args:
            request: the request 
            response: the response to be sent
            load_page(WebPage): the WebPage to handle
            new_cookie(bool|Response): True if there is a new cookie. Or Response if cookie was invalid
        """
        if isinstance(new_cookie, Response):
            return new_cookie
        if SESSIONS and new_cookie:
            cookie_value = cookie_signer.sign(request.state.session_id)
            cookie_value = cookie_value.decode("utf-8")
            response.set_cookie(
                SESSION_COOKIE_NAME, cookie_value, max_age=COOKIE_MAX_AGE, httponly=True
            )
            for k, v in load_page.cookies.items():
                response.set_cookie(k, v, max_age=COOKIE_MAX_AGE, httponly=True)
        return response
WolfgangFahl commented 2 years ago

This is linked to #180

WolfgangFahl commented 1 year ago

you might want to try the direct FastAPI approach now see #662 and https://fastapi.tiangolo.com/advanced/custom-response/

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()

@app.get("/")
async def main():
    return FileResponse(some_file_path)

app=JustpyApp.app and the routing needs to be in the constructor of your JustpyApp