Содержание


Создание WebSocket-приложения для визуализации количества твитов на карте

В этом руководстве показано, как с использованием WebSocket, механизма поиска в Twitter и API Google Maps создать веб-приложение Node.js, отображающее источники твитов на карте в режиме реального времени. Я также покажу, как развернуть это приложение в IBM® Bluemix™ (и вообще в любой PaaS на основе Cloud Foundry).

Я написал это приложение, потому что хотел получить представление о географии происхождения твитов на определенную тему. Например: в какой части мира люди «твитят» об инновациях?

Приложение TOTEM (Tweets On ThE Map) имеет простой пользовательский интерфейс, в который вводится запрос (как в Twitter), и на карте начинают появляться твиты, место происхождения которых указывается мигающим значком твита .

Скриншот карты с расположением твитов
Скриншот карты с расположением твитов

Меня интересует количество твитов из данного места — а не содержание сообщений, — поэтому приложение TOTEM отмечает местоположение кружком, размер которого пропорционален числу твитов. При нажатии на кружок вы видите название места, количество твитов с момента начала поиска и последний поступивший оттуда твит.

Вместо использования сложной логики опроса через HTTP, приложение открывает единственный дуплексный канал WebSocket, через который клиент и сервер могут обмениваться сообщениями, следуя своему собственному протоколу.

Что требуется для создания приложения

  • Node.js, среда разработки Node.js и знакомство с написанием веб-приложений с помощью Node и Express. (Для разработки приложения я использовал Eclipse с плагином Nodeclipse.)
  • Учетная запись Twitter.
  • Знакомство с JavaScript-API Google Maps.
  • Для развертывания в Bluemix:

Это руководство можно использовать и для создания других подобных приложений. Если, получив код на шаге 1, вы захотите поэкспериментировать с приложением, то можете проверить свои изменения локально, а затем развернуть приложение в Bluemix (как описано на шаге 5). Инструкции по локальному запуску приложения см. в файле README.md в моем проекте на DevOps Services.

Запустить приложениеПолучить код

Вместо использования сложной логики опроса через HTTP, приложение открывает единственный дуплексный канал WebSocket, через который клиент и сервер могут обмениваться сообщениями, следуя своему собственному протоколу.

Шаг 1. Получение исходного кода и настройка среды

  1. Чтобы получить исходный код путем клонирования Git-репозитория проекта TOTEM, используйте любой клиент Git:
    git clone https://hub.jazz.net/git/mcrudele/totem
  2. Выполните команду npm install из корневой папки. Она устанавливает зависимости, перечисленные в файле package.json, в каталог node_modules. В число зависимостей входит библиотека WebSocket ws и асинхронный клиентский API Twitter ntwitter вместе с Express и Jade.
  3. Перейдите в папку node_modules/ntwitter в своем локальном проекте и внесите необходимое исправление в пакет ntwitter:
    • в файле lib/keys.js замените search_base: 'http://search.twitter.com' на search_base: 'https://api.twitter.com/1.1/search'
    • в файле lib/twitter.js замените var url = this.options.search_base + '/search.json'; на var url = this.options.search_base + '/tweets.json';
  4. Сгенерируйте маркер доступа Twitter и ключ API. (Если вы не знакомы с API Twitter, читайте инструкцию Как получить ключ API или переходите прямо к разделу Управление приложениями Twitter.)

Шаг 2. Добавление конечной точки WebSocket на сервере Node.js

Я создал каркас своего веб-приложения с помощью Nodeclipse, перейдя к перспективе Node и нажав кнопку New > Node.js Express. Вы можете получить тот же каркас, выполнив команду express, которая включена в пакет express NPM.

Каркас состоит из файла app.js, который является сервером Node.js, а также включает другие каталоги, содержащие статические ресурсы, такие как файлы изображений, CSS и HTML (например, public), файлы шаблонов Jade (представления) и файлы обработчиков REST (маршруты).

Приложение TOTEM определяет конечную точку WebSocket, которая принимает строку запроса по событию message, инициирует периодический поиск твитов, соответствующих запросу, и отправляет эти твиты в клиент.

