Статьи

Быстрые и масштабируемые веб-приложения Clojure Ring с Comsat

Как уже знают многие клоюристы, минималистичная лаконичность и ясность Клоюра могут принести пользу производительности и обслуживания для повседневных задач разработки, а также эстетическое блаженство и даже радость для нашей работы; Благодаря  Pulsar  вы также можете наслаждаться высокой производительностью волокон и улучшенной абстракцией параллелизма в традиционных приложениях Clojure.  Недавно был выпущен Comsat 0.3.0, который также предоставляет возможности волокон для веб-разработки Ring: давайте кратко рассмотрим, что возможно.

Кольцо, в ближайшее время

В то время как  рассекает API сервлетов  мы уже имели освежающий взгляд на  кольцо  от моделирования и API точки зрения дизайна. Кольцо давно стало основой выбора для большинства веб-приложений и фреймворков Clojure.

На самом низком уровне веб-приложение Ring — это просто  обработчик , то есть функция, преобразующая карту, представляющую запрос HTTP, в карту, представляющую ответ HTTP.

Поскольку необходимость выполнения предварительной и последующей обработки запросов является очень распространенной, Ring предлагает использовать  промежуточное программное обеспечение , то есть функции высшего порядка, превращающие обработчик в новый обработчик с дополнительной логикой. Ring уже предоставляет некоторые промежуточные программы, например, для обслуживания файлов и ресурсов classpath или для обогащения карты запросов информацией из нескольких частей. Конечно, промежуточное программное обеспечение может быть  связано  в определенном порядке посредством обычной функциональной композиции, потому что то, что мы получаем, применяя промежуточное программное обеспечение к обработчику, является другим обработчиком, к которому затем может быть применено еще несколько промежуточных программ.

Наконец, Ring предоставляет на основе Jetty HTTP-  адаптер  для обработчиков. Помимо Ring есть несколько других адаптеров, но обычно любой из них будет функцией, получающей обработчик в качестве основного входа и карту дополнительных опций (например, для прослушивающих интерфейсов и портов, размеров пула потоков и т. Д.), Которые будут запускать бесконечное обслуживание HTTP. петля.

Основы: вставка волоконно-оптического адаптера Comsat

Оптоволоконный адаптер Comsat Ring основан на Jetty 9 и опирается на поддержку Servlet Async (доступно с Servlet 3.0). Для каждого запроса он порождает новое волокно и немедленно возвращается, так что дорогие серверные потоки освобождаются как можно скорее; волокно выполнит обработчик и передаст ответ позже, когда обработка и построение ответа будут завершены.

Волокна Comsat Ring порождается через Pulsar, поэтому обработчик и все вокруг применяются промежуточное программной она должна быть  суспендируемыми : это можно сделать легко с помощью либо пульсар  sfn /  defsfn макросов или  suspendable! функции. В качестве вежливости адаптер сделает это за вас в последнем обработчике, который передается.

Давайте перенесем следующее простое веб-приложение «Hello World» Ring на адаптер блокировки волокна:

(ns myapp
  (:use ring.adapter.jetty))

(defn- hello-world [request]
  (Thread/sleep 100)
  {:status  200
   :headers {"Content-Type" "text/plain"}
   :body    "Hello World"})

