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/

 
17
Kudos
 
17
Kudos

Now read this

译:理解 Elixir 的宏-1

(原作者这一系列讲 Elixir macro 的文章写的非常好,我的认识还远远不够,所以在这里翻译一下,这是第一篇。翻译水平有限,见谅) 原文链接: http://www.theerlangelist.com/2014/06/understanding-elixir-macros-part-1.html 这是讨论宏(macro)的一系列文章的第一篇。我原本打算在之后要出版的 <Elixir in Action> 一书中来探讨这个问题,但又决定不这么做了。... Continue →