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

Пишем REST web-сервис на Scala за 15 минут

Мой сайт написан на Node.js, и иногда мне требуется сделать что-то, для чего Node.js не предназначен [1]: например, произвести какие-нибудь математические вычисления.

В этом примере мы будем вычислять «хеш» пароля.

Доверим эту работу «бекенду», написанному на (подходящем для вычислений) функциональном языке программирования. Например, на Scala [2]. Функционально это будет так: Node.js отправляет GET-запрос на хеширование на «бекенд», «бекенд» думает-думает и в ответ отсылает вычисленный хеш в формате Json. Обычный HTTP запрос-ответ, ничего сложного.

Есть множество подходов к решению поставленной задачи в плане выбора набора «фреймворков».

Я немного писал на Ruby (не на «Рельсах»), с удовольствием пользуясь фреймворком Sinatra [3]. И на Node.js пишу, пользуясь клоном Sinatra по имени Express [4]. Выяснилось, что и для Scala тоже написан соответствующий клон — Scalatra [5]. Однако для своей работы она требует некоей непонятной штуки под названием Sbt [6] (Scala Build Tool). Эта штука — типа клон Явовского Maven [7]’а, только гораздо страшнее… Я так и не смог понять, что она делает…

«Хочешь сделать что-то хорошо — сделай это сам» (народная мудрость)

В этой статье мы заложим свой путь написания REST’ового web-сервиса на Scala, без лишних заморочек, как и обещано, за 15 минут.

В качестве REST’ового фреймворка мы воспользуемся Jersey [8]. Это воплощение API JAX-RS [9]. Этот 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 [10], который мне больше напоминает старого доброго Ant [11]’а, чем Maven’а, в том, что не запирает разработчика в жёсткие рамки, а, наоборот, даёт полную свободу творчества, да ещё и на адекватном языке Groovy [12] (клон Ruby в мире Явы).

Установка

Ставим JDK [13]. Проверяем: java -version

Качаем Scala [14], и прописываем bin в PATH. Проверяем: scala –version

Качаем Gradle [15], и прописываем bin в PATH. Проверяем: gradle –v

Проект

Древо проекта

image

Сам проект расположился на github'е [16]. Здесь я приведу код двух основных файлов.

Инструкции по сборке для 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» и «compileScala», а потом уже выполняется сама
task go(dependsOn: ['compileJava', 'compileScala'], type: JavaExec) {
	main = 'Main'
	classpath = sourceSets.main.runtimeClasspath
	standardInput = System.in
	//args 'arguments`'
	systemProperty 'port', '8090'
}

// наборы исходных кодов
sourceSets
{
	// главный - Ява
	main
	{
		java
		{
			// исходники искать в папке «sources»
			srcDir 'sources'
		}
		scala
		{
			// исходники искать в папке «sources»
			srcDir 'sources'
		}
	}
}

// скомпилированные Ява-классы класть в папку «classes»
sourceSets.main.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
	
	// прочие библиотеки, используемые в программе
	
	compile group: 'asm', name: 'asm', version: '3.3.1'
	compile group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'
	compile group: 'com.sun.jersey', name: 'jersey-bundle', version: jersey_version
	compile group: 'com.sun.jersey', name: 'jersey-client', version: jersey_version
	compile group: 'com.sun.jersey', name: 'jersey-core', version: jersey_version
	compile group: 'com.sun.jersey', name: 'jersey-server', version: jersey_version
	compile group: 'com.sun.jersey', name: 'jersey-grizzly2', version: jersey_version
	compile 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», 
	// и они тоже подхватятся в качестве библиотек
	
	compile fileTree(dir: 'libraries', include: '*.jar')
	compile files('classes')
	
	runtime fileTree(dir: 'libraries', include: '*.jar')
	runtime files('classes')
}

// откуда качать используемые библиотеки
repositories
{
    mavenCentral()
}

И наш REST web-сервис, дающий возможность захешировать пароль на Whirlpool [17] или SHA-512 [18]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("что" -> что, "хѣш" -> Whirlpool.hash(что))).toString
	}

	@GET 
	@Path("SHA/{что}")
	@Produces(Array(MediaType.APPLICATION_JSON))
	def sha(@DefaultValue("") @PathParam("что") что : String) : String =
	{
		if (что == "")
			throw new IllegalArgumentException("Что захешировать?")
	
		Json.build(Map("что" -> что, "хѣш" -> SHA.hash(что))).toString
	}
}

Запускаем

Качаем архив [19], распаковываем, заходим в папку и выполняем команду 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 из другого браузера — всё работает.

Приветствие [20]
Хешируем наш пароль по алгоритму SHA-512 [21]
Хешируем наш пароль по алгоритму Whirlpool [22]

Если вам нечем заняться (или интересно), то можете почитать ещё

Как писать сборку на Gradle [23]
Простой REST-сервис на Jersey [24]
О Gradle по-русски [25]
Что такое Scala и чем она удобна [26]
Unfiltered — лёгкий REST фреймворк для Scala [27]
Spray — продвинутый REST фреймворк для Scala [28]

Автор: kuchumovn


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

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

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

[1] не предназначен: http://habrahabr.ru/post/128772/

[2] Scala: http://ru.wikipedia.org/wiki/Scala_(%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)

[3] Sinatra: http://titusd.co.uk/2010/04/07/a-beginners-sinatra-tutorial/

[4] Express: http://expressjs.com/

[5] Scalatra: http://www.scalatra.org/

[6] Sbt: https://github.com/harrah/xsbt/wiki

[7] Maven: http://ru.wikipedia.org/wiki/Maven

[8] Jersey: http://www.vogella.com/articles/REST/article.html

[9] JAX-RS: http://en.wikipedia.org/wiki/JAX-RS

[10] Gradle: http://gradle.org/

[11] Ant: http://ru.wikipedia.org/wiki/Apache_Ant

[12] Groovy: http://ru.wikipedia.org/wiki/Groovy

[13] JDK: http://www.oracle.com/technetwork/java/javase/downloads/index.html

[14] Scala: http://www.scala-lang.org/downloads

[15] Gradle: http://gradle.org/downloads

[16] на github'е: https://github.com/kuchumovn/scala_rest_tutorial

[17] Whirlpool: http://ru.wikipedia.org/wiki/Whirlpool

[18] SHA-512: http://ru.wikipedia.org/wiki/SHA-512

[19] Качаем архив: https://github.com/kuchumovn/scala_rest_tutorial/zipball/master

[20] Приветствие: http://localhost:8090/захѣшировать

[21] Хешируем наш пароль по алгоритму SHA-512: http://localhost:8090/захѣшировать/SHA/Господи Исѹсе Христе Сыне Божиi помилѹй мѧ грѣшнаго

[22] Хешируем наш пароль по алгоритму Whirlpool: http://localhost:8090/захѣшировать/Whirlpool/Господи Исѹсе Христе Сыне Божиi помилѹй мѧ грѣшнаго

[23] Как писать сборку на Gradle: http://www.gradle.org/docs/current/userguide/userguide.html

[24] Простой REST-сервис на Jersey: http://habrahabr.ru/post/115718/

[25] О Gradle по-русски: http://habrahabr.ru/post/106717/

[26] Что такое Scala и чем она удобна: http://www.scala-lang.org/docu/files/ScalaByExample.pdf

[27] Unfiltered — лёгкий REST фреймворк для Scala: http://unfiltered.databinder.net

[28] Spray — продвинутый REST фреймворк для Scala: https://github.com/spray/spray/wiki