Giter Site home page Giter Site logo

edgurgel / httpoison Goto Github PK

View Code? Open in Web Editor NEW
2.2K 26.0 340.0 447 KB

Yet Another HTTP client for Elixir powered by hackney

Home Page: https://hex.pm/packages/httpoison

License: MIT License

Elixir 100.00%
elixir hackney http-client http hacktoberfest

httpoison's Introduction

HTTPoison logo

HTTPoison Build Status Hex pm hex.pm downloads

HTTP client for Elixir, based on HTTPotion (documentation).

Installation

First, add HTTPoison to your mix.exs dependencies:

def deps do
  [
    {:httpoison, "~> 2.0"}
  ]
end

and run $ mix deps.get. Add :httpoison to your applications list if your Elixir version is 1.3 or lower:

def application do
  [applications: [:httpoison]]
end

Upgrading to 2.x.x

The main change that caused a major version is that ssl option now merges with the default options where previously it would override the ssl options. The new option ssl_override was added to allow people to keep the previous behaviour but it's more explicit now.

defp default_ssl_options() do
    [
      {:versions, [:"tlsv1.2", :"tlsv1.3"]},
      {:verify, :verify_peer},
      {:cacertfile, :certifi.cacertfile()},
      {:depth, 10},
      {:customize_hostname_check,
       [
         match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
       ]}
    ]
  end

More context here: #466

Usage

iex> HTTPoison.start
iex> HTTPoison.get! "https://postman-echo.com/get"
%HTTPoison.Response{
  status_code: 200,
  body: "{\n  \"args\": {},\n  \"headers\": {\n    \"x-forwarded-proto\": \"https\",\n    \"x-forwarded-port\": \"443\",\n    \"host\": \"postman-echo.com\",\n    \"x-amzn-trace-id\": \"Root=1-644624fb-769bca0458e739dc07f6b630\",\n    \"user-agent\": \"hackney/1.18.1\"\n  },\n  \"url\": \"https://postman-echo.com/get\"\n}",
  headers: [ ... ]
}

iex> HTTPoison.get! "http://localhost:1"
** (HTTPoison.Error) :econnrefused
iex> HTTPoison.get "http://localhost:1"
{:error, %HTTPoison.Error{id: nil, reason: :econnrefused}}

iex> HTTPoison.post "https://postman-echo.com/post", "{\"body\": \"test\"}", [{"Content-Type", "application/json"}]
{:ok,
 %HTTPoison.Response{
   status_code: 200,
   body: "{\n  \"args\": {},\n  \"data\": {\n    \"body\": \"test\"\n  },\n  \"files\": {},\n  \"form\": {},\n  \"headers\": {\n    \"x-forwarded-proto\": \"https\",\n    \"x-forwarded-port\": \"443\",\n    \"host\": \"postman-echo.com\",\n    \"x-amzn-trace-id\": \"Root=1-6446255e-703101813ec2e395202ab494\",\n    \"content-length\": \"16\",\n    \"user-agent\": \"hackney/1.18.1\",\n    \"content-type\": \"application/json\"\n  },\n  \"json\": {\n    \"body\": \"test\"\n  },\n  \"url\": \"https://postman-echo.com/post\"\n}",
   headers: [ ... ]
 }}

You can also easily pattern match on the HTTPoison.Response struct:

case HTTPoison.get(url) do
  {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
    IO.puts body
  {:ok, %HTTPoison.Response{status_code: 404}} ->
    IO.puts "Not found :("
  {:error, %HTTPoison.Error{reason: reason}} ->
    IO.inspect reason
end

Here is the list of all possible error reasons.

Options

There are a number of supported options(not to be confused with the HTTP options method), documented here, that can be added to your request. The example below shows the use of the :ssl and :recv_timeout options for a post request to an api that requires a bearer token. The :ssl option allows you to set options accepted by the Erlang SSL module, and :recv_timeout sets a timeout on receiving a response, the default is 5000ms.

token = "some_token_from_another_request"
url = "https://example.com/api/endpoint_that_needs_a_bearer_token"
headers = ["Authorization": "Bearer #{token}", "Accept": "Application/json; Charset=utf-8"]
options = [ssl: [{:versions, [:'tlsv1.2']}], recv_timeout: 500]
{:ok, response} = HTTPoison.get(url, headers, options)

And the example below shows the use of the :ssl options for a post request to an api that requires a client certification.

url = "https://example.org/api/endpoint_that_needs_client_cert"
options = [ssl: [certfile: "certs/client.crt"]]
{:ok, response} = HTTPoison.post(url, [], options)

Wrapping HTTPoison.Base

You can also use the HTTPoison.Base module in your modules in order to make cool API clients or something. The following example wraps HTTPoison.Base in order to build a client for the GitHub API (Poison is used for JSON decoding):

defmodule GitHub do
  use HTTPoison.Base

  @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) do
    "https://api.github.com" <> url
  end

  def process_response_body(body) do
    body
    |> Poison.decode!
    |> Map.take(@expected_fields)
    |> Enum.map(fn({k, v}) -> {String.to_atom(k), v} end)
  end
