gmile

gmile

:httpc cheatsheet

:httpc HTTP client is part of Erlang standard library, and as such can be easily used in Elixir code too. One particular advantage of using :httpc is that you can do a lot with it even with a minimal number of external dependencies. I prefer to use :httpc in one-off scripts and in production.

The :httpc reference is available at the Erlang standard library documentation website, here:

httpc — OTP 29.0.2 (inets 9.7.1)

For now, I just wanted to publish an initial version of compilation of my own notes. Later I intent to update this post by adding notes to existing examples, adding references and even more examples. Feel free to modify the contents of the post too!

These examples are self-contained, can be copied to a script.exs file and executed via elixir script.exs command. Enjoy! :slight_smile:

Index:

  • sending a GET request returning a JSON payload
  • sending a POST request with JSON request payload
  • sending a POST request with no payload
  • downloading a file
  • uploading a file
  • low-level tracing of HTTP interaction

General notes

  • for :httpc to work, :inets app needs to be started beforehand; in case you intend to interact with HTTPS endpoints (which is what you want to do in most cases when making requests on public internet), :ssl app needs to be started as well; all examples bellow start both apps,

  • all examples below pass a big-looking “parameter” called http_request_opts - this is how you make sure :httpc performs validation of the server’s TLS certificate; more on why this is necessary is described in “Erlang standard library: inets” page of “Secure Coding and Deployment Hardening Guidelines” by ERLef; without params under ssl key, you are likely to see the following warning on OTP < 26:

    [warning] Description: 'Authenticity is not established by certificate path validation'
         Reason: 'Option {verify, verify_peer} and cacertfile/cacerts is missing'
    
  • :httpc appears to like char lists a lot; a common mistake I made in the beginning when using :httpc is feeding it with "https://example.com" a string, while it should be a a list of characters, e.g. ~c"https://example.com"

Sending a GET request returning a JSON payload

Example

:inets.start()
:ssl.start()

url = ~s"https://httpbin.org/get"
headers = [{~s"accept", ~s"application/json"}]

http_request_opts = [
  ssl: [
    cacerts: :public_key.cacerts_get()
  ]
]

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

Sending a POST request with JSON request payload

Example

:inets.start()
:ssl.start()

url = ~c"https://httpbin.org/post"
headers = []
content_type = ~c"application/json"
body = :json.encode(%{hello: "world"})

http_request_opts = [
  ssl: [
    cacerts: :public_key.cacerts_get()
  ]
]

:httpc.request(:post, {url, headers, content_type, body}, http_request_opts, [])

Sending a POST request with no payload

Example

:inets.start()
:ssl.start()

url = ~s"https://httpbin.org/post"
headers = []
content_type = ~c""
body = ~c""

http_request_opts = [
  ssl: [
    cacerts: :public_key.cacerts_get()
  ]
]

:httpc.request(:post, {url, headers, content_type, body}, http_request_opts, [])

Downloading a file

Example

:inets.start()
:ssl.start()

url = ~c"https://www.rfc-editor.org/rfc/pdfrfc/rfc1149.txt.pdf"
headers = []

path_to_file =
  System.tmp_dir!()
  |> Path.join("rfc1149.txt.pdf")
  |> String.to_charlist()

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

{:ok, :saved_to_file} =
  :httpc.request(:get, {url, headers}, http_request_opts, [stream: path_to_file])

Uploading a file

Example

Mix.install([:multipart])

:inets.start()
:ssl.start()

part = Multipart.Part.file_field("/tmp/rfc1149.txt.pdf", "my_file")

multipart =
  Multipart.new()
  |> Multipart.add_part(part)

content_length =
  multipart
  |> Multipart.content_length()
  |> Integer.to_string()
  |> String.to_charlist()

content_type =
  multipart
  |> Multipart.content_type("multipart/form-data")
  |> String.to_charlist()

url = ~c"https://httpbin.org/anything"
headers = [{~c"Content-Length", content_length}]
payload = Multipart.body_binary(multipart)