(defn run [] (run-jetty hello-world {:port 8080}))`

Окончательный файл проекта Leiningen  project.clj будет выглядеть примерно так: мы включаем его  co.paralleluniverse/comsat-ring-jetty9 в качестве зависимости,  ring/ring-jetty-adapter а не настраиваем инструмент инструментария Quasar для запуска:

(defproject myapp "0.1.0-SNAPSHOT"
            :description "Comsat Ring Hello World example."
            :min-lein-version "2.4.3"

            :dependencies
            [[org.clojure/clojure "1.6.0"]

             [co.paralleluniverse/comsat-ring-jetty9 "0.3.0"]]

            :main myapp.core/run

            :java-agents [[co.paralleluniverse/quasar-core "0.6.2"]])

Прежде всего, немного измените ваши предложения use / require для использования адаптера блокировки волокна и объявите обработчик приостановленным; затем измените ваш режим блокировки потока на блокирующий волокно, просто чтобы убедиться, что обработчик действительно работает внутри волокна:

(ns myapp.core
  (:use co.paralleluniverse.fiber.ring.jetty9)
  (:require [co.paralleluniverse.pulsar.core :refer [sfn defsfn suspendable!]])
  (:import (co.paralleluniverse.fibers Fiber)))

(defsfn hello-world [request]
  (Fiber/sleep 1000)
  {:status  200
   :headers {"Content-Type" "text/plain"}
   :body    "Hello World"})

(defn run [] (run-jetty hello-world {:port 8080}))

Это так просто: обработчик вашего существующего приложения теперь работает внутри эффективных волокон, а не потребляет дорогие потоки. lein run будет обслуживать наш  Hello World через  порт 8080.

Применение промежуточного программного обеспечения

Давайте сделаем небольшой шаг вперед: что, если мы хотим применить промежуточное ПО? Это очень просто: мы собираемся использовать обычный  многопоточный макрос ->  с дополнительной заботой о том, чтобы сделать приостановленным результат каждого приложения промежуточного программного обеспечения, чтобы  run-jetty запускать обогащенный обработчик, а не основной. Вот пример:

(ns myapp.core
  (:use co.paralleluniverse.fiber.ring.jetty9
        ring.middleware.file)
  (:require [co.paralleluniverse.pulsar.core :refer [sfn defsfn suspendable!]])
  (:import (co.paralleluniverse.fibers Fiber)))

(defn- fiber-sleep-middleware [h] #(do (Fiber/sleep 1000) ((suspendable! h) %)))

(defsfn hello-world [request]
  {:status  200
   :headers {"Content-Type" "text/plain"}
   :body "Hello World"})

(defn run [] (run-jetty
               (-> hello-world
                   fiber-sleep-middleware
                   (wrap-file "public")
                   fiber-sleep-middleware)
               {:port 8080}))

На самом деле есть еще несколько изменений, чем строго необходимо:

  • Fiber/sleep Вызов теперь часть промежуточного слоя , который будет выполнять его перед вызовом обработчика, поэтому мы можем применить его вокруг любого из них. Кроме того, это сделает обработчик приостановленным, если это еще не сделано.
  • run-jetty оборачивает обработчик нашим  fiber-sleep-middleware, затем промежуточным программным обеспечением Ring, которое будет пытаться обслуживать статические файлы из  public каталога в нашем проекте (и делегировать внутреннему обработчику, если он не сможет их найти), затем снова с  fiber-sleep-middleware.

Давайте теперь добавим любой файл в наш  public каталог, например, некоторые  testPage.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
  </head>
  <body>
    <h1>Static test page!</h1>
  </body>
</html>

Когда мы просматриваем  http: // localhost: 8080,  через пару секунд мы получаем наш красивый, блокирующий волокно, динамически генерируемый текст «Hello World», поскольку в этом случае все слои промежуточного программного обеспечения пересекаются; если вместо этого нажать  http: // localhost: 8080 / testPage.html , промежуточное ПО для обслуживания файлов возьмет на себя и мы получим нашу «Статическую тестовую страницу!» title через одну секунду, так как внутренние обработчики больше не будут вызываться.

Использование сохраняющих кольцо структур маршрутизации

Некоторые микро-фреймворки предоставляют удобные средства, такие как маршрутизация, но при этом полностью используют концепции Ring. Например,  Mustache предоставляет только один макрос,  appкоторый будет использоваться в качестве препроцессора маршрутизации с существующими обработчиками Ring.

Этот  шаблон Leinigen  предоставляет отправную точку на основе Comsat-Ring и Mustache на стороне сервера, в то время как сторона браузера использует ClojureScriptcore.async  и  Om .

Давайте посмотрим на основной серверный модуль:

(ns ring-sample.core
    (:use co.paralleluniverse.fiber.ring.jetty9)
    (:require
      [co.paralleluniverse.pulsar.core :refer [sfn defsfn suspendable!]]
      [ring.middleware.json :as midjson]
      [ring.middleware.resource :as midres]
      [net.cgrand.moustache :as moustache]
      [ring.util.response :as ringres])
    (:import (co.paralleluniverse.fibers Fiber)))

(def ^:private app-routes
  (moustache/app
    [] (sfn [_] (Fiber/sleep 100) (ringres/resource-response "index.html" {:root "public"}))
    ["widgets"] (sfn [_] (Fiber/sleep 100) (ringres/response [{:name "Widget 1"} {:name "Widget 2"}]))))

(def app
  (-> app-routes
      suspendable!
      (midres/wrap-resource "/public")
      suspendable!
      (midjson/wrap-json-body)
      suspendable!
      (midjson/wrap-json-response)))

(defn run [] (run-jetty app {:port 8080}))

moustache/app Макрос строит новый обработчик Ring , который будет маршрут тэ запрос на другие функции , основываясь на его URL, в этом случае дифференцирующего между корнем и «виджетами» путем. Мы просто определяем анонимные однопутевые обработчики как приостанавливаемые, а затем гарантируем, что те, которые созданы Mustache и обогащены промежуточным программным обеспечением, также являются приостановленными.

Интеграция других веб-фреймворков Clojure

Другие структуры на стороне сервера Clojure часто в конечном итоге создание Ring-совместимых обработчиков , но обеспечивают разработчику абстракций, которые отличаются от кольца, таких как  Compojure «s  маршрутов .

Using these frameworks in fiber-blocking mode may require some more work, as they can stack additional calls on top of the user-provided logic, all of which need to be made suspendable too. This can be as easy as adding a few suspendable! statements but in other cases some more tweaking is needed as these calls can be inaccessible (e.g. anonymous functions). Compojure itself is a very popular choice but at present it is not supported out-of-the-box.

Pulsar’s automatic suspendables in the works

We have just realized that explicitly declaring suspendables can be as easy as adding a few statements or it can a bit trickier, especially when dealing with third-party libraries.

Luckily automatic instrumentation is in the works as part of Pulsar: this basically means that the only needed change will be replacing the call to your previous Ring adapter with a call to Comsat’s and your web application will then fly on high-performance fibers rather than running on threads. It will no longer be necessary to declare your functions (or protocols) as suspendable – any Clojure code will work seemlessly with fibers. If this sounds intriguing, stay tuned.

Enjoying both bliss and top-notch performance

To me, Parallel Universe’s stack together with Clojure is a developer’s dream coming true. Beauty and expression power finally don’t fight anymore with performance and scalability; on the contrary Quasar, Pulsar and Comsat bring unprecedented efficiency and best-of-breed scalability abstractions to the JVM at large and specifically to Clojure.

I hope you enjoyed having a quick taste of what’s possible; soon we’ll be publishing deeper explorations and more tutorials. In the meanwhile enjoy a highly maintainable, efficient, scalable and, last but not least, joyful Clojure coding experience with the Parallel Universe stack.