end
iex> GitHub.start
iex> GitHub.get!("/users/myfreeweb").body[:public_repos]
37

It's possible to extend the functions listed below:

def process_request_body(body), do: body

def process_request_headers(headers) when is_map(headers) do
  Enum.into(headers, [])
end

def process_request_headers(headers), do: headers

def process_request_options(options), do: options

def process_request_url(url), do: url

def process_response_body(body), do: body

def process_response_chunk(chunk), do: chunk

def process_response_headers(headers), do: headers

def process_response_status_code(status_code), do: status_code

Async requests

HTTPoison now comes with async requests!

iex> HTTPoison.get! "https://github.com/", %{}, stream_to: self
%HTTPoison.AsyncResponse{id: #Reference<0.0.0.1654>}
iex> flush
%HTTPoison.AsyncStatus{code: 200, id: #Reference<0.0.0.1654>}
%HTTPoison.AsyncHeaders{headers: %{"Connection" => "keep-alive", ...}, id: #Reference<0.0.0.1654>}
%HTTPoison.AsyncChunk{chunk: "<!DOCTYPE html>...", id: #Reference<0.0.0.1654>}
%HTTPoison.AsyncEnd{id: #Reference<0.0.0.1654>}
:ok

Warning: this option can flood a receiver in messages.

If a server may send very large messages the async: :once option should be used. This will send only a single chunk at a time the receiver can call HTTPoison.stream_next/1 to indicate ability to process more chunks.

Cookies

HTTPoison allows you to send cookies:

iex> HTTPoison.get!("https://postman-echo.com/cookies", %{}, hackney: [cookie: ["session=a933ec1dd923b874e691; logged_in=true"]])
%HTTPoison.Response{
  status_code: 200,
  body: "{\n  \"cookies\": {\n    \"session\": \"a933ec1dd923b874e691\",\n    \"logged_in\": \"true\"\n  }\n}",
  headers: [ ... ]
}

You can also receive cookies from the server by reading the "set-cookie" headers in the response:

iex(1)> response = HTTPoison.get!("https://postman-echo.com/cookies/set?foo=1")
iex(2)> cookies = Enum.filter(response.headers, fn
...(2)> {key, _} -> String.match?(key, ~r/\Aset-cookie\z/i)
...(2)> end)
[ {"set-cookie", "foo=1; Path=/"}, ...]

You can see more usage examples in the test files (located in the test/) directory.

Connection Pools

Normally hackney opens and closes connections on demand, but it also creates a default pool of connections which are reused for requests to the same host. If the connection and host support keepalive, the connection is kept open until explicitly closed.

To use the default pool, you can just declare it as an option:

HTTPoison.get("httpbin.org/get", [], hackney: [pool: :default])

It is possible to use different pools for different purposes when a more fine grained allocation of resources is necessary.

Simple pool declaration

The easiest way is to just pass the name of the pool, and hackney will create it if it doesn't exist. Pools are independent from each other (they won't compete for connections) and are created with the default configuration.

HTTPoison.get("httpbin.org/get", [], hackney: [pool: :first_pool])
HTTPoison.get("httpbin.org/get", [], hackney: [pool: :second_pool])

Explicit pool creation

If you want to use different configuration options you can create a pool manually when your app starts with :hackney_pool.start_pool/2.

:ok = :hackney_pool.start_pool(:first_pool, [timeout: 15000, max_connections: 100])

From the already linked hackney's readme:

timeout is the time we keep the connection alive in the pool, max_connections is the number of connections maintained in the pool. Each connection in a pool is monitored and closed connections are removed automatically.

Disabling pool

If you don't want to use a pool for a single http request, you can do it by passing an option:

HTTPoison.get("httpbin.org/get", [], hackney: [pool: false])

If you want to disable the usage of the pool for every request you can do it by adding this to your environment configuration:

config :hackney, use_default_pool: false

You can find a little explanation here hackney's readme.

Pools as supervised processes

A third option is to add the pool as part of your supervision tree:

children = [
  :hackney_pool.child_spec(:first_pool, [timeout: 15000, max_connections: 100])
]

Add that to the application supervisor and first_pool will be available to be used by HTTPoison/hackney.

Multipart

Request

HTTPoison supports making multipart requests. E.g. with a local file:

HTTPoison.post("https://myurl.php", {:multipart, [{:file, "test.txt", {"form-data", [{"name", "mytest"}, {"filename", "test.txt"}]}, []}]})

Sometimes you may already have the file contents in memory and want to upload it elsewhere. A common example is fetching the file from a service like S3 and uploading it somewhere else. There is no need to persist the file locally, you can do the below:

binary_file_content = "Something you fetched and now have it in memory"
token = "some_token_from_another_request"
headers = [{"Authorization", "Bearer #{token}"}, {"Content-Type", "multipart/form-data"}]
options = [ssl: [{:versions, [:'tlsv1.2']}], recv_timeout: 500]

HTTPoison.request(
  :post,
  "https://myurl.com",
  {:multipart,
   [{"file", binary_file_content, {"form-data", [name: "file", filename: "a_file_name.txt"]}, []}]},
  headers,
  options
)

Further examples of multipart requests can be found in the issues (e.g.: here and here).

For more complex queries regarding multipart requests, you should follow the hackney docs for the multipart API.

Response

HTTPoison supports parsing multipart responses. E.g.:

iex(1)> response = %HTTPoison.Response{
...(1)>   body: "--123\r\nContent-type: application/json\r\n\r\n{\"1\": \"first\"}\r\n--123\r\nContent-type: application/json\r\n\r\n{\"2\": \"second\"}\r\n--123--\r\n",
...(1)>   headers: [{"Content-Type", "multipart/mixed;boundary=123"}],
...(1)>   request_url: "http://localhost",
...(1)>   status_code: 200
...(1)> }
%HTTPoison.Response{
  body: "--123\r\nContent-type: application/json\r\n\r\n{\"1\": \"first\"}\r\n--123\r\nContent-type: application/json\r\n\r\n{\"2\": \"second\"}\r\n--123--\r\n",
  headers: [{"Content-Type", "multipart/mixed;boundary=123"}],
  request_url: "http://localhost",
  status_code: 200
}

iex(2)> HTTPoison.Handlers.Multipart.decode_body(response)
[
  {[{"Content-Type", "application/json"}], "{\"1\": \"first\"}"},
  {[{"Content-Type", "application/json"}], "{\"2\": \"second\"}"}
]

For more complex queries regarding multipart response parsing, you should follow the hackney docs for the hackney_multipart API.

Logging

If you're running on top of hackney (which you probably are) there's a handy way to get detailed request logging:

# Add :runtime_tools to :extra_applications in mix.exs
def application do
  [extra_applications: [:logger, :runtime_tools]]
end
iex(1)> :hackney_trace.enable(:max, :io)

Just throw this in your code before your HTTPoison call and you'll get low-level log output.

License

Copyright ยฉ 2013-present Eduardo Gurgel <[email protected]>

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.

httpoison's People

Contributors

antedeguemon avatar ch4s3 avatar d0rc avatar darksheik avatar edgurgel avatar guillaumemilan avatar gushonorato avatar jadlr avatar jlgeering avatar joemerriweather-webb avatar lilactown avatar lowks avatar mmrobins avatar niklaslong avatar nscyclone avatar optikfluffel avatar pedep avatar perrycate avatar quinnwilton avatar reset avatar rhruiz avatar rodrigues avatar ryanwinchester avatar samhamilton avatar shahnerodgers avatar skie avatar thbar avatar tompave avatar vereis avatar whatyouhide avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

httpoison's Issues

params accepts map, but not a tuple

Line 136 of httpoison/lib/httpoison/base.ex has the following description:

        * `:params` - an enumerable consisting of two-item tuples that will be appended to the url as query string parameters

But, and maybe I'm just doing it incorrectly, I can only seem to get this to work with a map like so:

#within a module
  HTTPoison.get!(@lci_url <> "/visitors", @headers, [params: %{"state" => "chatting"}])

If I try with a two item tuple like [params: {"state", "chatting"}], I receive the following error:

** (Protocol.UndefinedError) protocol Enumerable not implemented for {"state", "chatting"}
       (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
       (elixir) lib/enum.ex:112: Enumerable.reduce/3
       (elixir) lib/enum.ex:1274: Enum.reduce/3
       (elixir) lib/enum.ex:1007: Enum.map_join/3
    (httpoison) lib/httpoison.ex:66: HTTPoison.request/5
    (httpoison) lib/httpoison.ex:66: HTTPoison.request!/5

Am I just not passing a two-item tuple properly, or is this supposed to accept a Map instead?

Looking up a specific header in a response

Hello!

Currently it seems that HTTPoison's responses' headers are a list of pairs of strings, like this:

[
  {"foo", "one"},
  {"bar", "two"},
]

Unless I'm missing something this makes it awkward to look up a specific header (I'm interested in the content type), as I have to iterate over each element of the list until I find the one I want, say with Enum.find_value/2.

I feel like I want headers to be a Dict of some type, so I can use foo["bar"].

Is there a better way to do this out of the box?

Thanks,
Louis

Ensure headers passed to Hackney as case-insensitive?

I've run into an issue where I'm passing headers (including a host header) coming from a Plug's connection into HTTPoison, which default to lower-case, thus causing :hackney to override the Host header (because it matches a capitalized one).

The referred use-case is for an app acting as a reverse-proxy where transparently passing headers, specially the Host header is very important.

A workaround have been suggested, which I feel would be best implemented at the httpoison lib level to avoid this issue for other users as well.

What do you guys think?

The unlimited recv_timout causes HTTPoison to eventually stop working

Hey @edgurgel,

First off let me thank you for this awesome library, which has become my go-to everywhere I use Elixir (which is everywhere).

However we noticed this problem in ex_aws before and I have it across different apps in which I use HTTPoison:
After running for a long while (say a week) any HTTPoison requests just stop working. They don't fail, they don't time out, they simply never execute, stalling the entire process with them.

What we believe is happening is that the :infinity, that's the default in hackney for recv_timeout causes each worker in the pool (I believe it's 50 in the default config) to wait for an ack. It happens rarely that they don't receive the ack but it happens and which a high enough n eventually every worker is waiting and the pool exhausted. Now I could pass a different recv_timeout in each HTTPoison.get call but that would be bothersome and I am lazy.

Any chance we can change the default here? For example by using a application configuration option?

Thanks,
Michael

Getting the first 1024 bytes of a URL's content

Hi, I'm currently facing a situation when I have to fetch the first 1024 bytes of a URL's content (especially when it's an image, to determine its dimensions) without having to download it on disk/RAM.
Could you tell me if HTTPoison has such an option?

Edit: Okay nevermind, I could do that with a Range header.

POSTing with basic-auth error

Passing the basic-auth argument on a POST query returns the following:

iex> auth = [hackney: [basic_auth: {"user", "password"}]]
iex> HTTPoison.post "http://example.com/basic_auth", "{\"data\":1}", auth
** (ArgumentError) argument error
                :erlang.bit_size(:hackney)
      (hackney) src/hackney_lib/hackney_bstr.erl:39: :hackney_bstr.to_lower/1
      (hackney) src/hackney_lib/hackney_headers.erl:52: anonymous fn/2 in :hackney_headers.update/2
       (stdlib) lists.erl:1261: :lists.foldl/3
      (hackney) src/hackney_client/hackney_request.erl:52: :hackney_request.perform/2
      (hackney) src/hackney_client/hackney.erl:350: :hackney.send_request/2
    (httpoison) lib/httpoison.ex:40: HTTPoison.request/5

Support streaming requests and responses

According to the Hackney documentation we can stream a response by passing {:async, :once}ย and then fetch the remaining chunks by calling stream_next/1 but I can't find this function in HTTPoison. I don't want to use stream_to because I want to throttle the data.

I haven't yet gotten to the streaming request part, is it supported? I didn't find any reference to send_body.

HTTPoison / Hackney Crash

I just ran into something - any pointers n where to go next would be great - just started with Elixir etc.

When I run this:

HTTPoison.get("http://tmblr.co/Zjsg5q1x9XOT4", [], [ hackney: hackney ])

I see this:

** (MatchError) no match of right hand side value: {:error, {:closed, ""}}
(hackney) src/hackney_client/hackney.erl:796: :hackney.redirect/2
(hackney) src/hackney_client/hackney.erl:761: :hackney.maybe_redirect1/3
(hackney) src/hackney_client/hackney.erl:367: :hackney.send_request/2
(hackney) src/hackney_client/hackney.erl:832: :hackney.redirect/2
(hackney) src/hackney_client/hackney.erl:761: :hackney.maybe_redirect1/3
(hackney) src/hackney_client/hackney.erl:367: :hackney.send_request/2
(httpoison) lib/httpoison/base.ex:375: HTTPoison.Base.request/9

Async request to streaming server - How to keep alive or extend connection timeout?

I am trying to access a third-party streaming api ( the flowdock api to be exact ) and expected I could make a request and just handle the AsyncChunk messages as they roll in. The api sends periodic keep-alive responses, apparently, but I'm not getting that far due to timeouts.

I was able to put something real crude together and everything is ok as long as I keep causing chunks to come in (by creating messages in Flowdock). However, if I let things go idle for about five seconds - I end up with a %HTTPoison.Error{id: #Reference<0.0.2.15>, reason: {:closed, :timeout}}.

defmodule FlowdockTest do
  use GenServer

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def init(:ok) do
    url = "https://foo:[email protected]/flows/foo/bar"
    {:ok, HTTPoison.get!(url, %{"Accept" => "application/json"}, [stream_to: self])}
  end

  def handle_info(msg, state) do
    IO.inspect {:handle_info, msg}
    {:noreply, state}
  end

end

Am I doing something terribly wrong here or is this not a good approach for accessing a streaming api? I know the code example is simple and less than ideal, but it feels like I'm missing an option or something to force HTTPoison or Hackney to keep the connection open.

Close an async request manually

I'm using the :stream_to parameter to stream data from a HTTP endpoint.
The endpoint streams forever, which means that i cannot wait for an %HTTPoison.AsyncEnd{} message.

Is there a way to close the connection? Maybe using the %HTTPoison.AsyncResponse id?

JSON response causes error

I'm trying to consume a JSON API and I get this error, ** (Protocol.UndefinedError) protocol String.Chars not implemented for {:closed, "{.

The rest of it's just a bunch of JSON. Any ideas?

Load balancing and workers

Is there a way or need to load balance requests through a supervisor-worker tree ?

I have several modules that use HTTPoison.Base

When following redirects, I'd like to be able to access the final location

As it stands now, following a redirect only returns the final response, and there is no way to determine what URL that response maps to. For example, the following call hits tinfoil-fake-site.com, but follows a redirect, and ends up on https://tinfoilsecurity.com

HTTPoison.request(:get, "tinfoil-fake-site.com", "", [], [hackney: [{:follow_redirect, true}]])

Since the final response does not have a location header (As it shouldn't!), we have no way of determining what page we ended up on.

The Hackney documentation claims that the "Last Location is stored in the location property of the client state," but I don't believe we have access to this from HTTPoison.

I'll let you know if I find a way of accessing this information. I'm also happy to contribute a fix upstream if you have any ideas on a possible solution!

Dialyzer returns a few warnings

$ mix dialyze
Finding applications for analysis
Finding suitable PLTs
Looking up modules in dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Looking up modules in dialyze_erlang-18.1_elixir-1.1.1.plt
Finding applications for dialyze_erlang-18.1_elixir-1.1.1.plt
Finding modules for dialyze_erlang-18.1_elixir-1.1.1.plt
Checking 356 modules in dialyze_erlang-18.1_elixir-1.1.1.plt
Finding applications for dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Finding modules for dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Copying dialyze_erlang-18.1_elixir-1.1.1.plt to dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Looking up modules in dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Checking 356 modules in dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Adding 30 modules to dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
Finding modules for analysis
Analysing 10 modules with dialyze_erlang-18.1_elixir-1.1.1_deps-dev.plt
lib/httpoison.ex:66: Guard test is_atom(_@9::#{}) can never succeed

lib/httpoison.ex:66: Guard test is_binary(_@8::#{}) can never succeed

lib/httpoison/base.ex:392: The pattern {'ok', _status_code@2, _headers@2} can never match the type {'error',_} | {'ok',_} | {'ok',integer(),[any()],_}

I noticed this while running Dialyzer on my own project, that wraps HTTPoison.Base. I haven't looked into the issue in any detail, but it might point towards a bug or two in that case statement.

No AsyncEnd message is received on a 204 response missing content-length or transfer-encoding headers

I imagine this is basically the same issue as #10, but I ran into this today on the async side of things -- making an async request against a rails api that's running on phusion passenger, I get a 204 status and headers correctly, but I never get an %HTTPotion.AsyncEnd message -- I'm thinking this is because the server isn't supplying a content-length="0" header, nor a transfer-encoding header, so perhaps hackney is never sending the "done" message to HTTPoison?

My erlang skills are pretty weak, so I'm not sure this is an issue in Hackney and not HTTPoison, but it seems likely. I tried writing a test for this in HTTPoison's test suite, but, as you mention in your comment from August 21, HTTParrot (via cowboy) always returns a content-length header, in which case the AsyncEnd message is being received.

For now I'll just not wait for an AsyncEnd message if the status is 204 or 304, but I wonder if HTTPoison should just go ahead and generate an AsyncEnd message if there's no content-length or transfer-encoding headers?

Receiving connect_timeout on specific url

Hello everyone,

I'm trying to do a request on this url https://qasecommerce.cielo.com.br/servicos/ecommwsec.do but I'm receiving this error {:error, :connect_timeout}.
I think it is something related to SSL and TSLv1, does anybody know if it is and erlang issue and how to fix it?

Doing the request with curl works fine.

Thank you.

multipart upload

any plans to support multipart upload? Is this possilbe today, or do you need to use hackney_multipart somehow?

named file upload

Hey :)

Just wondering what the correct way to POST a file with a named key is, for example the following code sends the file as the filename, where I would like to specify the parameter name it's sent as:

file = "file.jpg"
HTTPoison.post!(url, {:multipart, [{"device_timestamp", timestamp}, {:file, file}]}, headers, options)

Is it possible to send the :file with a name like "photo"? When I proxy the request I can see now it's named "file.jpg"

Thanks!

EDIT: actually I just inspected the raw request and I can see

content-disposition: form-data; name=file; filename="file.jpg"

httpoison should not define large chunk of code in my modules

Although I don't agree with the Base approach in httpoison, the minimum that could be done is to at least keep the function definitions short. For example, instead of:

      def transformer(target) do
        receive do
          {:hackney_response, id, {:status, code, _reason}} ->
            send target, %HTTPoison.AsyncStatus{id: id, code: process_status_code(code)}
            transformer(target)
          {:hackney_response, id, {:headers, headers}} ->
            send target, %HTTPoison.AsyncHeaders{id: id, headers: process_headers(headers)}
            transformer(target)
          {:hackney_response, id, :done} ->
            send target, %HTTPoison.AsyncEnd{id: id}
          {:hackney_response, id, {:error, reason}} ->
            send target, %HTTPoison.Error{id: id, reason: reason}
          {:hackney_response, id, chunk} ->
            send target, %HTTPoison.AsyncChunk{id: id, chunk: process_response_chunk(chunk)}
            transformer(target)
        end
      end

httpoison should inject:

def transformer(target) do
  HTTPoison.Base.transformer(__MODULE__, target)
end

Make the body of a POST request optional

Would a PR that makes the body of a HTTPoison.post call optional be acceptable? I think this would make the library more flexible. My particular use case is for interacting with ElasticSearch's Search API. Thank you.

Segmentation fault when executing POST

The following works (I know this is not the correct way to call this API but it serves as a working example):

HTTPoison.request(:post, "https://api.github.com", "asdf", [{"Accept", "application/json"}])

But if I do

HTTPoison.request(:post, "https://identitysso.betfair.com/api/login", "asdf", [{"Accept", "application/json"}])

then iex crashes with a segmentation fault every time.

Unwrap responses from {:ok | :error, _} tuples

Hey there,
Would you be interested in a PR that unwraps the results of :hackey.request from ok/error tuples?

Keeping :ok / :error seems unnecessary when you're already wrapping the actual results in structures that can be pattern matched against to determine success/error status.

In code terms, the client API would change to something like this

#instead of this, taken from Readme
case HTTPoison.get(url) do
  {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
    IO.puts body
  {:ok, %HTTPoison.Response{status_code: 404}} ->
    IO.puts "Not found :("
  {:error, %HTTPoison.Error{reason: reason}} ->
    IO.inspect reason
end
# This!
case HTTPoison.get(url) do
  %HTTPoison.Response{status_code: 200, body: body} ->
    IO.puts body
  %HTTPoison.Response{status_code: 404} ->
    IO.puts "Not found :("
  %HTTPoison.Error{reason: reason} ->
    IO.inspect reason
end

Error compiling deps

Hi! I'm an Elixir beginner and I'm trying to create my first app following the Programming Elixir book.
On one of it's sample applications it uses httpoison but I can't get ir working.
I add the dep, get dependencies and compile everything:

$ mix deps.get
Running dependency resolution
* Updating hackney (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/hackney-1.4.5.tar)
Using locally cached package
Unpacked package tarball (/Users/myuser/.hex/packages/hackney-1.4.5.tar)
* Getting ssl_verify_hostname (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/ssl_verify_hostname-1.0.5.tar)
Using locally cached package
Unpacked package tarball (/Users/myuser/.hex/packages/ssl_verify_hostname-1.0.5.tar)
* Getting mimerl (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/mimerl-1.0.0.tar)
Using locally cached package
Unpacked package tarball (/Users/myuser/.hex/packages/mimerl-1.0.0.tar)
* Getting idna (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/idna-1.0.2.tar)
Using locally cached package
Unpacked package tarball (/Users/myuser/.hex/packages/idna-1.0.2.tar)
* Getting certifi (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/certifi-0.3.0.tar)
Using locally cached package
Unpacked package tarball (/Users/myuser/.hex/packages/certifi-0.3.0.tar)

$iex -S mix
Erlang/OTP 18 [erts-7.1] [source-2882b0c] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

==> idna (compile)
Compiled src/punycode.erl
Compiled src/idna_unicode.erl
Compiled src/idna.erl
Compiled src/idna_ucs.erl
Compiled src/idna_unicode_data.erl
WARN:  Missing plugins: [rebar3_hex]
==> mimerl (compile)
Compiled src/mimerl.erl
==> ssl_verify_hostname (compile)
Compiled src/ssl_verify_hostname.erl
==> certifi (compile)
Compiled src/certifi.erl
Compiled src/certifi_weak.erl
Compiled src/certifi_cacerts.erl
WARN:  Expected /Users/myuser/Documents/projects/elixir/issues/_build/dev/lib/cowboy to be an app dir (containing ebin/*.app), but no .app found.
==> hackney (compile)
WARN:  Expected /Users/myuser/Documents/projects/elixir/issues/_build/dev/lib/cowboy to be an app dir (containing ebin/*.app), but no .app found.
Dependency not available: cowboy-.* ({git,
                                      "https://github.com/ninenines/cowboy.git",
                                      {tag,"1.0.4"}})
ERROR: compile failed while processing /Users/myuser/Documents/projects/elixir/issues/deps/hackney: rebar_abort
** (Mix) Could not compile dependency :hackney, "/Users/myuser/.mix/rebar" command failed. You can recompile this dependency with "mix deps.compile hackney", update it with "mix deps.update hackney" or clean it with "mix deps.clean hackney"

As I'm a newbie I don't really now what's going on, but it looks like as it's having some kind of issue regarding hackney and cowboy deps.

Is this also happening to anyone else?

Thanks in advance :)

Receiving error message starting hackney when using v0.7.1

Just a heads up that I tried adding HTTPoison v0.7.1 to a project and received the message:

could not find application file: ssl_verify_hostname.app

Now it's related to hackney but once I reverted to HTTPoison v0.7 everything runs as expected. I can take a look but it might be later today.

Stubbing HTTPoison responses

Hey guys,

I'm new to using HTTPoison, but I had a quick question. I'm looking to build a simple API wrapper with HTTPoison as I learn more about how to use it. How would I stub HTTPoison's requests (GET/POST/etc.) so that I can control the test results while still ensuring the proper URL, etc. are being received.

Any best practices about stubbing responses would be helpful. Thanks!

Could not compile dependency ssl_verify_hostname

I added httpoison to my mix.exs, but when I run mix deps.compile, I get the following error:

==> idna (compile)
==> ssl_verify_hostname (compile)
Compiling src/ssl_verify_hostname.erl failed:
src/ssl_verify_hostname.erl:8: can't find include lib "public_key/include/public_key.hrl"
src/ssl_verify_hostname.erl:30: undefined macro 'id-ce-subjectAltName'
src/ssl_verify_hostname.erl:20: record 'AttributeTypeAndValue' undefined
src/ssl_verify_hostname.erl:22: variable 'CN' is unbound
src/ssl_verify_hostname.erl:170: record 'OTPTBSCertificate' undefined
src/ssl_verify_hostname.erl:175: record 'OTPCertificate' undefined
src/ssl_verify_hostname.erl:177: function extract_dns_names/1 undefined
src/ssl_verify_hostname.erl:38: Warning: function extract_dns_names_from_alt_names/2 is unused
ERROR: compile failed while processing /home/jerome/Development/soundcloud/deps/ssl_verify_hostname: rebar_abort
** (Mix) Could not compile dependency ssl_verify_hostname, /home/jerome/.mix/rebar command failed. If you want to recompile this dependency, please run: mix deps.compile ssl_verify_hostname

Do I need to install a dependency or something ?

Should I read body to avoid leaks?

I am using HTTPoison to make several thousand requests/sec. I am recently been plagued with :system_limit error and I filed an issue with hackney here: benoitc/hackney#257

The author says that I should read the response body of all requests. I am not sure if it has the same implication when I use HTTPoison and not use hackney directly. Can you shed some light?

Module :leex is not available

Environment is Ubuntu 12.04, using the default Elixir install (via erlang-solutions_1.0_all.deb addition to package sources). The resulting Elixir version is 1.1.0, and erlang is 7.1 with otp release 18 (but I can't figure out how to get the specific minor version on Ubuntu). Floki is 0.6 (and I've also tried 0.6.1). I'm getting this:

    # mix deps.compile floki
    ==> floki
    could not compile dependency :floki, "mix compile" failed. You can recompile this dependency with "mix deps.compile floki", update it with "mix deps.update floki" or clean it with "mix deps.clean floki"
    ** (UndefinedFunctionError) undefined function: :leex.file/2 (module :leex is not available)
        :leex.file('src/floki_selector_lexer.xrl', [scannerfile: 'src/floki_selector_lexer.erl', report: true])
        (mix) lib/mix/compilers/erlang.ex:84: anonymous fn/3 in Mix.Compilers.Erlang.compile/3
        (elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
        (mix) lib/mix/compilers/erlang.ex:83: Mix.Compilers.Erlang.compile/3
        (elixir) lib/enum.ex:1043: anonymous fn/3 in Enum.map/2
        (elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
        (elixir) lib/enum.ex:1043: Enum.map/2
        (mix) lib/mix/tasks/compile.all.ex:19: anonymous fn/1 in Mix.Tasks.Compile.All.run/1

Process response body based on content type

Hi,

There are circumstances in which processing the response body depends on which Content-Type header is returned, and possibly other headers. Specifically, I would like to be able to do this in a module using Base:

defp process_response_body(body, headers) do
  ...
end

or even this:

defp process_response_body(request) do
  ...
end

The reason is because I have some URLs returning text/plain, which I want to keep as strings, and other URLs returning application/json which I want to run through a JSON decoder.

Which of the two examples above would you prefer be implemented? Is there a way to do this such that it is backwards compatible? Please let me know and I can do a pull request.

Thanks!

Request state variable

It would be great to pass an (optional?) state variable through process_ handlers, for complicated request processes.

Unknown CA error with HTTPoison 0.7

iex(1)> HTTPoison.get("https://app.promoter.io")
[error] SSL: :certify: ssl_handshake.erl:1401:Fatal error: unknown ca

{:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'unknown ca'}}}

Tested on Erlang 17.5 and 18, with the same effect. Works fine in HTTPotion, though.

hackney not compiling due to rebar_abort.

When compiling HTTPoison I'm getting this error:

==> hackney (compile)
Compiling src/hackney_connect/hackney_http_connect.erl failed:
src/hackney_connect/hackney_http_connect.erl:11: can't find include lib "kernel/src/inet_dns.hrl"
ERROR: compile failed while processing /home/elbow/elixir-sips-progress/issues/deps/hackney: rebar_abort
** (Mix) Could not compile dependency hackney, /home/elbow/.mix/rebar command failed. If you want to recompile this dependency, please run: mix deps.compile hackney

While I realize this is likely not an issue with HTTPoison's source this does make this dependency unusable (at least for me).

I'm using { :httpoison, "~> 0.5" } and Elixir 1.0.2.

Timeouts when making POST requests

Hound uses HTTPoison under the covers to make requests to the WebDriver JSON API.

Making a POST request to the chrome driver fails. Here are steps to reproduce.

Requirements

Reproducing the error

body = Poison.encode! %{desiredCapabilities: %{}}
headers = [{"Content-Type", "text/json"}]
url = "http://localhost:9515/session"
HTTPoison.post(url, body, headers)

The request times out after a long time. Should be useful to pass the [recv_timeout: 5000] option to debug faster.

The same request is successful with a response when using curl in the command line:

$ curl --data "{\"desiredCapabilities\": {}}" --header "Content-type: text/json" http://localhost:9515/session
{"sessionId":"87d6b9cfb5bd2bd1782e65f2b6a30511","status":0,"value":{"acceptSslCerts":true,"applicationCacheEnabled":false,"browserConnectionEnabled":false,"browserName":"chrome","chrome":{"userDataDir":"/var/folders/96/1sp35x9j2n116stp3gtk3m5w0000gn/T/.org.chromium.Chromium.MBiD8D"},"cssSelectorsEnabled":true,"databaseEnabled":false,"handlesAlerts":true,"hasTouchScreen":false,"javascriptEnabled":true,"locationContextEnabled":true,"mobileEmulationEnabled":false,"nativeEvents":true,"platform":"Mac OS X","rotatable":false,"takesHeapSnapshot":true,"takesScreenshot":true,"version":"46.0.2490.80","webStorageEnabled":true}}

Please do let me know if I'm doing something wrong.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.