HTTPehaviour 
HTTP client for Elixir, based on HTTPoison.
But why not HTTPoison?
HTTPoison does not provide a clean way of overriding steps of the HTTP request. This project is an attempt to fix this.
Installation
First, add HTTPehaviour to your mix.exs dependencies:
def deps do
[{:httpehaviour, "~> 0.9"}]
end
and run $ mix deps.get. Now, list the :httpehaviour application as part of your application dependencies:
def application do
[applications: [:httpehaviour]]
endUsage
iex> HTTPehaviour.start
iex> HTTPehaviour.get! "http://httparrot.herokuapp.com/get"
%HTTPehaviour.Response{
body: "{\n \"args\": {},\n \"headers\": {} ...",
headers: %{"connection" => "keep-alive", "content-length" => "517", ...},
status_code: 200
}
iex> HTTPehaviour.get! "http://localhost:1"
** (HTTPehaviour.Error) :econnrefused
iex> HTTPehaviour.get "http://localhost:1"
{:error, %HTTPehaviour.Error{id: nil, reason: :econnrefused}}
You can also easily pattern match on the HTTPehaviour.Response struct:
case HTTPehaviour.get(url) do
{:ok, %HTTPehaviour.Response{status_code: 200, body: body}} ->
IO.puts body
{:ok, %HTTPehaviour.Response{status_code: 404}} ->
IO.puts "Not found :("
{:error, %HTTPehaviour.Error{reason: reason}} ->
IO.inspect reason
endOverriding parts of the request
The request will follow like this:
init_request/1which will come with the original Request;process_request_url/2,process_request_body/2&process_request_headers/2;- The request is executed to the HTTP server;
process_response_status_code/2,process_response_headers/2,process_request_body/2orprocess_response_chunk/2;- Then finally terminate_request/1 is called to do any cleanup and change the state;
- Response will have the state that got passed through the previous functions.
If any callback is called and returns { :halt, state }, it will finish it and return HTTPehaviour.Error
You can define a module that implement the following callbacks
defcallback init_request(request :: HTTPehaviour.Request.t) :: { :continue, any } | { :halt, any }
defcallback process_request_url(url :: binary, state :: any) :: { :continue, binary, any } | { :halt, any }
defcallback process_request_body(body :: binary, state :: any) :: { :continue, binary, any } | { :halt, any }
defcallback process_request_headers(headers :: HTTPehaviour.headers, state :: any) :: { :continue, HTTPehaviour.headers, any } | { :halt, any }
defcallback process_response_status_code(status_code :: integer, state :: any) :: { :continue, integer, any } | { :halt, any }
defcallback process_response_headers(headers :: HTTPehaviour.headers, state :: any) :: { :continue, HTTPehaviour.headers, any } | { :halt, any }
defcallback process_response_body(body :: binary, state :: any) :: { :continue, binary, any } | { :halt, any }
defcallback process_response_chunk(chunk :: binary, state :: any) :: { :continue, binary, any } | { :halt, any }
defcallback terminate_request(state :: any) :: anyHere's a simple example to build a client for the GitHub API
defmodule GitHub do
use HTTPehaviour.Client
@expected_fields ~w(
login id avatar_url gravatar_id url html_url followers_url
following_url gists_url starred_url subscriptions_url
organizations_url repos_url events_url received_events_url type
site_admin name company blog location email hireable bio
public_repos public_gists followers following created_at updated_at)
def process_request_url(url, state) do
{ :continue, "https://api.github.com" <> url, state }
end
def process_response_body(body, state) do
body = body |> Poison.decode!
|> Dict.take(@expected_fields)
|> Enum.map(fn({k, v}) -> {String.to_atom(k), v} end)
{ :continue, body, state }
end
def users do
get!("/users/edgurgel", [], behaviour: __MODULE__).body[:public_repos]
end
end
One can pass state data through the request and even get the final state back after the request is completed.
The request will run:
init_request -> process_request_url -> process_request_headers -> process_request_body -> process_response_status_code -> process_request_headers -> process_response_body -> terminate_request
For async requests it will do process_response_chunk instead of process_response_body
This is still a work in progress.
You can see more usage examples in the test files (located in the test/) directory.
License
Copyright © 2015 Eduardo Gurgel <eduardo@gurgel.me>
This work is free. You can redistribute it and/or modify it under the
terms of the MIT License. See the LICENSE file for more details.