Давайте рассмотрим создание минимального HTTP API Endpoint используя Elixir. Так же, как и Rack в Ruby, Elixir идет в комплекте с Plug. Это универсальный инструмент для работы с HTTP соединениями.



Использование Plug: строим Endpoint HTTP

Во-первых, давайте создадим новый проект Elixir:

$ mix new http_api --sup


Новое Elixir OTP приложение создано. Теперь нужно добавить :cowboy и :plug в виде шестнадцатиричных и примененных зависимостей.

# Измените следующие части в <code>mix.exs</code>

  def application do
    [applications: [:logger, :cowboy, :plug],
     mod: {HttpApi, []}]
  end

  defp deps do
    [
      {:cowboy, "~>1.0.4"},
      {:plug, "~>1.1.0"}
    ]
  end


Plug комплектуется маршрутизатором, который мы можем использовать для простого создания Endpoint HTTP. Давайте создадим модуль, чтобы инкапсулировать маршрутизатор:

# lib/http_api/router.ex
defmodule HttpApi.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/" do
    send_resp(conn, 200, "Hello Plug!")
  end

  match _ do
    send_resp(conn, 404, "Nothing here")
  end
end


Если Вы работали с фреймворками, подобными sinatra, все это покажется Вам знакомым. Можете изучить документацию по маршрутизатору, если Вам любопытно узнать, как это все работает.

Для запуска сервера нужно, чтобы супервизор данного приложения запустил Plug Cowboy адаптер

# lib/http_api.ex

defmodule HttpApi do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # `start_server` function is used to spawn the worker process
      worker(__MODULE__, [], function: :start_server)
    ]
    opts = [strategy: :one_for_one, name: HttpApi.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Start Cowboy server and use our router
  def start_server do
    { :ok, _ } = Plug.Adapters.Cowboy.http HttpApi.Router, []
  end
end


Полный код для приведенного выше примера можно найти здесь. Вы можете запустить сервер с помощью:

$ iex -S mix


Команда запускает интерактивную оболочку Elixir, а также и Ваше приложение на Erlang VM. Теперь можем перейти к более веселой части.

Визуализация использования процессов: observer


В подсказке iex запустите Erlang :observer инструмент, используя эту команду:

iex> :observer.start

Команда открывает GUI интерфейс, который выглядит примерно так:



На левой стороне панели Applications Вы видите список всех приложений, в настоящее время работающих на Erlang VM — это включает наше приложение (http_api) и все его зависимости. Важными для нас являются Cowboy и Ranch.

Cowboy и Ranch


Cowboy — популярный HTTP сервер в мире Erlang. И он использует Ranch — библиотеку Erlang для обработки TCP соединений.
Когда мы запускаем маршрутизатор Plug, то переходим на модуль маршрутизатора для Plug’s Cowboy адаптера. Получая соединение, Cowboy передает его к Plug, а тот, в свою очередь, обрабатывает соединение и посылает запрос обратно.

Параллельные запросы


Plug по умолчанию просит, чтобы Cowboy запустил 100 TCP принимающих соединений Ranch. Вы можете видеть 100 принимающих процессов для себя, если видите график использования приложения в Ranch используя :observer.



Означает ли это, что может быть только 100 параллельных соединений? Давайте узнаем. Мы изменим число получателей к 2, передавая его в качестве параметра для адаптера Plug’s Cowboy:

Plug.Adapters.Cowboy.http HttpApi.Router, [], [acceptors: 2]


Давайте посмотрим, как процессы выглядят теперь:



Хорошо, таким образом у нас есть только 2 принимающих процесса соединения TCP. Давайте попытаемся выполнить 5 длительных параллельных запросов и посмотреть, что из этого получится.

# lib/http_api/router.ex

# Modify router to add some sleep
defmodule HttpApi.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  # Sleep for 100 seconds before sending the reponse
  get "/" do
    :timer.sleep(100000)
    send_resp(conn, 200, "Hello Plug!")
  end

  match _ do
    send_resp(conn, 404, "Nothing here")
  end
end


Теперь давайте выполним 5 запросов, делая это в подсказке iex:

for n <- 1..5, do: spawn(fn -> :httpc.request('http://localhost:4000') end)


Запустите :observer от использования iex :observer.start и посмотрите график процесса:



Мы видим, что все еще есть только 2 принимающих процесса, а 5 других были порождены где-то в другом месте. Это процессы соединения, которые содержат принятые соединения. Что значит — принимающие процессы не диктуют, сколько процессов мы можем принимать за один раз. Нет, они просто ограничивают новые процессов, которые могут быть приняты за один раз. Даже если Вы хотите обслуживать 1000 параллельных запросов, безопасно оставить число принимающих процессов в значении по умолчанию 100.

Итог


Вы можете создать простые конечные точки HTTP, используя Plug маршрутизатор.
Ranch способен обработать многократные соединения TCP за один раз, создавая процессы.
Erlang :observer — отличный способ визуализировать параллелизм в Ваших приложениях.
Получатель обрабатывает только принятые соединения. Из них Вам нужны лишь 100.

APD: Оригинал данного поста доступен тут.

Комментарии (5)