В этой статье я поэтапно покажу как пользоваться 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.
- Инициализируем проект:
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.
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 = "<html>";
foreach(each; respond(weather))
{
auto writer = res.bodyWriter();
if (!each.label.empty)
{
result ~=format("<b>%s</b>:<br/>"%s" = %s<br/>", each.label, each.name, each.value);
}
else
{
result ~= format("<i>%s</i><br/>", each.name);
}
}
result ~= "</html>";
res.writeBody(result);
}
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 = "<html>";
foreach(each; respond(weather))
{
auto writer = res.bodyWriter();
if (!each.label.empty)
{
result ~=format("<b>%s</b>:<br/>"%s" = %s<br/>", each.label, each.name, each.value);
}
else
{
result ~= format("<i>%s</i><br/>", each.name);
}
}
result ~= "</html>";
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
Автор: ntstv