如何在没有任何框架的情况下在Elixir上实现JSON API端点?
来自翻译者:
本文提供了一个非常简单的Web应用程序示例,可以将其视为Hello,World!。 在Elixir上创建最简单的API。
为了匹配库的当前版本,对示例代码进行了一些修改。
带有更改的完整示例代码可以在GitHub上看到 。

新语言挑战
许多开发人员来自Ruby世界。 就可用的库和框架的数量而言, 这是一个非常成熟的环境 。 在Elixir中,这种成熟有时对我来说还不够。 当我需要第三方服务时,合适的搜索结果可能如下:
- 有一个官方支持良好的图书馆(很少见);
- 有一个官方的但过时的或错误的图书馆(有时会发生);
- 有一个由社区某人(有时不时)开发的功能强大的库;
- 有一个由社区中的某人开发的图书馆,但是不再受支持(一个非常常见的情况);
- 有几个库,每个库都是由某人为满足自己的需要编写的,它缺少必要的功能(最受欢迎的选项);
- 有我自己的库,结合了以上所有优点……(经常发现)。
Elixir上的简单JSON API

您可能会感到惊讶,但是Ruby并不总是处于危险之中 ( 还记得Ruby on Rails吗?-译者注 )。 也不一定总是需要与网络通信。 尽管在这种情况下,让我们谈谈网络。
在实现单个RESTful端点时,通常有很多选择:
这些是我个人使用的工具的示例。 我的同事对Sinatra用户感到满意。 他们设法尝试了Hanami。 我可以选择适合我的任何选项,即使这取决于我当前的心情。
但是当我切换到Elixir时,事实证明选择是有限的。 尽管有几种替代的“框架”(其名称出于明显的原因,我在这里将不提及),但几乎无法使用它们!
我整天都在整理Internet上提到的每个图书馆。 作为一个Slack机器人,我试图在Heroku上部署一个简单的HTTP2服务器 ,但最终我放弃了。 从字面上看,我发现所有选项都无法实现基本要求。
并非始终是解决方案-凤凰城
Phoenix是我最喜欢的Web框架,只是有时它是多余的。 我不想使用它,只是为了一个端点就将整个框架引入到项目中。 没关系,它很简单。
我也不能使用现成的库,因为,正如我已经说过的那样,我发现的所有库要么都不适合我的需要(需要基本路由和JSON支持),要么不方便便捷地部署到Heroku。 我想退后一步。

但是实际上,凤凰城本身是建立在某种基础之上的 ,不是吗?
Plug&Cowboy来营救
如果您需要在Ruby上创建真正的简约服务器,则只需使用rack
-Ruby Web服务器的模块化接口。
幸运的是,Elixir中提供了类似的功能。 在这种情况下,我们将使用以下元素:
- Cowboy是用于Erlang / OTP的小型快速HTTP服务器,它实现了完整的HTTP堆栈和路由,并进行了优化以最大程度地减少延迟和内存使用量;
- 插件 -一组用于Erlang VM中运行的各种Web服务器的适配器; 每个适配器为位于其后面的Web服务器提供直接接口;
- toxic是用于在Elixir上处理JSON的库。
实作
我想实现诸如Endpoint(端点),Router(路由器)和JSON Parser(JSON处理程序)之类的组件。 然后,我想将结果部署在Heroku上并能够处理传入的请求。 让我们看看如何实现这一目标。
应用程式
确保您的Elixir项目包含主管。 为此,创建一个像这样的项目:
mix new minimal_server --sup
确保mix.exs包含:
def application do [ extra_applications: [:logger], mod: {MinimalServer.Application, []} ] end
并创建文件 lib/minimal_server/application.ex
:
defmodule MinimalServer.Application do use Application def start(_type, _args), do: Supervisor.start_link(children(), opts()) defp children do [] end defp opts do [ strategy: :one_for_one, name: MinimalServer.Supervisor ] end end
图书馆
必须在mix.exs
指定以下库:
defp deps do [ {:poison, "~> 4.0"}, {:plug, "~> 1.7"}, {:cowboy, "~> 2.5"}, {:plug_cowboy, "~> 2.0"} ] end
然后下载并编译依赖项:
mix do deps.get, deps.compile, compile
终点
现在一切准备就绪,可以创建服务器的入口点。 让我们创建一个具有以下内容的lib/minimal_server/endpoint.ex
文件:
defmodule MinimalServer.Endpoint do use Plug.Router plug(:match) plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Poison ) plug(:dispatch) match _ do send_resp(conn, 404, "Requested page not found!") end end
Plug
模块包含Plug.Router
用于根据使用的路径和HTTP方法重定向传入的请求。 收到请求后,路由器将调用模块:match
,由match/2
函数表示,该模块负责查找相应的路由,然后将其重定向到模块:dispatch
,它将执行相应的代码。
由于我们希望我们的API与JSON兼容,因此我们需要实现Plug.Parsers
。 由于它使用给定的:json_decoder
处理application/json
请求,因此我们将使用它来分析请求正文。
结果,我们创建了一条与所有请求匹配的临时路由“任何请求”,并使用未找到的HTTP(404)代码进行响应。
路由器
实现路由器将是创建我们的应用程序的最后一步。 这是我们创建的整个管道的最后一个元素:从接收来自Web浏览器的请求开始,到形成响应为止。
路由器将处理来自客户端的传入请求,并以所需格式发送一些消息( 将给定代码添加到文件lib/minimal_server/router.ex
译者注 ):
defmodule MinimalServer.Router do use Plug.Router plug(:match) plug(:dispatch) get "/" do conn |> put_resp_content_type("application/json") |> send_resp(200, Poison.encode!(message())) end defp message do %{ response_type: "in_channel", text: "Hello from BOT :)" } end end
在上面的Router
模块中,仅当请求是通过GET
方法发送并沿着/
路由发送时,该请求才会被处理。 Router模块将以Content-Type
头作为响应,该头包含application/json
和主体:
{ "response_type": "in_channel", "text": "Hello from BOT :)" }
全部放在一起
现在是时候更改Endpoint
模块以将请求转发到路由器,并修改Application
以启动Endpoint
模块本身了。
第一个可以通过添加到MinimalServer.Endpoint
[ 在match _ do ... end
规则之前-大约。 翻译者 ]字符串
forward("/bot", to: MinimalServer.Router)
这样可以确保对/bot
所有请求都将路由到Router
模块并由其处理。
第二个可以通过将函数child_spec/1
和start_link/1
endpoint.ex
文件来实现:
defmodule MinimalServer.Endpoint do # ... def child_spec(opts) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [opts]} } end def start_link(_opts), do: Plug.Cowboy.http(__MODULE__, []) end
现在,您可以通过将MinimalServer.Endpoint
添加到children/0
函数返回的列表中来修改application.ex
。
defmodule MinimalServer.Application do # ... defp children do [ MinimalServer.Endpoint ] end end
要启动服务器,只需执行以下操作:
mix run --no-halt
最后,您可以访问地址http://本地主机:4000 / bot并查看我们的消息:)
部署方式

