Current section

Files

Jump to
plug lib plug adapters cowboy.ex
Raw

lib/plug/adapters/cowboy.ex

defmodule Plug.Adapters.Cowboy do
@moduledoc """
Adapter interface to the Cowboy webserver.
## Options
* `:ip` - the ip to bind the server to.
Must be a tuple in the format `{x, y, z, w}`.
* `:port` - the port to run the server.
Defaults to 4000 (http) and 4040 (https).
* `:acceptors` - the number of acceptors for the listener.
Defaults to 100.
* `:max_connections` - max number of connections supported.
Defaults to `:infinity`.
* `:dispatch` - manually configure Cowboy's dispatch.
If this option is used, the plug given plug won't be initialized
nor dispatched to (and doing so becomes the user responsibility).
* `:ref` - the reference name to be used.
Defaults to `plug.HTTP` (http) and `plug.HTTPS` (https).
This is the value that needs to be given on shutdown.
* `:compress` - Cowboy will attempt to compress the response body.
"""
# Used for testing of cowboy.
@doc false
def args(scheme, plug, opts, options) do
options
|> Keyword.put_new(:ref, build_ref(plug, scheme))
|> Keyword.put_new(:dispatch, options[:dispatch] || dispatch_for(plug, opts))
|> normalize_options(scheme)
|> to_args()
end
@doc """
Run cowboy under http.
## Example
# Starts a new interface
Plug.Adapters.Cowboy.http MyPlug, [], port: 80
# The interface above can be shutdown with
Plug.Adapters.Cowboy.shutdown MyPlug.HTTP
"""
@spec http(module(), Keyword.t, Keyword.t) ::
{:ok, pid} | {:error, :eaddrinuse} | {:error, term}
def http(plug, opts, options \\ []) do
run(:http, plug, opts, options)
end
@doc """
Run cowboy under https.
Besides the options described in the module documentation,
this module also accepts all options defined in [the `ssl`
erlang module] (http://www.erlang.org/doc/man/ssl.html),
like keyfile, certfile, cacertfile and others.
The certificate files can be given as a relative path.
For such, the `:otp_app` option must also be given and
certificates will be looked from the priv directory of
the given application.
## Example
# Starts a new interface
Plug.Adapters.Cowboy.https MyPlug, [],
port: 443,
password: "SECRET",
otp_app: :my_app,
keyfile: "ssl/key.pem",
certfile: "ssl/cert.pem"
# The interface above can be shutdown with
Plug.Adapters.Cowboy.shutdown MyPlug.HTTPS
"""
@spec https(module(), Keyword.t, Keyword.t) ::
{:ok, pid} | {:error, :eaddrinuse} | {:error, term}
def https(plug, opts, options \\ []) do
:application.start(:asn1)
:application.start(:public_key)
:application.start(:ssl)
run(:https, plug, opts, options)
end
@doc """
Shutdowns the given reference.
"""
def shutdown(ref) do
:cowboy.stop_listener(ref)
end
@doc """
Returns a child_spec to be supervised by your application.
"""
def child_spec(scheme, plug, opts, options \\ []) do
[ref, nb_acceptors, trans_opts, proto_opts] = args(scheme, plug, opts, options)
ranch_module = case scheme do
:http -> :ranch_tcp
:https -> :ranch_ssl
end
:ranch.child_spec(ref, nb_acceptors, ranch_module, trans_opts, :cowboy_protocol, proto_opts)
end
## Helpers
@http_options [port: 4000]
@https_options [port: 4040]
@not_options [:acceptors, :dispatch, :ref, :otp_app, :compress]
defp run(scheme, plug, opts, options) do
:application.start(:crypto)
:application.start(:ranch)
:application.start(:cowlib)
:application.start(:cowboy)
case apply(:cowboy, :"start_#{scheme}", args(scheme, plug, opts, options)) do
{:ok,pid} -> {:ok,pid}
{:error, {{:shutdown,{_, _,{{_,{:error, :eaddrinuse}},_}}},_}} ->
{:error, :eaddrinuse}
result -> result
end
end
defp normalize_options(options, :http) do
Keyword.merge @http_options, options
end
defp normalize_options(options, :https) do
options = Keyword.merge @https_options, options
options = Enum.reduce [:keyfile, :certfile, :cacertfile], options, &normalize_ssl_file(&1, &2)
options = Enum.reduce [:password], options, &to_char_list(&2, &1)
options
end
defp to_args(options) do
ref = options[:ref]
acceptors = options[:acceptors] || 100
dispatch = :cowboy_router.compile(options[:dispatch])
compress = options[:compress] || false
options = Keyword.drop(options, @not_options)
[ref, acceptors, options, [env: [dispatch: dispatch], compress: compress]]
end
defp build_ref(plug, scheme) do
Module.concat(plug, scheme |> to_string |> String.upcase)
end
defp dispatch_for(plug, opts) do
opts = plug.init(opts)
[{:_, [ {:_, Plug.Adapters.Cowboy.Handler, {plug, opts}} ]}]
end
defp normalize_ssl_file(key, options) do
value = options[key]
cond do
nil?(value) ->
options
Path.type(value) == :absolute ->
Keyword.put(options, key, to_char_list(value))
true ->
new = Path.expand(value, otp_app(options)) |> to_char_list
Keyword.put(options, key, new)
end
end
defp otp_app(options) do
if app = options[:otp_app] do
:code.priv_dir(app)
else
raise ArgumentError, message: "to use relative certificate with https, the :otp_app " <>
"option needs to be given when invoking the handler"
end
end
defp to_char_list(options, key) do
if value = options[key] do
Keyword.put options, key, to_char_list(value)
else
options
end
end
end