zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

cowboy.ex (16176B)


      1 defmodule Plug.Cowboy do
      2   @moduledoc """
      3   Adapter interface to the Cowboy2 webserver.
      4 
      5   ## Options
      6 
      7     * `:net` - If using `:inet` (IPv4 only - the default) or `:inet6` (IPv6)
      8 
      9     * `:ip` - the ip to bind the server to.
     10       Must be either a tuple in the format `{a, b, c, d}` with each value in `0..255` for IPv4,
     11       or a tuple in the format `{a, b, c, d, e, f, g, h}` with each value in `0..65535` for IPv6,
     12       or a tuple in the format `{:local, path}` for a unix socket at the given `path`.
     13       If you set an IPv6, the `:net` option will be automatically set to `:inet6`.
     14       If both `:net` and `:ip` options are given, make sure they are compatible
     15       (i.e. give a IPv4 for `:inet` and IPv6 for `:inet6`).
     16       Also, see "Loopback vs Public IP Addresses".
     17 
     18     * `:port` - the port to run the server.
     19       Defaults to 4000 (http) and 4040 (https).
     20       Must be 0 when `:ip` is a `{:local, path}` tuple.
     21 
     22     * `:dispatch` - manually configure Cowboy's dispatch.
     23       If this option is used, the given plug won't be initialized
     24       nor dispatched to (and doing so becomes the user's responsibility).
     25 
     26     * `:ref` - the reference name to be used.
     27       Defaults to `plug.HTTP` (http) and `plug.HTTPS` (https).
     28       Note, the default reference name does not contain the port so in order
     29       to serve the same plug on multiple ports you need to set the `:ref` accordingly,
     30       e.g.: `ref: MyPlug_HTTP_4000`, `ref: MyPlug_HTTP_4001`, etc.
     31       This is the value that needs to be given on shutdown.
     32 
     33     * `:compress` - Cowboy will attempt to compress the response body.
     34       Defaults to false.
     35 
     36     * `:stream_handlers` - List of Cowboy `stream_handlers`,
     37       see [Cowboy docs](https://ninenines.eu/docs/en/cowboy/2.5/manual/cowboy_http/).
     38 
     39     * `:protocol_options` - Specifies remaining protocol options,
     40       see [Cowboy docs](https://ninenines.eu/docs/en/cowboy/2.5/manual/cowboy_http/).
     41 
     42     * `:transport_options` - A keyword list specifying transport options,
     43       see [Ranch docs](https://ninenines.eu/docs/en/ranch/1.7/manual/ranch/).
     44       By default `:num_acceptors` will be set to `100` and `:max_connections`
     45       to `16_384`.
     46 
     47   All other options given at the top level must configure the underlying
     48   socket. For HTTP connections, those options are listed under
     49   [`ranch_tcp`](https://ninenines.eu/docs/en/ranch/1.7/manual/ranch_tcp/).
     50   For example, you can set `:ipv6_v6only` to true if you want to bind only
     51   on IPv6 addresses.
     52 
     53   For HTTPS (SSL) connections, those options are described in
     54   [`ranch_ssl`](https://ninenines.eu/docs/en/ranch/1.7/manual/ranch_ssl/).
     55   See `https/3` for an example and read `Plug.SSL.configure/1` to
     56   understand about our SSL defaults.
     57 
     58   When using a Unix socket, OTP 21+ is required for `Plug.Static` and
     59   `Plug.Conn.send_file/3` to behave correctly.
     60 
     61   ## Safety limits
     62 
     63   Cowboy sets different limits on URL size, header length, number of
     64   headers and so on to protect your application from attacks. For example,
     65   the request line length defaults to 10k, which means Cowboy will return
     66   414 if a larger URL is given. You can change this under `:protocol_options`:
     67 
     68       protocol_options: [max_request_line_length: 50_000]
     69 
     70   Keep in mind though increasing those limits can pose a security risk.
     71   Other times, browsers and proxies along the way may have equally strict
     72   limits, which means the request will still fail or the URL will be
     73   pruned. You can [consult all limits here](https://ninenines.eu/docs/en/cowboy/2.5/manual/cowboy_http/).
     74 
     75   ## Loopback vs Public IP Addresses
     76 
     77   Should your application bind to a loopback address, such as `::1` (IPv6) or
     78   `127.0.0.1` (IPv4), or a public one, such as `::0` (IPv6) or `0.0.0.0`
     79   (IPv4)? It depends on how (and whether) you want it to be reachable from
     80   other machines.
     81 
     82   Loopback addresses are only reachable from the same host (`localhost` is
     83   usually configured to resolve to a loopback address). You may wish to use one if:
     84 
     85   - Your app is running in a development environment (such as your laptop) and
     86   you don't want others on the same network to access it.
     87   - Your app is running in production, but behind a reverse proxy. For example,
     88   you might have Nginx bound to a public address and serving HTTPS, but
     89   forwarding the traffic to your application running on the same host. In that
     90   case, having your app bind to the loopback address means that Nginx can reach
     91   it, but outside traffic can only reach it via Nginx.
     92 
     93   Public addresses are reachable from other hosts. You may wish to use one if:
     94 
     95   - Your app is running in a container. In this case, its loopback address is
     96   reachable only from within the container; to be accessible from outside the
     97   container, it needs to bind to a public IP address.
     98   - Your app is running in production without a reverse proxy, using Cowboy's
     99   SSL support.
    100 
    101   ## Logging
    102 
    103   You can configure which exceptions are logged via `:log_exceptions_with_status_code`
    104   application environment variable. If the status code returned by `Plug.Exception.status/1`
    105   for the exception falls into any of the configured ranges, the exception is logged.
    106   By default it's set to `[500..599]`.
    107 
    108       config :plug_cowboy,
    109         log_exceptions_with_status_code: [400..599]
    110 
    111   ## Instrumentation
    112 
    113   Plug.Cowboy uses the `:telemetry` library for instrumentation. The following
    114   span events are published during each request:
    115 
    116     * `[:cowboy, :request, :start]` - dispatched at the beginning of the request
    117     * `[:cowboy, :request, :stop]` - dispatched at the end of the request
    118     * `[:cowboy, :request, :exception]` - dispatched at the end of a request that exits
    119 
    120   A single event is published when the request ends with an early error:
    121     * `[:cowboy, :request, :early_error]` - dispatched for requests terminated early by Cowboy
    122 
    123   See [`cowboy_telemetry`](https://github.com/beam-telemetry/cowboy_telemetry#telemetry-events)
    124   for more details on the events.
    125 
    126   To opt-out of this default instrumentation, you can manually configure
    127   cowboy with the option `stream_handlers: [:cowboy_stream_h]`.
    128 
    129   ## WebSocket support
    130 
    131   Plug.Cowboy supports upgrading HTTP requests to WebSocket connections via 
    132   the use of the `Plug.Conn.upgrade_adapter/3` function, called with `:websocket` as the second
    133   argument. Applications should validate that the connection represents a valid WebSocket request
    134   before calling this function (Cowboy will validate the connection as part of the upgrade
    135   process, but does not provide any capacity for an application to be notified if the upgrade is
    136   not successful). If an application wishes to negotiate WebSocket subprotocols or otherwise set
    137   any response headers, it should do so before calling `Plug.Conn.upgrade_adapter/3`.
    138 
    139   The third argument to `Plug.Conn.upgrade_adapter/3` defines the details of how Plug.Cowboy
    140   should handle the WebSocket connection, and must take the form `{handler, handler_opts,
    141   connection_opts}`, where values are as follows:
    142 
    143   * `handler` is a module which implements the
    144     [`:cowboy_websocket`](https://ninenines.eu/docs/en/cowboy/2.6/manual/cowboy_websocket/)
    145     behaviour. Note that this module will NOT have its `c:cowboy_websocket.init/2` callback
    146     called; only the 'later' parts of the `:cowboy_websocket` lifecycle are supported
    147   * `handler_opts` is an arbitrary term which will be passed as the argument to
    148     `c:cowboy_websocket.websocket_init/1`
    149   * `connection_opts` is a map with any of [Cowboy's websockets options](https://ninenines.eu/docs/en/cowboy/2.6/manual/cowboy_websocket/#_opts)
    150 
    151   """
    152 
    153   require Logger
    154 
    155   @doc false
    156   def start(_type, _args) do
    157     Logger.add_translator({Plug.Cowboy.Translator, :translate})
    158     Supervisor.start_link([], strategy: :one_for_one)
    159   end
    160 
    161   # Made public with @doc false for testing.
    162   @doc false
    163   def args(scheme, plug, plug_opts, cowboy_options) do
    164     {cowboy_options, non_keyword_options} = Enum.split_with(cowboy_options, &match?({_, _}, &1))
    165 
    166     cowboy_options
    167     |> normalize_cowboy_options(scheme)
    168     |> to_args(scheme, plug, plug_opts, non_keyword_options)
    169   end
    170 
    171   @doc """
    172   Runs cowboy under http.
    173 
    174   ## Example
    175 
    176       # Starts a new interface
    177       Plug.Cowboy.http MyPlug, [], port: 80
    178 
    179       # The interface above can be shutdown with
    180       Plug.Cowboy.shutdown MyPlug.HTTP
    181 
    182   """
    183   @spec http(module(), Keyword.t(), Keyword.t()) ::
    184           {:ok, pid} | {:error, :eaddrinuse} | {:error, term}
    185   def http(plug, opts, cowboy_options \\ []) do
    186     run(:http, plug, opts, cowboy_options)
    187   end
    188 
    189   @doc """
    190   Runs cowboy under https.
    191 
    192   Besides the options described in the module documentation,
    193   this function sets defaults and accepts all options defined
    194   in `Plug.SSL.configure/1`.
    195 
    196   ## Example
    197 
    198       # Starts a new interface
    199       Plug.Cowboy.https MyPlug, [],
    200         port: 443,
    201         password: "SECRET",
    202         otp_app: :my_app,
    203         keyfile: "priv/ssl/key.pem",
    204         certfile: "priv/ssl/cert.pem",
    205         dhfile: "priv/ssl/dhparam.pem"
    206 
    207       # The interface above can be shutdown with
    208       Plug.Cowboy.shutdown MyPlug.HTTPS
    209 
    210   """
    211   @spec https(module(), Keyword.t(), Keyword.t()) ::
    212           {:ok, pid} | {:error, :eaddrinuse} | {:error, term}
    213   def https(plug, opts, cowboy_options \\ []) do
    214     Application.ensure_all_started(:ssl)
    215     run(:https, plug, opts, cowboy_options)
    216   end
    217 
    218   @doc """
    219   Shutdowns the given reference.
    220   """
    221   def shutdown(ref) do
    222     :cowboy.stop_listener(ref)
    223   end
    224 
    225   @doc """
    226   A function for starting a Cowboy2 server under Elixir v1.5+ supervisors.
    227 
    228   It supports all options as specified in the module documentation plus it
    229   requires the following two options:
    230 
    231     * `:scheme` - either `:http` or `:https`
    232     * `:plug` - such as `MyPlug` or `{MyPlug, plug_opts}`
    233 
    234   ## Examples
    235 
    236   Assuming your Plug module is named `MyApp` you can add it to your
    237   supervision tree by using this function:
    238 
    239       children = [
    240         {Plug.Cowboy, scheme: :http, plug: MyApp, options: [port: 4040]}
    241       ]
    242 
    243       Supervisor.start_link(children, strategy: :one_for_one)
    244 
    245   """
    246   def child_spec(opts) do
    247     scheme = Keyword.fetch!(opts, :scheme)
    248 
    249     {plug, plug_opts} =
    250       case Keyword.fetch!(opts, :plug) do
    251         {_, _} = tuple -> tuple
    252         plug -> {plug, []}
    253       end
    254 
    255     # We support :options for backwards compatibility.
    256     cowboy_opts =
    257       opts
    258       |> Keyword.drop([:scheme, :plug, :options])
    259       |> Kernel.++(Keyword.get(opts, :options, []))
    260 
    261     cowboy_args = args(scheme, plug, plug_opts, cowboy_opts)
    262     [ref, transport_opts, proto_opts] = cowboy_args
    263 
    264     {ranch_module, cowboy_protocol, transport_opts} =
    265       case scheme do
    266         :http ->
    267           {:ranch_tcp, :cowboy_clear, transport_opts}
    268 
    269         :https ->
    270           %{socket_opts: socket_opts} = transport_opts
    271 
    272           socket_opts =
    273             socket_opts
    274             |> Keyword.put_new(:next_protocols_advertised, ["h2", "http/1.1"])
    275             |> Keyword.put_new(:alpn_preferred_protocols, ["h2", "http/1.1"])
    276 
    277           {:ranch_ssl, :cowboy_tls, %{transport_opts | socket_opts: socket_opts}}
    278       end
    279 
    280     case :ranch.child_spec(ref, ranch_module, transport_opts, cowboy_protocol, proto_opts) do
    281       {id, start, restart, shutdown, type, modules} ->
    282         %{
    283           id: id,
    284           start: start,
    285           restart: restart,
    286           shutdown: shutdown,
    287           type: type,
    288           modules: modules
    289         }
    290 
    291       child_spec when is_map(child_spec) ->
    292         child_spec
    293     end
    294   end
    295 
    296   ## Helpers
    297 
    298   @protocol_options [:compress, :stream_handlers]
    299 
    300   defp run(scheme, plug, opts, cowboy_options) do
    301     case Application.ensure_all_started(:cowboy) do
    302       {:ok, _} ->
    303         nil
    304 
    305       {:error, {:cowboy, _}} ->
    306         raise "could not start the Cowboy application. Please ensure it is listed as a dependency in your mix.exs"
    307     end
    308 
    309     start =
    310       case scheme do
    311         :http -> :start_clear
    312         :https -> :start_tls
    313         other -> :erlang.error({:badarg, [other]})
    314       end
    315 
    316     :telemetry.attach(
    317       :plug_cowboy,
    318       [:cowboy, :request, :early_error],
    319       &__MODULE__.handle_event/4,
    320       nil
    321     )
    322 
    323     apply(:cowboy, start, args(scheme, plug, opts, cowboy_options))
    324   end
    325 
    326   defp normalize_cowboy_options(cowboy_options, :http) do
    327     Keyword.put_new(cowboy_options, :port, 4000)
    328   end
    329 
    330   defp normalize_cowboy_options(cowboy_options, :https) do
    331     cowboy_options
    332     |> Keyword.put_new(:port, 4040)
    333     |> Plug.SSL.configure()
    334     |> case do
    335       {:ok, options} -> options
    336       {:error, message} -> fail(message)
    337     end
    338   end
    339 
    340   defp to_args(opts, scheme, plug, plug_opts, non_keyword_opts) do
    341     {timeout, opts} = Keyword.pop(opts, :timeout)
    342 
    343     if timeout do
    344       Logger.warn("the :timeout option for Cowboy webserver has no effect and must be removed")
    345     end
    346 
    347     opts = Keyword.delete(opts, :otp_app)
    348     {ref, opts} = Keyword.pop(opts, :ref)
    349     {dispatch, opts} = Keyword.pop(opts, :dispatch)
    350     {protocol_options, opts} = Keyword.pop(opts, :protocol_options, [])
    351 
    352     dispatch = :cowboy_router.compile(dispatch || dispatch_for(plug, plug_opts))
    353     {extra_options, opts} = Keyword.split(opts, @protocol_options)
    354 
    355     extra_options = set_stream_handlers(extra_options)
    356     protocol_and_extra_options = :maps.from_list(protocol_options ++ extra_options)
    357     protocol_options = Map.merge(%{env: %{dispatch: dispatch}}, protocol_and_extra_options)
    358     {transport_options, socket_options} = Keyword.pop(opts, :transport_options, [])
    359 
    360     {net, socket_options} = Keyword.pop(socket_options, :net)
    361     socket_options = List.wrap(net) ++ non_keyword_opts ++ socket_options
    362 
    363     transport_options =
    364       transport_options
    365       |> Keyword.put_new(:num_acceptors, 100)
    366       |> Keyword.put_new(:max_connections, 16_384)
    367       |> Keyword.update(
    368         :socket_opts,
    369         socket_options,
    370         &(&1 ++ socket_options)
    371       )
    372       |> Map.new()
    373 
    374     [ref || build_ref(plug, scheme), transport_options, protocol_options]
    375   end
    376 
    377   @default_stream_handlers [:cowboy_telemetry_h, :cowboy_stream_h]
    378 
    379   defp set_stream_handlers(opts) do
    380     compress = Keyword.get(opts, :compress)
    381     stream_handlers = Keyword.get(opts, :stream_handlers)
    382 
    383     case {compress, stream_handlers} do
    384       {true, nil} ->
    385         Keyword.put_new(opts, :stream_handlers, [:cowboy_compress_h | @default_stream_handlers])
    386 
    387       {true, _} ->
    388         raise "cannot set both compress and stream_handlers at once. " <>
    389                 "If you wish to set compress, please add `:cowboy_compress_h` to your stream handlers."
    390 
    391       {_, nil} ->
    392         Keyword.put_new(opts, :stream_handlers, @default_stream_handlers)
    393 
    394       {_, _} ->
    395         opts
    396     end
    397   end
    398 
    399   defp build_ref(plug, scheme) do
    400     Module.concat(plug, scheme |> to_string |> String.upcase())
    401   end
    402 
    403   defp dispatch_for(plug, opts) do
    404     opts = plug.init(opts)
    405     [{:_, [{:_, Plug.Cowboy.Handler, {plug, opts}}]}]
    406   end
    407 
    408   defp fail(message) do
    409     raise ArgumentError, "could not start Cowboy2 adapter, " <> message
    410   end
    411 
    412   @doc false
    413   def handle_event(
    414         [:cowboy, :request, :early_error],
    415         _,
    416         %{reason: {:connection_error, :limit_reached, specific_reason}, partial_req: partial_req},
    417         _
    418       ) do
    419     Logger.error("""
    420     Cowboy returned 431 because it was unable to parse the request headers.
    421 
    422     This may happen because there are no headers, or there are too many headers
    423     or the header name or value are too large (such as a large cookie).
    424 
    425     More specific reason is:
    426 
    427         #{inspect(specific_reason)}
    428 
    429     You can customize those limits when configuring your http/https
    430     server. The configuration option and default values are shown below:
    431 
    432         protocol_options: [
    433           max_header_name_length: 64,
    434           max_header_value_length: 4096,
    435           max_headers: 100
    436         ]
    437 
    438     Request info:
    439 
    440         peer: #{format_peer(partial_req.peer)}
    441         method: #{partial_req.method || "<unable to parse>"}
    442         path: #{partial_req.path || "<unable to parse>"}
    443     """)
    444   end
    445 
    446   def handle_event(_, _, _, _) do
    447     :ok
    448   end
    449 
    450   defp format_peer({addr, port}) do
    451     "#{:inet_parse.ntoa(addr)}:#{port}"
    452   end
    453 end