Страничка курса: https://maxcom.github.io/scala-course-2020/
Основные источники асинхронности в JVM:
Протоколы, в которых клиент открывает tcp соединение с сервером и они обмениваются запросами/сообщениями.
Это HTTP, SMTP, XMPP и многие другие.
private val executor: Executor = 
    Executors.newCachedThreadPool()
val serverSocket: ServerSocket = new ServerSocket(2000)
val greetings: Array[Byte] = 
  "Hello, world!".getBytes(US_ASCII)
while (true) { // упрощенный вариант сервера
  val socket: Socket = serverSocket.accept()
  executor.execute { () ⇒
    // блокирующийся ввод-вывод
    socket.getOutputStream.write(greetings)
    socket.close()
  }
}упрощенная схема реализации
"Пассивные" клиенты занимают потоки:
Не предполагает распараллеливания обработки одного запроса
Трудности с обработкой таймаутов и fallback.
Можно ли обработать всех клиентов в одном потоке?
Да, с использованием мультиплексированного ввода вывода.
Такая архитектура показала свою эффективность.
Например:
Java NIO - низкоуровневые API для ввода-вывода.
"New IO" - "новый" API (2002 год).
Дополняется в каждой новой версии JDK.
Java IO - потоковые чтение/запись
Java NIO - блочные чтение/запись
Блочный I/O оптимальнее потокового, он избегает лишних копирований
					и преобразований данных.*
					* ByteBuffer - кошмар разработчика.
					
Нам NIO интересен тем, что только в нем есть неблокирующий ввод-вывод.
socketChannel.configureBlocking(false)
Блокирущий read: читает сколько может;
 если данных нет - ждет 
					
Неблокирующий read: мгновенно читает сколько может; нет данных - пустой результат
Блокирующий write: пишет всё что передали; если клиент не успевает - ждет и продолжает
Неблокирующий write: мгновенно пишет сколько может; если клиент не готов - ничего не пишет
  executor.execute { () ⇒
    val buffer: ByteBuffer = ByteBuffer.wrap(greetings)
    while (buffer.hasRemaining) { // busy wait - плохая идея
      socket.write(buffer)
    }
    socket.close()
  }
val selector: Selector = Selector.open()
socket.configureBlocking(false)
val key = socket.register(selector, SelectionKey.OP_WRITE)
while (buffer.hasRemaining) {
  selector.select() // блокируется
  if (key.isWritable) {
    socket.write(buffer)
  }
}
Вернулись к блокировкам?
val selector: Selector = Selector.open()
// можно зарегистрировать много сокетов
serverSocket.register(selector, SelectionKey.OP_ACCEPT, 
  () -> println("Got new connection"))
while (true) {
  selector.select()
  val selectionKeys = selector.selectedKeys.asScala
  selectionKeys.foreach { sk ⇒
    sk.attachment().asInstanceOf[Runnable].run()
  }
}
Итого:
Сложности?
Запутанная логика чтения - нарезка потока от клиента на произвольные блоки.
Запись только по готовности клиента
Запутанная машина состояний TLS
Обычно не проблема - программист работает или с готовым сервером, или с framework (netty, akka-io и т.п.)
(пример реализации)
Раньше наши обработчики работали
 прямо в нашем потоке.
					
Сделаем их функциями 
 Unit ⇒ Future[Action]
					
Action - кодирует следующую операцию над сокетом
selectionKeys.foreach { sk ⇒
  sk.interestOps(0) // не ждем новых событий
  sk.attachment().asInstanceOf[() ⇒ Future[Action]].apply()
}
 // неблокирующаяся очередь, построенная на CAS операциях
val queue = new ConcurrentLinkedQueue[Runnable]
val executor: Executor = task ⇒ queue.add(task)
var task: Runnable = _
while ({ task = queue.poll(); task } != null) {
  task.run()
}
val reactorExecutor = ExecutionContext.fromExecutor { r ⇒
  queue.add(r)
  selector.wakeup()
}
selectionKeys.foreach { sk ⇒
  sk.interestOps(0)
  sk.attachment()
    .asInstanceOf[() ⇒ Future[Action]]
    .apply()
    .onComplete(processAction)(reactorExecutor)
}
Итого мы получили:
Все современные сервера на JVM реализуют комбинированный подход.
Похожий event-loop используется в клиентах к веб-сервисам и к некоторым СУБД.
Функция выполнения запроса возвращает Future, которая обрабатывается в event loop клиента.
Протокол передачи данных, придуманный для Web.
Придуман для "классических" сайтов:
Позже эволюционировали в JavaScript-приложения
HTTP используется для взаимодействия сервисов между собой.
Две "живых" версии стандарта:
Будем говорить про HTTP/1.1
Схема "запрос-ответ".
Сервер ожидает соединений клиентов.
Сервер получает запрос и формирует ответ.
В одном соединении запросы обрабатываются последовательно.
Клиент может открыть несколько соединений к серверу (браузеры - до 6).
Запрос состоит из:
Ответ состоит из
Строка запроса
GET /scala-course-2020/slides/day7.html HTTP/1.1
					Метод, путь ресурса и версия протокола
Путь - "адрес" ресурса на сервере;
"метод" - действие над ресурсом
				
Основные методы:
Всего около 20 методов, если включить все расширения
Заголовки запроса:
Host: localhost:9000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
If-Modified-Since: Sun, 25 Mar 2018 07:25:21 GMT
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
					Переносы строк запрещены в современном HTTP
Тело запроса: данные, передаваемые на сервер
Тело - данные запроса, поля формы, загружаемые данные и т.п.
Тело может быть почти у любого метода, даже у GET (см. RFC 7231, раздел 4.3.1).
Два режима передачи тела запроса:
 известной длины и неизвестной (chunked).
				
Строка ответа
HTTP/1.1 200 OK
					версия протокола и статус ответа
Статус - трехзначный код плюс его описание
1xx - информационные коды
2xx - успешные коды
3xx - перенаправления
4xx - ошибки клиента
5xx - ошибки сервера
Хороший справочник по кодам - в английской википедии.
Заголовки ответа
Server: QRATOR
Date: Sun, 25 Mar 2018 08:57:43 GMT
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=15
Set-Cookie: CSRF_TOKEN="GjxRyqELgF6WoDWtvm3dMQ=="; Version=1; Max-Age=64281600; Expires=Tue, 07-Apr-2020 08:57:43 GMT; Path=/
Cache-Control: private
Strict-Transport-Security: max-age=7776000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Expires: Sat, 24 Mar 2018 12:57:43 GMT
Last-Modified: Sun, 25 Mar 2018 08:57:41 GMT
Set-Cookie: JSESSIONID=1485581EB69CB469B9E766D8DCEF4652; Path=/; Secure; HttpOnly
					Тело ответа - содержимое ресурса
План задания
Как строить API поверх HTTP?
Можно инкапсулировать протокол поверх HTTP.
Но это сложно и не позволяет использовать полезные свойства протокола.
REST - методика создания API поверх протокола HTTP
"REST" как функционально программирование - его все любят, но никто точно не знает что это такое.
Подробнее рассмотрим на примере Play Framework
Современный "легкий" framework для создания сайтов и веб-приложений, альтернатива традиционным Java-фреймворкам.
Никаких сервлетов, "контейнеров" и т.п.
Быстрый асинхронный веб-сервер на базе Netty или Akka-HTTP.
плюс опциональные
 компоненты:
					
Play подходит для "классических" сайтов, для разработки JavaScript-приложений, и для разработки сервисов.
 
				sbt-web - сборка JavaScript приложения
 средствами sbt
					
webjars - управление зависимостями JavaScript
Добавляем Play в готовый проект
(создать проект с "нуля" проще - см. документацию)
Выбираем версию sbt: project/build.properties
sbt.version=1.3.8
Создаем project/plugins.sbt
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.0")
Включаем Play и используем стандартные пути файлов:
libraryDependencies += guice
enablePlugins(PlayScala)
disablePlugins(PlayLayoutPlugin)
PlayKeys.playMonitoredFiles ++= 
  (sourceDirectories in 
    (Compile, TwirlKeys.compileTemplates)).value
build.sbt
Создаем пустой конфиг
 src/main/resources/application.conf
				
Отключаем фильтр по имени хоста в application.conf:
play.filters.disabled+=play.filters.hosts.AllowedHostsFilter
В демо-проекте он нам не нужен,
 в реальной жизни его
					нужно настроить
				
На этом этапе можно уже запустить веб-сервер:
sbt run
в терминале или через меню в IDEA
Сервер запускается в режиме разработчика
					http://localhost:9000/
					
при изменениях сервер не нужно перезапускать:
 изменения
					автоматически подхватываются
				
Hello, world!
					src/main/scala/controllers/HelloWorld.scala
package controllers
import javax.inject.Inject
import play.api.mvc.{AbstractController, ControllerComponents}
class HelloWorld @Inject()(cc: ControllerComponents) 
    extends AbstractController(cc) {
  def hello() = Action {
    Ok("Hello, world!") // просто text/plain
  }
}
Таблица роутинга запросов:
					src/main/resources/routes
GET         /             controllers.HelloWorld.hello
Action может быть асинхронным:
class HelloWorld @Inject()(cc: ControllerComponents) 
    extends AbstractController(cc) {
  implicit val ec: ExecutionContext = defaultExecutionContext
  def hello() = Action.async {
    Future {
      Ok("Hello, world!")
    }
  }
}
Java-сервлеты научились быть асинхронными через 10 лет после появления первой версии.
И еще 3 года появился асинхронный ввод-вывод.
Вернемся к REST:
					URI должен указывать на объект на сервере
					
Например профиль пользователя admin
					/users/admin
				
Действия над пользователями:
В Play роутинг может быть такой:
GET    /users         controllers.Users.users()
POST   /users         controllers.Users.createUser()
GET    /users/:login  controllers.Users.getAdmin(login)
PUT    /users/:login  controllers.Users.setAdmin(login)
PATCH  /users/:login  controllers.Users.updateAdmin(login)
DELETE /users/:login  controllers.Users.dropAdmin(login)
Коды HTTP классифицируют ошибки
					(подробности - в теле ответа)
				
Пример
Ошибки клиента
Ошибки сервера
Будем делать "классический" сайт, а не приложение.
(не будем писать код на front-end)
Задание:
Сделаем HTML форму для ввода текста.
Обработчик вернет класс текста (позитив/негатив) и исходный текст с диагностикой.
Используем HTML шаблоны Twirl
src/main/twirl/views/form.scala.html
@(text: Option[String], category: Option[String], 
        debug: Option[String])
Классификатор
@if(category.nonEmpty) {
    Результат: @category
    
    @debug
}В контроллере возвращаем HTML:
def showForm() = Action {
  Ok(views.html.form(None, None, None))
}
					Данные формы при методе GET передаются параметрами в URL
В функцию обработки запроса нужно добавить параметр, Play передаст туда значение из URL.
Страшный "программистский" дизайн?
Можно легко исправить
(опционально, если есть желание)
Используем готовый CSS шаблон
Популярный вариант: Bootstrap
Его можно подключить как зависимость в sbt
Пример - на сайте webjars
Использование - в документации на Bootstrap
Напоминаю: