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

Kubernetes 源码解读 - 由一次 debug 学到的

在最近公司办的第一次 Open L 活动中,我们分享了为什么我们要用 Kubernetes,其中吸引我们的一方面就是 autoscaling,它能够根据 CPU 等指标动态调整 pod 的个数,以此提高机器的利用率。 但最近却发现它并不能按预期正常地工作,deployment 的 Horizontal Pod Autoscaler(HPA) 显示的 CPU 并不能反应实际情况,所以也就不能正常地对 pod 的数量进行调整。查了 log 又 Google 一番后,... Continue →