README.md

# HTTPehaviour [![Build Status](https://travis-ci.org/edgurgel/httpehaviour.svg?branch=master)](https://travis-ci.org/edgurgel/httpehaviour)

HTTP client for Elixir, based on [HTTPoison](https://github.com/edgurgel/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:

```elixir
def deps do
  [{:httpehaviour, "~> 0.9"}]
end
```

and run `$ mix deps.get`. Now, list the `:httpehaviour` application as part of your application dependencies:

```elixir
def application do
  [applications: [:httpehaviour]]
end
```

## Usage

```iex
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:

```elixir
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

```elixir
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

```elixir
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/`](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.