Packages
livebook
0.7.0
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/utils/http.ex
defmodule Livebook.Utils.HTTP do
@moduledoc false
@type status :: non_neg_integer()
@type headers :: list(header())
@type header :: {String.t(), String.t()}
@doc """
Retrieves the header value from response headers.
"""
@spec fetch_header(headers(), String.t()) :: {:ok, String.t()} | :error
def fetch_header(headers, key) do
case Enum.find(headers, &match?({^key, _}, &1)) do
{_, value} -> {:ok, value}
_ -> :error
end
end
@doc """
Retrieves content type from response headers.
"""
@spec fetch_content_type(headers()) :: {:ok, String.t()} | :error
def fetch_content_type(headers) do
with {:ok, value} <- fetch_header(headers, "content-type") do
{:ok, value |> String.split(";") |> hd()}
end
end
@doc """
Makes an HTTP request.
## Options
* `headers` - request headers, defaults to `[]`
* `body` - request body given as `{content_type, body}`
* `timeout` - request timeout, defaults to 10 seconds
"""
@spec request(atom(), String.t(), keyword()) ::
{:ok, status(), headers(), binary()} | {:error, term()}
def request(method, url, opts \\ [])
when is_atom(method) and is_binary(url) and is_list(opts) do
headers = build_headers(opts[:headers] || [])
request =
case opts[:body] do
nil -> {url, headers}
{content_type, body} -> {url, headers, to_charlist(content_type), body}
end
http_opts = [
ssl: http_ssl_opts(),
timeout: opts[:timeout] || 10_000
]
opts = [
body_format: :binary
]
case :httpc.request(method, request, http_opts, opts) do
{:ok, {{_, status, _}, headers, body}} ->
{:ok, status, parse_headers(headers), body}
{:error, error} ->
{:error, error}
end
end
defp build_headers(entries) do
headers =
Enum.map(entries, fn {key, value} ->
{to_charlist(key), to_charlist(value)}
end)
[{~c"user-agent", ~c"livebook"} | headers]
end
defp parse_headers(headers) do
Enum.map(headers, fn {key, val} ->
{String.downcase(to_string(key)), to_string(val)}
end)
end
# Load SSL certificates
crt_file = CAStore.file_path()
crt = File.read!(crt_file)
pems = :public_key.pem_decode(crt)
ders = Enum.map(pems, fn {:Certificate, der, _} -> der end)
# Note: we need to load the certificates at compilation time,
# as we don't have access to package files in Escript.
@cacerts ders
defp http_ssl_opts() do
# Use secure options, see https://gist.github.com/jonatanklosko/5e20ca84127f6b31bbe3906498e1a1d7
[
verify: :verify_peer,
cacerts: @cacerts,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
end
end