Я написал класс TwitterFinder (в модуле tfinder.js) для обработки периодического поиска с помощью модуля Node.js ntwitter. TwitterFinder – это EventEmitter Node.js, создающий событие data при получении новых твитов, удовлетворяющих условиям запроса.

Этот код создает конечную точку WebSocket /search:

var WebSocket = require('ws');
var WebSocketServer = WebSocket.Server;

var app = express();

var server = http.createServer(app).listen(app.get('port'), function() {
  console.log('TOTEM server listening on port ' + app.get('port'));
});

// Создание конечной точки WebSocket, которая выполняет работу
//
var searchServer = new WebSocketServer( {server: server, path: '/search'});

WebSocketServer определен в модуле ws Node.js WebSocket. WebSocketServer определен в модуле Node.js EventEmitter, который реализует серверную часть WebSocket. Итак, я добавил обработчик событий connection, который срабатывает при создании нового WebSocket-соединения с клиентом. Это событие приносит в качестве параметра клиент WebSocket, который нужно использовать в течение всего соединения. Именно в этом сокете необходимо обработать событие message, чтобы принять строку запроса:

// Одно соединение управляет одним поиском twitter.
// Чтобы остановить текущий поиск, просто закройте это соединение.
//
searchServer.on('connection', function(ws) {

  // Создание поиска twitter на время соединения
  //
  var finder = new TwitterFinder(TwitterKeys);

  ws.on('message', function(searchstring, flags) {

    // Игнорировать сообщение, если поиск уже выполняется
    // Чтобы остановить поиск, клиент должен закрыть соединение
    //
    if ( finder.isRunning() ) {
      console.log('Finder already running.');
      return;
    }

    // Установить обработчик данных для поиска twitter
    //
    finder.on('data', function(tweets) {

      // Формат объекта, который нужно передать:
      //
      // {
      //   address: "user.location attribute of the tweet",
      //   text: "text attribute of the tweet formatted as html"
      // }
      //
      // Получение твитов из нижней части массива, чтобы начать с самых старых.
      // 
      //
      for (var i=tweets.length-1; i>=0; i--) {
        var data = { address: tweets[i].user.location,
                     text:     tweetToText(tweets[i]) };
        ws.send(JSON.stringify(data));
      }

    });

    console.log('Start a new search for: ', searchstring);
    finder.start(searchstring);
  });

});

Шаг 3. Создание HTML-представления

Затем я написал HTML-файл для взаимодействия с пользователем (public/index.html). Веб-макет состоит из страницы, показанной во введении к этому руководству, с добавлением поля с некоторыми статистическими данными:

  • общее количество твитов на карте;
  • количество ошибочных твитов. Это твиты, которые нельзя разместить на карте, потому что атрибут user.location пуст или имеет значение, которое служба Google Geocoder не воспринимает. (Пользователи Twitter иногда указывают в своих профилях всякие причудливые топонимы);
  • количество твитов, не размещенных на карте, потому что служба Google Geocoder неоднократно возвращала ошибку query limit, которая может возникать, когда клиент получает большое количество твитов.

Ниже приведен код веб-макета TOTEM:

  <body>
    <div id="panel">
      <input id="search" type="search" title="Write a search, click the Search button, 
      and see what's going on on Twitter! Tips: use OR, AND logical operators for multiple-words query">
      <input type="button" value="Search" onclick="startSearch()" title="Click to start searching">
    </div>
    <div id="map-canvas"></div>
    <div id="panel-monitor">
      <div>Tweets on the Map</div>
      <input id="num-tweets" type="text" readonly title="Number of tweets posted to the map"><br>
      <div>Tweets in error</div>
      <input id="num-tweets-in-error" type="text" readonly 
      title="Number of tweets not posted to the map because of invalid location provided"><br>
      <div>Tweets discarded</div>
      <input id="num-tweets-discarded" type="text" readonly title="Number of tweets discarded 
      because of errors with the Geocoder service (query limit)"><br>
      <div>Tweets/secs</div>
      <input id="tweets-per-sec" type="text" readonly title="Tweets per seconds posted to the map">
    </div>
  </body>

Шаг 4. Создание клиентского кода JavaScript

Я написал логику клиента для приема запросов пользователя, подключения к конечной точке WebSocket и обработки входящих твитов.

Все начинается с обработчика onclick кнопки Search, startSearch():

function startSearch() {
  var search = document.getElementById('search').value;
  if ( search.trim().length===0 ) {
    alert('Cannot submit an empty search.');
    return;
  }

  // Создание соединения для нового поиска
  // При работе в Bluemix используйте безопасный WebSocket (wss) 
  //
  if ( window.document.location.host.match(/localhost/) ) {
    var wsUri = 'ws://' + window.document.location.host + '/search';
  } else {
    var wsUri = 'wss://' + window.document.location.host + '/search';
  }
  wssearch = new WebSocket(wsUri);
  wssearch.onopen = function(evt) { onOpen(evt) };
  wssearch.onclose = function(evt) { onClose(evt) };
  wssearch.onmessage = function(evt) { onMessage(evt) };
  wssearch.onerror = function(evt) { onError(evt) };
}

function onOpen(e) {
  // Отправка поискового запроса на сервер
  //
  var search = document.getElementById('search').value.trim();
  wssearch.send(search);
}

Функция startSearch() создает новый объект WebSocket и устанавливает обработчики событий open, close, message и error. По завершении рукопожатия при открытии WebSocket вызывается обработчик onOpen(), который получает запрос пользователя и передает его, чтобы инициировать поиск.

Наконец, самое интересное: обработчик onMessage(), который анализирует данные и выводит их на карту. Я использовал службу Google Google для получения координат msg.address. Эта служба возвращает сообщение об ошибке OVER_QUERY_LIMIT, если обращаться к ней слишком часто, но я использовал пару трюков, чтобы смягчить это ограничение. Во-первых, я храню полученные адреса в памяти, чтобы уменьшить число вызовов. Во-вторых, я обрабатываю сообщения с переменным временным интервалом, увеличивая его, когда возникает ошибка OVER_QUERY_LIMIT.

Посмотрите в файле public/index.html кода проекта, как функционирует обработчик onMessage() и работают другие функции, реализующие логику отображения результатов на карте.

Шаг 5. Развертывание приложения в Bluemix

  1. Переименуйте файл manifest.yml.change_me (он расположен в корневом каталоге) в manifest.yml.
  2. В файле manifest.yml присвойте значению host уникальное имя своего приложения и заполните значения TWITTER_KEYS:
     ---
      applications:
      - name: totem
        memory: 512M
        domain: mybluemix.net
        instances: 1
        host: CHANGE_ME
        path: .
        env:
          TWITTER_KEYS: '{"consumer_key":"CHANGE_ME","consumer_secret":
          "CHANGE_ME","access_token_key":"CHANGE_ME","access_token_secret":"CHANGE_ME"}'

    Значения consumer_key и consumer_secret – это параметры API Key и API Secret, взятые со страницы Twitter Application Settings.
  3. Отредактируйте файл package.json, расположенный в корневом каталоге, и исключите из зависимости модуль ntwitter, чтобы при переходе в Bluemix он загружался из локального каталога. (Этот шаг нужен для того, чтобы сохранить исправление, внесенное в пакет ntwitter; в противном случае Bluemix переопределит исправленный модуль при подготовке приложения).
  4. Выполните команду cf push из корневого каталога приложения и откройте в браузере URL https://имя_вашего_приложения.mybluemix.net/, чтобы увидеть приложение в действии.

Заключение

Приложение TOTEM демонстрирует возможности WebSocket по созданию веб-приложений, когда требуется связь в режиме реального времени. WebSocket решает основные проблемы, которые делают HTTP непригодным для приложений подобного типа. По сравнению с опросом по протоколу HTTP, WebSocket повышает производительность и упрощает работу, и это стандартная часть спецификации HTML5.


Ресурсы для скачивания


Похожие темы

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, Облачные вычисления
ArticleID=1007875
ArticleTitle=Создание WebSocket-приложения для визуализации количества твитов на карте
publish-date=06082015