Skip to content

Commit

Permalink
use httpoison as an adapter for Req (#488)
Browse files Browse the repository at this point in the history
* use http poison as an adapter for Req

* upgrade version
  • Loading branch information
gBillal authored Oct 9, 2024
1 parent 0160ff7 commit 1dc036f
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 65 deletions.
132 changes: 76 additions & 56 deletions apps/ex_nvr/lib/ex_nvr/http.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,9 @@ defmodule ExNVR.HTTP do
call(:post, url, body, opts)
end

@spec build_digest_auth(map(), Keyword.t()) :: {:ok, binary()} | {:error, binary()}
def build_digest_auth(resp, opts) do
with digest_opts when is_map(digest_opts) <- digest_auth_opts(resp, opts) do
digest_auth = encode_digest(digest_opts)
{:ok, digest_auth}
end
end

defp call(method, url, body, opts) do
opts = Keyword.merge(opts, method: method, url: url)

result = do_call(method, body, opts)

with {:ok, %{status: 401} = resp} <- result,
{:ok, digest_auth} <- build_digest_auth(resp, opts) do
opts = Keyword.merge(opts, auth_type: :digest, digest_auth: digest_auth)
do_call(method, body, opts)
else
_other ->
result
end
end

defp digest_auth_opts(%{headers: headers}, opts) do
headers
|> Enum.map(fn {key, value} -> {String.downcase(key), value} end)
|> Map.new()
|> Map.fetch("www-authenticate")
|> case do
{:ok, ["Digest " <> digest]} ->
build_digest_auth_opts(digest, opts)

{:ok, "Digest " <> digest} ->
build_digest_auth_opts(digest, opts)

_other ->
nil
end
do_call(method, body, opts)
end

defp build_digest_auth_opts(digest, opts) do
Expand All @@ -65,13 +30,60 @@ defmodule ExNVR.HTTP do
}
end

