Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Equivalent of base_url? #220

Closed
glorietaru opened this issue Feb 17, 2021 · 21 comments · Fixed by #459
Closed

Equivalent of base_url? #220

glorietaru opened this issue Feb 17, 2021 · 21 comments · Fixed by #459
Assignees
Labels
enhancement New feature or request wontfix This will not be worked on
Milestone

Comments

@glorietaru
Copy link

Running justpy below the root url

  • I run a jupyter notebook at eg https://<my_site>/jupyter.
  • This is enabled by setting c.NotebookApp.base_url = '/jupyter/' in my jupyter_notebook_config.
  • Is there an equivalent justpy setting that will allow me to run a development version of justpy at eg https://<my_site>/dvl?
    • (I'm running behind nginx with location /dvl making a proxy_pass to the justpy instance.)
    • justpy reports: JustPy says: Page not found.
    • I've tried running uvicorn --host 0.0.0.0 --port $JUSTPY_PORT --root-path "/dvl" server:app
      • but this does the opposite of what I want: "GET /dvl/dvl HTTP/1.1" 200 OK.
@elimintz
Copy link
Collaborator

I think the following example does what you want:

import justpy as jp

base_url = '/test_site'

@jp.SetRoute(base_url + '/')
def page1(request):
    wp = jp.WebPage()
    jp.Div(text='First page', classes='m-4 text-xl', a=wp)
    return wp


@jp.SetRoute(base_url + '/info')
def page2(request):
    wp = jp.WebPage()
    jp.Div(text='info page', classes='m-4 text-xl', a=wp)
    return wp


def error_page(request):
    wp = jp.WebPage()
    jp.Div(text=f'Unknown url error page for test version with base url: {base_url}', classes='m-4 text-xl', a=wp)
    return wp

jp.justpy(error_page)

Please let me know if this is not what you had in mind.

@glorietaru
Copy link
Author

  • I'm not sure I follow this, to be honest.
  • My aim is to have production and development services on the same machine but at separate urls.
  • I may be going about this the wrong way. (But as I say, something similar works with jupyter.)

nginx

  upstream justpy {
      server localhost:8000 fail_timeout=0;
  }

  upstream dvljustpy {
      server localhost:7999 fail_timeout=0;
  }
       location / {
            proxy_pass http://justpy;  # justpy service.
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            # WebSocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
  • The above all works fine with justpy running on :8000
  • But this doesn't:
        location /dvl {
            proxy_pass http://dvljustpy;  # justpy service.
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            # WebSocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }

justpy server code

base_url = "/dvl" if os.environ.get("JUSTPY_STAGE") == "dvl" else ""

....

@jp.SetRoute(base_url + "/")
def root(request):
    wp = jp.WebPage(delete_flag=False)
    """ useful code here """
    return wp

....

jp.justpy()

Result:

  • JustPy says: Page not found
  • If I have instead:
jp.justpy(error_page)
  • The result is: Unknown url error page for test version with base url: /dvl.

@elimintz
Copy link
Collaborator

What happens when you change the last line to:

jp.justpy(error_page, port=7999)

And then got to /dv1/ ?

@glorietaru
Copy link
Author

Unknown url error page for test version with base url: /dvl

Is there some way for me to investigate the JustPy says: Page not found response to see how it is reached?

@elimintz
Copy link
Collaborator

It is reached when no other route matches. What is the full url you are using and getting the error?

@glorietaru
Copy link
Author

@elimintz
Copy link
Collaborator

Please try https://glorieta.kingsgroup.org/dvl/
(an extra slash at the end)

@glorietaru
Copy link
Author

glorietaru commented Feb 19, 2021 via email

@rodja
Copy link
Collaborator

rodja commented May 23, 2021

When trying the above configuration the justpy main page loads successfully. But ressources like vue.js are loaded from /templates/local and not /test_site/templates/local.

@elimintz
Copy link
Collaborator

If you set the NO_INTERNET configuration variable to False, the libraries will be loaded from the internet. https://justpy.io/tutorial/configuration/#working-locally-without-an-internet-connection

Alternatively, you can change the template files, but that is a more complex. https://justpy.io/tutorial/static/#advanced-configuration

What I do is run the test system on the same url but use a different port (8001 for example). You can supply the port directly in the justpy command and it overrides what is found in the justpy.env file. This requires using a test_flag also and is not as elegant a solution as I would like.

A cleaner solution is required for setting up a testing site with a different url. I will try to figure this out.

@rodja
Copy link
Collaborator

rodja commented May 24, 2021

If you set the NO_INTERNET configuration variable to False, the libraries will be loaded from the internet. https://justpy.io/tutorial/configuration/#working-locally-without-an-internet-connection

Nice hack. This seems to work for my scenario. Almost. Somehow the page constantly reloads now about every 2 seconds.

Alternatively, you can change the template files, but that is a more complex. https://justpy.io/tutorial/static/#advanced-configuration

What I do is run the test system on the same url but use a different port (8001 for example). You can supply the port directly in the justpy command and it overrides what is found in the justpy.env file. This requires using a test_flag also and is not as elegant a solution as I would like.

Sure, a different port would work. But we want to run multiple Justpy containers beside other microservices. All managed and routed by a single Traefik proxy.

A cleaner solution is required for setting up a testing site with a different url. I will try to figure this out.

Maybe you need to pass the referer path of the request to the template:

from urllib.parse import urlparse
...
context = {'request': request, 'page_id': load_page.page_id, 'justpy_dict': json.dumps(page_dict, default=str),
                  'use_websockets': json.dumps(WebPage.use_websockets), 'options': template_options, 'page_options': page_options,
                  'html': load_page.html, 'path_prefix': urlparse(request.headers['Referer']).path}

Then the templates could use this path_prefix like this:

<link href="{{ path_prefix }}templates/local/tailwind/base.css" rel="stylesheet">

@rodja
Copy link
Collaborator

rodja commented May 24, 2021

It was a bit more complicated than stated above. See my pull request #258 for a complete diff. I hope I've not missed something.

@WolfgangFahl WolfgangFahl added the enhancement New feature or request label Aug 20, 2022
@WolfgangFahl
Copy link
Collaborator

Can this be closed?

@rodja
Copy link
Collaborator

rodja commented Aug 21, 2022

Not jet. We found a nicer solution which I'll provide as a pull request in the next days.

@rodja
Copy link
Collaborator

rodja commented Aug 27, 2022

I now created #459 with the solution we have used in NiceGUI and already use in production environments. It uses the <base> tag to reduce the code clutter from our previous solution which can be viewed in #258.

Also @frankie567 created #451 which uses the url_for() call from Starlette to solve the problem.

To better compare and test the different approaches I've just pushed a reverse proxy docker setup: 72f94a9.

Usage:

  1. cd docker/reverse_proxy_demo
  2. docker-compose up -d
  3. open browser at http://localhost:8000 to see the JustPy "hello world" served directly from uvicorn
  4. open browser at http://localhost:8888/justpy to see the JustPy "hello world" served from behind Traefik reverse proxy at a non-root-path

@rodja
Copy link
Collaborator

rodja commented Aug 31, 2022

@WolfgangFahl I don't think the issue is completed. You merged and then reverted #459. I'm surprised by both actions: the pull request was not ready because had not decided whether to use the <base> tag or not. And the revert happened due to failing tests. But the test have been passing on the pull request and the main branch is currently failing. Even without the changes made in #459.

@rodja rodja reopened this Aug 31, 2022
@WolfgangFahl
Copy link
Collaborator

@rodja thanks for getting back to issues and pull request. Indeed the testing and release procedure has not settled to a satisfactory state at this point and quite a few of the recent actions were try and error style. The refactoring is currently risky and that's why i increased the release numbers to make sure people can revert to 0.2.x versions should the refactoring be too trouble some. This morning I'll work on #481 together with Tim and we'll at least check all http://jpdemo.bitplan.com/ examples and our own applications.

Please note that #478 now uses inheritance for Routing and Route should therefore be replaced with JpRoute. The base_url handling could IMHO therefore be using the JpRouter functionality. To do this properly we IMHO need to do a better initialization of jp.app

app = Starlette(middleware=middleware,debug=DEBUG)

in comparison to https://www.starlette.io/applications/ and https://www.starlette.io/routing/ where the routes are parameters of the startup.

What compatbility issues do you see with the current 0.4.x state of affairs and are the planned milestones still ok for you?

Will the new routing approach work for your usescases:

from starlette.routing import Route, Match
import typing

class JpRoute(Route):
    '''
    extends starlette Routing
    
    see 
       https://www.starlette.io/routing/
    
       https://github.com/encode/starlette/blob/master/starlette/routing.py
    '''
    # map for all routes that are defined
    routesByPath={}
    
    @classmethod
    def reset(cls):
        JpRoute.routesByPath={}
        
    @classmethod
    def getFuncForRequest(cls,request):
        '''
        get the function for the given request
        
        Args:
            request: the starlette request
            
        Returns:
            Callable: the function that is bound to the path of the given request
        '''
        scope=request.scope
        return JpRoute.getFuncForScope(scope)
    
    @classmethod
    def getFuncForScope(cls,scope):
        '''
        get the function (endpoint in starlette jargon) for the given scope
        
        Args:
            path: the path to check
        Returns:
            Callable: the function that is bound to the given path 
        '''
        for _path,route in JpRoute.routesByPath.items():
            match,_matchScope=route.matches(scope)
            if match is not Match.NONE:
                func_to_run=route.endpoint
                return func_to_run
        return None
     
    def __init__(self, path: str, endpoint: typing.Callable,**kwargs):
        '''
        constructor
        '''
        # call super constructor
        Route.__init__(self, path=path,endpoint=endpoint,**kwargs)
        # remember my routes 
        JpRoute.routesByPath[path]=self
        
    def __repr__(self):
        return f'{self.__class__.__name__}(name: {self.name}, path: {self.path}, format: {self.path_format}, func: {self.endpoint.__name__}, regex: {self.path_regex})'
  
class SetRoute:
    '''
    Justpy specific route annotation
    '''

    def __init__(self, route, **kwargs):
        '''
        constructor
        
        Args:
            route(Route): the starlette route to set
            **kwargs: Arbitrary keyword arguments.
        '''
        self.route = route
        self.kwargs = kwargs

    def __call__(self, fn, **_instance_kwargs):
        '''
        Args:
            fn(Callable): the function
            **_instance_kwargs: Arbitrary keyword arguments (ignored).
        
        '''
        # create a new route
        JpRoute(path=self.route, endpoint=fn,  name=self.kwargs.get('name', None))
        return fn

@rodja
Copy link
Collaborator

rodja commented Sep 1, 2022

I would rather like to wait until the code is more stable again. It's very time consuming to prepare and test a pull request. If things are changing a lot its not the best time to do more changes.

@WolfgangFahl
Copy link
Collaborator

With #502 fixed it should now be possible to use standard starlette mounting/routing system

@frankie567
Copy link

@WolfgangFahl Unless I'm mistaken, I don't think the underlying problem of serving Justpy through a path prefix is solved. There are still URL that are generated statically, in particular the WebSocket URL:

if (location.protocol === 'https:') {
var protocol_string = 'wss://'
} else {
protocol_string = 'ws://'
}
var ws_url = protocol_string + document.domain;
if (location.port) {
ws_url += ':' + location.port;
}
socket = new WebSocket(ws_url);

Could you clarify this?

@WolfgangFahl WolfgangFahl reopened this Sep 16, 2022
@WolfgangFahl WolfgangFahl added this to the 0.11 milestone Nov 23, 2022
@WolfgangFahl WolfgangFahl modified the milestones: 0.11, 0.12 Dec 19, 2022
@WolfgangFahl WolfgangFahl added the wontfix This will not be worked on label Sep 20, 2023
@WolfgangFahl
Copy link
Collaborator

see #685

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request wontfix This will not be worked on
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants