Elixir上最简单的JSON RESTful API

如何在没有任何框架的情况下在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/1start_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 

在这种情况下,当应用程序以testproddev模式启动时,如果不更改这些设置,它将接收端口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.exstest.exsdev.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上可用。

Source: https://habr.com/ru/post/zh-CN444554/


All Articles