Предоставление данных для внешних приложений
Код, написанный на занятии можно скачать по ссылке.
В сети Интернет сейчас в качестве клиентов для веб-приложений зачастую выступают веб-браузеры. Браузеры запрашивают документы с сервера, обрабатывают их и показывают пользователю. Зачастую документами являются отображаемые HTML-страницы или файлы, которые необходимы для неё: стилевые файлы, изображения и так далее.
Однако даже для таких страниц используются встраиваемые скрипты, которые могут запрашивать у сервера другие документы. Данные документы обрабатываются скриптами и производится динамическое изменение структуры отображаемой пользователю страницы. Документы могут быть статическими, однако зачастую они создаются веб-приложением в зависимости от запрашиваемых параметров.
Другим клиентом для веб-приложения может стать другое приложение, которое формирует HTTP-запрос по указанному адресу, передаёт некоторые параметры на сервер и обрабатывает возвращённые данные. Примером такого приложения является клиент для социальных сетей, который публикует записи одновременно в нескольких социальных сетях, упрощая распространение информации. Вы также можете предусмотреть интерфейс для своего веб-приложения, чтобы другие приложения наряду с людьми смогли выполнять действия, облегчая работу последним.
Стоит отметить также и мобильные приложения, которые зачастую являются продвинутыми клиентами для веб-приложений. Из-за небольшого экрана и ограниченности веб-платформы работа с классическими веб-страницами на мобильных устройствах затруднена. Для обеспечения качественного доступа к содержимому веб-приложения создаются мобильные приложения, которые обращаются к серверу за получением данных уже не в формате HTML-документов, а в удобном для приложения формате. Ввиду увеличения количества клиентских мобильных устройств, а также развития концепции «интернет вещей» следует ожидать увеличения второго рода клиентов.
Описание Интернет-ресурсов с помощью REST
Концепции при разработке REST
На основании протокола HTTP можно построить различные схемы взаимодействия, удовлетворяющие тем или иным требованиям. Ввиду того, что постоянно изменяются требования к работе приложений, данные схемы постоянно эволюционируют и адаптируются к новым условиям. Мы рассмотрим одну из таких схем, которая получила широкое распространение для описания машинных интерфейсов веб-приложений.
Аббревиатура REST расшифровывается как Representational state transfer, репрезентативная передача состояния. Основная задача, которая стояла перед разработчиками данной схемы, - это обеспечение понятной схемы взаимодействия с ресурсами. Если клиент знает название ресурса, то автоматически знает и основные принципы взаимодействия с данными ресурсами.
Стоит отметить, что большинство веб-приложений оперирует сразу наборами элементов, а не уникальными сущностями. Это возникает ввиду их базовой задачи - обслуживания множества клиентов одновременно. Если для одного клиента какой-то ресурс является уникальным, то с точки зрения самого приложения этот ресурс будет одним из списка. Точно такой же принцип применён при разработке REST - за каждым описываем ресурсом скорее всего стоит список из очень похожих элементов.
Другой базовый принцип REST - это признание использование протокола HTTP в качестве основы для передачи данных. То есть для получения данных используются GET-запросы, позволяющие кешировать результаты запросов, а для отправки данных - POST-запросы.
Про другие подходы при проектировании REST можно прочитать на странице в википедии, а более глубоко погрузится в тематику REST можно с помощью следующих источников:
Реализация REST для веб-приложений
Для определения REST-интерфейса необходимо определить следующие моменты:
- URI ресурса, над которым производится работа. Например для определения ресурса пирожков можно выделить путь
/pies
. Для Sinatra-приложения по умолчанию полный путь будет выглядеть какhttp://localhost:4567/pies
. В дальнейшем будем использовать короткое наименование пути. - Формат сообщений, которые будет принимать и возвращать ваш сервис. Зачастую используются форматы XML и JSON. Ввиду встроенной поддержки со стороны браузера последний формат является наиболее распространённым. Вы также можете сделать сервис, который поддерживает сразу несколько стандартов, но этого не требуется в рамках курса.
- Определить действия, которые будут доступны для ваших пользователей и через какие HTTP-методы они будут использоваться.
Рассмотрим в качестве примера пирожки. Каждый пирожок будем описывать названием, ценой и датой производства. Базовым ресурсом будет являться путь /pies
. При работе над данной коллекцией выделим следующие методы:
GET /pies
- возвращает всю коллекцию текущих пирожков. Описание пирожков может быть компактным и не включать в себя всех полей пирожка, а только, к примеру, их названия.PUT /pies
- команда на замену всей коллекции пирожков на новую. В качестве параметров к данному запросу передаётся также и новый список, который должен заменить текущий.POST /pies
- запрос на создание нового объекта. В качестве параметра должен быть передан новый объект.DELETE /pies
- запрос на удаление всей коллекции пирожков.
Следующим уровнем идёт взаимодействие на уровне отдельного элемента списка. Обычно каждому элементу назначается уникальный номер, по которому данный элемент доступен. В самом простом случае - это номер элемента, число, а в более сложных случаях может быть использован инструмент наподобие UUID.
В нашем случае мы будем считать пирожки с помощью номеров. Для формирования ссылки на 5 пирожок будем использовать /pies/5
. Для данного пирожка будут доступны следующие методы:
GET /pies/5
- получить полную информацию о данном пирожке.PUT /pies/5
- заменить информацию о пирожке на новую. Новая информация должна быть передана вместе с существующей.PATCH /pies/5
- обновить информацию о пирожке. В запросе передаются новые данные о пирожке, которые должны заменить старые. Если в запросе не были переданы какие-либо поля, то они не должны изменяться в данном пирожке.DELETE /pies/5
- удалить информацию о пирожке. Важно понимать, что лучшем случае удаление информации о пирожке не должно приводить к изменению номеров других пирожков.
Если запрос по какой-либо причине не удался, то приложение должно возвращать соответствующий код ошибки. Список кодов можно посмотреть в документации.
Предоставление различных типов документов Sinatra-приложением
По умолчанию все обработчики запросов Sinatra-приложения настроены на возвращение HTML-документов. Однако для формирования программного API нам необходимо возвратить документы другого типа, в частности JSON. Для указания типа передаваемого документа в протоколе HTTP используется заголовок Content-Type
. Используя этот тип, клиент способен распознать передаваемый по протоколу HTTP-документ и корректно его обработать.
Значением заголовка является один из MIME-типов, с помощью которых уникально и однозначно определяется тип документа. Например, для описания JSON-документа используется тип application/json
, для XML - application/xml
, а для PNG-изображений image/png
.
Для указания типа документа есть 2 метода: mime_type
и content_type
. Первый используется для регистрации соответствия между расшириением файла на жёстком диске и его типом, а второй используется для указания заголовка для конкретного ответа.
Для формирования соответствия между расширением файла myjson
и MIME-типом application/json
необходимо добавить следующий код в блок конфигурации:
configure do
mime_type :myjson, 'application/json'
end
Затем данное определение можно использовать для указания возвращаемого значения:
get '/' do
content_type :myjson
'{"test": "passed"}'
end
Вместо символа можно, конечно, указать строку, но это слишком горомоздко.
Предоставление JSON-документов
Ввиду популярности JSON-документов как результата работы Sinatra-запросов в библиотеку sinatra-contrib
был добавлен специальный модуль Sinatra::JSON, который упрощает задачу возвращения таких документов.
Для его использования вам необходимо установить библиотеку sinatra-contrib
и подключить модуль sinatra/json
. После этого вам становится доступен метод json
, который можно использовать вместо erb
для предоставления документов пользователю. Рассмотрим пример:
require "sinatra"
require "sinatra/json"
# define a route that uses the helper
get '/' do
json :foo => 'bar'
end
В результате запроса на маршрут /
будет возвращён JSON-документ {"foo" : "bar"}
.
Метод json
принимает в качестве аргумента любой объект и пытается его преобразовать к JSON-представлению путём вызова на этом объекте метода to_json
. Для встроенных базовых типов преобразование происходит простым образом, однако для собственных типов его желательно самостоятельно определить.
Помимо данного джема рекомендуется подключить также и джем json
, чтобы расширить покрытие поддерживаемых объектов.
Предоставление PNG-документов
Для того, чтобы ваше приложение динамическим образом работала с изображениями есть 2 принципиальных подхода:
- Создание файлов-изображений.
- Динамическое возвращение изображения по запросу.
Ввиду природы веб-приложений всегда предпочтителен первый вариант: зачастую количество запросов на чтение велико и тратить на каждый запрос процессорное время на создание изображения не является самой хорошей идеей. В приложении вы должны определить момент, при котором должно создаваться изображение, обычно это происходит при передаче данных в веб-приложение с помощью POST-запроса. В этот момент необходимо создать файл изображения и положить его в public
. Теперь клиент может обратиться за изображением в любой момент времени и это будет почти бесплатной операцией для сервера.
Второй вариант подходит для случаев, когда изображение будет предоставляться одному или двум клиентам, при этом его ценность в будущем незначительна. В таком случае можно создавать изображения динамически и передавать их клиентскому приложению. Для решения этой задачи потребуется сделать всего 2 действия: предоставить в качестве ответа на запрос корректный заголовок и преобразовать изображение в формат, пригодный для передачи в качестве HTTP-ответа.
Рассмотрим пример запроса, который возвращает чёрный квадрат:
get '/image' do
content_type 'image/png'
image = ChunkyPNG::Image.new(100, 100, ChunkyPNG::Color('black'))
image.to_blob
end
В начале запроса мы устанавливаем формат возвращаемых данных, затем создаём ченое изображение 100 на 100 пикселей и на последней строке преобразуем его в строковый формат. Для того, чтобы отобразить такое изображение на HTML-странице достаточно добавить тег img
:
<img src="/image" />
Пример приложения, которое использует второй подход можно скачать по ссылке.
Получение файлов от пользователей через форму
Обычным способом получения данных от пользователя в веб-приложении является переход по конкретным ссылкам и заполнение форм. Этого обычно достаточно, чтобы иметь возможность заполнять сложные структуры данных. Плюсом такого подхода является также наглядная возможность указывать пользователю на ошибку сразу же, не требуя выполнять сложные манипуляции над данными.
Однако для сложных систем применим также метод передачи данных путём загрузки специально подготовленных файлов. В этих файлах обычно содержится гораздо больше информации, чем можно ввести через одну форму. Зачастую эти файлы создаются другими системами, а не конечными пользователями.
Для того, чтобы добавить возможность обрабатывать документы на строне Sinatra-приложения необходимо выполнить следующие шаги:
- В форме указать метод отправки данных
post
:method=post
. - В форме указать режим кодировки передаваемых данных:
enctype="multipart/form-data"
. - Добавить на форму поле для указания файла:
<input type="file" name="input-file">
. - Добавить обработку данного параметра на стороне веб-приложения:
post '/' do
temp_file = params['input-file'][:tempfile]
sent_data = temp_file.read
...
end
В первой строчке обработчика мы обращаемся к объекту класса File, содержащего ссылку на временный файл, который был создан автоматически после получения всех данных с клиента. Мы можем выполнять над этим объектом все операции, которые необходимы нам, в том числе и копирование данного файла на постоянное местоположение. Данный файл будет автоматически удалён по окончании обработки запроса от пользователя.
На второй строчке обработчика мы считываем данные из файла для дальнейшей обработки. Данная обработка может включать в себя сохранение информации в хранилище, создание новых объектов в нём и так далее. В результате POST-запроса не забудьте перенаправить пользвоателя на страницу, на которой он сможет просмотреть результат своих действий, если в присланной форме не было ошибок.
Пример приложения, которое обрабатывает JSON-документ, отправляемый через форму на HTML-странице можно скачать по ссылке.
Задача
Реализуйте простое веб-приложение, которое будет ориентировано на предоставление информации через REST API. Передача данных должна осуществляться в формате JSON-документов. Для тестирования работы веб-приложения также напишите второе приложение, которое будет обращаться к вашему веб-приложению и выполнять на нём необходимые запросы.
Веб-приложение должно оперировать списком задач. Дополнительно можете сделать так, чтобы списки задач были уникальны для каждого пользователя. Задача описывается следующими полями:
- Название задачи. Обязательное поле. Формат: обычный текст длиной не более 280 символов.
- Срок выполнения задачи. Обязательное поле. Формат:
ГГГГ-ММ-ДД
, например2018-06-23
. - Описание задачи. Необязательное поле. Формат: обычный текст.
- Исполнитель задачи. Необязательное поле. Формат:
Имя Фамилия <почтовый адрес>
, напримерПётр Петрович <sidor@mail.com>
. - Статус задачи. Обязательное поле. Формат: одна из констант
new
,in progress
,completed
.
Веб-приложение должно обеспечивать выполнение всех действий над ресурсом задач, включая:
- Отображение всех незавершённых на настоящее время задач,
get /tasks
. - Отображение всех задач,
get /tasks?status=any
. - Добавление новой задачи в список
post /tasks
. - Получение одной задачи по номеру в списке
get /tasks/10
. - Обновление одной задачи по номеру в списке
put /tasks/10
. - Удаление одной задачи по номеру в списке
delete /tasks/10
.
Для проверки работоспособности данного веб-приложения создайте простое консольное приложение, которое будет выполнять следующие сценарии.
- Отобразить текущий список задач, которые запланированы у пользователя.
- Отобразить все задачи, которые запланированы у пользователя.
- Добавить новую задачу.
- Обновить существующую задачу.
При реализации сценариев клиент не должен выполнять сложной предварительной обработки данных. Достаточно их получить, упаковать в запрос и отправить на сторону веб-приложения. Если в запросе содержаться ошибки, то они должны быть обработаны на стороне сервера и возвращены клиентскому приложению. Последнее может показать их конечному пользователю.
Полезные ссылки
- Стандартная библиотека net/http для выполнения HTTP-запросов к серверу.
- Стандартная библиотека open-uri для получения доступа к сетевым ресурсам. Может выполнять только GET-запросы.
- Стандартная библиотека json для сериализации и десериализации объектов в JSON-структуры.
Простейший пример
Простейшего примера двух приложений, которые взаимодействуют путём передачи друг другу JSON-сообщений можно скачать по ссылке. Для распаковки архива используйте команду 7z x ruby-json.7z
.
В распакованном каталоге вы найдёте веб-приложение, web-application.rb
, отвечающее на запросы GET и POST, а также клиентское приложение, client-application.rb
, которое их выполняет. Для проверки работоспособности выполните следующие шаги:
- Установите зависимости с помощью Bundler.
- Запустите веб-приложение,
bundle exec ruby web-application.rb
. - В другом терминале запустите клиенское приложение
bundle exec ruby client-application.rb
.
Удостоверьтесь, что взаимодействие прошло и приложения смогли обменять друг с другом данными.