Когда я почти закончил статью, я заметил, что веб-розетки также поддерживаются в Play 2.0. Мне очень нравится разработка в Play и в Scala, поэтому в качестве эксперимента я переписал внутреннюю часть стека Jetty / Java / JavaCV в стек Play2.0 / Scala / JavaCV. Если вы хотите сделать это для себя, убедитесь, что вы начинаете с кода внешнего интерфейса здесь . Так как код внешнего интерфейса не изменился, кроме места, где прослушиваются веб-сокеты.
Настройка среды Play 2.0
Я не буду много говорить о том, как начать проект Play 2.0 / Scala. Вы можете найти подробности в некоторых других моих постах, если вам потребуется дополнительная информация. Нам нужно настроить зависимости для JavaCV, чтобы их можно было использовать из Play 2. Я вручную добавил их в свой локальный репозиторий ivy, чтобы я мог ссылаться на них из конфигурации sbt, как и любая другая зависимость. Для моего примера я создал следующую структуру каталогов для библиотек JavaCV:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
./play-2.0-RC2/repository/cache/javacv./play-2.0-RC2/repository/cache/javacv/javacpp./play-2.0-RC2/repository/cache/javacv/javacpp/ivy-2.3.1.xml./play-2.0-RC2/repository/cache/javacv/javacpp/ivydata-2.3.1.properties./play-2.0-RC2/repository/cache/javacv/javacv./play-2.0-RC2/repository/cache/javacv/javacv/ivy-2.3.1.xml./play-2.0-RC2/repository/cache/javacv/javacv/ivydata-2.3.1.properties./play-2.0-RC2/repository/cache/javacv/javacv-macosx-x86_64./play-2.0-RC2/repository/cache/javacv/javacv-macosx-x86_64/ivy-2.3.1.xml./play-2.0-RC2/repository/cache/javacv/javacv-macosx-x86_64/ivydata-2.3.1.properties./play-2.0-RC2/repository/local/javacv./play-2.0-RC2/repository/local/javacv/javacpp./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/ivys./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/ivys/ivy.xml./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/jars./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/jars/javacpp.jar./play-2.0-RC2/repository/local/javacv/javacv./play-2.0-RC2/repository/local/javacv/javacv/2.3.1./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/ivys./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/ivys/ivy.xml./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/jars./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/jars/javacv.jar./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/ivys./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/ivys/ivy.xml./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/jars./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/jars/javacv-macosx-x86_64.jar |
Как вы можете видеть из этого списка, я только что добавил три javacv-файла в мой локальный репозиторий. Я также добавил минимальный файл ivy.xml, чтобы его можно было использовать из ivy и sbt. Этот минимальный ivy.xml выглядит так:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<?xml version="1.0" encoding="UTF-8"?> <info organisation="javacv" module="javacv-macosx-x86_64" revision="2.3.1" status="release"> </info> <publications> <artifact type="jar"/> </publications></ivy-module> |
С этими файлами, добавленными в мой репозиторий, я могу установить зависимости для проекта Play 2.0 в файле Build.scala.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
import sbt._import Keys._import PlayProject._object ApplicationBuild extends Build { val appName = "PlayWebsocketJavaCV" val appVersion = "1.0-SNAPSHOT" val appDependencies = Seq( "javacv" % "javacv" % "2.3.1", "javacv" % "javacpp" % "2.3.1", "javacv" % "javacv-macosx-x86_64" % "2.3.1" ) val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings( // Add your own project settings here )} |
Теперь запустите «play update» и «play eclipsify», чтобы обновить зависимости и конфигурацию Eclipse (если вы используете Eclipse).
Настройка веб-сокетов в Play
Использование веб-сокетов в Play 2.0 очень просто. Первое, что вам нужно сделать, это добавить URL к вашей конфигурации маршрутов в каталоге conf.
|
1
|
GET /wsrequest controllers.Application.wsrequest |
И, конечно же, вам нужно выполнить действие, на которое указывает этот маршрут:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
/** * Simple websocket listener configured in play 2. This uses a synchronous model, where * the same channel is used to send the response. For this usecase this is useful, if * we want async processing we could have used Akka actors together with play 2.0 async * support. */def wsrequest = WebSocket.using[Array[Byte]] { request => // Create the outbound value that is called for each val out = Enumerator.imperative[Array[Byte]](); val in = Iteratee.foreach[Array[Byte]](content => { out.push(FaceDetect.detect(content)); }) // tie the in and out values to each other (in, out)} |
В этом коде мы настраиваем входной канал (вход) и выходной канал (выход) и подключаем их к сокету. Всякий раз, когда клиент HTML5 отправляет запрос через веб-сокет, вызывается наш метод «in», и когда мы хотим что-то отправить клиенту, мы можем использовать канал «out». Канал «in» должен быть определен как Iteratee (более подробную информацию см. В этих документах Play). Это означает, что для каждого входного сообщения, которое мы получаем, мы запускаем метод specifici. В этом случае мы запускаем операцию FaceDetect.detect (подробнее об этом позже), и результат этой операции передается обратно клиенту через канал «out». Этот «выходной» канал сам по себе определен как Enumerator (см. Эти документы для воспроизведения). Мы можем присоединить разных слушателей, если хотим к этому перечислителю, но в этом случае мы ничего не делаем с сообщением, просто передаем его клиенту.
Использование JavaCV из Scala
Последний шаг — это код функции FaceDetect.detect. Версия Java, см. Ранее упомянутую статью , очень легко конвертируется в версию Scala.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
package javacvimport com.googlecode.javacv.cpp.opencv_core._import com.googlecode.javacv.cpp.opencv_imgproc._import com.googlecode.javacv.cpp.opencv_highgui._import com.googlecode.javacv.cpp.opencv_objdetect._import com.googlecode.javacpp.BytePointerimport java.nio.ByteBufferimport javax.imageio.ImageIOimport java.io.ByteArrayOutputStreamimport scala.tools.nsc.io.VirtualFileobject FaceDetect { var CASCADE_FILE =PATH_TO_CASCADE_FILE; var minsize = 20; var group = 0; var scale = 1.1; def detect(imageData:Array[Byte]) : Array[Byte] = { // we need to wrap the input array, since BytePointer doesn't accept // a bytearray as input. It accepts a byte varargs, but Array[Byte] // doesn't convert automatically var wrappedData = ByteBuffer.wrap(imageData); var originalImage = cvDecodeImage(cvMat(1, imageData.length,CV_8UC1, new BytePointer(wrappedData))); // convert to grayscale for easy detection var grayImage = IplImage.create(originalImage.width(), originalImage.height(), IPL_DEPTH_8U, 1); cvCvtColor(originalImage, grayImage, CV_BGR2GRAY); // storage is needed to store information during detection var storage = CvMemStorage.create(); // load and run the cascade var cascade = new CvHaarClassifierCascade(cvLoad(CASCADE_FILE)); var faces = cvHaarDetectObjects(grayImage, cascade, storage, scale, group, minsize); // draw a rectangle for the detected faces for (i <- 0 until faces.total) { var r = new CvRect(cvGetSeqElem(faces, i)); cvRectangle(originalImage, cvPoint(r.x, r.y), cvPoint(r.x + r.width(), r.y + r.height), CvScalar.YELLOW, 1, CV_AA, 0); } // convert to bytearray and return var bout = new ByteArrayOutputStream(); var imgb = originalImage.getBufferedImage(); ImageIO.write(imgb, "png", bout); bout.toByteArray() }} |
Единственная проблема, с которой я столкнулся, была с конструктором BytePointer. Одна из подписей принимает переменные типа byte. В Java это позволяет мне просто предоставить этому конструктору байт [], но в Scala это не работает. К счастью, BytePointer также может быть создан с использованием ByteBuffer. В остальном это преобразование Java в Scala.
Выполнение кода
И это почти все. По умолчанию play прослушивает порт 9000, в примере на базе Jetty сервер работал на 9999 и слушал корневой контекст. Для работы с нашим сервером на базе Play2 / Scala нам просто нужно указать браузеру правильный URL-адрес сервера веб-сокетов.
|
1
|
ws = new WebSocket("ws://127.0.0.1:9000/wsrequest"); |
И теперь, когда мы запускаем его, мы используем Play 2 в качестве нашего сервера и запускаем код JavaCV с помощью scal. И что еще более важно это все еще работает:
Ссылка: бинарные веб-сокеты с Play 2.0 и Scala (и чуть более JavaCV / OpenCV) от нашего партнера JCG Йоса Дирксена в блоге Smart Java .