http_request_opts = [
  ssl: [
    cacerts: :public_key.cacerts_get()
  ]
]

:httpc.request(:post, {url, headers, content_type, payload}, http_request_opts, [])

Notes

  • some HTTP clients offer convenient API around sending file payloads; UX varies on the library API design, and sometime can serve as a source of confusion (see this discussion, for instance),

  • because design of :httpc appears to make little-to-no assumptions regarding the semantics of request payload, one can fairly say that :httpc simply send bytes. As such, we should be able to “manually” construct any payload, including one to upload a file, and :httpc will just handle it,

  • multipart package, originally mentioned by its author @engineeringdept here focuses on just putting together a bunch of bytes that look like a correct “HTTP payload containing a file upload”,

Low-level tracing of HTTP interaction

Example

:inets.start()
:ssl.start()

url = ~c"https://httpbin.org/get"
headers = []

http_request_opts = [
  ssl: [
    cacerts: :public_key.cacerts_get()
  ]
]

:httpc.set_options([verbose: :debug])
:httpc.request(:get, {url, headers}, http_request_opts, [])

Notes

  • :debug option value will easily generate several megabytes of information, while :trace value can potentially generate several tens of megabytes, so use it with care.

Most Liked

paulo-f-oliveira

paulo-f-oliveira

There is actually no working solution when you search online

There is a solution online, though it might not be immediately found if you’re searching for Elixir, and not Erlang: Erlang standard library: ssl | EEF Security WG. In the page body you’ll find

%% Erlang (OTP 25 or later)
ssl:connect("example.net", 443, [
    {verify, verify_peer},
    {cacerts, public_key:cacerts_get()},
    {depth, 3},
    {customize_hostname_check, [
        {match_fun, public_key:pkix_verify_hostname_match_fun(https)}
    ]}
]).

The Secure Coding and Deployment Hardening Guidelines, by the Erlang Ecosystem Foundation’s Security Working Group, is a great read, btw.

voltone

voltone

Nit:

There is no need to set the “Content-Type” header in a request that does not include a body. If you want to indicate to the server that you are ready to accept a JSON response body, us the “Accept” header instead:

headers = [{'accept', 'application/json'}]
darnahsan

darnahsan

Just appreciation response. I just started exploring escript yesterday and was trying out elixir clients and they kept failing on castore and then moved to httpc. Though it worked but it kept showing following warning

[warning] Description: 'Authenticity is not established by certificate path validation'
     Reason: 'Option {verify, verify_peer} and cacertfile/cacerts is missing'

There is actually no working solution when you search online but your post has the the solution I needed and now its works like a charm without any warning.

Thanks and gotta love the elixir/erlang/beam community

Where Next?

Popular in Wikis Top

swelham
Introduction After some discussion with a few other members we thought it might be an idea to start a thread where we can post about libr...
New
OvermindDL1
It was stated at elixir-lang.org blocked in Russia · Issue #6172 · elixir-lang/elixir · GitHub that Mirrors of the primary Elixir website...
New
ibgib
Popular Elixir Packages List of popular Atom.io Elixir Packages. These can be installed via atom’s package center or a command line with ...
New
BartOtten
A wiki for Doom Emacs Doom is a configuration framework for GNU Emacs. It focuses on performance and customizability. It can be a founda...
New
blackode
This is a wiki - anyone at Trust Level 1 or higher can help keep it updated. Elixir Pocket Syntax Uncommon Logical stuff of Elixir modul...
New
axelson
Preamble This Wiki is intended to be a community-maintained (see the Contribution Guidelines if interested) resource of common “gotchas” ...
New
Eiji
At start some definitions: HTTPS (is a protocol for secure communication over a computer network which is widely used on the Internet) -...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
AstonJ
Wonder if we can compile a list of learning resources, blog posts, talks, threads etc to help those who are just learning about Contexts....
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement