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只是拼装了一些参数,并用它来调:cowboy
的start_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传进去的MyPlug
的call
函数,通过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/