Мой сайт написан на Node.js, и иногда мне требуется сделать что-то, для чего Node.js не предназначен: например, произвести какие-нибудь математические вычисления.
В этом примере мы будем вычислять «хеш» пароля.
Доверим эту работу «бекенду», написанному на (подходящем для вычислений) функциональном языке программирования. Например, на Scala. Функционально это будет так: Node.js отправляет GET-запрос на хеширование на «бекенд», «бекенд» думает-думает и в ответ отсылает вычисленный хеш в формате Json. Обычный HTTP запрос-ответ, ничего сложного.
Есть множество подходов к решению поставленной задачи в плане выбора набора «фреймворков».
Я немного писал на Ruby (не на «Рельсах»), с удовольствием пользуясь фреймворком Sinatra. И на Node.js пишу, пользуясь клоном Sinatra по имени Express. Выяснилось, что и для Scala тоже написан соответствующий клон — Scalatra. Однако для своей работы она требует некоей непонятной штуки под названием Sbt (Scala Build Tool). Эта штука — типа клон Явовского Maven’а, только гораздо страшнее… Я так и не смог понять, что она делает…
«Хочешь сделать что-то хорошо — сделай это сам» (народная мудрость)
В этой статье мы заложим свой путь написания REST’ового web-сервиса на Scala, без лишних заморочек, как и обещано, за 15 минут.
В качестве REST’ового фреймворка мы воспользуемся Jersey. Это воплощение API JAX-RS. Этот API пришёл из мира Явы Enterprise Edition, но, несмотря на это, не является какой-нибудь монструозной Годзиллой, а представляет собой вполне себе адекватный небольшой фреймворк, в той своей части, которая нам потребуется.
Jersey — это API, и его нужно запустить на каком-нибудь Явовом веб-сервере. Jetty, Tomcat, Grizzly — можно выбирать любой. Я выбрал Grizzly, просто потому что он попался мне на глаза. А так, я не знаю, чем он лучше того же Jetty. Может быть, побыстрее будет.
Как всё это дело запускать
Если по дзен-буддистски, то достаточно двух файлов: build.sh вида “javac -d classes *.java; scalac -classpath […] -d classes *.scala”
и run.sh вида “scala -classpath […] -Dпорт=8090 Main”
.
Однако лучше использовать «сборщика», чтобы не возиться с масками имён файлов, и с длинным classpath в одну строку: правильный сборщик (не Maven, не Sbt) даст вам гораздо больше свободы (и читаемости), чем shell. Он сам за вас скачает все используемые библиотеки, и сам добавит каждый Jar’ник поимённо в classpath (и вам не придётся делать это руками).
На должность адекватного сборщика мы возьмём молодой (вот-вот выйдет версия 1.0) и развивающийся проект Gradle, который мне больше напоминает старого доброго Ant’а, чем Maven’а, в том, что не запирает разработчика в жёсткие рамки, а, наоборот, даёт полную свободу творчества, да ещё и на адекватном языке Groovy (клон Ruby в мире Явы).
Установка
Ставим JDK. Проверяем: java -version
Качаем Scala, и прописываем bin в PATH. Проверяем: scala –version
Качаем Gradle, и прописываем bin в PATH. Проверяем: gradle –v
Проект
Древо проекта
Сам проект расположился на github'е. Здесь я приведу код двух основных файлов.
Инструкции по сборке для Gradle — build.gradle
// используем язык Scala
apply plugin: 'scala'
ext.scala_version = '2.9.1'
ext.jersey_version = '1.12'
ext.description = 'Accessing various calculation tasks'
ext.classes_directory = new File('classes')
// вспомогательная функция удаления папки — аналог «rm -rf»
def delete_folder(File folder)
{
if (folder.isDirectory())
{
String[] children = folder.list()
int i = 0
while (i < children.length)
{
boolean success = delete_folder(new File(folder, children[i]))
if (!success)
{
return false
}
i++
}
}
// The directory is now empty so delete it
return folder.delete()
}
// эта задача очищает папку «classes» — можете запустить вручную, если будет нужно
task clean_output_folders <<
{
delete_folder(classes_directory)
classes_directory.mkdirs()
}
// основная задача — запускает наш веб-сервис
// сначала запускает «compileJava» и «compileScalaScala», а потом уже выполняется сама
task go(dependsOn: ['compileJava', 'compileScalaScala'], type: JavaExec) {
main = 'Main'
classpath = sourceSets.main.runtimeClasspath
standardInput = System.in
//args 'arguments`'
systemProperty 'port', '8090'
}
// наборы исходных кодов
sourceSets
{
// главный - Ява
main
{
java
{
// исходники искать в папке «sources»
srcDir 'sources'
}
}
// ещё один - Scala
scala
{
scala
{
// исходники искать в папке «sources»
srcDir 'sources'
}
}
}
// скомпилированные Ява-классы класть в папку «classes»
sourceSets.main.output.classesDir = 'classes'
// скомпилированные Scala-классы класть в папку «classes»
sourceSets.scala.output.classesDir = 'classes'
// используемые библиотеки
dependencies
{
// служебные библиотеки для обработки Scala из Gradle
scalaTools group: 'org.scala-lang', name: 'scala-compiler', version: scala_version
scalaTools group: 'org.scala-lang', name: 'scala-library', version: scala_version
// сама Scala, нужна для компиляции кода на Scala
compile group: 'org.scala-lang', name: 'scala-library', version: scala_version
scalaCompile group: 'org.scala-lang', name: 'scala-library', version: scala_version
// прочие библиотеки, используемые в программе
scalaCompile group: 'asm', name: 'asm', version: '3.3.1'
scalaCompile group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'
scalaCompile group: 'com.sun.jersey', name: 'jersey-bundle', version: jersey_version
scalaCompile group: 'com.sun.jersey', name: 'jersey-client', version: jersey_version
scalaCompile group: 'com.sun.jersey', name: 'jersey-core', version: jersey_version
scalaCompile group: 'com.sun.jersey', name: 'jersey-server', version: jersey_version
scalaCompile group: 'com.sun.jersey', name: 'jersey-grizzly2', version: jersey_version
scalaCompile group: 'com.sun.jersey', name: 'jersey-bundle', version: jersey_version
runtime group: 'asm', name: 'asm', version: '3.3.1'
runtime group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'
runtime group: 'com.sun.jersey', name: 'jersey-bundle', version: jersey_version
runtime group: 'com.sun.jersey', name: 'jersey-client', version: jersey_version
runtime group: 'com.sun.jersey', name: 'jersey-core', version: jersey_version
runtime group: 'com.sun.jersey', name: 'jersey-server', version: jersey_version
runtime group: 'com.sun.jersey', name: 'jersey-bundle', version: jersey_version
runtime group: 'com.sun.jersey', name: 'jersey-grizzly2', version: jersey_version
// до кучи можно просто класть jar-ники в папку «libraries»,
// и они тоже подхватятся в качестве библиотек
scalaCompile fileTree(dir: 'libraries', include: '*.jar')
scalaCompile files('classes')
runtime fileTree(dir: 'libraries', include: '*.jar')
runtime files('classes')
}
// компилирует код на Scala
compileScala
{
// какой-то «демон» для Scala — убыстряет компиляцию
scalaCompileOptions.useCompileDaemon = true
// куда
destinationDir = file('classes')
// откуда
source = file('sources')
}
// откуда качать используемые библиотеки
repositories
{
mavenCentral()
}
И наш REST web-сервис, дающий возможность захешировать пароль на Whirlpool или SHA-512 — Hasher.scala
package resources
import javax.ws.rs._
import javax.ws.rs.core._
import hash.Whirlpool
import hash.SHA
import com.twitter.json.Json
@Path("/захѣшировать")
class Hasher
{
@GET
@Produces(Array("text/plain"))
def приветствие() : String =
{
"Доступные алгоритмы: Whirlpool, SHA"
}
@GET
@Path("Whirlpool/{что}")
@Produces(Array(MediaType.APPLICATION_JSON))
def whirlpool(@DefaultValue("") @PathParam("что") что : String) : String =
{
if (что == "")
throw new IllegalArgumentException("Что захешировать?")
Json.build(Map("hash" -> Whirlpool.hash(что))).toString
}
@GET
@Path("SHA/{что}")
@Produces(Array(MediaType.APPLICATION_JSON))
def sha(@DefaultValue("") @PathParam("что") что : String) : String =
{
if (что == "")
throw new IllegalArgumentException("Что захешировать?")
Json.build(Map("hash" -> SHA.hash(что))).toString
}
}
Запускаем
Качаем архив, распаковываем, заходим в папку и выполняем команду gradle go
. При успешном выполнении вы узреете в консоли запуск веб-сервера:
Starting grizzly... May 11, 2012 9:13:59 PM com.sun.jersey.api.core.PackagesResourceConfig init INFO: Scanning for root resource and provider classes in the packages: resources May 11, 2012 9:13:59 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses INFO: Root resource classes found: class resources.Hasher May 11, 2012 9:13:59 PM com.sun.jersey.api.core.ScanningResourceConfig init INFO: No provider classes found. May 11, 2012 9:13:59 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate INFO: Initiating Jersey application, version 'Jersey: 1.12 02/15/2012 05:30 PM' May 11, 2012 9:14:00 PM org.glassfish.grizzly.http.server.NetworkListener start INFO: Started listener bound to [localhost:8090] May 11, 2012 9:14:00 PM org.glassfish.grizzly.http.server.HttpServer start INFO: [HttpServer] Started.
Проверяем
* Я заметил такой глюк своего браузера (Chrome): если зайти по этим адресам до запуска веб-сервера, и жать-жать-жать на «Обновить», то после того, как веб-сервер запуститися, Chrome будет по-прежнему выдавать текст «404», хотя если после этого зайти на тот же Url из другого браузера — всё работает.
Приветствие
Хешируем наш пароль по алгоритму SHA-512
Хешируем наш пароль по алгоритму Whirlpool
Если вам нечем заняться (или интересно), то можете почитать ещё
Как писать сборку на Gradle
Простой REST-сервис на Jersey
О Gradle по-русски
Что такое Scala и чем она удобна
Автор: kuchumovn