Продолжаем готовить виджет погоды. 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 . Тестируем полученное приложение.
Получаем код виджета.
Отображаем виджет.
Интерфейс мягко говоря аскетичный, "шустрость" проверяем сами.
За сим прощаюсь. Покупайте наших слонов :).
Комментариев нет:
Отправить комментарий
Комментарий будет опубликован после модерации