REST API на Sinatra
Мы научились работать с чужими API, настало время научиться писать свое собственное.
Sinatra - это уже нечто большее, чем библиотека, это фреймворк.
Главная отличительная особенность заключается в том, что уже не разработчик пишет как будет работать его программа, а фреймворк, предоставляя разработчику возможности для конфигурации своего собственного поведения.
Для того, чтобы принимать запросы по сети, обрабатывать их и тд, вам как разработчику понадобится уйма времени и сил на реализацию. Фреймворк предоставляет вам коробочное решение. которое закрывает эту большую проблему, а вы, как разработчик, будете заниматься более важной для вас работой - описывать поведение вашего сервера.
Вообще если говорить про сетевой стек, то его можно разделить на 3 модуля:
Rack
- контракт или интерфейс, описывающий как должен вести себя сервер. Rack не содержит в себе никакого кода для работы сети, он лишь говорит, что "если мы дернем метод А, то получим Б", в то время как реализация этого метода А, каким образом мы будем получать Б, это уже задача другого компонента-реализацииRack реализация
- по другому еще называют Rack-совместимый сервер. Это уже готовая библиотека с реализацией сетевых протоколов, которые являются реализацией методов Rack. Сюда также входит логика организации многопоточных обработок, чтобы наш сервер мог обрабатывать не по 1 запросу от пользователя, а десятки, сотни или даже тысячи запросов от пользователей. К таким реализациям можно отнести гемыthin
,puma
,unicorn
,falcon
и другиеHTTP фреймворк
- гем, который в своей основе использует Rack-совместимый сервер и предоставляет разработчику удобный способ взаимодействия с ним, так как в головом виде пользоваться Rack-совместимым сервером будет не так удобно, по сравнению с такими фреймворками как Sinatra или Rails
Теперь давайте добавим в наш Gemfile следующие гемы:
thin
- простая реализация Rack сервераsinatra
- наш фреймворк для удобной работы с Rack сервером
После добавления гемов в Gemfile вызовите bundle install
Теперь создадим самый простой сервер с помощью нашего фреймворка:
запустим наш .rb
файл, если все хорошо, то увидим сообщение Listening on localhost:8000, CTRL+C to stop
Теперь протестируем через клиент в VSCode, дернув нашу новосозданную ручку. В адрес ресурса указываем метод GET
и путь localhost:8000
, где localhost это синоним для локального IP адреса, а 8000
- порт, который слушает наше приложение.
Если в ответ на ваш запрос вы получили строку Hello World !
, значит можно вас поздравить ! Вы запустили свое первое серверное приложение.
Теперь разберемся как нам научиться принимать части относительного пути. Создадим новую 'ручку':
Теперь попробуем отправить запрос по адресу localhost:8000/person/john/23
и получим ответ:
Hello, john. You are 23 y.o.
Отлично.
Таким образом, если в отностельном пути мы можем изменять определенные части и наше приложение будет уметь их распозновать.
А что на счет параметров, которые передаются после вопросительного знака ? Sinatra по умолчанию считает все такие параметры необязательными, поэтому они нигде не объявляются в коде. При этом сами параметры мы можем получить точно так же как и куски пути. Например:
А теперь если мы отправим запрос на адрес localhost:8888/person/John/23?country=Russia
, то мы увидим сообщение:
Hello, John from Russia. You are 23 y.o.
Хотя ни в каком пути мы параметр country
не объявляли
Все, конечно, здорово, но хорошо бы нам научиться отдавать JSON. Возможно вы уже догадались, что нужно сделать.
Для начала создадим объект-хранилище. Такие объекты принято называть DTO (Data Transfer Object) и если дословно перевести, то сразу становится понятно, что такие объекты выполняют только одну функцию - транспортировка данных.
Теперь добавим зависимости в файл нашего сервера:
После чего соберем класс Person и вернем его в качестве ответа в формате JSON
Снова отправим запрос на localhost:8888/person/John/23?country=Russia
и уже в ответ получим JSON:
Структура проекта
В реальной жизни никто не будет писать сервера со всеми ручками в одном файле, поэтому с точки зрениях структуры проекта принято располагать ручки в отдельных классах (их еще называют роутеры Routers
, а сами ручки роуты Route
). При этом отдельный роутер отвечает только за свою отвественность.
Пример: У нас есть интернет-магазин с товаром. Какие области отвественности могут быть у магазина ? Товар, заказ, личный кабинет и тд. Как будет выглядить наша структура проекта:
При этом сам роутер будет выглядеть следующим образом:
в то время как routes/init.rb
будет служить файлом для объединения наших роутеров
А можно в динамике с помощью Dir
вытащить пути до всех файлов в папке с файлом init.rb
и автоматически их импортировать
Файл server.rb
содержит в себе класс который будет хранить в себе конфигурации для нашего Sinatra приложения, а также за счет run! if app_file == $PROGRAM_NAME
мы сможем запускать наше приложение простым вызовом ruby path/to/server.rb
.
файл config.ru
является конфигурационным файлом для утилиты rackup, а не через вызов ruby. Зачем ? Чтобы если мы захотим поменять сервер-имплементацию Rack спецификации или изменить порт для запуска приложения нам бы не пришлось менять исходный код, а всего лишь поменять значения в команде запуска
И теперь простой командой запусаем наш сервер rackup путь/до/config.ru -s thin -p 8000
. Как видим указание какой Rack совместимый сервер использовать, а также порт прослушиваемый приложением переехал из кода в командную строку.
Возможно у вас возник вопрос: А почему мы называем все классы одинаково и нарушаем правило имя_файла == ИмяКласса ? На самом деле это хитрость метапрограммирования из урока 9_professional/1_metaprogramming
, которая позволяет за счет одинакового названия классов их смешать в один целый и большой класс.
Скорее всего эта часть урока вам могла показаться чересчур сложной за счет манипуляций с файлами и динамического импорта этих файлов, но если посидеть и разборать код по строчно, то все встанет на свои места через какое-то время изучения. Цель данных примеров - показать не только как организовать структуру проекта, но и какие сложные и в то же время простые вещи нам позволяет делать Ruby, так как при изучении Rails в дальнейшем нам часто придется сталкиваться с такими хитростями, которые реализовывает сам фреймворк.
Задание weather_info
Внимание! это задание необходимо разместить в директории модуля 11_network/weather_info
Сделайте из вашего консольного приложения серверное на базе фреймворка sinatra
где:
адрес вашей ручки будет
weathers
, которая:принимает страну в виде параметра запроса
country
принимает город в виде параметра запроса
city
если один из параметров не заполнен, то выбросить 400 ошибку с указанием какая переменная не была передана
Вернуть ответ в формате JSON
Пример запроса:
GET localhost:8000/weathers?country=Russia&city=Moscow
GET localhost:8000/weathers?city=Astana
400 Bad Request - param country is empty
Рекоммендации:
Для разделения логики клиентов и севеерной логики, разместите классы клиентоав в отдельном каталоге
clients
Дополнительный материал
Last updated