Java + Rhino + Jetty + FreeMarker vs Node.js

от автора


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

Mozilla Rhino

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

Прежде всего, 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 

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, которую можно включить в 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. Jetty — контейнер servlet-ов, а заодно и гибкий в настройке web-server с поддержкой SPDY, WebSocket, OSGi, JMX, JNDI и JAAS. Скачать дистрибутив можно тут.

Простейший код, запускающий 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 для JDBC.

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

FreeMarker

FreeMarker — определенно лучший шаблонизатор для 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 заменяет в подставляемой переменной те самые спецсимволы на 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.

ссылка на оригинал статьи http://habrahabr.ru/post/183334/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *