/
http_provider.ex
129 lines (98 loc) · 4.14 KB
/
http_provider.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
defmodule K8s.Client.HTTPProvider do
@moduledoc """
HTTPoison and Jason based `K8s.Client.Behaviour`
"""
@behaviour K8s.Client.Behaviour
alias K8s.Conf.RequestOptions
@impl true
def request(method, url, body, headers, opts) do
{duration, response} = :timer.tc(HTTPoison, :request, [method, url, body, headers, opts])
measurements = %{duration: duration}
metadata = %{method: method}
case handle_response(response) do
{:ok, any} ->
K8s.Sys.Event.http_request_succeeded(measurements, metadata)
{:ok, any}
{:error, any} ->
K8s.Sys.Event.http_request_failed(measurements, metadata)
{:error, any}
end
end
@doc """
Handle HTTPoison responses and errors
## Examples
Parses successful JSON responses:
iex> body = ~s({"foo": "bar"})
...> K8s.Client.HTTPProvider.handle_response({:ok, %HTTPoison.Response{status_code: 200, body: body}})
{:ok, %{"foo" => "bar"}}
Parses successful JSON responses:
iex> body = "line 1\\nline 2\\nline 3\\n"
...> K8s.Client.HTTPProvider.handle_response({:ok, %HTTPoison.Response{status_code: 200, body: body, headers: [{"Content-Type", "text/plain"}]}})
{:ok, "line 1\\nline 2\\nline 3\\n"}
Handles unauthorized responses:
iex> K8s.Client.HTTPProvider.handle_response({:ok, %HTTPoison.Response{status_code: 401}})
{:error, :unauthorized}
Handles not found responses:
iex> K8s.Client.HTTPProvider.handle_response({:ok, %HTTPoison.Response{status_code: 404}})
{:error, :not_found}
Passes through HTTPoison 4xx responses:
iex> K8s.Client.HTTPProvider.handle_response({:ok, %HTTPoison.Response{status_code: 410, body: "Gone"}})
{:error, %HTTPoison.Response{status_code: 410, body: "Gone"}}
Passes through HTTPoison error responses:
iex> K8s.Client.HTTPProvider.handle_response({:error, %HTTPoison.Error{reason: "Foo"}})
{:error, %HTTPoison.Error{reason: "Foo"}}
"""
@impl true
def handle_response(resp) do
case resp do
{:ok, %HTTPoison.Response{status_code: code, body: body, headers: headers}}
when code in 200..299 ->
content_type = List.keyfind(headers, "Content-Type", 0)
{:ok, decode(body, content_type)}
{:ok, %HTTPoison.AsyncResponse{id: ref}} ->
{:ok, ref}
{:ok, %HTTPoison.Response{status_code: 401}} ->
{:error, :unauthorized}
{:ok, %HTTPoison.Response{status_code: 404}} ->
{:error, :not_found}
{:ok, %HTTPoison.Response{status_code: code} = resp} when code in 400..599 ->
{:error, resp}
{:error, %HTTPoison.Error{} = err} ->
{:error, err}
end
end
@doc """
Generates HTTP headers from `K8s.Conf.RequestOptions`
* Adds `{"Accept", "application/json"}` to all requests.
* Adds `Content-Type` base on HTTP method.
## Example
Sets `Content-Type` to `application/merge-patch+json` for PATCH operations
iex> opts = %K8s.Conf.RequestOptions{headers: [{"Authorization", "Basic AF"}]}
...> K8s.Client.HTTPProvider.headers(:patch, opts)
[{"Accept", "application/json"}, {"Content-Type", "application/merge-patch+json"}, {"Authorization", "Basic AF"}]
Sets `Content-Type` to `application/json` for all other operations
iex> opts = %K8s.Conf.RequestOptions{headers: [{"Authorization", "Basic AF"}]}
...> K8s.Client.HTTPProvider.headers(:get, opts)
[{"Accept", "application/json"}, {"Content-Type", "application/json"}, {"Authorization", "Basic AF"}]
"""
@impl true
def headers(method, %RequestOptions{} = opts) do
defaults = [{"Accept", "application/json"}, content_type_header(method)]
defaults ++ opts.headers
end
@spec content_type_header(atom()) :: {binary(), binary()}
defp content_type_header(:patch) do
{"Content-Type", "application/merge-patch+json"}
end
defp content_type_header(_http_method) do
{"Content-Type", "application/json"}
end
@spec decode(binary(), {binary(), binary()} | nil) :: list | map | nil
defp decode(body, {_, "text/plain"}), do: body
defp decode(body, _default_json_decoder) do
case Jason.decode(body) do
{:ok, data} -> data
{:error, _} -> nil
end
end
end