[Из песочницы] Универсальный web-GUI для произвольных RESTful сервисов
Во многих компаниях, как и моей, есть много проектов и продуктов. И у продуктов бывают веб-интерфейсы, чтобы этими продуктами как-то манипулировать. В нашем случае это простенькие RESTful веб-сервисы, а поверх них ещё более простенькие веб-странички с формочками и кнопочками. Все эти веб-странички до того похожи друг на друга, что возникла мысль написать унифицированный продукт, который бы спрашивал сервер о поддерживаемых сервисах, и получал бы полное описание параметров к этим сервисам, так чтобы можно было нарисовать те самые простенькие формочки. То есть, веб-сервисы должны описывать себя, достаточно исчерпывающе, чтобы наш клиент мог построить GUI для них, и ничего не надо было бы делать руками. Как раз такая картинка гуглится по запросу «REST»: Сходу нашлась такая технология, как WADL — Web Application Description Language. Но это XML, с ним не хотелось возиться в javascript, python и perl. Был выбран его более легковесный JSON-аналог — JSDL — JSON Service Description Language.
Требования к сервисамОказалось, что в JSDL (как и в WADL) не помещается вся информация, нужная для формирования полноценных формочек, и ряд дополнительных соглашений с веб-сервисами должны быть установлены:
Для начала нужно получить список сервисов, и как они называются. Понятие сервиса (в терминах JSDL или WADL) здесь соответствует манипулируемой сущности (ресурсу в терминах REST). У сервиса есть набор операций, которые и описывает JSDL. Поэтому сервисов может быть (и обычно есть) несколько, и они могут быть объединены в группы для удобства отображения. Поэтому первым делом наш клиент запрашивает URL GET /service_groups и получает JSON вида [ { «description»: «Manipulate resource1», «services»: [«resource1»] }, { «description»: «Manipulate resource2 and its statistics», «services»: [«resource2», «resource2_stat»] } ] Далее нужно получить JSDL для всех этих сервисов. Можно сделать по запросу для каждого сервиса, но для удобства было так же реализовано получение всех объектов одним запросом GET /jsdls. Получаем JSON вида [[«resource1», {…object…}], [«resource2», {…object…}], [«resource2_stat», {…object…}]] где object — JSDL-описание сервиса (операции, их параметры и т.д.). Казалось бы, всё. Но не всё. Наш клиент — просто html-файлик, который может быть доступен с произвольного домена, либо вообще без домена, если мы натравим броузер прямо на него. Далее мы вводим в формочку корневой URL нашего RESTful API, и туда делаются запросы, являющиеся cross-domain. И мы неизбежно сталкиваемся с безопасностью. Если бы мы могли ограничиться GET-запросами, можно было бы использовать JSONP, но и это наложило бы дополнительные требования собственно на RESTful API.Вместо этого сервер должен поддерживать OPTIONS метод, про который мало кто знает, и который практически не используется, хотя броузер неявно делает такой запрос при кросс-доменных ajax-вызовах. В RFC про этот метод написано по-моему довольно расплывчато. На практике сервер должен выдавать на этот запрос заголовок Allow: Allow: HEAD, GET, PUT, POST, DELETE, OPTIONSКроме того, для каждого запроса нужно слать заголовки вида: Access-Control-Allow-Origin: * Access-Control-Allow-Headers: content-type, X-Requested-With Access-Control-Allow-Methods: HEAD, GET, PUT, POST, DELETE, OPTIONSТогда броузер доволен. Валидируем JSDL Наш клиент валидирует пришедшие от сервера JSON-объекты в соответствии со схемой JSDL, описанной с помощью JSON Schema. Мы просто взяли JSDL-схему, метасхему JSONa, и первый валидатор из предложенных на сайте, вот этот. Метасхема нужна, так как параметр в JSDL является схемой и описывается метасхемой, которую мы немного расширили, как указано ниже. Надеюсь, понятно (мне лично не всегда).Расширяем JSDL К сожалению, средствами стандартного JSDL всё равно не получается полностью выразить наши формочки.Схеме параметра не хватало следующих выразительных средств: Параметру нужно имя. Его нужно задать. Параметр передаётся серверу разными способами: В URL, после ресурса: api.com/resource1/paramvalue1/paramvalue2 В строке запроса (если GET) или в теле запроса: api.com/resource1? param1=value1¶m2=value2 В теле запроса, единым куском, не образуя key-value пары. Специфичный случай, но встречается Для каких параметров типа string нужно рисовать textarea, а каким достаточно text input? Для решения этих проблем схему JSDL-описания параметра (ту самую метасхему) пришлось расширить следующим образом:
//добавим атрибут name ParamJSONSchema[«properties»][«name»] = { «type»: «string» } //добавим атрибут passing — как передавать значение ParamJSONSchema[«properties»][«passing»] = { «type»: «string», «default»: «keyvalue», «enum»: [«keyvalue», «positional», «raw»] } //добавим в типы параметра multistring для случая textarea ParamJSONSchema[«definitions»][«simpleTypes»][«enum»].push («multistring»); Теперь есть все данные, чтобы нарисовать формочку для запроса и правильно отправить её содержимое сервису. Результат выводим на страничку.
Сам проект лежит на гитхабе и может быть использован для реализации единого веб-интерфейса к сервисам в вашей компании, например.