conn.ex (4397B)
1 defmodule Plug.Cowboy.Conn do 2 @behaviour Plug.Conn.Adapter 3 @moduledoc false 4 5 def conn(req) do 6 %{ 7 path: path, 8 host: host, 9 port: port, 10 method: method, 11 headers: headers, 12 qs: qs, 13 peer: {remote_ip, _} 14 } = req 15 16 %Plug.Conn{ 17 adapter: {__MODULE__, req}, 18 host: host, 19 method: method, 20 owner: self(), 21 path_info: split_path(path), 22 port: port, 23 remote_ip: remote_ip, 24 query_string: qs, 25 req_headers: to_headers_list(headers), 26 request_path: path, 27 scheme: String.to_atom(:cowboy_req.scheme(req)) 28 } 29 end 30 31 @impl true 32 def send_resp(req, status, headers, body) do 33 headers = to_headers_map(headers) 34 status = Integer.to_string(status) <> " " <> Plug.Conn.Status.reason_phrase(status) 35 req = :cowboy_req.reply(status, headers, body, req) 36 {:ok, nil, req} 37 end 38 39 @impl true 40 def send_file(req, status, headers, path, offset, length) do 41 %File.Stat{type: :regular, size: size} = File.stat!(path) 42 43 length = 44 cond do 45 length == :all -> size 46 is_integer(length) -> length 47 end 48 49 body = {:sendfile, offset, length, path} 50 headers = to_headers_map(headers) 51 req = :cowboy_req.reply(status, headers, body, req) 52 {:ok, nil, req} 53 end 54 55 @impl true 56 def send_chunked(req, status, headers) do 57 headers = to_headers_map(headers) 58 req = :cowboy_req.stream_reply(status, headers, req) 59 {:ok, nil, req} 60 end 61 62 @impl true 63 def chunk(req, body) do 64 :cowboy_req.stream_body(body, :nofin, req) 65 end 66 67 @impl true 68 def read_req_body(req, opts) do 69 length = Keyword.get(opts, :length, 8_000_000) 70 read_length = Keyword.get(opts, :read_length, 1_000_000) 71 read_timeout = Keyword.get(opts, :read_timeout, 15_000) 72 73 opts = %{length: read_length, period: read_timeout} 74 read_req_body(req, opts, length, []) 75 end 76 77 defp read_req_body(req, opts, length, acc) when length >= 0 do 78 case :cowboy_req.read_body(req, opts) do 79 {:ok, data, req} -> {:ok, IO.iodata_to_binary([acc | data]), req} 80 {:more, data, req} -> read_req_body(req, opts, length - byte_size(data), [acc | data]) 81 end 82 end 83 84 defp read_req_body(req, _opts, _length, acc) do 85 {:more, IO.iodata_to_binary(acc), req} 86 end 87 88 @impl true 89 def inform(req, status, headers) do 90 :cowboy_req.inform(status, to_headers_map(headers), req) 91 end 92 93 @impl true 94 def upgrade(req, :websocket, args) do 95 case args do 96 {handler, _state, cowboy_opts} when is_atom(handler) and is_map(cowboy_opts) -> 97 :ok 98 99 _ -> 100 raise ArgumentError, 101 "expected websocket upgrade on Cowboy to be on the format {handler :: atom(), arg :: term(), opts :: map()}, got: " <> 102 inspect(args) 103 end 104 105 {:ok, Map.put(req, :upgrade, {:websocket, args})} 106 end 107 108 def upgrade(_req, _protocol, _args), do: {:error, :not_supported} 109 110 @impl true 111 def push(req, path, headers) do 112 opts = 113 case {req.port, req.sock} do 114 {:undefined, {_, port}} -> %{port: port} 115 {port, _} when port in [80, 443] -> %{} 116 {port, _} -> %{port: port} 117 end 118 119 :cowboy_req.push(path, to_headers_map(headers), req, opts) 120 end 121 122 @impl true 123 def get_peer_data(%{peer: {ip, port}, cert: cert}) do 124 %{ 125 address: ip, 126 port: port, 127 ssl_cert: if(cert == :undefined, do: nil, else: cert) 128 } 129 end 130 131 @impl true 132 def get_http_protocol(req) do 133 :cowboy_req.version(req) 134 end 135 136 ## Helpers 137 138 defp to_headers_list(headers) when is_list(headers) do 139 headers 140 end 141 142 defp to_headers_list(headers) when is_map(headers) do 143 :maps.to_list(headers) 144 end 145 146 defp to_headers_map(headers) when is_list(headers) do 147 # Group set-cookie headers into a list for a single `set-cookie` 148 # key since cowboy 2 requires headers as a map. 149 Enum.reduce(headers, %{}, fn 150 {key = "set-cookie", value}, acc -> 151 case acc do 152 %{^key => existing} -> %{acc | key => [value | existing]} 153 %{} -> Map.put(acc, key, [value]) 154 end 155 156 {key, value}, acc -> 157 case acc do 158 %{^key => existing} -> %{acc | key => existing <> ", " <> value} 159 %{} -> Map.put(acc, key, value) 160 end 161 end) 162 end 163 164 defp split_path(path) do 165 segments = :binary.split(path, "/", [:global]) 166 for segment <- segments, segment != "", do: segment 167 end 168 end