[Из песочницы] REST интерфейс генератор фреймворка vibe.d
В этой статье я поэтапно покажу как пользоваться rest interface генератором встроенным во фреймворк vibe.d. Если вы интересуетесь D и его особенностями, то данная статья, надеюсь, немного прояснит новичкам как на практике использовать D и познать всю его мощь.Для этого нами понадобитсяНастроить eclipse для работы с vibe.d.новичкам Запустить «Hello World» на vibe.d. новичкам Создать клиента для api OpenWeatherMap. Создать дублирующий сервер на основе api OpenWeatherMap В документации к vibe.d имеется пример пользования генератором. Однако данный пример, на мой взгляд, слишком прост и не раскрывает механизма работы генератора.ПодготовкаДля языка D существует множество средств программирования, включая как отдельные полноценные ide, так и плагины к уже существующим. Я выбрал для себя плагин DDT для eclipse, так как благодаря кроссплатформенности eclipse позволяет мне не привязываться к одной операционной системе.
Инструменты Скачать и установить dmd. Скачать и установить dub. С этого момента можно программировать на D. Однако нам нужна ide. Скачать и установить eclipse. Установить ddt. На этом можно считать установку оконченной. Далее будем настраивать проект. К слову, стандартные настройки DDT годятся для написания простых программ, вроде «hello world», хотя для таких целей и обычная консоль идеально подходит, не говоря уже про встроенную поддержку D редактором sublime text.Для чего нибудь посложнее, в качестве сборщика удобно использовать dub. Для этого отключим стандартные билдеры ddt: Project→Properties→Builders, и добавим туда dub, запускаемым из директории с вашим проектом и аргументом build. Так же советую проверить, чтобы в меню Window→Preferences→DDT→Compilers был компилятор dmd. Если его нет, указать до него путь. Тогда ddt получит доступ к стандартной библиотеке phobos.
Hello World на vibe.d Инициализируем проект: dub init helloVibe Отредактируем dub.json. Добавим туда следующее «libs-posix»: [«dl»], «versions»: [«VibeCustomMain»], «dependencies»: { «vibe-d»:»~master» } Затем из директории helloVibe dub run Поздравляю, ваша первая программа на D заработала. Создадим проект в eclipse. И в качестве библиотеки добавим папку source %APPDATA%/dub/packages/vibe-d-master/source Теперь автодополнение наш помощник. Запустим vibe.d. Для этого нам необходимо начать слушать порт и запустить петлю событий. Делается это функциями listenHTTP (HTTPServerSettings, URLRouter) runEventLoop () соответственно. Еще необходимо создать настройки сервера и настроить маршрутизатор. Настройки создаем так: auto settings = new HTTPServerSettings;
settings.bindAddresses = [»127.0.0.1»];
settings.port = 80; Не забываем подключить vibe: import vibe.d; Маршрутизатор у нас будет включать один адрес, доступный по методу GET auto router = new URLRouter;
router.get (»/», &index); И функция обработчик: void index (HTTPServerRequest req, HTTPServerResponse res) { res.writeBody («Hello World!»); } Обработчик может быть как функцией, так и делегатом, что очень удобно и позволяет использовать в качестве обработчиков методы класса. Такая особенность используется как в REST interface генераторе, так и в Web interface генераторе.
Осталось все вызвать в нужном порядке: import std.stdio;
import vibe.d;
void main () { writeln («Edit source/app.d to start your project.»);
void index (HTTPServerRequest req, HTTPServerResponse res) { res.writeBody («Hello World!»); }
auto settings = new HTTPServerSettings;
settings.bindAddresses = [»127.0.0.1»];
settings.port = 80;
auto router = new URLRouter;
router.get (»/», &index);
listenHTTP (settings, router);
runEventLoop (); } dub run . Поздравляю, ваш первый сервер на vibe.d готов. Rest interface генератор Клиент Vibe.d годится как для написания серверов, так и клиентов.Чтобы создать rest interface, необходимо объявить interface (что логично). В этой статье будем рассматривать клиент для API OpenWeatherMap. Данный API имеет всего один метод weather c параметром q. Так и напишем
interface OpenWeather { Weather getWeather (string q); } Ответ от сервера будет автоматически преобразовываться в структуру Weather. Ее опишем согласно API OpenWeatherMap
Структура, в которую преобразуется ответ сервера struct Weather { @Label («City identification») long id; @Label («Data receiving time, unix time, GMT») ulong dt; @Label («City name») string name; WCoord coord; WSys sys; WMain main; WWind wind; WClouds clouds; //@optional () //WConditions[] weather; @optional () WRain rain; @optional () WSnow snow; }
struct WCoord { @Label («City geo location, lat») double lat; @Label («City geo location, lon») double lon; }
struct WSys { @Label («System parameter, do not use it») double message; @Label («Country (GB, JP etc.)») string country; @Label («Sunrise time, unix, UTC») ulong sunrise; @Label («Sunset time, unix, UTC») ulong sunset; }
struct WMain { @Label («Temperature, Kelvin (subtract 273.15 to convert to Celsius)») double temp; @Label («Humidity, %») double humidity; @Label («Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)») double temp_min; @Label («Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)») double temp_max; @Label («Atmospheric pressure, hPa ») double pressure; @Label («Atmospheric pressure on the sea level, hPa») @optional () double sea_level; @Label («Atmospheric pressure on the ground level, hPa») @optional () double grnd_level; }
struct WWind { @Label («Wind speed, mps») double speed; @Label («Wind direction, degrees (meteorological)») double deg; @Label («Wind gust, mps») @optional () double gust; }
struct WClouds { @Label («Cloudiness, %») double all; }
struct WConditions { @Label («Weather condition id») long id; @Label («Group of weather parameters (Rain, Snow, Extreme etc.)») Weather main; @Label («Weather condition within the group») string description; @Label («Weather icon id») string icon; }
struct WRain { @Label («Precipitation volume for last 3 hours, mm») @optional () double _3h;
}
struct WSnow { @Label («Snow volume for last 3 hours, mm») @optional () double _3h; } Пара слов о косяках vibe.d.Во первых, плохо работает десериализация массивов из пользовательских типов.Во вторых, в силу особенностей с подобного языка программирования, переменные не могут начинаться с цифры (так не только в d же). Я бы записал этот косяк на счет API OpenWeatherMap, а не vibe.d, так как, имхо, этот момент был плохо продуман разработчиками API. D позволяет нам централизованно управлять ответом. Аттрибут Label () (тут «собачка») содержит описание переменной, @optional () говорит генератору, что данное поле может и не содержаться в ответе сервера. Label () является user defined, это позволяет, к примеру, не беспокоиться о соответсвии описания и переменной. Label является структурой: struct Label { string text; } Все что осталось сделать, это создать клиент интерфейса. Делается это так:
auto client = new RestInterfaceClient! OpenWeather («http://api.openweathermap.org/data/2.5/»); Здесь через »!» передан интерфейс. Это означает, что шаблонный класс RestInterfaceClient использует его в compiletime. В D ваша программа сначала выполняется в compiletime, а затем в runtime. В compiletime происходит конкретизация шаблона, иными словами генерируется специальный класс, объект которого и будет использовться при выполнении программы.Если метод начинается с get*, то генератор зарегистрирует этот обработчик по методу GET, аналогично и для post*, put*, delete*. Можно и вовсе не указывать метод, тогда генератор сам определит как с ним быть. Подробнее описано в документации к vibe.d.
Перепишем функцию index void index (HTTPServerRequest req, HTTPServerResponse res) { auto client = new RestInterfaceClient! OpenWeather («http://api.openweathermap.org/data/2.5/»);
auto weather = client.getWeather («Moscow»);
string result = »»;
foreach (each; respond (weather)) { auto writer = res.bodyWriter ();
if (! each.label.empty)
{
result ~=format (»%s:
\»%s\» = %s
», each.label, each.name, each.value);
}
else
{
result ~= format (»%s
», each.name);
}
}
result ~= »»;
res.writeBody (result); } Функция respond import std.traits; import std.conv;
struct A { string name;
string value;
string label; }
string getLabel (alias ST, alias mem)() {
foreach (attr; __traits (getAttributes, __traits (getMember, ST, mem))) { if (is (typeof (attr) == Label)) { return attr.text; } }
return »;
}
A[] respond (ST)(ST st) { A[] ret = new A[0];
foreach (mem; __traits (derivedMembers, ST)) { static if (is (typeof (__traits (getMember, ST, mem)) == struct)) { ret ~= A (mem,»,»);
ret ~= respond!(typeof (__traits (getMember, ST, mem)))(__traits (getMember, st, mem)); } else { ret ~= A (mem, to! string (__traits (getMember, st, mem)), getLabel!(ST, mem)); } }
return ret;
} Шаблон getLabel, восстанавливает описание к переменной (я выше писал про соответствие переменной и описания). Шаблон respond собирает имена переменных, их значения и описания в удобный массив из структур A.
После сборки проекта и запуска, по адресу 127.0.0.1/ получим подробную информацию о погоде в Москве.
Сервер Создать сервер, можно создав класс релизующий интерфейс API, т.е class OpenWeatherMapImpl. class OpenWeatherImpl: OpenWeather { Weather getWeather (string q) { auto client = new RestInterfaceClient! OpenWeather («http://api.openweathermap.org/data/2.5/»);
return client.getWeather (q); } } Этот класс дублирует показания официального сервера OpenWeatherMap. Зарегистриуерм интерфейс
router.registerRestInterface (new OpenWeatherImpl); Все вместе import std.traits; import std.conv;
struct Label { string text; }
struct A { string name;
string value;
string label; }
string getLabel (alias ST, alias mem)() {
foreach (attr; __traits (getAttributes, __traits (getMember, ST, mem))) { if (is (typeof (attr) == Label)) { return attr.text; } }
return »;
}
A[] respond (ST)(ST st) { A[] ret = new A[0];
foreach (mem; __traits (derivedMembers, ST)) { static if (is (typeof (__traits (getMember, ST, mem)) == struct)) { ret ~= A (mem,»,»);
ret ~= respond!(typeof (__traits (getMember, ST, mem)))(__traits (getMember, st, mem)); } else { ret ~= A (mem, to! string (__traits (getMember, st, mem)), getLabel!(ST, mem)); } }
return ret;
}
struct Weather { @Label («City identification») long id; @Label («Data receiving time, unix time, GMT») ulong dt; @Label («City name») string name; WCoord coord; WSys sys; WMain main; WWind wind; WClouds clouds; //@optional () //WConditions[] weather; @optional () WRain rain; @optional () WSnow snow; }
struct WCoord { @Label («City geo location, lat») double lat; @Label («City geo location, lon») double lon; }
struct WSys { @Label («System parameter, do not use it») double message; @Label («Country (GB, JP etc.)») string country; @Label («Sunrise time, unix, UTC») ulong sunrise; @Label («Sunset time, unix, UTC») ulong sunset; }
struct WMain { @Label («Temperature, Kelvin (subtract 273.15 to convert to Celsius)») double temp; @Label («Humidity, %») double humidity; @Label («Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)») double temp_min; @Label («Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)») double temp_max; @Label («Atmospheric pressure, hPa ») double pressure; @Label («Atmospheric pressure on the sea level, hPa») @optional () double sea_level; @Label («Atmospheric pressure on the ground level, hPa») @optional () double grnd_level; }
struct WWind { @Label («Wind speed, mps») double speed; @Label («Wind direction, degrees (meteorological)») double deg; @Label («Wind gust, mps») @optional () double gust; }
struct WClouds { @Label («Cloudiness, %») double all; }
struct WConditions { @Label («Weather condition id») long id; @Label («Group of weather parameters (Rain, Snow, Extreme etc.)») Weather main; @Label («Weather condition within the group») string description; @Label («Weather icon id») string icon; }
struct WRain { @Label («Precipitation volume for last 3 hours, mm») @optional () double _3h;
}
struct WSnow { @Label («Snow volume for last 3 hours, mm») @optional () double _3h; }
interface OpenWeather { Weather getWeather (string q); }
class OpenWeatherImpl: OpenWeather { Weather getWeather (string q) { auto client = new RestInterfaceClient! OpenWeather («http://api.openweathermap.org/data/2.5/»);
return client.getWeather (q); } }
import std.stdio;
import vibe.d;
import std.string;
void main () { writeln («Edit source/app.d to start your project.»);
void index (HTTPServerRequest req, HTTPServerResponse res) { auto client = new RestInterfaceClient! OpenWeather («http://api.openweathermap.org/data/2.5/»);
auto weather = client.getWeather («Moscow»);
string result = »»;
foreach (each; respond (weather)) { auto writer = res.bodyWriter ();
if (! each.label.empty)
{
result ~=format (»%s:
\»%s\» = %s
», each.label, each.name, each.value);
}
else
{
result ~= format (»%s
», each.name);
}
}
result ~= »»;
res.writeBody (result); }
auto settings = new HTTPServerSettings;
settings.bindAddresses = [»127.0.0.1»];
settings.port = 80;
auto router = new URLRouter;
router.get (»/», &index); router.registerRestInterface (new OpenWeatherImpl);
listenHTTP (settings, router);
runEventLoop (); } По адресу 127.0.0.1/weather? q=«Moscow» получим json ответ о пагоде в Москве.Примеры из статьи в удобном формате github.com/ntstv/viberest