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\""