Current section

Files

Jump to
httpotion lib httpotion.ex
Raw

lib/httpotion.ex

defmodule HTTPotion.Base do
defmacro __using__(_) do
quote do
def start do
:application.ensure_all_started(:httpotion)
end
def spawn_worker_process(url, options \\ []) do
GenServer.start(:ibrowse_http_client, url |> process_url |> String.to_char_list, options)
end
def spawn_link_worker_process(url, options \\ []) do
GenServer.start_link(:ibrowse_http_client, url |> process_url |> String.to_char_list, options)
end
def stop_worker_process(pid), do: :ibrowse.stop_worker_process(pid)
def process_url(url) do
unless url =~ ~r/\Ahttps?:\/\//, do: "http://" <> url, else: url
end
def process_request_body(body), do: body
def process_request_headers(headers), do: headers
def process_status_code(status_code), do: elem(:string.to_integer(status_code), 0)
def process_response_body(body = {:file, filename}), do: IO.iodata_to_binary(filename)
def process_response_body(body), do: IO.iodata_to_binary(body)
def process_response_chunk(body = {:file, filename}), do: IO.iodata_to_binary(filename)
def process_response_chunk(chunk), do: IO.iodata_to_binary(chunk)
def process_response_headers(headers) do
Enum.reduce(headers, [], fn { k, v }, acc ->
key = String.to_atom(to_string(k))
value = to_string(v)
Dict.update(acc, key, value, &[value | List.wrap(&1)])
end) |> Enum.sort
end
@spec process_arguments(atom, String.t, :dict.dict) :: :dict.dict
def process_arguments(method, url, options) do
body = Dict.get(options, :body, "")
headers = Dict.get(options, :headers, [])
timeout = Dict.get(options, :timeout, 5000)
ib_options = Dict.get(options, :ibrowse, [])
if stream_to = Dict.get(options, :stream_to) do
ib_options = Dict.put(ib_options, :stream_to, spawn(__MODULE__, :transformer, [stream_to]))
end
%{
method: method,
url: url |> to_string |> process_url |> to_char_list,
body: body |> process_request_body,
headers: headers |> process_request_headers |> Enum.map(fn ({k, v}) -> { to_char_list(k), to_char_list(v) } end),
timeout: timeout,
ib_options: ib_options
}
end
def transformer(target) do
receive do
{ :ibrowse_async_headers, id, status_code, headers } ->
send(target, %HTTPotion.AsyncHeaders{
id: id,
status_code: process_status_code(status_code),
headers: process_response_headers(headers)
})
transformer(target)
{ :ibrowse_async_response, id, chunk } ->
send(target, %HTTPotion.AsyncChunk{
id: id,
chunk: process_response_chunk(chunk)
})
transformer(target)
{ :ibrowse_async_response_end, id } ->
send(target, %HTTPotion.AsyncEnd{ id: id })
end
end
@doc """
Sends an HTTP request.
Args:
* method - HTTP method, atom (:get, :head, :post, :put, :delete, etc.)
* url - URL, binary string or char list
* options - orddict of options
Options:
* body - request body, binary string or char list
* headers - HTTP headers, orddict (eg. ["Accept": "application/json"])
* timeout - timeout in ms, integer
* stream_to - if you want to make an async request, the pid of the process
* direct - if you want to use ibrowse's direct feature, the pid of
the worker spawned by spawn_worker_process or spawn_link_worker_process
Returns HTTPotion.Response or HTTPotion.AsyncResponse if successful.
Raises HTTPotion.HTTPError if failed.
"""
@spec request(atom, String.t, :dict.dict) :: HTTPotion.Response | HTTPotion.AsyncResponse
def request(method, url, options \\ []) do
args = process_arguments(method, url, options)
if conn_pid = Dict.get(options, :direct) do
:ibrowse.send_req_direct(conn_pid, args[:url], args[:headers], args[:method], args[:body], args[:ib_options], args[:timeout])
else
:ibrowse.send_req(args[:url], args[:headers], args[:method], args[:body], args[:ib_options], args[:timeout])
end |> handle_response
end
@doc "Deprecated form of request; body and headers are now options, see request/3."
def request(method, url, body, headers, options) do
request(method, url, options |> Dict.put(:body, body) |> Dict.put(:headers, headers))
end
@doc "Deprecated form of request with the direct option; body and headers are now options, see request/3."
def request_direct(conn_pid, method, url, body \\ "", headers \\ [], options \\ []) do
request(method, url, options |> Dict.put(:direct, conn_pid))
end
def handle_response(response) do
case response do
{ :ok, status_code, headers, body, _ } ->
%HTTPotion.Response{
status_code: process_status_code(status_code),
headers: process_response_headers(headers),
body: process_response_body(body)
}
{ :ok, status_code, headers, body } ->
%HTTPotion.Response{
status_code: process_status_code(status_code),
headers: process_response_headers(headers),
body: process_response_body(body)
}
{ :ibrowse_req_id, id } ->
%HTTPotion.AsyncResponse{ id: id }
{ :error, { :conn_failed, { :error, reason }}} ->
raise HTTPotion.HTTPError, message: to_string(reason)
{ :error, :conn_failed } ->
raise HTTPotion.HTTPError, message: "conn_failed"
{ :error, reason } ->
raise HTTPotion.HTTPError, message: to_string(reason)
end
end
def get(url, options \\ []), do: request(:get, url, options)
def put(url, options \\ []), do: request(:put, url, options)
def head(url, options \\ []), do: request(:head, url, options)
def post(url, options \\ []), do: request(:post, url, options)
def patch(url, options \\ []), do: request(:patch, url, options)
def delete(url, options \\ []), do: request(:delete, url, options)
def options(url, options \\ []), do: request(:options, url, options)
defoverridable Module.definitions_in(__MODULE__)
end
end
end
defmodule HTTPotion do
@moduledoc """
The HTTP client for Elixir.
"""
defmodule Response do
defstruct status_code: nil, body: nil, headers: []
def success?(%__MODULE__{ status_code: code }) do
code in 200..299
end
def success?(%__MODULE__{ status_code: code } = response, :extra) do
success?(response) or code in [302, 304]
end
end
defmodule AsyncResponse do
defstruct id: nil
end
defmodule AsyncHeaders do
defstruct id: nil, status_code: nil, headers: []
end
defmodule AsyncChunk do
defstruct id: nil, chunk: nil
end
defmodule AsyncEnd do
defstruct id: nil
end
defmodule HTTPError do
defexception [:message]
end
use HTTPotion.Base
end