Статьи

Веб-розетки в игре

Прошло почти 4 месяца с тех пор, как я выпустил первую бета-версию электронной книги Play Framework ( http://www.the-play-book.co.uk ), после чего последовал финальный выпуск, сопровождаемый выпуском в мягкой обложке. Комментарии и отзывы были фантастическими, так что спасибо всем!

Я выпустил книгу, полностью обновленную до версии 1.1 для Play, и теперь 4 месяца спустя Play 1.2 был выпущен с множеством новых функций. В качестве быстрого снимка эти функции включают

  • Управление зависимостями — используя Apache Ivy
  • Evolutions — для отслеживания и организации изменений в схемах вашей базы данных
  • H2 в базе данных памяти — заменяет старый HSQLDB и поставляется с веб-консолью, доступной по URL / @ db
  • Обновления TestRunner
  • Плюс множество исправлений ошибок и небольших улучшений в кодовой базе Play

Все эти функции звучат абсолютно великолепно, но есть один набор функций, которые я с нетерпением ждал, и это WebSockets!

Использование WebSockets
Итак, почему так интересно? Проще говоря, несколько лет назад в свое свободное время я писал сценарии для многопользовательских игр на основе Java Swing. Обычно они были клонами популярных настольных игр, но у них была настоящая проблема! Никому не понравилось скачивать и устанавливать игру. Они хотели это по требованию, в браузере, легко доступны. Но веб-программирование не очень хорошо подходило для такого типа мира. Play позволил очень легко приблизиться, используя AJAX и его метод длинного опроса (обсуждаемый в главе 12) о приостановке HTTP-запроса до тех пор, пока не появятся обновления, но для меня я жаждал Websockets.

В эти выходные я наконец-то начал играть с Websockets, и должен сказать, что я впечатлен. Ребята из Play Dev проделали большую работу с этим. Документация не фантастически многословна, поэтому этот пост — попытка немного облегчить жизнь тем, кто хочет поиграть с Websockets, используя мой любимый фреймворк!

Что вам нужно?
Проще говоря, вам нужна Play 1.2 и веб-браузер с поддержкой веб-сокетов. Итак, выключите IE (даже если у вас IE9!) И используйте Chrome. Если у вас Firefox или Opera, протокол веб-сокета был отключен из-за некоторых проблем с безопасностью, что вызывает разочарование, но в какой-то момент они будут устранены, поэтому сейчас я буду продолжать изучать и играть с веб-сокетами, так как они будут отличными конкурентное преимущество позже.

Давайте попробуем это.
В отличие от метода длинного опроса, который проверяет базу данных на наличие изменений в модели через заданные интервалы, идея веб-сокета заключается в том, чтобы всегда быть открытым, ожидая событие, а затем передавая это событие всем, кому необходимо знать. Таким образом, способ достижения этого в Play состоит в том, чтобы объект Stateful находился на стороне сервера. Я знаю, что это разрушает многие безгражданские, RESTful идеалы Play, но это единственный способ заставить эту работу. Итак, начнем с Stateful Model.

Создайте новое приложение под названием websocket.

play new websocket

Теперь создайте новый файл StatefulModel.java в каталоге app / models и добавьте следующий код.

package models;

import play.libs.F;

public class StatefulModel {
public static StatefulModel instance = new StatefulModel();
public final F.EventStream event = new F.EventStream();

private StatefulModel() { }
}

Итак, очень простой класс. Он имеет закрытый конструктор и единственную статическую переменную экземпляра для применения шаблона Singleton, то есть на сервере может существовать только один экземпляр этой модели. Единственный другой атрибут — это объект EventStream. Это важная часть. EventStream — это ядро ​​реализации WebSockets в Play. Он позволяет публиковать события, что уведомляет всех ожидающих слушателей о том, что событие было опубликовано. В этом примере мы используем стандартный EventStream, который просто предоставляет доступ к текущему событию. Play также предоставляет ArchivedEventStream (посмотрите пример приложения чата, чтобы увидеть его в действии), который также предоставляет доступ ко всем заархивированным сообщениям. Чтобы объяснить эту концепцию EventStream более подробно, давайте взглянем на контроллер.

Откройте файл Application.java в каталоге app / controllers и добавьте следующий код.

package controllers;

import play.mvc.*;
import models.*;

public class Application extends Controller {

public static void index() {
render();
}

public static class WebSocket extends WebSocketController {

public static void listen() {
while(inbound.isOpen()) {
String event = await(StatefulModel.instance.event.nextEvent());
outbound.send(event);
}
}
}
}

Так что здесь тоже не так много. Действие index просто отобразит страницу индекса, а затем у нас будет действие listen внутри статического класса WebSocket. Статический класс WebSocket реализует новый тип контроллера, представленный в Play 1.2, который называется WebSocketController. Разница между WebSocketController и стандартным контроллером заключается в том, что он устраняет модель запроса / ответа и на его месте имеет входящую и исходящую модель. Здесь код просто зацикливается, когда входящий канал открыт (что означает, что браузер все еще открыт, а веб-сокет все еще подключен). Затем мы ожидаем новое событие из EventStream нашего StatefulModel, вызывая nextEvent.

Функция await заменяет функцию приостановки в play1.1. Функция await приостанавливает http-запрос, освобождая инфраструктуру воспроизведения для продолжения обработки запросов, пока новое событие не будет добавлено в нашу модель с сохранением состояния, после чего код продолжит обработку с того места, где он остановился. Это еще одно важное изменение в Play1.2. Он не вызывает метод с самого начала снова, он продолжает с того места, где остановился, делая код более читабельным.
Как только код продолжается, он отправляет данные из события на исходящий, который отправляет событие обратно в веб-браузер.

Итак, как браузер справляется с этим? Ну, это опять-таки очень просто.
Откройте views / application / index.html и добавьте следующий код.

#{extends 'main.html' /}
#{set title:'Home' /}

<div id="socketout"></div>

<script type="text/javascript">
// Create a socket
var socket = new WebSocket('@@{Application.WebSocket.listen}')

// Message received on the socket
socket.onmessage = function(event) {
$('#socketout').append(event.data+"<br />");
}
</script>

Итак, все, что у нас здесь есть, — это тег DIV, в который мы будем выводить события нашего веб-сокета, и немного javascript, который а) создает соединение веб-сокета и б) обрабатывает событие включения сообщения, добавляя данные в DIV.

