如何在不知道应用程序的根URL的情况下处理相对于应用程序根目录的URL引用?

时间:2016-12-18 08:19:50

标签: python-3.x cherrypy mako

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

也就是说,如果我想访问与我的应用程序根目录相关的资源,例如api/somethingstatic/application.js,那么无论我的应用程序基础是什么,我如何确保该引用都能正常工作网址是?

我最初的,天真的解决方案是使用相对URL,但当我想在多个端点返回相同的Mako模板时,这停止了工作。也就是说,如果我有一个用于显示项目的模板,也许我会想要在根页面(/)的某个地方显示该项目,也可能在其他地方也显示该项目(如/item ,也许/username/items。如果模板中包含相对URL,则该URL与相对于该位置 - 这意味着,由于我的静态CSS / JS /图像资源仅与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,以及我可以遍历DOM,获取该值,并在我尝试发出HTTP请求之前将其添加到我想要的任何URL。好处是,这在JavaScript中非常透明;缺点是,当我向应用程序添加越来越多的模板时,很容易忘记包含魔术隐藏的HTML元素。我想我可以通过使所有模板依赖于根模板来解决这个问题,但它看起来仍然像黑客一样。

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

有更好的选择吗?

其他问题

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

代码异味问题?

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

1 个答案:

答案 0 :(得分:1)

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

我如何使用Mako

首先,请注意我使用the Mako page on the CherryPy wiki中的MakoHandlerMakoLoader类。使用这些类看起来像这样(为简洁起见,轻微编辑):

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}

添加一组默认的变量替换

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

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在另一个模板中的模板(见下文)。

附带好处

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