Среду разработки
Netbeans для работы со Scala настроили, пишем и запускаем веб-приложение с фреймворком Unfiltered.
Веб-приложение на Scala с Unfiltered
Весь код нашего простого, но функционального веб-приложения находится в одном файле.
Начало традиционное - пэкэдж, импорты используемых классов, главный класс для текущего файла (object в Scala реализует паттерн "Класс-одиночка", который в Яве принято делать из обычного класса вручную).
ScalaUnfilteredBasicDemo/src/main/scala/edu/nntu/scalaunfiltereddemo/UnfilteredBasicDemo.scala package edu.nntu.scalaunfiltereddemo
import unfiltered.request.Path
import unfiltered.request.Seg
import unfiltered.response.Ok
import unfiltered.response.ContentType
import unfiltered.response.CssContent
import unfiltered.response.HtmlContent
import unfiltered.response.PlainTextContent
import unfiltered.response.ResponseBytes
import unfiltered.response.ResponseString
import unfiltered.response.Html
/**
* Базовый пример Http-сервера с Unfiltered
*
http://unfiltered.databinder.net/Try+Unfiltered.html *
* с нормально поддержкой UTF-8 в строке запроса и в теле ответа.
*
*/
object UnfilteredBasicDemo {
Определим вспомогательный класс для нормальной работы с юникодными символами в адресной строке (да, в 2014 году для этого всё еще приходится использовать специальные костыли, и это касается не только Unfiltered или Scala, послезавтра в 2015 скорее всего тоже ничего не изменится). Заметим, как начинают появляться маленькие фишечки Скалы: импорты можно помещать не только в начале класса, но и внутри вообще любого блока.
/**
* Перекодировщик URL для нормальной работы с UTF-8
* Отсюда:
http://stackoverflow.com/questions/18083311/url-decoding-with-unfiltered */
object Decode {
import java.net.URLDecoder
import java.nio.charset.Charset
trait Extract {
def charset: Charset
def unapply(raw: String) =
scala.util.Try(URLDecoder.decode(raw, charset.name())).toOption
}
object utf8 extends Extract {
val charset = Charset.forName("utf8")
}
}
Главная часть веб-приложения с фреймфорком Unfiltered - фильтр, работающий с сегментами строки URL, которую ввели адресную строку браузера или отправили программно через JavaScript при доступе к веб-приложению. Для разных сегментов веб-приложение возвращает разный результат (простой текст, HTML-страницу, CSS-файл, двоичный контент или что-то еще), собственном в этом и заключается вся его работа: пришел запрос - отправили ответ.
Каждый блок case соответствует одному или нескольким (для регулярных выражений) сегментам URL. В этом примере рассмотрены сегменты (это и есть структура веб-приложения):
- Nil: для корневого сегмента "/" (показываем простой текст с приветствием и инструкциями).
- /plain_text: пример простого текста
- /inline_html: пример кода HTML, встроенного прямо в код Scala
- /static_html: пример статического файла HTML, заргужаемого из ресурсов внутри jar-файла
- /site.css: файл CSS, загружается из ресурсов внутри jar-файла
- /lasto4ka.png и /buggy.png: картинки PNG, загружаются из ресурсов внутри jar-файла
последний блок case принимает любой сегмент и просто возвращает его обратно.
Очевидно, что для разных веб-приложений можно будет произвольно убирать и добавлять новые блоки.
Замечание 1: Подобные HTTP-серверы обычно используют для того, чтобы возвращать обычный текст (или текст в формате JSON) для обслуживая запросов JavaScript/Ajax, но, как видим, для раздачи любого другого веб-контента он вполне подходит, поэтому мы сможем сделать на нем полноценное веб-приложение целиком.
Замечание 2: Читать картинки по байтам из ресурсов архива jar - не самый лучший способ работы с графикой в веб-приложении, в первую очередь из-за производительности. Поэтому для работы с картинками в реальной инсталляции приложения, которая будет подразумевать хоть какую-то нагрузку, стоит использовать более подходящий для этого способ (работа с внешними ресурсами в Jetty или Netty, использовать внешний http-сервер для статического контента или что-то еще). Но мне нравится иметь теоретическую и практическую возможность хранить любые типы ресурсов в одном jar-файле, для персональных экспериментов производительности будет более чем достаточно.
// Для контейнера веб-приложений jetty
val handlePath = unfiltered.filter.Planify {
// Для контейнера веб-приложений netty
// val handlePath = unfiltered.netty.cycle.Planify {
// список определений доступных XXXContent:
//
https://github.com/unfiltered/unfiltered/blob/master/library/src/main/scala/response/types.scala // начальная страница (корень сайта)
case Path(Seg(Nil)) =>
Ok ~> PlainTextContent ~> ResponseString("Демонстрация работы фреймворка "
+ "Unfiltered для языка Scala, попробуйте сегменты в строке адреса: "
+ "plain_text, inline_html, static_html, site.css, lasto4ka.png, buggy.png")
// простой текст
case Path(Seg("plain_text" :: Nil)) =>
// PlainTextContent добавит заголовок Content-Type с UTF-8:
Ok ~> PlainTextContent ~> ResponseString("Это простой текст от сервера")
// Можно и так, но тогда браузер не распознает UTF-8 строки:
// Ok ~> ResponseString("Это простой текст от сервера" )
// демо html-страницы, встроенной в код scala
case Path(Seg("inline_html" :: Nil)) =>
Ok ~> HtmlContent ~> Html(
Scala Unfiltered framework basic demotitle>
head>
Демонстрация работы фреймворка Unfiltered для языка Scala:
это HTML-страница, встроенная в код файла scala.
div>
body>
html>)
// статическая html-страница, загружается из ресурсов
case Path(Seg("static_html" :: Nil)) =>
Ok ~> HtmlContent ~> ResponseString(
scala.io.Source.fromInputStream(getClass.getResourceAsStream("/html/static_html.html"),
"UTF-8").mkString)
/**************************/
// Ниже для интереса попробуем отдавать вручную разные ресурсы (css, картинки),
// но лучше для них использовать возможности контейнеров Jetty/Netty или
// внешний веб-сервер для раздачи статического контента типа nginx
// css-файл со стилем страницы, загружается из ресурсов
case Path(Seg("site.css" :: Nil)) =>
Ok ~> CssContent ~> ResponseString(
scala.io.Source.fromInputStream(
getClass.getResourceAsStream("/public/site.css"), "UTF-8").mkString)
// картинка, загружается из ресурсов
case Path(Seg("lasto4ka.png" :: Nil)) =>
val in = getClass.getResourceAsStream("/public/lasto4ka.png")
// так в некоторых случаях может не сработать - за один раз будет считан
// не весь файл, а только часть, поэтому придется через ByteArrayOutputStream
// (подробности:
https://groups.google.com/forum/#!topic/unfiltered-scala/czRtn5Vnoug)
// val bytes = new Array[Byte](in.available)
// in.read(bytes)
val buffer = new java.io.ByteArrayOutputStream()
val data = new Array[Byte](16384)
var nRead = in.read(data, 0, data.length)
while (nRead != -1) {
buffer.write(data, 0, nRead)
nRead = in.read(data, 0, data.length)
}
buffer.flush()
val bytes = buffer.toByteArray();
// Предопределенных классов Content для картинок в стандартной
// поставке не нашлось, добавим свои:
object JpegImageContent extends ContentType("image/jpeg")
object PngImageContent extends ContentType("image/png")
Ok ~> PngImageContent ~> ResponseBytes(bytes)
// другая картинка, загружается из ресурсов
case Path(Seg("buggy.png" :: Nil)) =>
val in = getClass.getResourceAsStream("/public/buggy.png")
// так в некоторых случаях может не сработать - за один раз будет считан
// не весь файл, а только часть, поэтому придется через ByteArrayOutputStream
// (подробности:
https://groups.google.com/forum/#!topic/unfiltered-scala/czRtn5Vnoug)
// val bytes = new Array[Byte](in.available)
// in.read(bytes)
val buffer = new java.io.ByteArrayOutputStream()
val data = new Array[Byte](16384)
var nRead = in.read(data, 0, data.length)
while (nRead != -1) {
buffer.write(data, 0, nRead)
nRead = in.read(data, 0, data.length)
}
buffer.flush()
val bytes = buffer.toByteArray();
// Предопределенных классов Content для картинок в стандартной
// поставке не нашлось, добавим свои:
object JpegImageContent extends ContentType("image/jpeg")
object PngImageContent extends ContentType("image/png")
Ok ~> PngImageContent ~> ResponseBytes(bytes)
/**************************/
// работа с сегментами напрямую (убрать при работе с внешними ресурсами,
// подробности:
https://groups.google.com/forum/#!topic/unfiltered-scala/czRtn5Vnoug)
case Path(Decode.utf8(Seg(path :: Nil))) =>
Ok ~> PlainTextContent ~> ResponseString("Ваш сегмент: " + path)
}
Запуск приложения - метод main, запускает встроенный HTTP-сервер Jetty на порте 8080 и развертывает внутри него веб-приложение
Замечание: Unfiltered умеет работать с двумя встроенными HTTP-серверами: Jetty и Netty. Для простых приложений не важно, какой из них использовать, различия могут проявиться под нагрузкой или при возникновении потребности в каких-то специфических возможностях.
def main(args: Array[String]) {
// Запустить контейнер веб-приложенией Jetty
println("Starting Scala Unfiltered web framework demo on Jetty http server...")
unfiltered.jetty.Http.apply(8080).plan(handlePath).run()
// println("Resources dir: " + getClass.getResource("/public"))
// unfiltered.jetty.Http.apply(8080).resources(getClass.getResource("/public")).plan(handlePath).run()
// Запустить контейнер веб-приложенией Netty
// println("Starting Scala Unfiltered web framework demo on Netty http server...")
// unfiltered.netty.Http(8080).plan(handlePath).run()
}
Запускаем, проверяем
1) Компилируем приложение (правой кнопкой на проекте, меню "Собрать" или "Очистить и собрать")
2) Запускаем приложение (правой кнопкой внутри открытого UnfilteredBasicDemo.scala, меню "Отладка файла")
3) Следим за сообщениями в окне "Отладка": видим, что HTTP-сервер запущен на порте 8080
4) Открываем любимый браузер и вводим адрес
http://localhost:8080/ : видим сообщение для корневого сегмента (блок Nil).
Пробуем другие сегменты,
http://localhost:8080/static_html : грузит HTML из ресурсов из файла
static_html.html (здесь же грузятся картинки
buggy.png и
lasto4ka.png и CSS
site.css)
http://localhost:8080/plain_text : обычный текст
Всё работает.
Попробуем немного JavaScript и Аякса
Основа веб-интерфейсов AJAX - это фоновый обмен данными между страницей HTML и HTTP-сервером через JavaScript. Посмотрим, как это работает.
Скачаем рядом другой проект:
snippets/scala-web/ScalaUnfilteredAjaxDemo Структура веб-приложения еще проще: одна статическая страница HTML ajax_demo и два текстовых вызова call1 и call2.
ScalaUnfilteredAjaxDemo/src/main/scala/edu/nntu/scalaunfiltereddemo/UnfilteredAjaxDemo.scala package edu.nntu.scalaunfiltereddemo
import unfiltered.request.Path
import unfiltered.request.Seg
import unfiltered.response.Ok
import unfiltered.response.HtmlContent
import unfiltered.response.PlainTextContent
import unfiltered.response.ResponseString
import unfiltered.response.Html
/**
* Базовый пример Http-сервера с Unfiltered
*
http://unfiltered.databinder.net/Try+Unfiltered.html *
* с нормально поддержкой UTF-8 в строке запроса и в теле ответа.
*
*/
object UnfilteredAjaxDemo {
val handlePath = unfiltered.filter.Planify {
// статическая html-страница со вставками Ajax, загружается из ресурсов
case Path(Seg("ajax_demo" :: Nil)) =>
Ok ~> HtmlContent ~> ResponseString(
scala.io.Source.fromInputStream(getClass.getResourceAsStream("/html/ajax_demo.html"), "UTF-8").mkString)
// rest-вызов 1: возвращает простой текст
case Path(Seg("call1" :: Nil)) =>
Ok ~> PlainTextContent ~> ResponseString("Это текст от сервера - результат вызова 1")
// rest-вызов 2: возвращает простой текст
case Path(Seg("call2" :: Nil)) =>
Ok ~> PlainTextContent ~> ResponseString("Это текст от сервера - результат вызова 2")
}
def main(args: Array[String]) {
println("Starting jetty http server demo...")
// Запустить веб-сервер
unfiltered.jetty.Http.apply(8080).filter(handlePath).run()
}
}
В страницу HTML встроен простой код JavaScript, который при нажатии ссылок "Прочитать с сервера значение 1" и "Прочитать с сервера значение 2" делает фоновые HTTP-запросы call1 и call2, результат прописывает в элемент "call_res"
ScalaUnfilteredAjaxDemo/src/main/resources/html/ajax_demo.html Scala Unfiltered framework basic demo
charset="UTF-8"/>
Демонстрация работы фреймворка Unfiltered для языка Scala:
это HTML-страница с демонстрацией Ajax.
href="javascript:call1()">Прочитать с сервера значение 1,
href="javascript:call2()">прочитать с сервера значение 2
Результат: id="call_res" style="font-style: italic">
Компилируем, запускаем, открываем в браузере http://localhost:8080/ajax_demo, кликаем на ссылки,
видим меняющийся результат.
Третья часть о том, как развернуть веб-приложение в облаке.
исходники занятия, подсветка синтаксиса.
облако,
типовые задачи,
сервер роботов