Наконец, нам нужно создать экземпляр ATH::View::FormatHandlerInterface, который будет обрабатывать процесс подключения всего, чтобы возвращаемое значение действия контроллера отображалось через Crinja и возвращалось клиенту. Создайте src/services/html_format_handler.cr со следующим содержимым:
@[ADI::Register]
class HTMLFormatHandler
include Athena::Framework::View::FormatHandlerInterface
private CRINJA = Crinja.new loader: Crinja::Loader::
FileSystem
Loader.new "#{__DIR__}/../views"
def call(view_handler : ATH::View::ViewHandlerInterface, view
: ATH::ViewBase, request : ATH::Request, format : String) :
ATH::Response
ann_configs = request.action.annotation_configurations
unless template_ann = ann_configs[Blog::Annotations::
Template]?
raise "Unable to determine the template for the
'#{request.attributes.get "_route"}' route."
end
unless (data = view.data).is_a? Crinja::Object
raise ATH::Exceptions::NotAcceptable.new "Cannot convert value of type '#{view.data.class}' to '#{format}'."
end
content = CRINJA.get_template(template_ann.name). render({data: view.data})
ATH::Response.new content, headers: HTTP::Headers{"content- type" => "text/html"}
end
def format : String
"html"
end
end
Помимо выполнения некоторых вещей, с которыми мы уже должны быть знакомы, таких как регистрация службы и включение модуля интерфейса, мы также определяем метод #format, который возвращает формат, который обрабатывает этот тип. Мы также создали одноэлементный экземпляр Crinja, который будет загружать шаблоны из папки src/views. Crinja считывает шаблоны при каждом вызове #get_template, поэтому нет необходимости перезапускать сервер, если вы только внесли изменения в шаблон. Однако в его нынешнем виде для этого потребуется, чтобы путь существовал и был действительным как в среде разработки, так и в производственной среде. Рассмотрите возможность использования переменной среды для указания пути.
Наконец, мы определили метод #call, который имеет доступ к различной информации, которую можно частично использовать для обработки ответа. В нашем случае нам нужны только параметры view и request, последний из которых используется для получения всех конфигураций аннотаций, определенных на соответствующем маршруте. Здесь в игру вступает аннотация, которую мы создали ранее, поскольку мы можем проверить, применяется ли ее экземпляр к действию контроллера, связанному с текущим запросом. См. https://athenaframework.org/Framework/View/ для получения дополнительной информации о том, что отображается через эти параметры.
Далее мы обрабатываем некоторые контексты ошибок, например, если конечная точка не имеет аннотации шаблона или возвращаемое значение не может быть отображено через Crinja. Я намеренно создаю общие исключения, чтобы возвращался ответ об ошибке 500, поскольку мы не хотим утечки внутренней информации за пределы API.
Наконец, мы используем Crinja для получения шаблона на основе имени в аннотации и его визуализации, используя значение, возвращаемое из действия контроллера, в качестве значения объекта данных. Затем мы используем визуализированное содержимое в качестве тела ответа для ATH::Response, устанавливая тип содержимого ответа на text/html.
Чтобы включить такое поведение, нам просто нужно применить аннотацию @ [Blog::Annotations::Template("article.html.j2")] к нашему методу #article в ArticleController. Мы можем все проверить, сделав еще один запрос:
curl --request GET 'http://localhost:3000/article/1' --header
'accept: text/html'
Ответом в этом контексте должен быть наш HTML-шаблон. Если вы установите заголовок application/json или вообще удалите его, ответом должен быть JSON.
Резюме
И вот она, реализация блога, в которой используются некоторые интересные функции Athena, которые, в свою очередь, сделали реализацию простой и очень гибкой. Мы использовали преобразователи параметров для обработки как десериализации тела запроса, так и для поиска и предоставления значения из базы данных. Мы создали специальный обработчик аннотаций и форматов для поддержки ответов в нескольких форматах посредством согласования содержимого. И самое главное, мы прикоснулись к компоненту DI, показав, как он упрощает повторное использование объектов, а также как можно использовать концепцию контейнера на запрос для предотвращения утечки состояния между запросами.
Как вы можете себе представить, Athena использует немало концепций метапрограммирования для реализации своих функций. В следующей главе мы собираемся изучить основную функцию метапрограммирования — макросы.