- PVSM.RU - https://www.pvsm.ru -

Node.js vs Java + Rhino + Jetty + FreeMarker

Node.js vs Java + Rhino + Jetty + FreeMarker
Хоть Node.js и обзавелся с момента своего появления множеством модулей [1], он все еще существенно уступает по возможностям мощному набору библиотек [2] Java. Так отчего бы не воспользоваться потенциалом Java для разработки web-приложений на JavaScript? Давайте посмотрим, как можно построить удобный JavaScript MVC framework на Java.

Mozilla Rhino

Прежде всего, начнем с носорогов. Для компиляции/интерпретации JavaScript будем использовать движок Mozilla Rhino [3], обеспечивающий отличную интеграцию кода ECMAScript в Java-приложения. Начиная с J2SE 6 Rhino включается в JRE в составе Java Scripting API, однако версия в JRE значительно устаревшая и, кроме того, с некоторыми неприятными особенностями [4] реализации от Sun, поэтому лучше воспользоваться свежим build-ом [5].

Прежде всего, helloworld.js:

print('Hey you!');

Предполагая, что библиотеки Rhino распакованы в ./lib, запускаем пример следующим образом:

java -Djava.ext.dirs=./lib org.mozilla.javascript.tools.shell.Main helloworld.js


Кстати, в комплекте идет и отладчик с неплохим UI, запускается он так:

java -Djava.ext.dirs=./lib org.mozilla.javascript.tools.debugger.Main helloworld.js

Node.js vs Java + Rhino + Jetty + FreeMarker

Rhino, помимо стандартных объектов ECMAScript, включает в глобальный контекст целый ряд функций, облегчающих связь JavaScript с Java. Да, если вы еще не поняли, из кода на JavaScript можно будет прозрачно работать с кодом на Java. Для работы с пакетами существует глобальный объект Packages. Например, так можно создать экземпляр java.io.File:

var file = new Packages.java.io.File('filename');

Впрочем, также существуют глобальные объекты java, com, org, edu и net, поэтому код можно сократить до следующего:

var file = new java.io.File('filename');

Для импорта можно пользоваться таким pattern-ом:

var File = java.io.File;
//...
var file = new File('filename');

Но все же удобнее так:

importClass(java.io.File);
//...
var file = new File('filename');

Или так:

importPackage(java.io);
//...
var file = new File('filename');

Rhino позволяет реализовывать интерфейсы Java удобным для JS-программиста способом:

var runnable = new java.lang.Runnable({run: function() { print("I'm running!"); }});
new java.lang.Thread(runnable).start();

Кстати, обратите внимание на то, что java.lang не импортируется в глобальный контекст во избежание конфликтов со встроенными типами ECMAScript.

А еще последние версии Rhino включают полноценную реализацию CommonJS [6], которую можно включить в Rhino Shell с помощью switch-а -require.

Если у нас есть модуль ./modules/math.js:

exports.sum = function(a, b) {
  return a + b;
}

То воспользоваться им можно так:

var math = require('math');
print(math.sum(2, 4));

Запускается этот код так:

java -Djava.ext.dirs=./lib org.mozilla.javascript.tools.shell.Main -require -modules ./modules test.js

Jetty

В качестве основы для HTTP-сервера возьмем Jetty [7]. Jetty — контейнер servlet-ов, а заодно и гибкий в настройке web-server с поддержкой SPDY, WebSocket, OSGi, JMX, JNDI и JAAS. Скачать дистрибутив можно тут [8].

Простейший код, запускающий Jetty:

importPackage(org.eclipse.jetty.server);

var server = new Server(8888); // Порт 8888
server.start();
server.join(); // Передадим управление Jetty

JAR-ы из дистрибутива Jetty следует также поместить в ./lib, в дальнейшем все будем запускать так:

java -Djava.ext.dirs=./lib org.mozilla.javascript.tools.shell.Main -require -modules ./modules server.js

Да, это все.

Этот сервер выдает HTTP 404 при любом запросе. Превратим его в простой файловый сервер.

importPackage(org.eclipse.jetty.server);
importPackage(org.eclipse.jetty.server.handler);

var resourceHandler = new ResourceHandler();
resourceHandler.setDirectoriesListed(true); // Разрешим просмотр списка файлов в папках
resourceHandler.setResourceBase('web'); // Установим базовой директорию ./web
resourceHandler.setWelcomeFiles(['index.html']); // В качестве главной страницы будет использоваться index.html

var server = new Server(8888);
server.setHandler(resourceHandler);
server.start();
server.join();

Теперь попробуем создать servlet.

importPackage(org.eclipse.jetty.server);
importPackage(org.eclipse.jetty.server.handler);
importPackage(org.eclipse.jetty.servlet);
importPackage(javax.servlet.http);

var contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath('/');
contextHandler.addServlet(
  new ServletHolder(new HttpServlet({ // Обратим еще раз внимание на то, как изящно реализуются интерфейсы
    doGet: function(request, response) {
      response.setContentType('text/plain');
      response.getWriter().println('Yes, it works!');
    }
  })),
  '/test'
);

var server = new Server(8888);
server.setHandler(contextHandler);
server.start();
server.join();

Наш servlet доступен на http://localhost:8888/test. В качестве еще одного примера оформим в виде модуля servlet, генерирующий на лету картинку с текстом.

importPackage(java.awt.image);
importClass(java.awt.Color);
importClass(javax.imageio.ImageIO); // Входит в состав J2SE 6

var width = 400, height = 400;

exports.doGet = function(request, response) {
  var image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  var graphics = image.createGraphics();
  var color = new Color(Math.random(), Math.random(), Math.random());
  graphics.setColor(color);
  graphics.fillRect(0, 0, width, height);
  graphics.setColor(color.brighter());
  graphics.drawString('On the fly!', 10, 20);

  response.setContentType('image/png');
  var outputStream = response.getOutputStream();
  ImageIO.write(image, 'png', outputStream);
  outputStream.close();
};

Поместим его в папку ./modules как imageServlet.js и включим в код сервера:

contextHandler.addServlet(
  new ServletHolder(new HttpServlet(require('imageServlet'))),
  '/image.png'
);

Что там с СУБД? Посмотрим, как получить список баз данных из MySQL.

importPackage(java.sql);

exports.doGet = function(request, response) {
  var resultSet = DriverManager.getConnection('jdbc:mysql://localhost/?', 'root', '')
    .createStatement()
    .executeQuery('show databases;');

  response.setContentType('text/html;charset=UTF-8');
  var writer = response.getWriter();
  writer.println('<h1>Databases</h1>');
  while (resultSet.next()) {
    writer.println(resultSet.getString('Database') + '<br />');
  }
};

Для этого кода понадобится MySQL Connector/J [9] для JDBC.

Теперь остался последний компонент, шаблонизатор.

FreeMarker

FreeMarker [10] — определенно лучший шаблонизатор для Java, причем не только для HTML и HTTP. Про его богатые возможности можно написать отдельную статью, так что сразу перейдем к конкретике.

Положим в ./templates/template.ftl такой вот шаблон:

<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
<#if message??>
  <pre>${message?html}</pre>
<#else>
  <form method="post">
    <textarea name="message"></textarea>
    <p><input type="submit" value="Post!"/></p>
  </form>
</#if>
</body>
</html>

Суффикс ?html заменяет в подставляемой переменной те самые спецсимволы [11] на escape-последовательности. Этот шаблон будет использовать следующий servlet:

importPackage(Packages.freemarker.template);
importPackage(Packages.freemarker.ext.rhino);

var configuration = new Configuration();
configuration.setObjectWrapper(new RhinoWrapper()); // Поистине приятный сюрприз.
var template = configuration.getTemplate('templates/template.ftl');

exports.doGet = function(request, response) {
  response.setContentType('text/html;charset=UTF-8');
  template.process(
    {'title': 'Compose a message'},
    response.getWriter()
  );
};

exports.doPost = function(request, response) {
  response.setContentType('text/html;charset=UTF-8');
  template.process(
    {
      'title': 'Message',
      'message': request.getParameter('message')
    },
    response.getWriter()
  );
};

Сравнение с Node.js я благородно сваливаю на читателя. Полный код примера доступен на GitHub [12].

Автор: dannote

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/36547

Ссылки в тексте:

[1] множеством модулей: https://github.com/joyent/node/wiki/modules

[2] мощному набору библиотек: http://en.wikipedia.org/wiki/Category:Java_libraries

[3] Mozilla Rhino: https://developer.mozilla.org/en-US/docs/Rhino

[4] неприятными особенностями: http://stackoverflow.com/questions/4801037/sun-org-mozilla-rhino-and-extending-java-abstract-classes

[5] свежим build-ом: https://developer.mozilla.org/en-US/docs/Rhino/Download_Rhino?redirectlocale=en-US&redirectslug=RhinoDownload

[6] CommonJS: http://wiki.commonjs.org/wiki/Modules

[7] Jetty: http://www.eclipse.org/jetty/

[8] тут: http://download.eclipse.org/jetty/stable-9/dist/

[9] MySQL Connector/J: http://dev.mysql.com/downloads/connector/j/

[10] FreeMarker: http://freemarker.sourceforge.net/

[11] те самые спецсимволы: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)

[12] GitHub: https://github.com/dannote/rhinomvc

[13] Источник: http://habrahabr.ru/post/183334/