defp match_pattern(pattern, digest) do
case Regex.run(~r/#{pattern}=\"(?<value>.*)\"/U, digest) do
[_match, value] -> value
defp do_call(method, body, opts) when is_map(body) do
build_request(method, opts) |> Req.request(json: body, auth: auth_from_opts(opts))
end

defp do_call(method, body, opts) do
build_request(method, opts) |> Req.request(body: body, auth: auth_from_opts(opts))
end

defp build_request(method, opts) do
Req.Request.new(method: method, url: opts[:url], adapter: http_poison_adapter())
|> Req.Steps.attach()
|> Req.Request.prepend_response_steps(
digest_auth: fn params -> digest_auth(params, {opts[:username], opts[:password]}) end
)
end

defp auth_from_opts(opts) do
case opts[:auth_type] do
:bearer -> {:bearer, opts[:token]}
:basic -> {:basic, "#{opts[:username]}:#{opts[:password]}"}
_other -> nil
end
end

defp digest_auth({request, %{status: 401} = response}, {username, password}) do
with ["Digest " <> digest | _] <- Req.Response.get_header(response, "www-authenticate"),
true <- not is_nil(username) and not is_nil(password) do
digest =
digest
|> build_digest_auth_opts(request.method, request.url, username, password)
|> encode_digest()

request
|> Req.Request.put_header("authorization", digest)
|> Req.Request.run_request()
else
_other -> {request, response}
end
end

defp digest_auth({request, response}, _credentials), do: {request, response}

defp build_digest_auth_opts(digest, method, url, username, password) do
%{
nonce: match_pattern("nonce", digest),
realm: match_pattern("realm", digest),
qop: match_pattern("qop", digest),
username: username,
password: password,
method: Atom.to_string(method) |> String.upcase(),
path: url.path
}
end

defp encode_digest(opts) do
ha1 = md5([opts.username, opts.realm, opts.password])
ha2 = md5([opts.method, opts.path])
Expand All @@ -96,6 +108,13 @@ defmodule ExNVR.HTTP do
"Digest " <> digest_token
end

defp match_pattern(pattern, digest) do
case Regex.run(~r/#{pattern}=\"(?<value>.*)\"/U, digest) do
[_match, value] -> value
_other -> nil
end
end

defp digest_response_attribute(ha1, ha2, %{qop: nil} = opts, _nonce_count, _client_nonce) do
md5([ha1, opts.nonce, ha2])
end
Expand All @@ -104,28 +123,29 @@ defmodule ExNVR.HTTP do
md5([ha1, opts.nonce, nonce_count, client_nonce, opts.qop, ha2])
end

defp do_call(method, body, opts) when is_map(body) do
Req.request(method: method, url: opts[:url], auth: auth_from_opts(opts), json: body)
end

defp do_call(method, body, opts) do
Req.request(method: method, url: opts[:url], auth: auth_from_opts(opts), body: body)
end

defp auth_from_opts(opts) do
case opts[:auth_type] do
:bearer -> {:bearer, opts[:token]}
:basic -> {:basic, "#{opts[:username]}:#{opts[:password]}"}
:digest -> opts[:digest_auth]
_other -> nil
end
end

defp md5(value) do
value
|> Enum.join(":")
|> IO.iodata_to_binary()
|> :erlang.md5()
|> Base.encode16(case: :lower)
end

defp http_poison_adapter() do
fn req ->
case HTTPoison.request(
req.method,
URI.to_string(req.url),
req.body || "",
req.headers
) do
{:ok, resp} ->
headers = for {name, value} <- resp.headers, do: {String.downcase(name, :ascii), value}
{req, %Req.Response{status: resp.status_code, headers: headers, body: resp.body}}

{:error, reason} ->
{req, reason}
end
end
end
end
2 changes: 1 addition & 1 deletion apps/ex_nvr/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule ExNVR.MixProject do
def project do
[
app: :ex_nvr,
version: "0.15.1",
version: "0.15.2",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
Expand Down
2 changes: 1 addition & 1 deletion apps/ex_nvr_rtsp/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule ExNvrRtsp.MixProject do
def project do
[
app: :ex_nvr_rtsp,
version: "0.15.1",
version: "0.15.2",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
Expand Down
4 changes: 2 additions & 2 deletions apps/ex_nvr_web/assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/ex_nvr_web/assets/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ex_nvr",
"version": "0.15.1",
"version": "0.15.2",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/ex_nvr_web/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule ExNVRWeb.MixProject do
def project do
[
app: :ex_nvr_web,
version: "0.15.1",
version: "0.15.2",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
Expand Down
2 changes: 1 addition & 1 deletion apps/ex_nvr_web/priv/static/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ openapi: 3.0.0
info:
title: ExNVR API
description: Manage ExNVR via API endpoints
version: 0.15.1
version: 0.15.2
servers:
- url: '{protocol}://{host}:{port}'
variables:
Expand Down
2 changes: 2 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ config :os_mon,

config :bundlex, :disable_precompiled_os_deps, apps: [:ex_libsrtp]

config :req, legacy_headers_as_lists: true

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ExNVR.Umbrella.MixProject do
use Mix.Project

@version "0.15.1"
@version "0.15.2"

def project do
[
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"},
"ex_sdp": {:hex, :ex_sdp, "1.0.1", "1608551d740a1882fe89d2b1df807167de62c44ab5409a795f257069e348ac05", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "0b8c53b15f18122feed9b65c1318603bebbe33cbad36efb3995b6e03b1bf27ee"},
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
"exqlite": {:hex, :exqlite, "0.24.2", "e3cbb5c5c207ec760d17658c2cefe8ea86b052d30bb894b8b14dc16aca6a01ea", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "e372f37854073025a7fc49d08031e5291ef93b7cad2c33d69f6218ee9d8cc4d2"},
"exqlite": {:hex, :exqlite, "0.25.0", "fb994735d65d87fa518fc4614aca1d5c6409880c05574822442c331285eaac37", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "33b1ea7b7f3397a21aff69328bda2dd6c171b5e50c22c32963f5279667fc4ad7"},
"fake_turn": {:hex, :fake_turn, "0.4.3", "3ccb56d813d3cd3dad7341ed8d1f4a7a3489f97008e68eaceced503389ad8b01", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "b80a21513c2f754b1a700f9d1a4e386ab1e75155b8c7d29e1833d5d3901344fd"},
"faker": {:hex, :faker, "0.18.0", "943e479319a22ea4e8e39e8e076b81c02827d9302f3d32726c5bf82f430e6e14", [:mix], [], "hexpm", "bfbdd83958d78e2788e99ec9317c4816e651ad05e24cfd1196ce5db5b3e81797"},
"fast_tls": {:hex, :fast_tls, "1.1.13", "828cdc75e1e8fce8158846d2b971d8b4fe2b2ddcc75b759e88d751079bf78afd", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d1f422af40c7777fe534496f508ee86515cb929ad10f7d1d56aa94ce899b44a0"},
Expand Down

0 comments on commit 1dc036f

Please sign in to comment.