设定档
大多数情况下,在本地环境中以及为操作起见,服务器的配置是不同的。 因此,我们需要为每种模式输入单独的设置。 首先,通过添加以下内容来config.exs
我们的config.exs
:
config :minimal_server, MinimalServer.Endpoint, port: 4000
在这种情况下,当应用程序以test
, prod
和dev
模式启动时,如果不更改这些设置,它将接收端口4000。
来自翻译在这一点上,原始作者忘记了如何修改config.exs,以便可以将不同的选项用于不同的模式。 为此,在config/config.exs
的最后一行中添加import_config "#{Mix.env()}.exs"
; import_config "#{Mix.env()}.exs"
结果是这样的:
use Mix.Config config :minimal_server, MinimalServer.Endpoint, port: 4000 import_config "#{Mix.env()}.exs"
之后,通过在每一行中的config
目录中创建文件prod.exs
, test.exs
, dev.exs
:
use Mix.Config
在生产中,我们通常不希望对端口号进行严格设置,而是依靠某些系统环境变量,例如:
config :minimal_server, MinimalServer.Endpoint, port: "PORT" |> System.get_env() |> String.to_integer()
将上面的文本添加到config/prod.exs
的末尾。 译者
之后,将在本地使用固定值,并且在操作操作中将使用环境变量的配置。
让我们在endpoint.ex
实现此方案( 替换start_link / 1函数-翻译者注释 ):
defmodule MinimalServer.Endpoint do # ... require Logger def start_link(_opts) do with {:ok, [port: port] = config} <- Application.fetch_env(:minimal_server, __MODULE__) do Logger.info("Starting server at http://localhost:#{port}/") Plug.Adapters.Cowboy2.http(__MODULE__, [], config) end end end
Heroku
Heroku提供最简单的一键式部署,而无需任何复杂的设置。 要部署我们的项目, 您需要准备几个简单的文件并创建一个远程应用程序 。

安装Heroku CLI后,您可以按以下方式创建新的应用程序:
$ heroku create minimal-server-habr Creating ⬢ minimal-server-habr... done https://minimal-server-habr.herokuapp.com/ | https://git.heroku.com/minimal-server-habr.git
现在将Elixir Build Kit添加到您的应用程序中:
heroku buildpacks:set \ https://github.com/HashNuke/heroku-buildpack-elixir.git
在进行此翻译时,Elixir和Erlang的当前版本是(加号或减号):
erlang_version=21.1 elixir_version=1.8.1
要配置构建工具包本身,请将以上各行添加到elixir_buildpack.config
文件中。
最后一步是创建一个Procfile,同样,它非常简单:
web: mix run --no-halt
译者注:为了避免在Heroku上进行构建时出错,您必须设置应用程序中使用的环境变量的值:
$ heroku config:set PORT=4000 Setting PORT and restarting ⬢ minimal-server-habr... done, v5 PORT: 4000
只要您提交新文件[ 使用git-大约。 译者 ],您可以将它们上传到Heroku:
$ git push heroku master Initializing repository, done. updating 'refs/heads/master' ...
仅此而已! 该应用程序可从https://minimal-server-habr.herokuapp.com获得 。
总结
至此,您已经了解了如何仅使用3( 4-大约Translator )库在不使用任何框架的情况下将最简单的JSON RESTful API和HTTP服务器实现到Elixir 。
当您需要提供对简单端点的访问权限时,无论它多么酷,以及任何其他框架,您都绝对不需要每次都使用Phoenix。
好奇为什么在plug
+ cowboy
和Phoenix之间没有可靠,经过良好测试和支持的框架? 也许真的没有必要实现简单的事情? 也许每个公司都使用自己的图书馆? 也许每个人都使用Phoenix或所介绍的方法?

仓库一如既往在我的GitHub上可用。