cat-bookmarker/deps/plug_cowboy/lib/plug/cowboy/conn.ex

169 lines
4.3 KiB
Elixir

defmodule Plug.Cowboy.Conn do
@behaviour Plug.Conn.Adapter
@moduledoc false
def conn(req) do
%{
path: path,
host: host,
port: port,
method: method,
headers: headers,
qs: qs,
peer: {remote_ip, _}
} = req
%Plug.Conn{
adapter: {__MODULE__, req},
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
headers = to_headers_map(headers)
status = Integer.to_string(status) <> " " <> Plug.Conn.Status.reason_phrase(status)
req = :cowboy_req.reply(status, headers, body, req)
{: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}
headers = to_headers_map(headers)
req = :cowboy_req.reply(status, headers, body, req)
{:ok, nil, req}
end
@impl true
def send_chunked(req, status, headers) do
headers = to_headers_map(headers)
req = :cowboy_req.stream_reply(status, headers, req)
{: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
:cowboy_req.push(path, to_headers_map(headers), 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(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