zf

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

conn.ex (6623B)


      1 defmodule Plug.Adapters.Test.Conn do
      2   @behaviour Plug.Conn.Adapter
      3   @moduledoc false
      4 
      5   ## Test helpers
      6 
      7   def conn(conn, method, uri, body_or_params) do
      8     maybe_flush()
      9     uri = URI.parse(uri)
     10 
     11     if is_binary(uri.path) and not String.starts_with?(uri.path, "/") do
     12       # TODO: Convert to an error
     13       IO.warn("the URI path used in plug tests must start with \"/\", got: #{inspect(uri.path)}")
     14     end
     15 
     16     method = method |> to_string |> String.upcase()
     17     query = uri.query || ""
     18     owner = self()
     19 
     20     {params, {body, body_params}, {query, query_params}, req_headers} =
     21       body_or_params(body_or_params, query, conn.req_headers, method)
     22 
     23     state = %{
     24       method: method,
     25       params: params,
     26       req_body: body,
     27       chunks: nil,
     28       ref: make_ref(),
     29       owner: owner,
     30       http_protocol: get_from_adapter(conn, :get_http_protocol, :"HTTP/1.1"),
     31       peer_data:
     32         get_from_adapter(conn, :get_peer_data, %{
     33           address: {127, 0, 0, 1},
     34           port: 111_317,
     35           ssl_cert: nil
     36         })
     37     }
     38 
     39     %Plug.Conn{
     40       conn
     41       | adapter: {__MODULE__, state},
     42         host: uri.host || conn.host || "www.example.com",
     43         method: method,
     44         owner: owner,
     45         path_info: split_path(uri.path),
     46         port: uri.port || 80,
     47         remote_ip: conn.remote_ip || {127, 0, 0, 1},
     48         req_headers: req_headers,
     49         request_path: uri.path,
     50         query_string: query,
     51         query_params: query_params || %Plug.Conn.Unfetched{aspect: :query_params},
     52         body_params: body_params || %Plug.Conn.Unfetched{aspect: :body_params},
     53         params: params || %Plug.Conn.Unfetched{aspect: :params},
     54         scheme: (uri.scheme || "http") |> String.downcase() |> String.to_atom()
     55     }
     56   end
     57 
     58   ## Connection adapter
     59 
     60   def send_resp(%{method: "HEAD"} = state, status, headers, _body) do
     61     do_send(state, status, headers, "")
     62   end
     63 
     64   def send_resp(state, status, headers, body) do
     65     do_send(state, status, headers, IO.iodata_to_binary(body))
     66   end
     67 
     68   def send_file(%{method: "HEAD"} = state, status, headers, _path, _offset, _length) do
     69     do_send(state, status, headers, "")
     70   end
     71 
     72   def send_file(state, status, headers, path, offset, length) do
     73     %File.Stat{type: :regular, size: size} = File.stat!(path)
     74 
     75     length =
     76       cond do
     77         length == :all -> size
     78         is_integer(length) -> length
     79       end
     80 
     81     {:ok, data} =
     82       File.open!(path, [:read, :binary], fn device ->
     83         :file.pread(device, offset, length)
     84       end)
     85 
     86     do_send(state, status, headers, data)
     87   end
     88 
     89   def send_chunked(state, _status, _headers), do: {:ok, "", %{state | chunks: ""}}
     90 
     91   def chunk(%{method: "HEAD"} = state, _body), do: {:ok, "", state}
     92 
     93   def chunk(%{chunks: chunks} = state, body) do
     94     body = chunks <> IO.iodata_to_binary(body)
     95     {:ok, body, %{state | chunks: body}}
     96   end
     97 
     98   defp do_send(%{owner: owner, ref: ref} = state, status, headers, body) do
     99     send(owner, {ref, {status, headers, body}})
    100     {:ok, body, state}
    101   end
    102 
    103   def read_req_body(%{req_body: body} = state, opts \\ []) do
    104     size = min(byte_size(body), Keyword.get(opts, :length, 8_000_000))
    105     data = :binary.part(body, 0, size)
    106     rest = :binary.part(body, size, byte_size(body) - size)
    107 
    108     tag =
    109       case rest do
    110         "" -> :ok
    111         _ -> :more
    112       end
    113 
    114     {tag, data, %{state | req_body: rest}}
    115   end
    116 
    117   def inform(%{owner: owner, ref: ref}, status, headers) do
    118     send(owner, {ref, :inform, {status, headers}})
    119     :ok
    120   end
    121 
    122   def upgrade(%{owner: owner, ref: ref}, :not_supported = protocol, opts) do
    123     send(owner, {ref, :upgrade, {protocol, opts}})
    124     {:error, :not_supported}
    125   end
    126 
    127   def upgrade(%{owner: owner, ref: ref} = state, protocol, opts) do
    128     send(owner, {ref, :upgrade, {protocol, opts}})
    129     {:ok, state}
    130   end
    131 
    132   def push(%{owner: owner, ref: ref}, path, headers) do
    133     send(owner, {ref, :push, {path, headers}})
    134     :ok
    135   end
    136 
    137   def get_peer_data(payload) do
    138     Map.fetch!(payload, :peer_data)
    139   end
    140 
    141   def get_http_protocol(payload) do
    142     Map.fetch!(payload, :http_protocol)
    143   end
    144 
    145   ## Private helpers
    146 
    147   defp get_from_adapter(conn, op, default) do
    148     case conn.adapter do
    149       {Plug.MissingAdapter, _} -> default
    150       {adapter, payload} -> apply(adapter, op, [payload])
    151     end
    152   end
    153 
    154   defp body_or_params(nil, query, headers, _method), do: {nil, {"", nil}, {query, nil}, headers}
    155 
    156   defp body_or_params(body, query, headers, _method) when is_binary(body) do
    157     {nil, {body, nil}, {query, nil}, headers}
    158   end
    159 
    160   defp body_or_params(params, query, headers, method) when is_list(params) do
    161     body_or_params(Enum.into(params, %{}), query, headers, method)
    162   end
    163 
    164   defp body_or_params(params, query, headers, method)
    165        when is_map(params) and method in ["GET", "HEAD"] do
    166     params = stringify_params(params, &to_string/1)
    167 
    168     from_query = Plug.Conn.Query.decode(query)
    169     params = Map.merge(from_query, params)
    170 
    171     query =
    172       params
    173       |> Map.merge(from_query)
    174       |> Plug.Conn.Query.encode()
    175 
    176     {params, {"", nil}, {query, params}, headers}
    177   end
    178 
    179   defp body_or_params(params, query, headers, _method) when is_map(params) do
    180     content_type_header = {"content-type", "multipart/mixed; boundary=plug_conn_test"}
    181     content_type = List.keyfind(headers, "content-type", 0, content_type_header)
    182     headers = List.keystore(headers, "content-type", 0, content_type)
    183 
    184     body_params = stringify_params(params, & &1)
    185     query_params = Plug.Conn.Query.decode(query)
    186     params = Map.merge(query_params, body_params)
    187 
    188     {params, {"--plug_conn_test--", body_params}, {query, query_params}, headers}
    189   end
    190 
    191   defp stringify_params([{_, _} | _] = params, value_fun),
    192     do: Enum.into(params, %{}, &stringify_kv(&1, value_fun))
    193 
    194   defp stringify_params([_ | _] = params, value_fun),
    195     do: Enum.map(params, &stringify_params(&1, value_fun))
    196 
    197   defp stringify_params(%{__struct__: mod} = struct, _value_fun) when is_atom(mod), do: struct
    198   defp stringify_params(fun, _value_fun) when is_function(fun), do: fun
    199 
    200   defp stringify_params(%{} = params, value_fun),
    201     do: Enum.into(params, %{}, &stringify_kv(&1, value_fun))
    202 
    203   defp stringify_params(other, value_fun), do: value_fun.(other)
    204 
    205   defp stringify_kv({k, v}, value_fun), do: {to_string(k), stringify_params(v, value_fun)}
    206 
    207   defp split_path(nil), do: []
    208 
    209   defp split_path(path) do
    210     segments = :binary.split(path, "/", [:global])
    211     for segment <- segments, segment != "", do: segment
    212   end
    213 
    214   @already_sent {:plug_conn, :sent}
    215 
    216   defp maybe_flush() do
    217     receive do
    218       @already_sent -> :ok
    219     after
    220       0 -> :ok
    221     end
    222   end
    223 end