Yet yet another HTTP client. This time using Behaviour
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
config
lib
test
.gitignore
.travis.yml
README.md
mix.exs
mix.lock

README.md

HTTPehaviour Build Status Hex pm

HTTP client for Elixir, based on HTTPoison.

Documentation

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]]
end

Usage

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
end

Overriding parts of the request

The request will follow like this:

  • init_request/1 which 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/2 or process_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) :: any

Here'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.