Давайте рассмотрим создание минимального 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.
Автор: ua-hosting.company