Продолжаем готовить виджет погоды. Google Weather API благополучно умер, поэтому для получения данных предлагаю использовать OpenWeatherMap API - бесплатно, без регистрации, раздает данные в формате JSON, в общем мне сервис понравился.
После ознакомления с документацией сервиса выполним запрос - в адресной строке браузера пишем: http://api.openweathermap.org/data/2.5/forecast/daily?q=Moscow&units=metric&cnt=1&lang=ru и переходим по указанному адресу.
Результат копируем в текстовый файл, приводим его в читабельный вид и сохраняем - пригодится для понимания структуры ответа сервера.
Принципиальная схема нашего веб-приложения будет выглядеть следующим образом:
"Шустрость" приложения (обозначенная в заголовке), на мой взгляд, заключается в использовании Redis в качестве хранилища данных.
Сразу хочу обратиться к читателю с просьбой не принимать создаваемое приложение за истину. Не исключаю ни недопонимания, ни каких-либо ошибок со своей стороны.
Теперь попробую обрисовать схему работы приложения вербально:
- для начала выполняем инициализацию - читаем шаблоны (template.html, form.html), список городов (cities.txt), сохраняем данные в хранилище Redis
- выполняем запрос к сервису OpenWeatherMap API, сохраняем ответ сервиса в хранилище Redis
- запускаем веб-сервер на основе фреймворка Express, в ответ на входящие запросы получаем шаблоны из хранилища Redis, подставляем в них необходимые данные, полученные из того же хранилища, после чего отправляем собранную таким образом HTML-страницу клиенту
Довольно лирики, let's get our hands dirty :).
Пишем код шаблона HTML-страницы, отображающей данные прогноза погоды.
Сохраняем файл с именем template.html в созданный в предыдущей статье каталог ~/_node/weather.
Обращаем внимание на двойные скобки в коде шаблона, после чего читаем документацию модуля mustache.
Становится понятно для чего нам нужен этот модуль - с его помощью удобно рендерить шаблоны (не нашел подходящее русское слово).
Похожим образом пишем код шаблона HTML-страницы настройки виджета.
Реализуем возможность выбора города, периода прогноза (от 1 до 7 дней), а также вертикального или горизонтального расположения.
Сохраняем файл с именем form.html.
Очевидно не хватает списка городов. Создаем файл cities.txt следующего содержания:
Полный список городов можно узнать из документации API.
Пора прочитать содержимое наших шаблонов и сохранить данные в хранилище Redis.
Но перед этим создадим модуль настроек нашего приложения, который будет экспортировать конструктор объекта. Назовем его settings.js.
Переходим к созданию модуля инициализации по имени init.js.
В коде модуля обращаю внимание на то, что для работы с Redis мы используем модуль node_redis.
Выполняем первоначальную настройку нашего приложения:
- запускаем redis-server
- проверим доступность сервера
- выберем хранилище с индексом 15 (согласно настройкам - файл settings.js)
- убедимся в том, что хранилище пустое
- переходим в каталог нашего приложения
- выполняем инициализицию, в случае успеха получаем OK на каждый сохраненный шаблон
- читаем ключи хранилища еще раз
- читаем значения ключей
Теперь в случае изменения какого-либо шаблона для обновления данных в хранилище достаточно выполнить в терминале (консоли) nodejs init (в Windows node init).
Кстати на счет Windows. Как вы уже наверняка заметили в настоящей статье на скриншотах исключительно Ubuntu.
На самом деле разработка нашего приложения в Windows практически ничем, за исключением нескольких мелочей, о которых я постараюсь сообщать читателю в процессе текущего повествования, не отличается от разработки в Linux.
Об одном отличие я уже сообщил: для выполнения кода в консоли Node.js Windows выполняем команду с префиксом node, в терминале Linux - с префиксом nodejs.
Для тех, кто не читал предыдущую статью, еще одно отличие: в Windows redis-server и redis-cli удобнее запускать в отдельной консоли путем выполнения одноименного exe-файла.
Подведем промежуточный итог. На текущем этапе содержание каталога приложения выглядит примерно так:
Вернемся к коду шаблона form.html.
В заголовке присутствует ссылка на скрипт, который выполняется по событию нажатия на кнопку "Получить код" и собирает код виджета для размещения на веб-сайте исходя из выбранных параметров, после чего отображает полученный код в текстовом поле.
Пишем код клиентского скрипта.
В каталоге приложения создаем каталог по имени public, сохраняем скрипт в созданный каталог, назовем его getcode.js.
Открываем файл в браузере и нажимаем на кнопку "Получить код".
Клиентский скрипт отработал как надо.
И еще на несколько секунд обратим внимание на код шаблона template.html, в заголовке которого есть ссылка на css-файл:
В зависимости от того, горизонтальное или вертикальное расположение виджета выбрал пользователь, здесь может быть ссылка на файл styleh.css или stylev.css соответственно.
Создадим эти файлы в каталоге public.
styleh.css:
stylev.css:
Возвращаемся в корневой каталог нашего приложения.
Пишем модуль обновления данных прогноза, который будет выполнять запрос и сохранять полученные данные в хранилище Redis. Назовем его request.js. Для выполнения запроса используем модуль node-request.
Закомментируем для теста единственную функцию, которую экспортирует созданный модуль.
Выполним код модуля.
По одному ОК на каждый город из списка. Не забываем раскомментировать обратно экспортируемую функцию.
В завершение пишем код главного файла приложения. Назовем его app.js.
Здесь главную роль играет фреймворк Express, с помощью которого создается http-сервер, ожидающий подключения на порту 3000.
В самом низу в коде располагается функция, которая следит за изменением текущей даты (по умолчанию каждые 30 минут), и в случае наступления события обновляет данные - выполняет запрос и сохраняет данные прогноза в хранилище.
Запускаем сервер.
Открываем в браузере адрес http://localhost:3000 . Тестируем полученное приложение.
Получаем код виджета.
Отображаем виджет.
Интерфейс мягко говоря аскетичный, "шустрость" проверяем сами.
За сим прощаюсь. Покупайте наших слонов :).
После ознакомления с документацией сервиса выполним запрос - в адресной строке браузера пишем: http://api.openweathermap.org/data/2.5/forecast/daily?q=Moscow&units=metric&cnt=1&lang=ru и переходим по указанному адресу.
Результат копируем в текстовый файл, приводим его в читабельный вид и сохраняем - пригодится для понимания структуры ответа сервера.
{
"cod":"200",
"message":0.022,
"city":{
"id":524901,
"name":"Moscow",
"coord":{
"lon":37.615555,
"lat":55.75222
},
"country":"RU",
"population":1000000
},
"cnt":1,
"list":[
{
"dt":1373187600,
"temp":{
"day":29.29,
"min":20.97,
"max":29.29,
"night":22.35,
"eve":27.73,
"morn":20.97
},
"pressure":1011.68,
"humidity":58,
"weather":[
{
"id":501,
"main":"Rain",
"description":"дождь",
"icon":"10d"
}
],
"speed":2.76,
"deg":258,
"clouds":32,
"rain":4.5
}
]
}
Принципиальная схема нашего веб-приложения будет выглядеть следующим образом:
"Шустрость" приложения (обозначенная в заголовке), на мой взгляд, заключается в использовании Redis в качестве хранилища данных.
Сразу хочу обратиться к читателю с просьбой не принимать создаваемое приложение за истину. Не исключаю ни недопонимания, ни каких-либо ошибок со своей стороны.
Теперь попробую обрисовать схему работы приложения вербально:
- для начала выполняем инициализацию - читаем шаблоны (template.html, form.html), список городов (cities.txt), сохраняем данные в хранилище Redis
- выполняем запрос к сервису OpenWeatherMap API, сохраняем ответ сервиса в хранилище Redis
- запускаем веб-сервер на основе фреймворка Express, в ответ на входящие запросы получаем шаблоны из хранилища Redis, подставляем в них необходимые данные, полученные из того же хранилища, после чего отправляем собранную таким образом HTML-страницу клиенту
Довольно лирики, let's get our hands dirty :).
Пишем код шаблона HTML-страницы, отображающей данные прогноза погоды.
<!DOCTYPE html>
<html>
<head><link rel="stylesheet" type="text/css" href="public/style{{align}}.css" /></head>
<body>
<div> {{city}} </div>
{{#days}}
<ul>
<li>{{dt}}</li>
<li>{{description}}</li>
<li>Утро: {{morn}} °C</li>
<li>День: {{day}} °C</li>
<li>Вечер: {{eve}} °C</li>
<li>Ночь: {{night}} °C</li>
<li>Давление: {{pressure}} мм</li>
<li>Влажность: {{humidity}} %</li>
<li>Облачность: {{clouds}} %</li>
<li>Ветер: {{speed}} м/с</li>
</ul>
{{/days}}
</body>
</html>
Сохраняем файл с именем template.html в созданный в предыдущей статье каталог ~/_node/weather.
Обращаем внимание на двойные скобки в коде шаблона, после чего читаем документацию модуля mustache.
Становится понятно для чего нам нужен этот модуль - с его помощью удобно рендерить шаблоны (не нашел подходящее русское слово).
Похожим образом пишем код шаблона HTML-страницы настройки виджета.
Реализуем возможность выбора города, периода прогноза (от 1 до 7 дней), а также вертикального или горизонтального расположения.
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="public/getcode.js"></script>
</head>
<body>
<form id="data" action="http://localhost:3000/get">
<div>Расположение</div>
<input type="radio" name="align" value="v" checked="checked" />Вертикальное<br/>
<input type="radio" name="align" value="h" />Горизонтальное<br/>
<div>Город</div>
<select form="data" name="city" id="city">
{{#cities}}
<option name="city" value="{{city}}">{{city}}</option>
{{/cities}}
</select><br/>
<div>Период (дней)</div>
<select form="data" name="days" id="days">
<option name="days" value="1">1</option>
<option name="days" value="2">2</option>
<option name="days" value="3">3</option>
<option name="days" value="4">4</option>
<option name="days" value="5">5</option>
<option name="days" value="6">6</option>
<option name="days" value="7">7</option>
</select><br/>
</form>
<input type="submit" value="Получить код" onclick="getCode()" />
<input type="submit" form="data" value="Показать" /><br/>
<textarea id="code" rows="10"></textarea>
</body>
</html>
Сохраняем файл с именем form.html.
Очевидно не хватает списка городов. Создаем файл cities.txt следующего содержания:
Saint Petersburg, Moscow, Nizhniy Novgorod
Полный список городов можно узнать из документации API.
Пора прочитать содержимое наших шаблонов и сохранить данные в хранилище Redis.
Но перед этим создадим модуль настроек нашего приложения, который будет экспортировать конструктор объекта. Назовем его settings.js.
module.exports = function (){
this.citiesPath = 'cities.txt',
this.dbCitiesKey = 'cities',
this.templatePath = 'template.html',
this.dbTemplateKey = 'template',
this.formPath = 'form.html',
this.dbFormKey = 'form',
this.dbDateKey = 'dt',
this.dbIndex = 15,
this.uriTemplate = 'http://api.openweathermap.org/data/2.5/forecast/daily?q={{city}}&units=metric&cnt=7&lang=ru',
this.interval = 1000 * 60 * 30, // 30 минут
this.port = 3000
}
Переходим к созданию модуля инициализации по имени init.js.
var Settings = require('./settings');
var settings = new Settings();
var fs = require('fs');
var redis = require("redis");
init(settings.citiesPath, settings.dbCitiesKey, settings.dbIndex);
init(settings.templatePath, settings.dbTemplateKey, settings.dbIndex);
init(settings.formPath, settings.dbFormKey, settings.dbIndex);
function init(path, key, dbIndex) {
fs.exists(path, function(exists) {
if (!exists) {
console.log('Path ' + path + ' does not exists');
} else {
fs.readFile(path, function(err, content) {
if (err) {
console.log('Cannot read file ' + path);
} else {
var client = redis.createClient();
client.on("error", function (err) {
console.log("Error " + err);
});
client.select(dbIndex, function() {
var value = (key == 'cities') ? JSON.stringify(String(content).replace(/^\s+/, "").split(/\s*,\s*/)) : String(content).replace(/^\s+/, "");
client.set(key, value, redis.print);
client.quit();
});
}
});
}
});
}
В коде модуля обращаю внимание на то, что для работы с Redis мы используем модуль node_redis.
Выполняем первоначальную настройку нашего приложения:
- запускаем redis-server
- проверим доступность сервера
- выберем хранилище с индексом 15 (согласно настройкам - файл settings.js)
- убедимся в том, что хранилище пустое
- переходим в каталог нашего приложения
- выполняем инициализицию, в случае успеха получаем OK на каждый сохраненный шаблон
- читаем ключи хранилища еще раз
- читаем значения ключей
Теперь в случае изменения какого-либо шаблона для обновления данных в хранилище достаточно выполнить в терминале (консоли) nodejs init (в Windows node init).
Кстати на счет Windows. Как вы уже наверняка заметили в настоящей статье на скриншотах исключительно Ubuntu.
На самом деле разработка нашего приложения в Windows практически ничем, за исключением нескольких мелочей, о которых я постараюсь сообщать читателю в процессе текущего повествования, не отличается от разработки в Linux.
Об одном отличие я уже сообщил: для выполнения кода в консоли Node.js Windows выполняем команду с префиксом node, в терминале Linux - с префиксом nodejs.
Для тех, кто не читал предыдущую статью, еще одно отличие: в Windows redis-server и redis-cli удобнее запускать в отдельной консоли путем выполнения одноименного exe-файла.
Подведем промежуточный итог. На текущем этапе содержание каталога приложения выглядит примерно так:
Вернемся к коду шаблона form.html.
В заголовке присутствует ссылка на скрипт, который выполняется по событию нажатия на кнопку "Получить код" и собирает код виджета для размещения на веб-сайте исходя из выбранных параметров, после чего отображает полученный код в текстовом поле.
Пишем код клиентского скрипта.
function getCode() {
var days = document.getElementById('days');
var city = document.getElementById('city')
var req = ['days=' + days[days.selectedIndex].value, 'city=' + city[city.selectedIndex].value];
var rads = document.getElementsByName('align');
for (i=0; i < rads.length; i++) {
if (rads[i]['checked']) {
req.push('align=' + rads[i].value);
break;
}
}
document.getElementById("code").value = '<iframe src="http://localhost:3000/get?' +
req.join('&') + '" width="100%" height="100%" frameborder="0"></iframe>';
}
В каталоге приложения создаем каталог по имени public, сохраняем скрипт в созданный каталог, назовем его getcode.js.
Открываем файл в браузере и нажимаем на кнопку "Получить код".
Клиентский скрипт отработал как надо.
И еще на несколько секунд обратим внимание на код шаблона template.html, в заголовке которого есть ссылка на css-файл:
<link rel="stylesheet" type="text/css" href="public/style{{align}}.css" />
В зависимости от того, горизонтальное или вертикальное расположение виджета выбрал пользователь, здесь может быть ссылка на файл styleh.css или stylev.css соответственно.
Создадим эти файлы в каталоге public.
styleh.css:
body{text-align: center;}
div{font: bold 16px verdana, sans-serif; color: #f00;}
ul{text-align: left; display: inline-block; margin: 0; padding: 0; list-style-type: none; margin: 10px;}
ul li:first-child{font-weight: bold; color: #00008b;}
ul li:nth-child(2){font-weight: bold; color: #008080;}
stylev.css:
body{text-align: left;}
div{font: bold 16px verdana, sans-serif; color: #f00;}
ul{text-align: left; margin: 0; padding: 0; list-style-type: none; margin-top: 10px;}
ul li:first-child{font-weight: bold; color: #00008b;}
ul li:nth-child(2){font-weight: bold; color: #008080;}
Возвращаемся в корневой каталог нашего приложения.
Пишем модуль обновления данных прогноза, который будет выполнять запрос и сохранять полученные данные в хранилище Redis. Назовем его request.js. Для выполнения запроса используем модуль node-request.
var Settings = require('./settings');
var settings = new Settings();
var request = require('request');
var redis = require("redis");
var mus = require('mustache');
exports.get = function() {
var client = redis.createClient();
client.on('error', function (err) {
console.log('Error ' + err);
});
client.select(settings.dbIndex, function() {
client.get('cities', function(err, reply) {
if (err) {
console.log('Error ' + err);
} else {
var cities = JSON.parse(reply);
for (var i=0; i<cities.length; i++) {
var url = {'url': mus.render(settings.uriTemplate, {'city': cities[i]})};
request(url, function (err, response, body) {
if (!err && response.statusCode == 200) {
var forecast = JSON.parse(body);
var client = redis.createClient();
client.on('error', function (err) {
console.log('Error ' + err);
});
client.select(settings.dbIndex, function() {
client.set('forecast:' + forecast.city.name, JSON.stringify(forecast), redis.print); // прогноз
client.set(settings.dbDateKey, new Date().toString()); // дата обновления
client.quit();
});
} else {
console.log('Error ' + err);
}
});
}
}
});
client.quit();
});
}
Закомментируем для теста единственную функцию, которую экспортирует созданный модуль.
Выполним код модуля.
По одному ОК на каждый город из списка. Не забываем раскомментировать обратно экспортируемую функцию.
В завершение пишем код главного файла приложения. Назовем его app.js.
var Settings = require('./settings');
var settings = new Settings();
var request = require('./request');
request.get();
var redis = require("redis");
var mus = require('mustache');
var express = require('express');
var app = express();
app.use("/public", express.static(__dirname + '/public'));
app.get('/get', function(req, res){
var query = {
'city': req.query.city || 'Moscow',
'days': req.query.days || 7,
'align': (!req.query.align) ? 'v' : req.query.align // по умолчанию - вертикальная
};
var client = redis.createClient();
client.on('error', function (err) {
console.log('Error ' + err);
});
client.select(settings.dbIndex, function() {
client.get('forecast:' + query.city, function(err, reply) {
if (err) {
console.log('Error ' + err);
} else {
var forecast = JSON.parse(reply);
var view = {'align': query.align, 'city': query.city, 'days':[]};
var toMm = 0.75006375541921; // гектопаскали в мм
for (var i=0; i<query.days; i++) {
var obj = {
'dt': new Date(forecast.list[i].dt * 1000).toLocaleDateString(),
'description': forecast.list[i].weather[0].description,
'morn': Math.round(forecast.list[i].temp.morn),
'day': Math.round(forecast.list[i].temp.day),
'eve': Math.round(forecast.list[i].temp.eve),
'night': Math.round(forecast.list[i].temp.night),
'pressure': Math.round(forecast.list[i].pressure * toMm),
'humidity': Math.round(forecast.list[i].humidity),
'clouds': Math.round(forecast.list[i].clouds),
'speed': Math.round(forecast.list[i].speed),
};
view.days.push(obj);
}
client.get(settings.dbTemplateKey, function(err, template) {
if (err) {
console.log('Error ' + err);
} else {
var output = mus.render(template, view);
res.send(output);
}
});
}
client.quit();
});
});
});
app.get('/', function(req, res){
var client = redis.createClient();
client.on('error', function (err) {
console.log('Error ' + err);
});
client.select(settings.dbIndex, function() {
client.get(settings.dbCitiesKey, function(err, reply) {
if (err) {
console.log('Error ' + err);
} else {
var cities = JSON.parse(reply);
var view = {'cities':[]};
for (var i=0; i<cities.length; i++) {
view.cities.push({'city': cities[i]});
}
client.get(settings.dbFormKey, function(err, template) {
if (err) {
console.log('Error ' + err);
} else {
var output = mus.render(template, view);
res.send(output);
}
});
}
client.quit();
});
});
});
app.listen(settings.port);
console.log('Listening on port ' + settings.port);
(function schedule() {
setTimeout(function update() {
var client = redis.createClient();
client.on('error', function (err) {
console.log('Error ' + err);
});
client.select(settings.dbIndex, function() {
client.get(settings.dbDateKey, function(err, reply) {
if (err) {
console.log('Error ' + err);
} else {
if (new Date().toLocaleDateString() != new Date(reply).toLocaleDateString()) {
request.get();
}
}
});
client.quit();
});
schedule();
}, settings.interval);
}());
Здесь главную роль играет фреймворк Express, с помощью которого создается http-сервер, ожидающий подключения на порту 3000.
В самом низу в коде располагается функция, которая следит за изменением текущей даты (по умолчанию каждые 30 минут), и в случае наступления события обновляет данные - выполняет запрос и сохраняет данные прогноза в хранилище.
Запускаем сервер.
Открываем в браузере адрес http://localhost:3000 . Тестируем полученное приложение.
Получаем код виджета.
Отображаем виджет.
Интерфейс мягко говоря аскетичный, "шустрость" проверяем сами.
За сим прощаюсь. Покупайте наших слонов :).
















Комментариев нет:
Отправить комментарий
Комментарий будет опубликован после модерации