[![ version](](
[![ downloads](](
[![API Docs](](
[![Build Status](](

# HTTPotion

HTTP client for [Elixir], based on [ibrowse].
Continues the HTTPun tradition of [HTTParty], [HTTPretty], [HTTParrot] and [HTTPie].

## Installation

Add HTTPotion to your project's dependencies in `mix.exs`:

  defp deps do
      {:httpotion, "~> 3.1.0"}

  def application do
    [ applications: [:httpotion] ]
    # Application dependency auto-starts it, otherwise: HTTPotion.start

And fetch your project's dependencies:

$ mix deps.get

## Usage

*Note*: You can load HTTPotion into the Elixir REPL by executing this command from the root of your project:

$ iex -S mix

Some basic examples:

iex> response = HTTPotion.get ""
  body: "{\n…",
  headers: %HTTPotion.Headers{ hdrs: %{"connection" => "keep-alive", …} },
  status_code: 200

iex> HTTPotion.Response.success?(response)

# Response headers are wrapped to allow case-insensitive access (and to support both atoms and strings)
iex> response.headers[:sErvEr]

iex> response.headers["ConTenT-TyPe"]

# Response headers can have multiple values
iex> response = HTTPotion.get ""
  body: "{\n…",
  headers: %HTTPotion.Headers{ hdrs: %{"foo" => ["1", "2"], "bar" => "1" …} },
  status_code: 200

# You can provide a map for the query string
iex> HTTPotion.get("", query: %{page: 2})
%HTTPotion.Response{body: "…", headers: …, status_code: 200}

# Follow redirects
iex> HTTPotion.get("", follow_redirects: true)
%HTTPotion.Response{body: "…<title>Example Domain</title>…", headers: …, status_code: 200}

# Send form data
iex> "", [body: "hello=" <> URI.encode_www_form("w o r l d !!"),
  headers: ["User-Agent": "My App", "Content-Type": "application/x-www-form-urlencoded"]]
%HTTPotion.Response{body: "…", headers: …, status_code: 200}

# Use a custom method
iex> HTTPotion.request :propfind, "", [body: "I have no idea what I'm doing"]
%HTTPotion.Response{body: "…", headers: …, status_code: 405}

# Send Basic auth credentials
iex> HTTPotion.get "", [basic_auth: {"foo", "bar"}]
  body: "…",
  headers: %HTTPotion.Headers { hdrs: %{"Access-Control-Allow-Credentials": "true", …} },
  status_code: 200

# Pass options to ibrowse (note that it usually takes char_lists, not elixir strings)
iex> HTTPotion.get "", [ ibrowse: [ ssl_options: [ versions, [:'tlsv1.1'] ] ] ]
%HTTPotion.Response{body: "…TLS SNI: present - Check TLS - (https,tls1.1,ipv4)…", headers: …, status_code: 200}

# Change the timeout (default is 5000 ms)
iex> HTTPotion.get "", [timeout: 10_000]

# If there is an error a `HTTPotion.ErrorResponse` is returned
iex> HTTPotion.get "http://localhost:1"
%HTTPotion.ErrorResponse{message: "econnrefused"}

# You can also raise `HTTPError` with the `bang` version of request
iex> HTTPotion.get! "http://localhost:1"
** (HTTPotion.HTTPError) econnrefused

The `Response` is [a struct](, you can access its fields like: `response.body`.

`response.headers` is a struct (`HTTPotion.Headers`) that wraps a map to provide case-insensitive access (so you can use `response.headers[:authorization]` and it doesn't matter if the server returned `AuThOrIZatIOn` or something).

`HTTPError` is [an exception]( that happens when a bang request (`request!` / `get!` / …) fails.

Available options and their default values:

  body: "",                # Request's body contents, e.g. "{json: \"string\"}"
  headers: [],             # Request's headers, e.g. [Accept: "application/json"]
  query: nil,              # Query string, e.g. %{page: 1}
  timeout: 5000,           # Timeout in milliseconds, e.g. 5000
  basic_auth: nil,         # Basic auth credentials, e.g. {"username", "password"}
  stream_to: nil,          # A process to stream the response to when performing async requests
  direct: nil,             # An ibrowse worker for direct mode
  ibrowse: [],             # ibrowse options
  auto_sni: true,          # Whether TLS SNI should be automatically configured (does URI parsing)
  follow_redirects: false, # Whether redirects should be followed


### Metaprogramming magic

You can extend `HTTPotion.Base` to make cool HTTP API wrappers (this example uses [Poison] for JSON):

defmodule GitHub do
  use HTTPotion.Base

  def process_url(url) do
    "" <> url

  def process_request_headers(headers) do
    Dict.put headers, :"User-Agent", "github-potion"

  def process_response_body(body) do
    body |> Poison.decode!

iex> GitHub.get("users/myfreeweb").body["public_repos"]

Read the source to see all the hooks.

Keep in mind that `process_response_body` and `process_response_chunk` get iodata.
By default, they call `IO.iodata_to_binary`.
But efficient parsers like Poison can work directly on iodata.

### Asynchronous requests

You can get the response streamed to your current process asynchronously:

iex> HTTPotion.get "", [stream_to: self]
%HTTPotion.AsyncResponse{id: -576460752303419903}

iex> flush
  id: -576460752303419903,
  status_code: 200,
  headers: %HTTPotion.Headers{ hdrs: %{"connection" => "keep-alive", …} }
  id: -576460752303419903,
  chunk: "{\n…"
  id: -576460752303419903

Note that instead of `process_response_body`, `process_response_chunk` is called on the chunks before sending them out to the receiver (the `stream_to` process).

### Direct access to ibrowse workers

ibrowse allows you to use its separate worker processes directly.
We expose this functionality through the `direct` option.

Don't forget that you have to pass the URL to the worker process, which means the worker only communicates with one server (domain!)

iex> {:ok, worker_pid} = HTTPotion.spawn_worker_process("")

iex> HTTPotion.get "", [direct: worker_pid]
%HTTPotion.Response{body: "…", headers: ["Connection": "close", …], status_code: 200}

You can even combine it with async!

iex> {:ok, worker_pid} = HTTPotion.spawn_worker_process("")

iex> "", [direct: worker_pid, stream_to: self, headers: ["User-Agent": "hello it's me"]]
%HTTPotion.AsyncResponse{id: {1372,8757,656584}}

### Type analysis

HTTPotion contains [typespecs] so your usage can be checked with [dialyzer], probably via [dialyxir] or [elixir-ls].

HTTPotion's tests are checked with dialyxir.


## Contributing

Please feel free to submit pull requests!

By participating in this project you agree to follow the [Contributor Code of Conduct](

[The list of contributors is available on GitHub](

## License

This is free and unencumbered software released into the public domain.  
For more information, please refer to the `UNLICENSE` file or [](