gmile

gmile

: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.

Showing Posts 1 to 10

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

arcadian

arcadian

Thanks, this is really great! Instant browser bookmark from me.

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.

BradS2S

BradS2S

Same here. I’ve been looking for something like this.

christhekeele

christhekeele

Just wanted to say, this helped me out a lot today. :smiley: Thanks!

soyjeansoy

soyjeansoy

For some odd reason whenever I pass this opts (this is for test environment), it threw an error:

     ** (ArgumentError) cannot convert the given list to a string.

     To be converted to a string, a list must either be empty or only
     contain the following elements:

       * strings
       * integers representing Unicode code points
       * a list containing one of these three elements

     Please check the given list or call inspect/1 to get the list representation, got:
    .....

So this http_request_opts, which is a list, is converted to string? If I’m not passing that opts (just empty list) it ran just fine. :thinking:

gmile

gmile OP

@soyjeansoy no idea, off the top of my head :thinking:

Could you show more of the code you have, preferably isolated to an elixir script (e.g. ending with .exs) where I could reproduce the problem? Also, which version of Elixir and Erlang are you using?

gmile

gmile OP

:information_source: All examples have been updated and now target Elixir 1.17 and Erlang 27

ghannam80

ghannam80

Hello,

Do erlang have a library that can be used with :httpc to support request signing using RSA keys in line with OAUTH specs , in addition to request/response encryption and decryption.
A sample of what I am trying to do is available below but using other languages: GitHub - Mastercard/mastercard-api-client-tutorial: Generating and Configuring a Mastercard API Client · GitHub
Thanks in advance

Where Next? Top

Trending in Wikis Top

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews