whisper server elixir

This commit is contained in:
2025-07-15 14:39:51 +00:00
parent 9fa14c348f
commit d4345eef8b
7680 changed files with 5447719 additions and 0 deletions

View 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

View 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

View 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

View 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