在不知道应用程序根 URL 的情况下,我应该如何处理与应用程序根相关的 URL 引用?
How should I handle URL references that are relative to the root of my application, without knowing the application's root URL?
我有一个正在使用 CherryPy、Mako HTML 模板和 JavaScript 编写的应用程序。我想让它安装到任何 URL - 也就是说,我希望有人能够将它安装到 http://example.com, or http://example.com/app, or http://this.is.an.example.com/some/application/whatever。我还希望它能像 Apache 后面的 WSGI 应用程序或使用 CherryPy 的内置网络服务器一样工作。但是,我在处理我的模板和 JavaScript 必须使用的 URL 时遇到了问题。
也就是说,如果我想访问与我的应用程序根目录相关的资源,如 api/something
或 static/application.js
,我如何确保无论我做什么,引用都有效应用的基础 URL 是?
我最初天真的解决方案是使用相对 URLs,但是当我想在多个端点 return 相同的 Mako 模板时,它就停止工作了。也就是说,如果我有一个用于显示项目的模板,也许我会想在页面的根目录 (/
) 的某处显示该项目,也可能在其他地方显示该项目(例如 /item
,也许还有 /username/items
)。如果模板中有一个相对的 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
在我的模板中看起来很奇怪。这是正确的方法吗?有没有更好的办法?如果它是自动的就好了,所以我不必记得自己包装 URLs。
JavaScript
我使用 CherryPy 的 tools.staticdir
选项提供静态 .js 文件。这工作正常,但我进行了几次 AJAX 调用,并且像我的 Mako 模板中的静态资源一样,URLs 是相对于我的应用程序根目录的。例如,如果 CherryPy 公开了一个 /api/something
URL,而我想从 JavaScript 访问它,我该如何编写我的 JS,以便无论我的 CherryPy 应用程序安装在哪里,它仍然可以访问?
我的第一个想法是,我可以向我的模板添加某种隐藏的 HTML 元素或注释,其中包含不带参数调用的 cherrypy.url()
的值,这将导致根 URL 我的应用程序,并且我可以遍历 DOM,获取该值,并在我尝试发出 HTTP 请求之前将其添加到我想要的任何 URL 之前。好处是这在 JavaScript 中是非常透明的;缺点是当我向应用程序添加越来越多的模板时,很容易忘记包含神奇的隐藏 HTML 元素。我想我可以通过使所有模板都依赖于根模板来解决这个问题,但它仍然看起来像是一个 hack。
我的第二个想法是将我的 JavaScript 文件本身变成 Mako 模板,而不是使用 tools.staticdir
来为它们提供服务,并使用我现有的 cherrypy.url()
方法Mako HTML 模板。这具有一致性的吸引力,并且在我现有的模板中不需要神奇的 HTML 元素,但这意味着文件必须经过整个模板渲染过程才能提供,理论上会损失一些速度, 感觉也有点不对。
还有更好的选择吗?
其他问题
虽然我目前没有这个问题,但我想将来我可能也想在我的静态 CSS 文件中使用应用程序相关的 URLs。
代码异味问题?
我花了一些时间在 Google 和 SO 上以及在 CherryPy 文档中试图找到解决这个问题的方法,但我没有找到任何东西。这是否表明我正在做一些奇怪的事情,并且有一些模式或最佳实践可以避免我只是没有遵循的这个问题?
我最终将裸 JavaScript 文件转换为 JavaScript 文件的 Mako 模板,并传入一个 baseurl
变量,设置为 cherrypy.url('/')
的值。由于我的应用程序的现有结构,我能够为每个呈现的模板自动执行此操作,这基本上满足了我的需求。
我如何使用 Mako
首先,请注意我使用的是 the Mako page on the CherryPy wiki 中的 MakoHandler
和 MakoLoader
class。使用那些 classes 看起来像这样(为简洁起见略作编辑):
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__()
传递给 [=19= 的 env
变量来向 所有 模板添加变量替换].考虑到这一点,我们可以将 MakoHandler
class 更改为如下所示:
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 模板中应用程序的根目录——这正是我想要的。它甚至可以与我在另一个模板中 <%include.../>
的模板一起使用(见下文)。
附带好处
之前,在我的 HTML 中,我有一个 <script>
标记指向由 CherryPy 提供的静态 JS 文件,浏览器发出单独的 HTTP 请求来获取该文件。但是当我转而使用 Mako 来模板我的 JavaScript 以添加 baseurl
变量时,我意识到我可以直接 <%include.../>
它在我的 HTML 中,省去一轮旅行。
我有一个正在使用 CherryPy、Mako HTML 模板和 JavaScript 编写的应用程序。我想让它安装到任何 URL - 也就是说,我希望有人能够将它安装到 http://example.com, or http://example.com/app, or http://this.is.an.example.com/some/application/whatever。我还希望它能像 Apache 后面的 WSGI 应用程序或使用 CherryPy 的内置网络服务器一样工作。但是,我在处理我的模板和 JavaScript 必须使用的 URL 时遇到了问题。
也就是说,如果我想访问与我的应用程序根目录相关的资源,如 api/something
或 static/application.js
,我如何确保无论我做什么,引用都有效应用的基础 URL 是?
我最初天真的解决方案是使用相对 URLs,但是当我想在多个端点 return 相同的 Mako 模板时,它就停止工作了。也就是说,如果我有一个用于显示项目的模板,也许我会想在页面的根目录 (/
) 的某处显示该项目,也可能在其他地方显示该项目(例如 /item
,也许还有 /username/items
)。如果模板中有一个相对的 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
在我的模板中看起来很奇怪。这是正确的方法吗?有没有更好的办法?如果它是自动的就好了,所以我不必记得自己包装 URLs。
JavaScript
我使用 CherryPy 的 tools.staticdir
选项提供静态 .js 文件。这工作正常,但我进行了几次 AJAX 调用,并且像我的 Mako 模板中的静态资源一样,URLs 是相对于我的应用程序根目录的。例如,如果 CherryPy 公开了一个 /api/something
URL,而我想从 JavaScript 访问它,我该如何编写我的 JS,以便无论我的 CherryPy 应用程序安装在哪里,它仍然可以访问?
我的第一个想法是,我可以向我的模板添加某种隐藏的 HTML 元素或注释,其中包含不带参数调用的 cherrypy.url()
的值,这将导致根 URL 我的应用程序,并且我可以遍历 DOM,获取该值,并在我尝试发出 HTTP 请求之前将其添加到我想要的任何 URL 之前。好处是这在 JavaScript 中是非常透明的;缺点是当我向应用程序添加越来越多的模板时,很容易忘记包含神奇的隐藏 HTML 元素。我想我可以通过使所有模板都依赖于根模板来解决这个问题,但它仍然看起来像是一个 hack。
我的第二个想法是将我的 JavaScript 文件本身变成 Mako 模板,而不是使用 tools.staticdir
来为它们提供服务,并使用我现有的 cherrypy.url()
方法Mako HTML 模板。这具有一致性的吸引力,并且在我现有的模板中不需要神奇的 HTML 元素,但这意味着文件必须经过整个模板渲染过程才能提供,理论上会损失一些速度, 感觉也有点不对。
还有更好的选择吗?
其他问题
虽然我目前没有这个问题,但我想将来我可能也想在我的静态 CSS 文件中使用应用程序相关的 URLs。
代码异味问题?
我花了一些时间在 Google 和 SO 上以及在 CherryPy 文档中试图找到解决这个问题的方法,但我没有找到任何东西。这是否表明我正在做一些奇怪的事情,并且有一些模式或最佳实践可以避免我只是没有遵循的这个问题?
我最终将裸 JavaScript 文件转换为 JavaScript 文件的 Mako 模板,并传入一个 baseurl
变量,设置为 cherrypy.url('/')
的值。由于我的应用程序的现有结构,我能够为每个呈现的模板自动执行此操作,这基本上满足了我的需求。
我如何使用 Mako
首先,请注意我使用的是 the Mako page on the CherryPy wiki 中的 MakoHandler
和 MakoLoader
class。使用那些 classes 看起来像这样(为简洁起见略作编辑):
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__()
传递给 [=19= 的 env
变量来向 所有 模板添加变量替换].考虑到这一点,我们可以将 MakoHandler
class 更改为如下所示:
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 模板中应用程序的根目录——这正是我想要的。它甚至可以与我在另一个模板中 <%include.../>
的模板一起使用(见下文)。
附带好处
之前,在我的 HTML 中,我有一个 <script>
标记指向由 CherryPy 提供的静态 JS 文件,浏览器发出单独的 HTTP 请求来获取该文件。但是当我转而使用 Mako 来模板我的 JavaScript 以添加 baseurl
变量时,我意识到我可以直接 <%include.../>
它在我的 HTML 中,省去一轮旅行。