Packages
nerves
1.3.1
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/nerves/utils/http_client.ex
defmodule Nerves.Utils.HTTPClient do
use GenServer
@progress_steps 50
@redirect_status_codes [301, 302, 303, 307, 308]
def start_link() do
{:ok, _} = Application.ensure_all_started(:nerves)
start_httpc()
GenServer.start_link(__MODULE__, [])
end
def stop(pid) do
GenServer.stop(pid)
end
def get(_, _, _ \\ [])
def get(_pid, %URI{host: nil, path: path}, _opts) do
path
|> Path.expand()
|> File.read()
end
def get(pid, %URI{} = uri, opts) do
url =
uri
|> URI.to_string()
|> URI.encode()
|> String.replace("+", "%2B")
get(pid, url, opts)
end
def get(pid, url, opts), do: GenServer.call(pid, {:get, url, opts}, :infinity)
def init([]) do
{:ok,
%{
url: nil,
content_length: 0,
buffer: "",
buffer_size: 0,
filename: "",
caller: nil,
number_of_redirects: 0,
progress?: true,
opts: []
}}
end
def handle_call({:get, _url, _opts}, _from, %{number_of_redirects: n} = s) when n > 5 do
GenServer.reply(s.caller, {:error, :too_many_redirects})
{:noreply, %{s | url: nil, number_of_redirects: 0, caller: nil}}
end
def handle_call({:get, url, opts}, from, s) do
progress? = Keyword.get(opts, :progress?, true)
headers =
Keyword.get(opts, :headers, [])
|> Enum.map(fn {k, v} ->
{to_charlist(k), to_charlist(v)}
end)
headers = [
{'User-Agent', 'Nerves HTTP Client #{Nerves.version()}'},
{'Content-Type', 'application/octet-stream'} | headers
]
http_opts =
[timeout: :infinity, autoredirect: false] ++
Nerves.Utils.Proxy.config(url) ++ Keyword.get(opts, :http_opts, [])
opts = [stream: :self, receiver: self(), sync: false]
:httpc.request(:get, {String.to_charlist(url), headers}, http_opts, opts, :nerves)
{:noreply, %{s | url: url, caller: from, opts: opts, progress?: progress?}}
end
def handle_info({:http, {_, :stream_start, headers}}, s) do
content_length =
case Enum.find(headers, fn {key, _} -> key == 'content-length' end) do
nil ->
0
{_, content_length} ->
{content_length, _} =
content_length
|> to_string()
|> Integer.parse()
content_length
end
filename =
case Enum.find(headers, fn {key, _} -> key == 'content-disposition' end) do
nil ->
Path.basename(s.url)
{_, filename} ->
filename
|> to_string
|> String.split(";")
|> List.last()
|> String.trim()
|> String.trim("filename=")
end
{:noreply, %{s | content_length: content_length, filename: filename}}
end
def handle_info({:http, {_, :stream, data}}, s) do
size = byte_size(data) + s.buffer_size
buffer = s.buffer <> data
if progress?(s) do
put_progress(size, s.content_length)
end
{:noreply, %{s | buffer_size: size, buffer: buffer}}
end
def handle_info({:http, {_, :stream_end, _headers}}, s) do
if progress?(s) do
IO.write(:stderr, "\n")
end
GenServer.reply(s.caller, {:ok, s.buffer})
{:noreply, %{s | filename: "", content_length: 0, buffer: "", buffer_size: 0, url: nil}}
end
def handle_info({:http, {_ref, {{_, status_code, _}, headers, _body}}}, s)
when status_code in @redirect_status_codes do
case Enum.find(headers, fn {key, _} -> key == 'location' end) do
{'location', next_location} ->
handle_call({:get, List.to_string(next_location), headers: headers}, s.caller, %{
s
| buffer: "",
buffer_size: 0,
number_of_redirects: s.number_of_redirects + 1
})
_ ->
GenServer.reply(s.caller, {:error, status_code})
end
end
def handle_info({:http, {_ref, {{_, status_code, reason}, _headers, _body}}}, s) do
reason = "Status #{to_string(status_code)} #{to_string(reason)}"
GenServer.reply(s.caller, {:error, reason})
{:noreply, s}
end
def put_progress(size, max) do
fraction = size / max
completed = trunc(fraction * @progress_steps)
percent = trunc(fraction * 100)
unfilled = @progress_steps - completed
IO.write(
:stderr,
"\r|#{String.duplicate("=", completed)}#{String.duplicate(" ", unfilled)}| #{percent}% (#{
bytes_to_mb(size)
} / #{bytes_to_mb(max)}) MB"
)
end
defp start_httpc() do
:inets.start(:httpc, profile: :nerves)
opts = [
max_sessions: 8,
max_keep_alive_length: 4,
max_pipeline_length: 4,
keep_alive_timeout: 120_000,
pipeline_timeout: 60_000
]
:httpc.set_options(opts, :nerves)
end
defp bytes_to_mb(bytes) do
trunc(bytes / 1024 / 1024)
end
defp progress?(%{progress?: progress?}) do
System.get_env("NERVES_LOG_DISABLE_PROGRESS_BAR") == nil and progress?
end
end