Pull to refresh

Пишем простейший REST web-сервис на Scala

Reading time5 min
Views18K
У меня есть сайт, написанный на Node.js, и иногда там требуется сделать что-то, для чего Node.js не предназначен: например, произвести какие-нибудь математические вычисления.

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

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

Инструментарий


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

Я немного писал на Ruby (не на «Рельсах»), с удовольствием пользуясь фреймворком Sinatra. И на Node.js пользуюсь клоном Sinatra по имени Express. Выяснилось, что и для Scala тоже написан соответствующий клон — Scalatra. Её мы и будем использовать.

Популярные Rest «фреймворки» для Scala


Существует также несколько признанных легковесных Rest «фреймворков» для Scala:

  • Spray — суперсовременный, быстрый, масштабируемый «фреймворк», основанный на Akka (Эрланг для Scala), который умеет всё
  • Blue Eyes — общепризнанный предшественник Spray
  • Unfiltered — очень маленький и самобытный «фреймворк», похожий по функциональности на Sinatra


Эти «фреймворки» были предложены мне пользователем btd в комментариях. Однако ни один из них мне не понравился — слишком там всё замудрёно, как и в самом языке Scala. Мне по душе что-нибудь попроще… Отчасти поэтому многие могут негодовать, что здесь я пишу не на Scala, а на Яве в синтаксисе Scala. Не спорю, мне больше по душе Яваскрипт, и Scala как раз даёт мне тот подход «функция есть объект», которого так не хватало Яве в сравнении с Яваскриптом.

Как всё это дело запускать


Если по дзен-буддистски, то достаточно двух файлов: build.sh вида “javac -d classes *.java; scalac -classpath […] -d classes *.scala” и run.sh вида “scala -classpath […] -Dпорт=8090 Main”.

Однако лучше использовать «сборщика», чтобы не возиться с масками имён файлов, и с длинным classpath в одну строку: правильный сборщик (не Maven) даст вам гораздо больше свободы (и читаемости), чем shell. Он сам за вас скачает все используемые библиотеки, и сам добавит каждый Jar’ник поимённо в classpath (и вам не придётся делать это руками).

На должность сборщика мы возьмём молодой (вот-вот выйдет версия 1.0) и развивающийся проект Gradle, который мне больше напоминает старого доброго Ant’а, чем Maven’а, в том, что не запирает разработчика в жёсткие рамки, а, наоборот, даёт полную свободу творчества, да ещё и на адекватном языке Groovy (клон Ruby в мире Явы).

Установка


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

* Возможно ещё понадобится натравить системную переменную JAVA_HOME на папку с JDK, и прописать bin в PATH.

Качаем 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'

// основная задача — запускает наш веб-сервис
// сначала запускает «compileJava» и «compileScala», а потом уже выполняется сама
task go(dependsOn: ['compileJava', 'compileScala'], type: JavaExec) {
	main = 'Main'
	classpath = sourceSets.main.runtimeClasspath
	standardInput = System.in
	systemProperty 'package', 'web'
	systemProperty 'port', '8090'
}

// наборы исходных кодов
sourceSets
{
	// главный
	main
	{
		java
		{
			// исходники Явы искать в папке «sources»
			srcDir 'sources'
		}
		scala
		{
			// исходники 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
	
	// прочие библиотеки, используемые в программе
	
	compile group: 'org.scalatra', name: 'scalatra_2.9.1', version: '2.1.0.M1'
	runtime group: 'org.scalatra', name: 'scalatra_2.9.1', version: '2.1.0.M1'
			
	compile group: 'org.mortbay.jetty', name: 'jetty', version: '7.0.0.pre5'
	runtime group: 'org.mortbay.jetty', name: 'jetty', version: '7.0.0.pre5'
		
	compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
            
	// до кучи можно просто класть jar-ники в папку «libraries», 
	// и они тоже подхватятся в качестве библиотек
	
	compile fileTree(dir: 'libraries', include: '*.jar')
	runtime fileTree(dir: 'libraries', include: '*.jar')
}

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


Теперь собственно наш REST web-сервис, дающий возможность захешировать пароль на Whirlpool или SHA-512Hasher.scala

package web

import hash.Whirlpool
import hash.SHA

class Hasher extends tools.WebService 
{
	def path = "/захѣшировать"
	
	get("/")
	{
		"Доступные алгоритмы: Whirlpool, SHA"
	}

	get("/Whirlpool/:what") 
	{
		val что = params("what")
		json("что" -> что, "хѣш" -> Whirlpool.hash(что))
	}

	get("/SHA/:what") 
	{
		val что = params("what")
		json("что" -> что, "хѣш" -> SHA.hash(что))
	}
}


И написанный мною пускатель веб-сервера (запускает все веб-сервисы из пакета «package», заданного в build.gradle) — Main.scala

import org.mortbay.jetty.Server
import org.mortbay.jetty.servlet.{Context, ServletHolder}

import tools._

object Main extends App
{
	// создаём Jetty
	val server = new Server(System.getProperty("port").toInt)
	val root = new Context(server, "/", Context.SESSIONS)
	
	// в каком пакете искать наши Rest веб-сервисы
	val package_name : String = System.getProperty("package")
	
	// каждый класс ScalatraServlet из этого пакета (и подпакетов) "замапить" на свой путь.
	// путь определяется методом "path" этого класса.
	for (handler <- PackageScanner.getClasses(package_name) if classOf[org.scalatra.ScalatraServlet].isAssignableFrom(handler))
	{
		// создаём сервлет из этого класса
		val servlet = handler.newInstance().asInstanceOf[javax.servlet.Servlet]
		
		// на какой путь "замапим" сервлет
		try
		{
			// получаем это из метода path()
			val path = handler.getMethod("path").invoke(servlet).toString
			
			// "мапим" сервлет на этот путь в Jetty
			root.addServlet(new ServletHolder(servlet), path + "/*")
			
			println(handler.getPackage().getName() + "." + handler.getName() + " is mapped to " + path)
		}
		catch 
		{
			// не написан метод path() у сервлета
			case error: NoSuchMethodException => 
				throw new RuntimeException("Method path() not found in class " + handler.getPackage().getName() + "." + handler.getName())
		}
	}
	
	// запускаем Jetty
	server.start()
	server.join()
}


Запускаем


Качаем архив, распаковываем, заходим в папку и выполняем команду gradle go. При успешном выполнении вы узреете в консоли запуск веб-сервера:

gradle go
:compileJava
:compileScala
:processResources UP-TO-DATE
:classes
:go
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further detail
> Building > :go


Проверяем


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

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


Scalatra
Как писать сборку на Gradle
Простой REST-сервис на Jersey
О Gradle по-русски
Что такое Scala и чем она удобна
Unfiltered — лёгкий REST фреймворк для Scala
Spray — продвинутый REST фреймворк для Scala
Blue Eyes — тоже продвинутый REST фреймворк для Scala
Tags:
Hubs:
+4
Comments27

Articles