Current section
13 Versions
Jump to
Current section
13 Versions
Compare versions
23
files changed
+1042
additions
-471
deletions
| @@ -5,12 +5,10 @@ | |
| 5 5 | [](https://coveralls.io/github/zhongwencool/maxwell) |
| 6 6 | [](http://hex.pm/packages/maxwell) |
| 7 7 | |
| 8 | - Maxwell is an HTTP client that provides a common interface over many adapters. |
| 8 | + Maxwell is an HTTP client that provides a common interface over [:httpc](http://erlang.org/doc/man/httpc.html), [:ibrowse](https://github.com/cmullaparthi/ibrowse), [:hackney](https://github.com/benoitc/hackney). |
| 9 9 | |
| 10 10 | [Documentation for Maxwell is available online](https://hexdocs.pm/maxwell). |
| 11 11 | |
| 12 | - [See the specific example here](https://gist.github.com/zhongwencool/6cd44df1acd699fc9c7159882ef3b597). |
| 13 | - |
| 14 12 | ## Usage |
| 15 13 | |
| 16 14 | Use `Maxwell.Builder` module to create the API wrappers. |
| @@ -26,7 +24,7 @@ defmodule GitHubClient do | |
| 26 24 | middleware Maxwell.Middleware.Json |
| 27 25 | middleware Maxwell.Middleware.Logger |
| 28 26 | |
| 29 | - adapter Maxwell.Adapter.Hackney # default adapter is Ibrowse |
| 27 | + adapter Maxwell.Adapter.Hackney # default adapter is Maxwell.Adapter.Httpc |
| 30 28 | |
| 31 29 | #List public repositories for the specified user. |
| 32 30 | #:hackney.request(:get, |
| @@ -35,7 +33,7 @@ defmodule GitHubClient do | |
| 35 33 | # [], |
| 36 34 | # [connect_timeout: 3000]) |
| 37 35 | def user_repos(username) do |
| 38 | - url("/users/" <> username <> "/repos") |> get |
| 36 | + put_path("/users/" <> username <> "/repos") |> get |
| 39 37 | end |
| 40 38 | |
| 41 39 | # Edit owner repositories |
| @@ -45,8 +43,9 @@ defmodule GitHubClient do | |
| 45 43 | # "{\"name\":\"name\",\"description\":\"desc\"}", |
| 46 44 | # [connect_timeout: 3000]) |
| 47 45 | def edit_repo_desc(owner, repo, name, desc) do |
| 48 | - url("/repos/#{owner}/#{repo}") |
| 49 | - |> body(%{name: name, description: desc}) |
| 46 | + new |
| 47 | + |> put_path("/repos/#{owner}/#{repo}") |
| 48 | + |> put_req_body(%{name: name, description: desc}) |
| 50 49 | |> patch |
| 51 50 | end |
| 52 51 | end |
| @@ -54,49 +53,44 @@ end | |
| 54 53 | ```ex |
| 55 54 | MIX_ENV=TEST iex -S mix |
| 56 55 | iex(1)> GitHubClient. |
| 57 | - body/1 body/2 edit_repo_desc/4 |
| 58 | - get!/0 get!/1 get/0 |
| 59 | - get/1 headers/1 headers/2 |
| 60 | - multipart/1 multipart/2 opts/1 |
| 61 | - opts/2 patch!/0 patch!/1 |
| 62 | - patch/0 patch/1 query/1 |
| 63 | - query/2 respond_to/1 respond_to/2 |
| 64 | - url/1 url/2 user_repos/1 |
| 56 | + edit_repo_desc/4 get!/0 get!/1 |
| 57 | + get!/2 get/0 get/1 |
| 58 | + patch!/0 patch!/1 patch!/2 |
| 59 | + patch/0 patch/1 user_repos/1 |
| 65 60 | iex(1)> GitHubClient.user_repos("zhongwencool") |
| 66 | - 23:34:56.632 [info] GET https://api.github.com/users/zhongwencool/repos |
| 67 | - <200(723.052ms) |
| 68 | - < |
| 69 | - Access-Control-Allow-Origin:* |
| 70 | - Access-Control-Expose-Headers:ETag, Link, X-GitHub-OTP, X-RateLimit-Limit... |
| 71 | - Cache-Control:public, max-age=60, s-maxage=60 |
| 72 | - Content-Length:137695 |
| 73 | - ...HEADERS... |
| 74 | - <[{"id":48745642,"name":"apns4erl","full_name":"zhongwencool/apns4erl","owner": |
| 75 | - ...BODY... |
| 76 | - ... |
| 61 | + 22:23:42.307 [info] get https://api.github.com <<<200(3085.772ms) |
| 62 | + %Maxwell.Conn{method: :get, opts: [connect_timeout: 3000, recv_timeout: 20000] |
| 63 | + ...(truncated) |
| 64 | + |
| 77 65 | ``` |
| 78 66 | if you don't want to defined a client module: |
| 79 67 | ```ex |
| 80 | - iex(2)> Maxwell.new("http://httpbin.org/drip") |> Maxwell.put_querystring(%{numbytes: 25, duration: 1, delay: 1, code: 200}) |> Maxwell.get |
| 68 | + iex(2)> Maxwell.Conn.new("http://httpbin.org/drip") |> Maxwell.Conn.put_query_string(%{numbytes: 25, duration: 1, delay: 1, code: 200}) |> Maxwell.get |
| 81 69 | {:ok, |
| 82 | - %Maxwell{body: '*************************', |
| 83 | - headers: %{'Access-Control-Allow-Credentials' => 'true', |
| 84 | - 'Access-Control-Allow-Origin' => '*', 'Connection' => 'keep-alive', |
| 85 | - 'Content-Length' => '25', 'Content-Type' => 'application/octet-stream', |
| 86 | - 'Date' => 'Thu, 24 Nov 2016 16:04:21 GMT', 'Server' => 'nginx'}, |
| 87 | - method: :get, opts: [], status: 200, |
| 88 | - url: "http://httpbin.org/drip?code=200&delay=1&duration=1&numbytes=25"}} |
| 70 | + %Maxwell.Conn{method: :get, opts: [], path: "", |
| 71 | + query_string: %{code: 200, delay: 1, duration: 1, numbytes: 25}, |
| 72 | + req_body: nil, req_headers: %{}, resp_body: '*************************', |
| 73 | + resp_headers: %{"access-control-allow-credentials" => {"access-control-allow-credentials", |
| 74 | + "true"}, |
| 75 | + "access-control-allow-origin" => {"access-control-allow-origin", "*"}, |
| 76 | + "connection" => {"connection", "keep-alive"}, |
| 77 | + "content-length" => {"content-length", "25"}, |
| 78 | + "content-type" => {"content-type", "application/octet-stream"}, |
| 79 | + "date" => {"date", "Sun, 18 Dec 2016 14:32:38 GMT"}, |
| 80 | + "server" => {"server", "nginx"}}, state: :sent, status: 200, |
| 81 | + url: "http://httpbin.org/drip"}} |
| 89 82 | ``` |
| 90 | - ### Request helper functions |
| 83 | + ### Maxwell.Conn helper functions |
| 91 84 | ```ex |
| 92 85 | new(request_url_string) |
| 93 | - |> put_querystring(request_query_map) |
| 94 | - |> put_req_headers(request_headers_map) |
| 86 | + |> put_query_string(request_query_map) |
| 87 | + |> put_req_header(request_headers_map) |
| 95 88 | |> put_option(request_opts_keyword_list) |
| 96 89 | |> put_req_body(request_body_term) |
| 97 90 | |> YourClient.{http_method}! |
| 91 | + |> get_resp_body |
| 98 92 | ``` |
| 99 | - For more examples see `h Maxwell.Conn` |
| 93 | + For more examples see `h Maxwell.Conn.XXX` |
| 100 94 | |
| 101 95 | ## Response result |
| 102 96 | ```ex |
| @@ -105,12 +99,93 @@ For more examples see `h Maxwell.Conn` | |
| 105 99 | resp_headers: reponse_headers_map, |
| 106 100 | status: reponse_http_status_integer, |
| 107 101 | resp_body: reponse_body_term, |
| 108 | - opts: request_opts_keyword_list, |
| 109 102 | url: request_urlwithquery_string, |
| 110 103 | }} |
| 111 104 | |
| 112 105 | # or |
| 113 | - {:error, reason_term} |
| 106 | + {:error, reason_term, conn} |
| 107 | + |
| 108 | + ``` |
| 109 | + |
| 110 | + ## Request Examples |
| 111 | + ```ex |
| 112 | + defmodule Client do |
| 113 | + #generate 4 function get/1, get!/1 post/1 post!/1 function |
| 114 | + use Maxwell.Builder, ~w(get post)a |
| 115 | + |
| 116 | + middleware Maxwell.Middleware.BaseUrl, "http://httpbin.org" |
| 117 | + middleware Maxwell.Middleware.Headers, %{"Content-Type" => "application/json"} |
| 118 | + middleware Maxwell.Middleware.Opts, [connect_timeout: 5000, recv_timeout: 10000] |
| 119 | + middleware Maxwell.Middleware.Json |
| 120 | + middleware Maxwell.Middleware.Logger |
| 121 | + |
| 122 | + adapter Maxwell.Adapter.Hackney |
| 123 | + |
| 124 | + @doc """ |
| 125 | + Simple get request |
| 126 | + Get origin ip |
| 127 | + """ |
| 128 | + def get_ip() do |
| 129 | + new |
| 130 | + |> put_path("/ip") |
| 131 | + |> get! |
| 132 | + |> get_resp_body("origin") |
| 133 | + end |
| 134 | + |
| 135 | + @doc """ |
| 136 | + Post whole file once |
| 137 | + ###Example |
| 138 | + Client.post_file_once("./mix.exs") |
| 139 | + """ |
| 140 | + def post_file_once(filepath) do |
| 141 | + new |
| 142 | + |> put_path("/post") |
| 143 | + |> put_req_body({:file, filepath}) |
| 144 | + |> post! |
| 145 | + |> get_resp_body("data") |
| 146 | + end |
| 147 | + |
| 148 | + @doc """ |
| 149 | + Post whole file by chunked |
| 150 | + ###Example |
| 151 | + Client.post_file_chunked("./mix.exs") |
| 152 | + """ |
| 153 | + def post_file_chunked(filepath) do |
| 154 | + new |
| 155 | + |> put_path("/post") |
| 156 | + |> put_req_header("transfer_encoding", "chunked") |
| 157 | + |> put_req_body({:file, filepath}) |
| 158 | + |> post! |
| 159 | + |> get_resp_body("data") |
| 160 | + end |
| 161 | + |
| 162 | + @doc """ |
| 163 | + Post by stream |
| 164 | + ###Example |
| 165 | + ["1", "2", "3"] |> Stream.map(fn(x) -> List.duplicate(x, 2) end) |> Client.post_stream |
| 166 | + """ |
| 167 | + def post_stream(stream) do |
| 168 | + new |
| 169 | + |> put_path("/post") |
| 170 | + |> put_req_body(stream) |
| 171 | + |> post! |
| 172 | + |> get_resp_body("data") |
| 173 | + end |
| 174 | + |
| 175 | + @doc """ |
| 176 | + Post multipart form |
| 177 | + ###Example |
| 178 | + Client.post_multipart_form({:multipart, [{:file, "./mix.exs"}]}) |
| 179 | + """ |
| 180 | + def post_multipart_form(multipart) do |
| 181 | + new |
| 182 | + |> put_path("/post") |
| 183 | + |> put_req_body(multipart) |
| 184 | + |> post! |
| 185 | + |> get_resp_body("data") |
| 186 | + end |
| 187 | + |
| 188 | + end |
| 114 189 | |
| 115 190 | ``` |
| 116 191 | |
| @@ -119,25 +194,37 @@ For more examples see `h Maxwell.Conn` | |
| 119 194 | 1. Add maxwell to your list of dependencies in `mix.exs`: |
| 120 195 | ```ex |
| 121 196 | def deps do |
| 122 | - [{:maxwell, "~> 2.0.0"}] |
| 197 | + [{:maxwell, "~> 2.1.0"}] |
| 123 198 | end |
| 124 199 | ``` |
| 125 200 | 2. Ensure maxwell has started before your application: |
| 126 201 | ```ex |
| 127 202 | def application do |
| 128 | - [applications: [:maxwell]] # **also add your adapter(ibrowse,hackney...) here ** |
| 203 | + [applications: [:maxwell]] # **also add your adapter(ibrowse,hackney) here ** |
| 129 204 | end |
| 130 205 | ``` |
| 131 206 | ## Adapters |
| 132 207 | |
| 133 208 | Maxwell has support for different adapters that do the actual HTTP request processing. |
| 134 209 | |
| 210 | + ### httpc |
| 211 | + |
| 212 | + Maxwell has built-in support for [httpc](http://erlang.org/doc/man/httpc.html) Erlang HTTP client. |
| 213 | + |
| 214 | + To use it simply include `adapter Maxwell.Adapter.Httpc` line in your API client definition. |
| 215 | + Setting global default adapter |
| 216 | + |
| 217 | + ```ex |
| 218 | + config :maxwell, |
| 219 | + default_adapter: Maxwell.Adapter.Httpc |
| 220 | + ``` |
| 221 | + |
| 135 222 | ### ibrowse |
| 136 223 | |
| 137 224 | Maxwell has built-in support for [ibrowse](https://github.com/cmullaparthi/ibrowse) Erlang HTTP client. |
| 138 225 | |
| 139 226 | To use it simply include `adapter Maxwell.Adapter.Ibrowse` line in your API client definition. |
| 140 | - global default adapter |
| 227 | + Setting global default adapter |
| 141 228 | |
| 142 229 | ```ex |
| 143 230 | config :maxwell, |
| @@ -150,7 +237,7 @@ NOTE: Remember to include `:ibrowse` in applications list. | |
| 150 237 | Maxwell has built-in support for [hackney](https://github.com/benoitc/hackney) Erlang HTTP client. |
| 151 238 | |
| 152 239 | To use it simply include `adapter Maxwell.Adapter.Hackney` line in your API client definition. |
| 153 | - global default adapter |
| 240 | + Setting global default adapter |
| 154 241 | |
| 155 242 | ```ex |
| 156 243 | config :maxwell, |
| @@ -182,10 +269,6 @@ See [Maxwell.Middleware.BaseUrl](https://github.com/zhongwencool/maxwell/blob/ma | |
| 182 269 | |
| 183 270 | ## TODO |
| 184 271 | |
| 185 | - * Support stream |
| 186 | - * Support multipart |
| 187 | - * More clear readme |
| 188 | - |
| 189 272 | ## Test |
| 190 273 | ```ex |
| 191 274 | mix test |
| @@ -1,10 +1,11 @@ | |
| 1 1 | {<<"app">>,<<"maxwell">>}. |
| 2 2 | {<<"build_tools">>,[<<"mix">>]}. |
| 3 3 | {<<"description">>,<<"Maxwell is an HTTP client adapter.">>}. |
| 4 | - {<<"elixir">>,<<"~> 1.2">>}. |
| 4 | + {<<"elixir">>,<<"~> 1.3">>}. |
| 5 5 | {<<"files">>, |
| 6 6 | [<<"lib/LICENSE">>,<<"lib/maxwell.ex">>,<<"lib/maxwell/adapter/adapter.ex">>, |
| 7 | - <<"lib/maxwell/adapter/hackney.ex">>,<<"lib/maxwell/adapter/ibrowse.ex">>, |
| 7 | + <<"lib/maxwell/adapter/hackney.ex">>,<<"lib/maxwell/adapter/httpc.ex">>, |
| 8 | + <<"lib/maxwell/adapter/ibrowse.ex">>,<<"lib/maxwell/adapter/util.ex">>, |
| 8 9 | <<"lib/maxwell/builder.ex">>,<<"lib/maxwell/builder/adapter.ex">>, |
| 9 10 | <<"lib/maxwell/builder/middleware.ex">>,<<"lib/maxwell/builder/util.ex">>, |
| 10 11 | <<"lib/maxwell/conn.ex">>,<<"lib/maxwell/error.ex">>, |
| @@ -13,7 +14,8 @@ | |
| 13 14 | <<"lib/maxwell/middleware/logger.ex">>, |
| 14 15 | <<"lib/maxwell/middleware/middleware.ex">>, |
| 15 16 | <<"lib/maxwell/middleware/opts.ex">>,<<"lib/maxwell/middleware/rels.ex">>, |
| 16 | - <<"lib/maxwell/multipart.ex">>,<<"LICENSE">>,<<"mix.exs">>,<<"README.md">>]}. |
| 17 | + <<"lib/maxwell/multipart.ex">>,<<"lib/test.ex">>,<<"LICENSE">>, |
| 18 | + <<"mix.exs">>,<<"README.md">>]}. |
| 17 19 | {<<"licenses">>,[<<"MIT">>]}. |
| 18 20 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/zhongwencool/maxwell">>}]}. |
| 19 21 | {<<"maintainers">>,[<<"zhongwencool">>]}. |
| @@ -35,4 +37,4 @@ | |
| 35 37 | {<<"name">>,<<"hackney">>}, |
| 36 38 | {<<"optional">>,true}, |
| 37 39 | {<<"requirement">>,<<"~> 1.6">>}]]}. |
| 38 | - {<<"version">>,<<"2.0.0">>}. |
| 40 | + {<<"version">>,<<"2.1.0">>}. |
| @@ -5,14 +5,14 @@ defmodule Maxwell do | |
| 5 5 | There are two kind of usages: Basic Usage and Advanced Middleware Usage. |
| 6 6 | |
| 7 7 | ### Basic Usage |
| 8 | - ```ex |
| 9 | - ## Returns Origin IP, for example %{"origin" => "127.0.0.1"} |
| 10 | - Maxwell.Conn.new |
| 11 | - |> Maxwell.Conn.put_url("http://httpbin.org/ip") |
| 12 | - |> Maxwell.get! |
| 13 | - |> Maxwell.get_resp_body |
| 14 | - |> Poison.decode! |
| 15 | - ``` |
| 8 | + |
| 9 | + ## Returns Origin IP, for example %{"origin" => "127.0.0.1"} |
| 10 | + Maxwell.Conn.new |
| 11 | + |> Maxwell.Conn.put_url("http://httpbin.org/ip") |
| 12 | + |> Maxwell.get! |
| 13 | + |> Maxwell.Conn.get_resp_body |
| 14 | + |> Poison.decode! |
| 15 | + |
| 16 16 | Find all `get_*&put_*` helper functions by `h Maxwell.Conn.xxx` |
| 17 17 | |
| 18 18 | ### Advanced Middleware Usage(Create API Client). |
| @@ -1,11 +1,82 @@ | |
| 1 1 | defmodule Maxwell.Adapter do |
| 2 2 | @moduledoc """ |
| 3 | - Define adapter behaviour |
| 4 | - ## Examples |
| 5 | - See `Maxwell.Adapter.Ibrowse` |
| 3 | + Define adapter behaviour. |
| 4 | + |
| 5 | + ### Examples |
| 6 | + See `Maxwell.Adapter.Ibrowse`. |
| 6 7 | """ |
| 8 | + |
| 9 | + defmacro __using__(_opts) do |
| 10 | + quote location: :keep do |
| 11 | + @behaviour Maxwell.Adapter |
| 12 | + |
| 13 | + alias Maxwell.Conn |
| 14 | + alias Maxwell.Adapter.Util |
| 15 | + |
| 16 | + @doc false |
| 17 | + def call(conn) do |
| 18 | + case conn.req_body do |
| 19 | + {:multipart, _} -> send_multipart(conn) |
| 20 | + {:file, _} -> send_file(conn) |
| 21 | + %Stream{} -> send_stream(conn) |
| 22 | + _ -> send_direct(conn) |
| 23 | + end |
| 24 | + end |
| 25 | + |
| 26 | + @doc """ |
| 27 | + Send request without chang it's body formant. |
| 28 | + |
| 29 | + * `conn` - `%Maxwell.Conn{}` |
| 30 | + |
| 31 | + Returns `{:ok, %Maxwell.Conn{}}` or `{:error, reason_term, %Maxwell.Conn{}}`. |
| 32 | + """ |
| 33 | + def send_direct(conn) do |
| 34 | + raise Maxwell.Error, {__MODULE__, "#{__MODULE__} Adapter doesn't implement send_direct/1", conn} |
| 35 | + end |
| 36 | + |
| 37 | + @doc """ |
| 38 | + Send multipart form request. |
| 39 | + |
| 40 | + * `conn` - `%Maxwell.Conn{}`, the req_body is `{:multipart, form_list}` |
| 41 | + see `Maxwell.Multipart.encode_form/2` for form_list |
| 42 | + |
| 43 | + Returns `{:ok, %Maxwell.Conn{}}` or `{:error, reason_term, %Maxwell.Conn{}}`. |
| 44 | + """ |
| 45 | + def send_multipart(conn) do |
| 46 | + raise Maxwell.Error, {__MODULE__, "#{__MODULE__} Adapter doesn't implement send_multipart/1", conn} |
| 47 | + end |
| 48 | + |
| 49 | + @doc """ |
| 50 | + Send file request. |
| 51 | + |
| 52 | + * `conn` - `%Maxwell.Conn{}`, the req_body is `{:file, filepath}`. |
| 53 | + Auto change to chunked mode if req_headers has `%{"transfer-encoding" => "chunked"` |
| 54 | + |
| 55 | + Returns `{:ok, %Maxwell.Conn{}}` or `{:error, reason_term, %Maxwell.Conn{}}`. |
| 56 | + """ |
| 57 | + def send_file(conn) do |
| 58 | + raise Maxwell.Error, {__MODULE__, "#{__MODULE__} Adapter doesn't implement send_file/1", conn} |
| 59 | + end |
| 60 | + |
| 61 | + @doc """ |
| 62 | + Send stream request. |
| 63 | + |
| 64 | + * `conn` - `%Maxwell.Conn{}`, the req_body is `Stream`. |
| 65 | + Always chunked mode |
| 66 | + |
| 67 | + Returns `{:ok, %Maxwell.Conn{}}` or `{:error, reason_term, %Maxwell.Conn{}}`. |
| 68 | + """ |
| 69 | + def send_stream(conn) do |
| 70 | + raise Maxwell.Error, {__MODULE__, "#{__MODULE__} Adapter doesn't implement send_stream/1", conn} |
| 71 | + end |
| 72 | + |
| 73 | + defoverridable [send_direct: 1, send_multipart: 1, send_file: 1, send_stream: 1] end |
| 74 | + end |
| 75 | + |
| 7 76 | @type return_t :: {:ok, Maxwell.Conn.t} | {:error, any, Maxwell.Conn.t} |
| 8 | - @callback call(Maxwell.Conn.t) :: return_t |
| 77 | + @callback send_direct(Maxwell.Conn.t) :: return_t |
| 78 | + @callback send_multipart(Maxwell.Conn.t) :: return_t |
| 79 | + @callback send_file(Maxwell.Conn.t) :: return_t |
| 9 80 | |
| 10 81 | end |
| @@ -1,45 +1,53 @@ | |
| 1 1 | if Code.ensure_loaded?(:hackney) do |
| 2 2 | defmodule Maxwell.Adapter.Hackney do |
| 3 | - @behaviour Maxwell.Adapter |
| 4 3 | @moduledoc """ |
| 5 4 | [`hackney`](https://github.com/benoitc/hackney) adapter |
| 6 5 | """ |
| 6 | + use Maxwell.Adapter |
| 7 7 | |
| 8 | - @doc """ |
| 9 | - * `conn` - `%Maxwell.Conn{}` |
| 10 | - |
| 11 | - Returns `{:ok, %Maxwell.Conn{}}` or `{:error, reason_term, %Maxwell.Conn{}}`. |
| 12 | - """ |
| 13 | - def call(conn) do |
| 14 | - conn |
| 15 | - |> send_req |
| 16 | - |> format_response(conn) |
| 8 | + def send_direct(conn) do |
| 9 | + %Conn{url: url, req_headers: req_headers, |
| 10 | + path: path,method: method, query_string: query_string, |
| 11 | + opts: opts, req_body: req_body} = conn |
| 12 | + url = Util.url_serialize(url, path, query_string) |
| 13 | + req_headers = Util.header_serialize(req_headers) |
| 14 | + result = :hackney.request(method, url, req_headers, req_body || "", opts) |
| 15 | + format_response(result, conn) |
| 17 16 | end |
| 18 17 | |
| 19 | - defp send_req(%Maxwell.Conn{url: url, req_headers: req_headers, |
| 20 | - path: path,method: method, query_string: query_string, |
| 21 | - opts: opts, req_body: req_body}) do |
| 22 | - req_headers = req_headers |> Map.to_list |
| 23 | - req_body = req_body || "" |
| 24 | - url = url |> Maxwell.Conn.append_query_string(path, query_string) |
| 25 | - :hackney.request(method, url, req_headers, req_body, opts) |
| 18 | + def send_multipart(conn), do: send_direct(conn) |
| 19 | + |
| 20 | + def send_file(conn), do: send_direct(conn) |
| 21 | + |
| 22 | + def send_stream(conn) do |
| 23 | + %Conn{url: url, req_headers: req_headers, |
| 24 | + path: path,method: method, query_string: query_string, |
| 25 | + opts: opts, req_body: req_body} = conn |
| 26 | + url = Util.url_serialize(url, path, query_string) |
| 27 | + req_headers = Util.header_serialize(req_headers) |
| 28 | + with {:ok, ref} <- :hackney.request(method, url, req_headers, :stream, opts) do |
| 29 | + for data <- req_body, do: :ok = :hackney.send_body(ref, data) |
| 30 | + ref |> :hackney.start_response |> format_response(conn) |
| 31 | + else |
| 32 | + error -> format_response(error, conn) |
| 33 | + end |
| 26 34 | end |
| 27 35 | |
| 28 36 | defp format_response({:ok, status, headers, body}, conn) when is_binary(body) do |
| 29 | - {:ok, %{conn | status: status, |
| 30 | - resp_headers: headers|> :maps.from_list, |
| 31 | - req_body: nil, |
| 32 | - state: :sent, |
| 37 | + headers = for {key, value} <- headers, into: %{} do |
| 38 | + down_key = key |> to_string |> String.downcase |
| 39 | + {down_key, {key, to_string(value)}} |
| 40 | + end |
| 41 | + {:ok, %{conn | status: status, |
| 42 | + resp_headers: headers, |
| 43 | + req_body: nil, |
| 44 | + state: :sent, |
| 33 45 | resp_body: body}} |
| 34 46 | end |
| 35 47 | defp format_response({:ok, status, headers, body}, conn) do |
| 36 | - with {:ok, body} <- :hackney.body(body) do |
| 37 | - {:ok, |
| 38 | - %{conn |status: status, |
| 39 | - resp_headers: headers|> :maps.from_list, |
| 40 | - req_body: nil, |
| 41 | - state: :sent, |
| 42 | - resp_body: body}} |
| 48 | + case :hackney.body(body) do |
| 49 | + {:ok, body} -> format_response({:ok, status, headers, body}, conn) |
| 50 | + {:error, _reason} = error -> format_response(error, conn) |
| 43 51 | end |
| 44 52 | end |
| 45 53 | defp format_response({:ok, status, headers}, conn) do |
Loading more files…