How does Plug work with Cowboy?

Plug的文档里有个通过Plug写应用程序的简单例子:

defmodule MyPlug do
  import Plug.Conn

  def init(options) do
    # initialize options

    options
  end

  def call(conn, _opts) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Hello world")
  end
end
# Run the server
$ iex -S mix
iex> c "path/to/file.ex"
[MyPlug]
iex> {:ok, _} = Plug.Adapters.Cowboy.http MyPlug, []
{:ok, #PID<...>}

本文就来简单聊一下这寥寥几行代码是如何起作用的。

让我们从最后看起,Cowboy是Erlang写的一个Small, fast, modular HTTP server,有点类似于ruby里的thin的角色。Plug则实现了Cowboy的Adapter,使我们可以方便的写出一个web app。

Plug.Adapters.Cowboy.http MyPlug, [],其实Cowboy只是拼装了一些参数,并用它来调:cowboystart_http函数。最后拼好的这些参数是:

[MyPlug.HTTP,                # ref
100,                         # default value for acceptors
[port: 4000],                # default value for options
[env: [
  dispatch: :cowboy_router.compile(dispatches), # important!
  compress: false],          # default value for compress
]]

于是cowboy就开始监听HTTP连接啦,之后就是如何路由:

When Cowboy receives a request, it tries to match the requested host and path to the resources given in the dispatch rules. If it matches, then the associated Erlang code will be executed.

Routing rules are given per host. Cowboy will first match on the host, and then try to find a matching path.

Routes need to be compiled before they can be used by Cowboy.

而我们交给cowboy编译的路由规则其实就是上边代码里的dispatches,在这里就是

#Dispatch format: {HostMatch, list({PathMatch, Handler, Opts})}
[{:_host, [{:_path, Plug.Adapters.Cowboy.Handler, {MyPlug, []}}]}]]

可以传入多个dispatch,cowboy会调用匹配的handler。这里只有一个dispatch,但是它能够匹配所有的host和path,统一交给Plug中的Plug.Adapters.Cowboy.Handler来处理。

Cowboy的handler都必须定义init函数,再根据需要的protocol来返回不同的结果,比如HTTP、REST、Websocket和自定义protocol等。而Plug为了能够支持多种protocol,选择了通过在init中返回{:upgrade, :protocol, :my_protocol}来定义自己的protocol,也就是:"Elixir.Plug.Adapters.Cowboy.Handler"

自定义protocol还需要做的,就是在handler里加入upgrade(Req, Env, Handler, HandlerOpts)函数,并返回{ok, Req, Env}给cowboy,之后cowboy经过后续处理再返回response给client,就完成了整个请求到接受的过程。

而在返回之前,也就是在Handler的upgrade函数里,还调用了之前我们当做Opts传进去的MyPlugcall函数,通过Plug.Adapters.Cowboy.Conn的一些方法调用,就完成了MyPlug的处理。

这里还想补充说明一点,Plug可以有两种形式,一种是定义了call函数的Module,一种是function,其实都得益于Handler中的这段代码plug.call(opts)

Refer:

http://ninenines.eu/docs/en/cowboy/HEAD/guide/getting_started/
http://ninenines.eu/docs/en/cowboy/1.0/guide/http_req_life/
http://ninenines.eu/docs/en/cowboy/HEAD/guide/routing/
http://ninenines.eu/docs/en/cowboy/1.0/guide/upgrade_protocol/

 
14
Kudos
 
14
Kudos

Now read this

流利说为什么要从 Ruby 转到 Elixir

可能有些同学知道我最近都在搞 Elixir,基本除了工作时间,都在写 Elixir,不管是博客还是 GitHub,都是 Elixir 的痕迹。虽然写的挺愉快的,收获也很多,但还是有些遗憾的是,不能真正用到生产当中去。当一个工具没有办法真正在线上使用时,你永远也无法知道它的坑点与优点。也有很多人问,流利说用 Elixir 吗,我总是尴尬的说,目前还没有,但将来说不定会呢。 没想到,将来来的那么快。我作为公司内部比较早期深入接触 Elixir 的开发者,... Continue →

Subscribe to Tony Han

Don’t worry; we hate spam with a passion.
You can unsubscribe with one click.

5LlAm7DiB9GD0l5vEcE