【问题标题】:How should I handle URL references that are relative to the root of my application, without knowing the application's root URL?在不知道应用程序的根 URL 的情况下,我应该如何处理与应用程序根目录相关的 URL 引用?
【发布时间】:2017-05-03 13:28:44
【问题描述】:

我正在使用 CherryPy、Mako HTML 模板和 JavaScript 编写一个应用程序。我希望允许将其安装到任何 URL - 也就是说,我希望有人能够将其安装到 http://example.comhttp://example.com/apphttp://this.is.an.example.com/some/application/whatever。我还希望它与 Apache 后面的 WSGI 应用程序或使用 CherryPy 的内置网络服务器一样工作。但是,我无法处理我的模板和 JavaScript 必须使用的 URL。

也就是说,如果我想访问与我的应用程序根目录相关的资源,例如 api/somethingstatic/application.js,我如何确保无论我的应用程序的基本 URL 是什么,引用都可以工作?

我最初的、幼稚的解决方案是使用相对 URL,但是当我想在多个端点返回相同的 Mako 模板时,它就停止了工作。也就是说,如果我有一个用于显示项目的模板,也许我想在页面的根目录 (/) 的某处显示该项目,也可能在其他地方显示该项目(比如可能是 /item,也许是 @ 987654328@ 也是)。如果模板中有一个相对 URL,则该 URL 相对于该位置 - 这意味着,由于我的静态 CSS/JS/image 资源仅与 root 相关,因此链接将被破坏/item/username/items 位置的模板。

Mako 模板

我发现我可以在 Mako 模板中将我的 URL 包装在 cherrypy.url() 中,从而解决了这个问题。例如,这个示例模板无论如何都会正确引用/link URL:

<%!
    import cherrypy
%>
<a href="${cherrypy.url('/link')}">Click here!</a>

这很好,但是有点麻烦,而且我的模板中的import cherrypy 看起来很奇怪。这是正确的方法吗?有没有更好的办法?如果它是自动的就好了,这样我就不必记得自己包装 URL。

JavaScript

我正在使用 CherryPy 的 tools.staticdir 选项提供静态 .js 文件。这工作正常,但我进行了几次 AJAX 调用,就像我的 Mako 模板中的静态资源一样,URL 是相对于我的应用程序根目录的。例如,如果 CherryPy 公开了一个 /api/something URL,并且我想从 JavaScript 访问它,我如何编写我的 JS 以便无论我的 CherryPy 应用程序安装在哪里都可以访问它?

我的第一个想法是,我可以在模板中添加某种隐藏的 HTML 元素或注释,其中包含不带参数调用的 cherrypy.url() 的值,这将导致我的应用程序的根 URL,并且我可以在尝试发出 HTTP 请求之前,遍历 DOM,获取该值,并将其添加到我想要的任何 URL 之前。好处是这在 JavaScript 中是非常透明的。缺点是当我向应用程序添加越来越多的模板时,很容易忘记包含神奇的隐藏 HTML 元素。我想我可以通过使所有模板依赖于根模板来解决这个问题,但它仍然看起来像一个 hack。

我的第二个想法是将我的 JavaScript 文件本身转换为 Mako 模板,而不是使用 tools.staticdir 来提供它们,并使用我在现有 Mako HTML 模板中使用的相同 cherrypy.url() 方法。这具有一致性的吸引力,并且不需要在我现有的模板中使用神奇的 HTML 元素,但这意味着文件必须经过整个模板渲染过程才能以理论上的速度损失,而且它还感觉有点不对。

还有更好的选择吗?

其他问题

虽然我目前没有这个问题,但我想将来我可能也想在我的静态 CSS 文件中使用与应用程序相关的 URL。

代码异味问题?

我在 Google 和 SO 以及 CherryPy 文档中花了一些时间试图找到解决这个问题的方法,但我没有发现任何东西。这是否表明我正在做一些奇怪的事情,并且有一些模式或最佳实践可以避免我没有遵循的这个问题?

【问题讨论】:

    标签: python-3.x cherrypy mako


    【解决方案1】:

    我最终将我的纯 JavaScript 文件转换为 JavaScript 文件的 Mako 模板,并传入一个 baseurl 变量,设置为 cherrypy.url('/') 的值。由于我的应用程序的现有结构,我能够为每个呈现的模板自动执行此操作,这主要满足了我的需求。

    我如何使用 Mako

    首先,请注意我使用了来自the Mako page on the CherryPy wikiMakoHandlerMakoLoader 类。使用这些类看起来像这样(为简洁起见稍作编辑):

    import cherrypy
    from mako.lookup import TemplateLookup
    
    class MakoHandler(cherrypy.dispatch.LateParamPageHandler):
        def __init__(self, template, next_handler):
            self.template = template
            self.next_handler = next_handler
        def __call__(self):
            env = globals().copy()
            env.update(self.next_handler())
            try:
                return self.template.render(**env)
            except:
                cherrypy.response.status = "500"
                return exceptions.html_error_template().render()
    
    class MakoLoader(object):
        def __init__(self):
            self.lookups = {}
        def __call__(self, filename, directories, module_directory=None,
                     collection_size=-1):
            key = (tuple(directories), module_directory)
            try:
                lookup = self.lookups[key]
            except KeyError:
                lookup = TemplateLookup(directories=directories,
                                        module_directory=module_directory,
                                        collection_size=collection_size)
                self.lookups[key] = lookup
            cherrypy.request.lookup = lookup
            cherrypy.request.template = t = lookup.get_template(filename)
            cherrypy.request.handler = MakoHandler(t, cherrypy.request.handler)
    
    main = MakoLoader()
    cherrypy.tools.mako = cherrypy.Tool('on_start_resource', main)
    

    然后允许您像这样引用 CherryPy 中的模板:

    @cherrypy.expose
    @cherrypy.tools.mako(filename="index.html")
    def index(name=None):
        return {'username': name}
    

    添加一组默认变量替换

    现在,使用此代码,您可以通过修改MakoHandler.__call__() 传递给self.template.renderenv 变量来将变量替换添加到所有 模板。考虑到这一点,我们可以将MakoHandler 类更改为如下所示:

    class MakoHandler(cherrypy.dispatch.LateParamPageHandler):
        def __init__(self, template, next_handler):
            self.template = template
            self.next_handler = next_handler
        def __call__(self):
            env = globals().copy()
            env.update(self.next_handler())
            env.update({'baseurl': cherrypy.url('/')})
            try:
                return self.template.render(**env)
            except:
                cherrypy.response.status = "500"
                return exceptions.html_error_template().render()
    

    这样,baseurl 变量被设置为 all 模板中应用程序的根目录——这正是我想要的。它甚至适用于我在另一个模板中&lt;%include.../&gt; 的模板(见下文)。

    附带好处

    之前,在我的 HTML 中,我有一个 &lt;script&gt; 标记,它指向由 CherryPy 提供的静态 JS 文件,并且浏览器发出单独的 HTTP 请求来获取该文件。但是当我开始使用 Mako 为我的 JavaScript 模板添加 baseurl 变量时,我意识到我可以直接在我的 HTML 中使用 &lt;%include.../&gt; 它,从而消除了一次往返。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-18
      • 2012-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-02
      相关资源
      最近更新 更多