Шрифт:
Интервал:
Закладка:
def create_article : Blog::Entities::Article
Blog::Entities::Article.new "Title", "Body"
end
Если вы отправите еще один запрос, вы увидите тот же ответ. Причина, по которой это работает, связана с Рис. 9.1, приведенным ранее в этой главе. Если действие контроллера возвращает ATH::Response, этот ответ возвращается клиенту в том виде, в каком он есть. Если возвращается что-то еще, генерируется событие просмотра, задачей которого является преобразование возвращаемого значения в ATH::Response.
Athena также предоставляет некоторые более специализированные подклассы ATH::Response. Например, ATH::RedirectResponse можно использовать для обработки перенаправлений, а ATH::StreamedResponse можно использовать для потоковой передачи данных клиенту посредством фрагментированного кодирования в тех случаях, когда в противном случае данные ответа были бы слишком большими, чтобы поместиться в памяти. Дополнительную информацию об этих подклассах см. в документации API: https://athenaframework.org/Framework/.
Предполагая, что наш API будет обслуживать отдельную базу кода внешнего интерфейса, нам нужно будет настроить CORS, чтобы внешний интерфейс мог получить доступ к данным. Athena поставляется в комплекте с прослушивателем, который его обрабатывает, и его нужно просто включить и настроить.
Чтобы все было организованно, давайте создадим новый файл src/config.cr и добавим следующий код, обязательно потребовав его и в src/blog.cr:
def ATH::Config::CORS.conРисунок : ATH::Config::CORS?
new(
allow_credentials: true,
allow_origin: ["*"],
)
end
В идеале значение источника должно быть фактическим доменом вашего приложения, например https://app.myblog.com. Однако в этой главе мы просто позволим все что угодно. Athena также поддерживает концепцию параметров, которые можно использовать для настройки независимо от окружающей среды. Дополнительную информацию см. на https://athenaframework.org/Components/config/.
Мы также используем не слишком широко известную функцию Crystal, чтобы сделать нашу логику настройки более краткой. Определению может быть присвоен префикс типа и точка перед именем метода в качестве ярлыка при определении метода класса для определенного типа. Например, предыдущий пример будет эквивалентен следующему:
struct ATH::Config::CORS
def self.conРисунок : ATH::Config::CORS?
new(
allow_credentials: true, allow_origin: ["*"],
)
end
end
Помимо того, что сокращенный синтаксис является более кратким, он устраняет необходимость выяснять, является ли тип структурой или классом. На этом этапе мы можем сделать запрос и получить обратно созданную статью, но, учитывая, что статья, возвращаемая из этой конечной точки, жестко запрограммирована, это бесполезно. Давайте проведем рефакторинг, чтобы мы могли создать статью на основе тела запроса.
Обработка тела запроса
Как мы видели ранее, поскольку мы включили JSON::Serializable в нашу сущность, мы можем преобразовать его в представление JSON. Мы также можем сделать обратное: создать экземпляр на основе строки JSON или I/O. Для этого мы можем обновить действие нашего контроллера, обновив его так:
def create_article(request : ATH::Request) :
Blog::Entities::Article
if !(body = request.body) || body.peek.try &.empty?
raise ATH::Exceptions::BadRequest.new "Request does not have a body."
end
Blog::Entities::Article.from_json body
end
Параметры действия контроллера, например параметры пути маршрута или запроса, передаются действию в качестве аргументов метода. Например, если путь действия был "/add/{val1}/{val2}", метод действия контроллера будет следующим: def add(val1 : Int32, val2 : Int32) : Int32, где разрешаются два добавляемых значения. из пути, преобразуются в ожидаемые типы и передаются методу. Аргументы действия также могут поступать из значений по умолчанию, аргументов типа ATH::Request или атрибутов запроса.
В этом примере мы используем типизированный параметр ATH::Request для получения доступа к телу запроса и его десериализации. Также технически возможно, что запрос не имеет тела, поэтому мы проверяем его существование, прежде чем продолжить, возвращая ответ об ошибке, если оно равно nil или если тело запроса отсутствует. Мы также выполняем десериализацию непосредственно из I/O тела запроса, поэтому не нужно создавать промежуточную строку, что приводит к более эффективному использованию памяти.
Обработка ошибок в Athena очень похожа на любую другую программу Crystal, поскольку для представления ошибок она использует исключения. Athena определяет набор общих типов исключений в пространстве имен ATH::Exceptions. Каждое из этих исключений наследуется от Athena::Exceptions::HTTPException, который представляет собой особый тип исключения, используемый для возврата ответов об ошибках HTTP. Например, если тела не было, оно будет возвращено клиенту с кодом состояния 400:
{
"code": 400,
"message": "Request does not have a body."
}
Базовый тип или дочерний тип также могут быть унаследованы для сбора дополнительных данных или добавления дополнительных функций. Любое возникающее исключение, не являющееся экземпляром Athena::Exceptions::HTTPException, рассматривается как внутренняя ошибка сервера 500. По умолчанию эти ответы об ошибках сериализуются в формате JSON, однако это поведение можно настроить. См. https://athenaframework.org/Framework/ErrorRendererInterface/ для получения дополнительной информации.
Теперь, когда мы убедились, что есть тело, мы можем продолжить и создать экземпляр нашей статьи, вернув тело Blog::Entities::Article.from_json. Если бы вы сделали тот же запрос, что и раньше, но с этой полезной нагрузкой, вы бы увидели, что все, что вы отправляете, вы получите обратно в ответ:
{
"title": "My Title",
"body": "My Body"
}
Соответствующая команда cURL будет выглядеть следующим образом:
curl --request POST 'http://localhost:3000/article'
--header 'Content-Type: application/json'
--data-raw '{
"title": "My Title",
"body": "My Body"
}'
Отлично! Но так же, как существовал лучший способ вернуть ответ, Athena предлагает довольно удобный способ упростить десериализацию тела ответа. У Athena есть уникальная концепция, называемая преобразователями параметров. Конвертеры параметров позволяют применять собственную логику для преобразования необработанных данных из запроса в более сложные типы. См. https://athenaframework. org/Framework/ParamConverter/ для получения дополнительной информации.
Примеры преобразователей параметров включают следующее:
• Преобразование строки даты и времени в экземпляр времени.
• Десериализация тела запроса в определенный тип.
• Преобразование параметра пути идентификатора пользователя в реальный экземпляр пользователя.
Athena предоставляет первые два в качестве встроенных преобразователей, но когда дело доходит до определения пользовательских конвертеров, нет предела. Давайте воспользуемся преобразователем параметров, чтобы упростить действие контроллера создания статьи. Обновите метод следующим образом:
@[ARTA::Post("/article")]
@[ATHA::ParamConverter("article", converter:
ATH::RequestBodyConverter)]
def create_article(article : Blog::Entities::Article) :
Blog::Entities::Article
article
end
Нам удалось сжать действие контроллера в одну строку! Основным нововведением здесь является аннотация ATHA::ParamConverter, а также обновление метода для приема экземпляра статьи вместо запроса. Первый позиционный аргумент в аннотации представляет, какой параметр действия