Как мне обрабатывать URL-ссылки, относящиеся к корневому каталогу моего приложения, не зная корневого URL-адреса приложения?

У меня есть приложение, которое я пишу с использованием CherryPy, HTML-шаблонов Mako и JavaScript. Я хочу разрешить его установку на любой URL-адрес, то есть я хочу, чтобы кто-то мог установить его на http://example.com или http://example.com/app или http://this.is.an.example.com/some/application/whatever . Я также хочу, чтобы оно работало так же хорошо, как приложение WSGI за Apache или с использованием встроенного веб-сервера CherryPy. Однако у меня возникают проблемы с URL-адресами, которые должны использовать мои шаблоны и JavaScript.

То есть, если я хочу получить доступ к ресурсу, относящемуся к корневому каталогу моего приложения, например api/something или static/application.js, как я могу убедиться, что ссылка будет работать независимо от базового URL-адреса моего приложения?

Моим первоначальным наивным решением было использование относительных URL-адресов, но оно перестало работать, когда я захотел вернуть один и тот же шаблон Mako на несколько конечных точек. То есть, если у меня есть шаблон, используемый для отображения элемента, возможно, я захочу отобразить этот элемент где-то на странице в корне (/), а может быть, и еще где-нибудь (например, может быть, /item, а может быть, и /username/items). Если в шаблоне есть относительный URL-адрес, этот URL-адрес является относительным этого местоположения. Это означает, что, поскольку мои статические ресурсы CSS/JS/изображения относятся только к корневому каталогу, ссылки будут разорваны для шаблоны в местах /item и /username/items.

Мако шаблоны

Я обнаружил, что могу обернуть свои URL-адреса в cherrypy.url() в своих шаблонах Mako, что решает эту проблему. Например, этот пример шаблона будет правильно ссылаться на URL-адрес /link, несмотря ни на что:

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

Это хорошо, но немного громоздко, и кажется странным import cherrypy в моих шаблонах. Это правильный способ сделать это? Есть ли способ лучше? Было бы неплохо, если бы это было автоматически, поэтому мне не нужно помнить, что нужно оборачивать URL-адреса самостоятельно.

JavaScript

Я обслуживаю статические файлы .js, используя опцию CherryPy tools.staticdir. Это работает нормально, но я делаю пару вызовов AJAX, и, как и статические ресурсы в моих шаблонах Mako, URL-адреса относятся к корневому каталогу моего приложения. Например, если CherryPy предоставляет URL-адрес /api/something, и я хочу получить доступ к нему из JavaScript, как я могу написать свой JS, чтобы он по-прежнему был доступен независимо от того, где смонтировано мое приложение CherryPy?

Моя первая мысль заключалась в том, что я мог бы добавить какой-то скрытый элемент HTML или комментарий к моим шаблонам, который содержит значение cherrypy.url(), вызываемое без аргументов, что приведет к корневому URL-адресу моего приложения, и что я мог бы пройти через DOM, захватить это значение и добавьте его к любому URL-адресу, который я хотел, прежде чем пытаться сделать HTTP-запрос. Положительным моментом является то, что это было бы довольно прозрачно в JavaScript; Недостатком является то, что было бы легко забыть включить волшебный скрытый элемент HTML, поскольку я добавляю в приложение все больше и больше шаблонов. Я полагаю, что мог бы решить эту проблему, сделав все шаблоны зависимыми от корневого шаблона, но это все еще кажется хаком.

Моя вторая мысль заключалась в том, чтобы превратить мои файлы JavaScript в шаблоны Mako, не использовать tools.staticdir для их обслуживания и использовать тот же метод cherrypy.url(), который я использую в своих существующих HTML-шаблонах Mako. Это привлекает согласованностью и не требует волшебного HTML-элемента в моих существующих шаблонах, но это означает, что файлы должны пройти через весь процесс рендеринга шаблона, прежде чем их можно будет обслуживать с теоретической потерей некоторой скорости, а также чувствует себя как-то неправильно.

Есть ли лучший вариант?

Другие проблемы

Хотя в настоящее время у меня нет этой проблемы, я полагаю, что в будущем я, возможно, захочу использовать URL-адреса, относящиеся к приложению, и в моих статических файлах CSS.

Проблема с запахом кода?

Я провел некоторое время в Google, SO и в документации CherryPy, пытаясь найти решение этой проблемы, но ничего не нашел. Является ли это признаком того, что я делаю что-то странное и что существует некий шаблон или передовая практика, позволяющая избежать этой проблемы, которой я просто не следую?


person Micah R Ledbetter    schedule 18.12.2016    source источник


Ответы (1)


В итоге я преобразовал мои голые файлы JavaScript в шаблоны Mako файлов JavaScript и передал переменную baseurl со значением cherrypy.url('/'). Из-за существующей структуры моего приложения я смог сделать это автоматически для каждого отображаемого шаблона, что в основном удовлетворяет мои потребности.

Как я использую Мако

Во-первых, обратите внимание, что я использовал классы MakoHandler и MakoLoader из страницы Mako на вики CherryPy. Использование этих классов выглядит так (слегка отредактировано для краткости):

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__() передает в 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 устанавливается в корень приложения во всех шаблонах — именно то, что я хотел. Он работает даже с шаблонами, которые я <%include.../> внутри другого шаблона (см. ниже).

Дополнительные преимущества

Раньше в моем HTML-коде был тег <script>, который указывал на статический JS-файл, обслуживаемый CherryPy, и браузер делал отдельный HTTP-запрос, чтобы получить этот файл. Но когда я перешел к использованию Mako для шаблона моего JavaScript, чтобы добавить переменную baseurl, я понял, что могу просто <%include.../> вставить ее прямо в свой HTML, избавившись от одного кругового обхода.

person Micah R Ledbetter    schedule 23.12.2016