Последний элемент, прежде чем мы начнем добавлять события в наш веб-сокет, — это файл маршрутов. В отличие от обычного запроса Catch-All, это не работает, поскольку catch все работает с именем Controller.action, и у нас также есть статический класс, с которым также приходится бороться. Поэтому, чтобы наша маршрутизация работала, нам также нужно добавить маршрут. Откройте файл маршрутов и добавьте следующий маршрут рядом с домашней страницей.

WS      /socket                                 Application.WebSocket.listen

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

Это наша реализация websocket. Последняя часть, однако, состоит в том, чтобы начать добавлять некоторые события. Если бы мы запустили наше приложение сейчас, мы бы просто увидели пустую страницу. Наш веб-сокет будет открыт, но не будет никаких данных, возвращающихся к нам с сервера. Итак, давайте создадим асинхронное задание, чтобы начать помещать некоторые сообщения в EventStream.

Создайте новый каталог с именем app / jobs, а затем создайте файл с именем Startup.java. Добавьте следующий код.

package job;

import play.jobs.*;
import models.StatefulModel;

@OnApplicationStart(async = true)
public class Startup extends Job {
   public void doJob() throws InterruptedException {
      int i = 0;

      while (true) {
         i++;
         Thread.sleep(1000);
         StatefulModel.instance.event.publish("On step " + i);
      }
   }
}

Это простая задача запуска, которая была настроена на асинхронный запуск. Он просто зацикливается навсегда (или до остановки сервера), и на каждой итерации он спит в течение 1 секунды, а затем публикует событие в EventStream нашего StatefulModel со строкой «On Step», за которой следует счетчик. Это будет продолжаться до тех пор, пока сервер не будет остановлен.

Чтобы заставить этот код выполняться, нам нужно было указать, что @OnApplicationStart должен был работать в асинхронном режиме. Без этого параметра задание Bootstrap будет настроено так, что оно ДОЛЖНО завершаться до выполнения первого запроса. Это имеет смысл, как если бы мы использовали задание Bootstrap для чтения настроек времени выполнения для нашего приложения, мы бы не хотели, чтобы оно запускалось, пока оно не выполнит всю свою работу. В этом случае, однако, мы просто хотим, чтобы он работал параллельно.

Если вы сейчас запустите сервер с

play run websocket

Теперь наведите ваш браузер на localhost: 9000, вы должны увидеть, как ваш браузер начинает считать с 1. Но что если вы откроете другой веб-браузер? Вы увидите, что он начнет отсчитывать время с другим браузером. Событие эффективно транслируется на все прослушивающие браузеры. Очевидно, что это разработано, и вы могли бы адаптировать способ создания приложения, но концепция является мощной.

И прежде чем я подпишусь, еще раз большое спасибо разработчикам Play. Этот запрос на Websockets пришел напрямую от очень активного сообщества Play, и ребята из Play Dev реализовали его великолепно, оставаясь верными фреймворку. Прекрасный кусок программной инженерии.

От http://playframework.wordpress.com/2011/04/25/websockets-in-play/