Webux Lab

By Studio Webux

Learning Elixir - Markdown and Http Client

TG
Tommy Gingras Studio Webux 2023-09-09

Learning Elixir: Markdown + Http Client

In this article I tried Earmark and httpc

The goal is to download a remote Markdown File, process it and return it as HTML.

Probably far from perfect, But I got something like:

mix.exs

# ...
def application do
    [
      extra_applications: [:logger, :inets, :ssl, :public_key]
    ]
end

# ...

defp deps do
    [
      {:earmark, "~> 1.4"}
    ]
end

# ...

libs/error.ex

defmodule Error do
  defexception message: "Unknown Error"
end

:httpc Cheatsheet : https://elixirforum.com/t/httpc-cheatsheet/50337

libs/parser.ex

defmodule Parser do
  defp extract_http_version(response), do: response |> Tuple.to_list() |> Enum.at(0)
  defp extract_status_code(response), do: response |> Tuple.to_list() |> Enum.at(1)
  defp extract_status_message(response), do: response |> Tuple.to_list() |> Enum.at(2)

  defp body_to_string(body), do: body |> Kernel.to_string()

  defp extract_headers(headers),
    do:
      Enum.reduce(headers, %{}, fn {key, value}, acc ->
        Map.put(acc, Kernel.to_string(key), Kernel.to_string(value))
      end)

  defp handle_response({http_response, headers, body}) do
    %{
      http_version: extract_http_version(http_response),
      status_code: extract_status_code(http_response),
      status_message: extract_status_message(http_response),
      headers: extract_headers(headers),
      body: body_to_string(body)
    }
  end


  def get_markdown(url) do
    Application.ensure_all_started(:inets)
    Application.ensure_all_started(:ssl)

    headers = [{'accept', 'text/markdown'}]

    http_request_opts = [
      ssl: [
        verify: :verify_peer,
        cacerts: :public_key.cacerts_get(),
        customize_hostname_check: [
          match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
        ]
      ]
    ]

    response = :httpc.request(:get, {url, headers}, http_request_opts, [])

    case response do
      {:ok, response} ->
        {:ok, handle_response(response)}

      {:error, error} ->
        {:error, error}
    end
  end

  def get_markdown!(url) do
    Application.ensure_all_started(:inets)
    Application.ensure_all_started(:ssl)

    headers = [{'accept', 'text/markdown'}]

    http_request_opts = [
      ssl: [
        verify: :verify_peer,
        cacerts: :public_key.cacerts_get(),
        customize_hostname_check: [
          match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
        ]
      ]
    ]

    response = :httpc.request(:get, {url, headers}, http_request_opts, [])

    case response do
      {:ok, response} ->
        handle_response(response)

      {:error, {error, reason}} ->
        raise Error, message: "#{Atom.to_string(error)} - #{inspect(reason)}"
    end
  end

  def parse(markdown), do: Earmark.as_html!(markdown, compact_output: true, escape: false)
end

To test the module, (yeah I need to write the documentation + inline tests)

iex -S mix

md = Parser.get_markdown("https://webuxlab-static.s3.ca-central-1.amazonaws.com/Devops/jenkins-docker-password/fr.md")

{_, %{headers: headers}} = md
IO.inspect(headers)
IO.inspect(headers["etag"])

{_, %{body: body}} = md
Parser.parse(body)

Example

md = Parser.get_markdown("https://webuxlab-static.s3.ca-central-1.amazonaws.com/Devops/jenkins-docker-password/fr.md")

Output:

{:ok,
 %{
   body: "## Changer un mot de passe d'un usager jenkins avec Docker\n\nLancer la commande suivante:\n\n```bash\ndocker exec -it jenkins /bin/bash\n```\n\nTrouver l'usager que vous voulez mettre à jour:\n\n```bash\nls -al var/jenkins_home/users/\n```\n\nPuis pour changer le mot de passe:\n\n```bash\nsed -i 's|<passwordHash>#jbcrypt:$2a$10$1zzxfM8EFdxh5wnoqUfsYe0BSkVHfo/sg6qQIamaT3a9LjnQXv3Eq</passwordHash>|<passwordHash>#jbcrypt:$2a$10$q7jINwCY.8LS9Ts/Z5.Eb.F71hYgn2e76icoqOcWDuSdqutRRR6Qe</passwordHash>|' var/jenkins_home/users/{usager}/config.xml\n```\n\nVous pouvez utiliser ce lien pour générer un nouveau mot de passe (comme mentionné dans l'article) : https://www.browserling.com/tools/bcrypt\n\nEnsuite vous devez redémarrer le conteneur pour appliquer le changement:\n\n```bash\ndocker restart jenkins\n```\n\n---\n\n## Sources\n\n- https://medium.com/@sdanerib/getting-started-with-jenkins-docker-part-iii-reset-jenkins-admin-password-when-you-have-a-ff81ffa6774f",
   headers: %{
     "accept-ranges" => "bytes",
     "content-length" => "940",
     "content-type" => "text/markdown",
     "date" => "Wed, 13 Sep 2023 02:00:53 GMT",
     "etag" => "\"f16249c6123e106ed692ac09a4f31f45\"",
     "last-modified" => "Sun, 10 Sep 2023 03:20:34 GMT",
     "server" => "AmazonS3",
     "x-amz-id-2" => "cnE0YPt3+KiQLhWp+agM9ceVok0d5GOrl/4bYj2E4vXKAiIwbRX1vH5W/8buLym5UaXfJRVs9NM=",
     "x-amz-request-id" => "6VFZ2M3C42DGJDP6",
     "x-amz-server-side-encryption" => "AES256"
   },
   http_version: 'HTTP/1.1',
   status_code: 200,
   status_message: 'OK'
 }}
{_, %{headers: headers}} = md
IO.inspect(headers)
IO.inspect(headers["etag"])

Output:

IO.inspect(headers)

%{
  "accept-ranges" => "bytes",
  "content-length" => "940",
  "content-type" => "text/markdown",
  "date" => "Wed, 13 Sep 2023 02:00:53 GMT",
  "etag" => "\"f16249c6123e106ed692ac09a4f31f45\"",
  "last-modified" => "Sun, 10 Sep 2023 03:20:34 GMT",
  "server" => "AmazonS3",
  "x-amz-id-2" => "cnE0YPt3+KiQLhWp+agM9ceVok0d5GOrl/4bYj2E4vXKAiIwbRX1vH5W/8buLym5UaXfJRVs9NM=",
  "x-amz-request-id" => "6VFZ2M3C42DGJDP6",
  "x-amz-server-side-encryption" => "AES256"
}

IO.inspect(headers[“etag”])

"\"f16249c6123e106ed692ac09a4f31f45\""


Search