whisper server elixir
This commit is contained in:
179
whisper_server/deps/plug_cowboy/lib/plug/cowboy/conn.ex
Normal file
179
whisper_server/deps/plug_cowboy/lib/plug/cowboy/conn.ex
Normal file
@ -0,0 +1,179 @@
|
||||
defmodule Plug.Cowboy.Conn do
|
||||
@behaviour Plug.Conn.Adapter
|
||||
@moduledoc false
|
||||
|
||||
@already_sent {:plug_conn, :sent}
|
||||
|
||||
def conn(req) do
|
||||
%{
|
||||
path: path,
|
||||
host: host,
|
||||
port: port,
|
||||
method: method,
|
||||
headers: headers,
|
||||
qs: qs,
|
||||
peer: {remote_ip, _}
|
||||
} = req
|
||||
|
||||
%Plug.Conn{
|
||||
adapter: {__MODULE__, Map.put(req, :plug_pid, self())},
|
||||
host: host,
|
||||
method: method,
|
||||
owner: self(),
|
||||
path_info: split_path(path),
|
||||
port: port,
|
||||
remote_ip: remote_ip,
|
||||
query_string: qs,
|
||||
req_headers: to_headers_list(headers),
|
||||
request_path: path,
|
||||
scheme: String.to_atom(:cowboy_req.scheme(req))
|
||||
}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def send_resp(req, status, headers, body) do
|
||||
req = to_headers_map(req, headers)
|
||||
status = Integer.to_string(status) <> " " <> Plug.Conn.Status.reason_phrase(status)
|
||||
req = :cowboy_req.reply(status, %{}, body, req)
|
||||
send(req.plug_pid, @already_sent)
|
||||
{:ok, nil, req}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def send_file(req, status, headers, path, offset, length) do
|
||||
%File.Stat{type: :regular, size: size} = File.stat!(path)
|
||||
|
||||
length =
|
||||
cond do
|
||||
length == :all -> size
|
||||
is_integer(length) -> length
|
||||
end
|
||||
|
||||
body = {:sendfile, offset, length, path}
|
||||
req = to_headers_map(req, headers)
|
||||
req = :cowboy_req.reply(status, %{}, body, req)
|
||||
send(req.plug_pid, @already_sent)
|
||||
{:ok, nil, req}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def send_chunked(req, status, headers) do
|
||||
req = to_headers_map(req, headers)
|
||||
req = :cowboy_req.stream_reply(status, %{}, req)
|
||||
send(req.plug_pid, @already_sent)
|
||||
{:ok, nil, req}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def chunk(req, body) do
|
||||
:cowboy_req.stream_body(body, :nofin, req)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def read_req_body(req, opts) do
|
||||
length = Keyword.get(opts, :length, 8_000_000)
|
||||
read_length = Keyword.get(opts, :read_length, 1_000_000)
|
||||
read_timeout = Keyword.get(opts, :read_timeout, 15_000)
|
||||
|
||||
opts = %{length: read_length, period: read_timeout}
|
||||
read_req_body(req, opts, length, [])
|
||||
end
|
||||
|
||||
defp read_req_body(req, opts, length, acc) when length >= 0 do
|
||||
case :cowboy_req.read_body(req, opts) do
|
||||
{:ok, data, req} -> {:ok, IO.iodata_to_binary([acc | data]), req}
|
||||
{:more, data, req} -> read_req_body(req, opts, length - byte_size(data), [acc | data])
|
||||
end
|
||||
end
|
||||
|
||||
defp read_req_body(req, _opts, _length, acc) do
|
||||
{:more, IO.iodata_to_binary(acc), req}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def inform(req, status, headers) do
|
||||
:cowboy_req.inform(status, to_headers_map(headers), req)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def upgrade(req, :websocket, args) do
|
||||
case args do
|
||||
{handler, _state, cowboy_opts} when is_atom(handler) and is_map(cowboy_opts) ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
raise ArgumentError,
|
||||
"expected websocket upgrade on Cowboy to be on the format {handler :: atom(), arg :: term(), opts :: map()}, got: " <>
|
||||
inspect(args)
|
||||
end
|
||||
|
||||
{:ok, Map.put(req, :upgrade, {:websocket, args})}
|
||||
end
|
||||
|
||||
def upgrade(_req, _protocol, _args), do: {:error, :not_supported}
|
||||
|
||||
@impl true
|
||||
def push(req, path, headers) do
|
||||
opts =
|
||||
case {req.port, req.sock} do
|
||||
{:undefined, {_, port}} -> %{port: port}
|
||||
{port, _} when port in [80, 443] -> %{}
|
||||
{port, _} -> %{port: port}
|
||||
end
|
||||
|
||||
req = to_headers_map(req, headers)
|
||||
:cowboy_req.push(path, %{}, req, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def get_peer_data(%{peer: {ip, port}, cert: cert}) do
|
||||
%{
|
||||
address: ip,
|
||||
port: port,
|
||||
ssl_cert: if(cert == :undefined, do: nil, else: cert)
|
||||
}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def get_http_protocol(req) do
|
||||
:cowboy_req.version(req)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp to_headers_list(headers) when is_list(headers) do
|
||||
headers
|
||||
end
|
||||
|
||||
defp to_headers_list(headers) when is_map(headers) do
|
||||
:maps.to_list(headers)
|
||||
end
|
||||
|
||||
defp to_headers_map(req, headers) do
|
||||
headers = to_headers_map(headers)
|
||||
Map.update(req, :resp_headers, headers, &Map.merge(&1, headers))
|
||||
end
|
||||
|
||||
defp to_headers_map(headers) when is_list(headers) do
|
||||
# Group set-cookie headers into a list for a single `set-cookie`
|
||||
# key since cowboy 2 requires headers as a map.
|
||||
Enum.reduce(headers, %{}, fn
|
||||
{key = "set-cookie", value}, acc ->
|
||||
case acc do
|
||||
%{^key => existing} -> %{acc | key => [value | existing]}
|
||||
%{} -> Map.put(acc, key, [value])
|
||||
end
|
||||
|
||||
{key, value}, acc ->
|
||||
case acc do
|
||||
%{^key => existing} -> %{acc | key => existing <> ", " <> value}
|
||||
%{} -> Map.put(acc, key, value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp split_path(path) do
|
||||
segments = :binary.split(path, "/", [:global])
|
||||
for segment <- segments, segment != "", do: segment
|
||||
end
|
||||
end
|
||||
113
whisper_server/deps/plug_cowboy/lib/plug/cowboy/drainer.ex
Normal file
113
whisper_server/deps/plug_cowboy/lib/plug/cowboy/drainer.ex
Normal file
@ -0,0 +1,113 @@
|
||||
defmodule Plug.Cowboy.Drainer do
|
||||
@moduledoc """
|
||||
Process to drain cowboy connections at shutdown.
|
||||
|
||||
When starting `Plug.Cowboy` in a supervision tree, it will create a listener that receives
|
||||
requests and creates a connection process to handle that request. During shutdown, a
|
||||
`Plug.Cowboy` process will immediately exit, closing the listener and any open connections
|
||||
that are still being served. However, in most cases, it is desirable to allow connections
|
||||
to complete before shutting down.
|
||||
|
||||
This module provides a process that during shutdown will close listeners and wait
|
||||
for connections to complete. It should be placed after other supervised processes that
|
||||
handle cowboy connections.
|
||||
|
||||
## Options
|
||||
|
||||
The following options can be given to the child spec:
|
||||
|
||||
* `:refs` - A list of refs to drain. `:all` is also supported and will drain all cowboy
|
||||
listeners, including those started by means other than `Plug.Cowboy`.
|
||||
|
||||
* `:id` - The ID for the process.
|
||||
Defaults to `Plug.Cowboy.Drainer`.
|
||||
|
||||
* `:shutdown` - How long to wait for connections to drain.
|
||||
Defaults to 5000ms.
|
||||
|
||||
* `:check_interval` - How frequently to check if a listener's
|
||||
connections have been drained. Defaults to 1000ms.
|
||||
|
||||
## Examples
|
||||
|
||||
# In your application
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
{Plug.Cowboy, scheme: :http, plug: MyApp, options: [port: 4040]},
|
||||
{Plug.Cowboy, scheme: :https, plug: MyApp, options: [port: 4041]},
|
||||
{Plug.Cowboy.Drainer, refs: [MyApp.HTTP, MyApp.HTTPS]}
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
@doc false
|
||||
@spec child_spec(opts :: Keyword.t()) :: Supervisor.child_spec()
|
||||
def child_spec(opts) when is_list(opts) do
|
||||
{spec_opts, opts} = Keyword.split(opts, [:id, :shutdown])
|
||||
|
||||
Supervisor.child_spec(
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [opts]},
|
||||
type: :worker
|
||||
},
|
||||
spec_opts
|
||||
)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(opts) do
|
||||
opts
|
||||
|> Keyword.fetch!(:refs)
|
||||
|> validate_refs!()
|
||||
|
||||
GenServer.start_link(__MODULE__, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
def init(opts) do
|
||||
Process.flag(:trap_exit, true)
|
||||
{:ok, opts}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
def terminate(_reason, opts) do
|
||||
opts
|
||||
|> Keyword.fetch!(:refs)
|
||||
|> drain(opts[:check_interval] || opts[:drain_check_interval] || 1_000)
|
||||
end
|
||||
|
||||
defp drain(:all, check_interval) do
|
||||
:ranch.info()
|
||||
|> Enum.map(&elem(&1, 0))
|
||||
|> drain(check_interval)
|
||||
end
|
||||
|
||||
defp drain(refs, check_interval) do
|
||||
refs
|
||||
|> Enum.filter(&suspend_listener/1)
|
||||
|> Enum.each(&wait_for_connections(&1, check_interval))
|
||||
end
|
||||
|
||||
defp suspend_listener(ref) do
|
||||
:ranch.suspend_listener(ref) == :ok
|
||||
end
|
||||
|
||||
defp wait_for_connections(ref, check_interval) do
|
||||
:ranch.wait_for_connections(ref, :==, 0, check_interval)
|
||||
end
|
||||
|
||||
defp validate_refs!(:all), do: :ok
|
||||
defp validate_refs!(refs) when is_list(refs), do: :ok
|
||||
|
||||
defp validate_refs!(refs) do
|
||||
raise ArgumentError,
|
||||
":refs should be :all or a list of references, got: #{inspect(refs)}"
|
||||
end
|
||||
end
|
||||
73
whisper_server/deps/plug_cowboy/lib/plug/cowboy/handler.ex
Normal file
73
whisper_server/deps/plug_cowboy/lib/plug/cowboy/handler.ex
Normal file
@ -0,0 +1,73 @@
|
||||
defmodule Plug.Cowboy.Handler do
|
||||
@moduledoc false
|
||||
@connection Plug.Cowboy.Conn
|
||||
@already_sent {:plug_conn, :sent}
|
||||
|
||||
def init(req, {plug, opts}) do
|
||||
conn = @connection.conn(req)
|
||||
|
||||
try do
|
||||
conn
|
||||
|> plug.call(opts)
|
||||
|> maybe_send(plug)
|
||||
|> case do
|
||||
%Plug.Conn{adapter: {@connection, %{upgrade: {:websocket, websocket_args}} = req}} = conn ->
|
||||
{handler, state, cowboy_opts} = websocket_args
|
||||
{__MODULE__, copy_resp_headers(conn, req), {handler, state}, cowboy_opts}
|
||||
|
||||
%Plug.Conn{adapter: {@connection, req}} ->
|
||||
{:ok, req, {plug, opts}}
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
exit_on_error(kind, reason, __STACKTRACE__, {plug, :call, [conn, opts]})
|
||||
after
|
||||
receive do
|
||||
@already_sent -> :ok
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def upgrade(req, env, __MODULE__, {handler, state}, opts) do
|
||||
:cowboy_websocket.upgrade(req, env, handler.module_info(:module), state, opts)
|
||||
end
|
||||
|
||||
defp copy_resp_headers(%Plug.Conn{} = conn, req) do
|
||||
Enum.reduce(conn.resp_headers, req, fn {key, val}, acc ->
|
||||
:cowboy_req.set_resp_header(key, val, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
defp exit_on_error(
|
||||
:error,
|
||||
%Plug.Conn.WrapperError{kind: kind, reason: reason, stack: stack},
|
||||
_stack,
|
||||
call
|
||||
) do
|
||||
exit_on_error(kind, reason, stack, call)
|
||||
end
|
||||
|
||||
defp exit_on_error(:error, value, stack, call) do
|
||||
exception = Exception.normalize(:error, value, stack)
|
||||
:erlang.raise(:exit, {{exception, stack}, call}, [])
|
||||
end
|
||||
|
||||
defp exit_on_error(:throw, value, stack, call) do
|
||||
:erlang.raise(:exit, {{{:nocatch, value}, stack}, call}, [])
|
||||
end
|
||||
|
||||
defp exit_on_error(:exit, value, _stack, call) do
|
||||
:erlang.raise(:exit, {value, call}, [])
|
||||
end
|
||||
|
||||
defp maybe_send(%Plug.Conn{state: :unset}, _plug), do: raise(Plug.Conn.NotSentError)
|
||||
defp maybe_send(%Plug.Conn{state: :set} = conn, _plug), do: Plug.Conn.send_resp(conn)
|
||||
defp maybe_send(%Plug.Conn{} = conn, _plug), do: conn
|
||||
|
||||
defp maybe_send(other, plug) do
|
||||
raise "Cowboy2 adapter expected #{inspect(plug)} to return Plug.Conn but got: " <>
|
||||
inspect(other)
|
||||
end
|
||||
end
|
||||
123
whisper_server/deps/plug_cowboy/lib/plug/cowboy/translator.ex
Normal file
123
whisper_server/deps/plug_cowboy/lib/plug/cowboy/translator.ex
Normal file
@ -0,0 +1,123 @@
|
||||
defmodule Plug.Cowboy.Translator do
|
||||
@moduledoc false
|
||||
|
||||
# Cowboy 2.12.0 and below error format
|
||||
@doc """
|
||||
The `translate/4` function expected by custom Logger translators.
|
||||
"""
|
||||
def translate(
|
||||
min_level,
|
||||
:error,
|
||||
:format,
|
||||
{~c"Ranch listener" ++ _, [ref, conn_pid, stream_id, stream_pid, reason, stack]}
|
||||
) do
|
||||
extra = [" (connection ", inspect(conn_pid), ", stream id ", inspect(stream_id), ?)]
|
||||
translate_ranch(min_level, ref, extra, stream_pid, reason, stack)
|
||||
end
|
||||
|
||||
# Cowboy 2.13.0 error format
|
||||
def translate(
|
||||
min_level,
|
||||
:error,
|
||||
:format,
|
||||
{~c"Ranch listener" ++ _, [ref, conn_pid, stream_id, stream_pid, {reason, stack}]}
|
||||
) do
|
||||
extra = [" (connection ", inspect(conn_pid), ", stream id ", inspect(stream_id), ?)]
|
||||
translate_ranch(min_level, ref, extra, stream_pid, reason, stack)
|
||||
end
|
||||
|
||||
def translate(_min_level, _level, _kind, _data) do
|
||||
:none
|
||||
end
|
||||
|
||||
## Ranch/Cowboy
|
||||
|
||||
defp translate_ranch(
|
||||
min_level,
|
||||
_ref,
|
||||
extra,
|
||||
pid,
|
||||
{reason, {mod, :call, [%Plug.Conn{} = conn, _opts]}},
|
||||
_stack
|
||||
) do
|
||||
if log_exception?(reason) do
|
||||
message = [
|
||||
inspect(pid),
|
||||
" running ",
|
||||
inspect(mod),
|
||||
extra,
|
||||
" terminated\n",
|
||||
conn_info(min_level, conn)
|
||||
| Exception.format(:exit, reason, [])
|
||||
]
|
||||
|
||||
crash_reason =
|
||||
case reason do
|
||||
{exception, _stack} when is_exception(exception) -> reason
|
||||
{{:nocatch, _value}, _stack} -> reason
|
||||
exit_reason -> {exit_reason, []}
|
||||
end
|
||||
|
||||
metadata =
|
||||
[
|
||||
crash_reason: crash_reason,
|
||||
domain: [:cowboy]
|
||||
] ++ maybe_conn_metadata(conn)
|
||||
|
||||
{:ok, message, metadata}
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_ranch(_min_level, ref, extra, pid, reason, stack) do
|
||||
{:ok,
|
||||
[
|
||||
"Ranch protocol ",
|
||||
inspect(pid),
|
||||
" of listener ",
|
||||
inspect(ref),
|
||||
extra,
|
||||
" terminated\n"
|
||||
| Exception.format_exit({reason, stack})
|
||||
], crash_reason: {reason, stack}, domain: [:cowboy]}
|
||||
end
|
||||
|
||||
defp log_exception?({%{__exception__: true} = exception, _}) do
|
||||
status_ranges =
|
||||
Application.get_env(:plug_cowboy, :log_exceptions_with_status_code, [500..599])
|
||||
|
||||
status = Plug.Exception.status(exception)
|
||||
|
||||
Enum.any?(status_ranges, &(status in &1))
|
||||
end
|
||||
|
||||
defp log_exception?(_), do: true
|
||||
|
||||
defp conn_info(_min_level, conn) do
|
||||
[server_info(conn), request_info(conn)]
|
||||
end
|
||||
|
||||
defp server_info(%Plug.Conn{host: host, port: :undefined, scheme: scheme}) do
|
||||
["Server: ", host, ?\s, ?(, Atom.to_string(scheme), ?), ?\n]
|
||||
end
|
||||
|
||||
defp server_info(%Plug.Conn{host: host, port: port, scheme: scheme}) do
|
||||
["Server: ", host, ":", Integer.to_string(port), ?\s, ?(, Atom.to_string(scheme), ?), ?\n]
|
||||
end
|
||||
|
||||
defp request_info(%Plug.Conn{method: method, query_string: query_string} = conn) do
|
||||
["Request: ", method, ?\s, path_to_iodata(conn.request_path, query_string), ?\n]
|
||||
end
|
||||
|
||||
defp maybe_conn_metadata(conn) do
|
||||
if Application.get_env(:plug_cowboy, :conn_in_exception_metadata, true) do
|
||||
[conn: conn]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp path_to_iodata(path, ""), do: path
|
||||
defp path_to_iodata(path, qs), do: [path, ??, qs]
|
||||
end
|
||||
Reference in New Issue
Block a user