Главная > Soft > Heroku.com: как получить реализацию Websockets на java (native подход)

Heroku.com: как получить реализацию Websockets на java (native подход)

Для реализации одной идеи возникла потребность разместить в интернете где-нибудь приложение с поддержкой Websockets, которое будет доступно из любого места. Главное условие — бесплатно. Ранее я уже делал вещание с камеры на websocket и java. Этот пример с небольшими изменениями я планировал попробовать разместить в глобальной сети. В качестве целевой площадки выбор пал на Heroku.com.

Heroku.com: как получить Websocket на java

На heroku есть описание того, как сделать Websockets на Java, но пример для традиционного программиста на java выглядит странно, поэтому было решено его не использовать, а пойти традиционным путём с war.

https://devcenter.heroku.com/articles/play-java-websockets

На сайте есть два примера. Один для Jetty, другой для Tomcat.

Tomcat — https://devcenter.heroku.com/articles/java-webapp-runner

Jetty — https://devcenter.heroku.com/articles/deploy-a-java-web-application-that-launches-with-jetty-runner

Оба примера очень похожи, суть которых в использовании специального jar, который запускает веб-контейнер для war файла.

Последние версии Jetty и Tomcat реализуют JSR 356, который описывает стандартный интерфейс для реализации WebSockets на java. Поэтому было решено подготовить тестовое приложение для экспериментов с использованием ServerEndpoint аннотации. Оно прекрасно работало на локальном сервере, но попытка запустить его с помощью runner не увенчалась успехом. Runner для Jetty и Tomcat просто не видели аннотаций, даже для сервлетов. Но они прекрасно обрабатывали web.xml.

До JSR 356 веб-серверы имели свои собственные реализации WebSockets, которые работали на основе сервлетов. Поэтому такой сервлет можно прописать в web.xml и runner его увидит. Попытка с Jetty не увенчалась успехом, а вот эксперимент с Tomcat 7 получился.

Сперва нужно создать с помощью Maven Dynamic Web Project:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

После этого нужно добавить runner в pom.xm

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<version>2.3</version>
	<executions>
		<execution>
			<phase>package</phase>
			<goals><goal>copy</goal></goals>
			<configuration>
				<artifactItems>
					<artifactItem>
						<groupId>com.github.jsimone</groupId>
						<artifactId>webapp-runner</artifactId>
						<version>7.0.57.2</version>
						<destFileName>webapp-runner.jar</destFileName>
					</artifactItem>
				</artifactItems>
			</configuration>
		</execution>
	</executions>
</plugin>

Тут используется версия 7.057.2, чтоб запускать war с использованием Tomcat 7.

Дальше подготовим классы и файлы для нашего примера:

PingServlet.java

package info.privateblog.ping;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;

public class PingServlet extends WebSocketServlet {
    @Override
    protected StreamInbound createWebSocketInbound(String s, HttpServletRequest httpServletRequest) {
        return new ProxySocketConnection();
    }
}

PingSocketConnection.java

package info.privateblog.ping;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.concurrent.ArrayBlockingQueue;

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;

public class PingSocketConnection extends MessageInbound {
	public static ArrayBlockingQueue<WsOutbound> connections = new ArrayBlockingQueue<WsOutbound>(100);
    private WsOutbound outbound;
 
    @Override
    protected void onTextMessage(CharBuffer charBuffer) throws IOException {
    	System.out.println("onTextMessage");
    	broadcast(charBuffer.toString());
    }
 
    @Override
    protected void onOpen(WsOutbound outbound) {
    	System.out.println("onOpen");
    	this.outbound = outbound;
        connections.add(outbound);

        for (int i = 0; i < 30; i++) {
        	try {
				outbound.writeTextMessage(CharBuffer.wrap("Ping: " + i));
			} catch (IOException e1) {}
        	try {
				Thread.currentThread().sleep(500);
			} catch (InterruptedException e) {}
        }
			
    }
 
    @Override
    protected void onClose(int status) {
    	System.out.println("onClose");
    	connections.remove(this.outbound);
    }
 
    private void broadcast(String message) {
    	for (WsOutbound connection : connections) {
            try {
                CharBuffer buffer = CharBuffer.wrap(message);
                connection.writeTextMessage(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

	@Override
	protected void onBinaryMessage(ByteBuffer arg0) throws IOException {
    	System.out.println("onBinaryMessage");
	}

}

web.xml

...
<servlet>
	<servlet-name>Ping</servlet-name>
	<servlet-class>info.privateblog.ping.PingServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>Ping</servlet-name>
	<url-pattern>/ping</url-pattern>
</servlet-mapping>
...

index.html

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
		<title>Index</title>
		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
		<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
	</head>
<body>
	<div id="ping"></div>
	<script type="text/javascript">
		$(function() {
			var loc = window.location, new_uri;
			if (loc.protocol === "https:") {
			    new_uri = "wss:";
			} else {
			    new_uri = "ws:";
			}
			new_uri += "//" + loc.host;
			new_uri += loc.pathname + "/ping";			
			
		    var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
		    var dateSocket = new WS(new_uri)
	
		    var receiveEvent = function(event) {
		        $("#ping").html("Last ping: "+event.data);
		    }
	
		    dateSocket.onmessage = receiveEvent;
		})
	</script>
</body>
</html>

Теперь можно попробовать собрать локально

mvn package

и запустить

java -jar target/dependency/webapp-runner.jar target/*.war

Если приложение запустится на порту 8080, то можно переходить к следующему шагу — размещение на сервере Heroku.

Для начала нужно в основной директории проекта создать файл Procfile для запуска приложения

web: java $JAVA_OPTS -jar target/dependency/webapp-runner.jar —port $PORT target/*.war

Добавим проект в репозиторий

git init

git add .

git commit -m «Ready to deploy»

Создадим приложение в Heroku и отправим код на сервер

heroku create

git push heroku master

если компиляции прошла успешно, то можно запустить приложение

heroku open

У меня приложение сразу не запустилось, поэтому пришлось ещё выполнять

heroku scale web=1

Полезные команды для heroku:

heroku logs -t - посмотреть логи

heroku ps — посмотреть запущенные приложения

 

Categories: Soft Tags: , , , ,
  1. Пока что нет комментариев.
  1. Пока что нет уведомлений.