{"id":13507113,"url":"https://github.com/edgurgel/httpoison","last_synced_at":"2025-05-14T07:07:42.966Z","repository":{"id":10551117,"uuid":"12750103","full_name":"edgurgel/httpoison","owner":"edgurgel","description":"Yet Another HTTP client for Elixir powered by hackney","archived":false,"fork":false,"pushed_at":"2025-04-20T05:00:43.000Z","size":478,"stargazers_count":2249,"open_issues_count":33,"forks_count":339,"subscribers_count":25,"default_branch":"main","last_synced_at":"2025-05-14T06:54:35.730Z","etag":null,"topics":["elixir","hackney","hacktoberfest","http","http-client"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/httpoison","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edgurgel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2013-09-11T07:21:25.000Z","updated_at":"2025-05-12T11:29:49.000Z","dependencies_parsed_at":"2023-02-16T04:01:25.898Z","dependency_job_id":"593b95d2-c313-498e-a640-353b356db9f0","html_url":"https://github.com/edgurgel/httpoison","commit_stats":{"total_commits":434,"total_committers":145,"mean_commits":"2.9931034482758623","dds":0.6082949308755761,"last_synced_commit":"4029183db55261f9f9b01c2fa9416ca62b27d6bb"},"previous_names":[],"tags_count":57,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fhttpoison","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fhttpoison/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fhttpoison/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fhttpoison/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edgurgel","download_url":"https://codeload.github.com/edgurgel/httpoison/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092776,"owners_count":22013290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","hackney","hacktoberfest","http","http-client"],"created_at":"2024-08-01T02:00:24.278Z","updated_at":"2025-05-14T07:07:42.898Z","avatar_url":"https://github.com/edgurgel.png","language":"Elixir","funding_links":[],"categories":["Top 20 packages","HTTP","Elixir","Uncategorized"],"sub_categories":["Uncategorized"],"readme":"![HTTPoison logo](logo.png)\n# HTTPoison [![Build Status](https://github.com/edgurgel/httpoison/actions/workflows/main.yml/badge.svg)](https://github.com/edgurgel/httpoison/actions/workflows/main.yml) [![Hex pm](https://img.shields.io/hexpm/v/httpoison.svg?style=flat)](https://hex.pm/packages/httpoison) [![hex.pm downloads](https://img.shields.io/hexpm/dt/httpoison.svg?style=flat)](https://hex.pm/packages/httpoison)\n\nHTTP client for Elixir, based on\n[HTTPotion](https://github.com/myfreeweb/httpotion)\n([documentation](https://hexdocs.pm/httpoison/)).\n\n\n## Installation\n\nFirst, add HTTPoison to your `mix.exs` dependencies:\n\n```elixir\ndef deps do\n  [\n    {:httpoison, \"~\u003e 2.0\"}\n  ]\nend\n```\n\nand run `$ mix deps.get`.\n\n## Upgrading to 2.x.x\n\nThe 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.\n\nThe default SSL options can be found on [hackney's codebase](https://github.com/benoitc/hackney/blob/befe2df2080704824487c3c0201417d0ddb3c686/src/hackney_connection.erl#L115-L148) as we simply use `:hackney_connections.merge_ssl_opts/2`\n\nMore context here: https://github.com/edgurgel/httpoison/pull/466\n\n\n## Usage\n\n```elixir\niex\u003e HTTPoison.start\niex\u003e HTTPoison.get! \"https://postman-echo.com/get\"\n%HTTPoison.Response{\n  status_code: 200,\n  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}\",\n  headers: [ ... ]\n}\n\niex\u003e HTTPoison.get! \"http://localhost:1\"\n** (HTTPoison.Error) :econnrefused\niex\u003e HTTPoison.get \"http://localhost:1\"\n{:error, %HTTPoison.Error{id: nil, reason: :econnrefused}}\n\niex\u003e HTTPoison.post \"https://postman-echo.com/post\", \"{\\\"body\\\": \\\"test\\\"}\", [{\"Content-Type\", \"application/json\"}]\n{:ok,\n %HTTPoison.Response{\n   status_code: 200,\n   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}\",\n   headers: [ ... ]\n }}\n\n```\n\nYou can also easily pattern match on the `HTTPoison.Response` struct:\n\n```elixir\ncase HTTPoison.get(url) do\n  {:ok, %HTTPoison.Response{status_code: 200, body: body}} -\u003e\n    IO.puts body\n  {:ok, %HTTPoison.Response{status_code: 404}} -\u003e\n    IO.puts \"Not found :(\"\n  {:error, %HTTPoison.Error{reason: reason}} -\u003e\n    IO.inspect reason\nend\n```\n\n[Here](https://www.erlang.org/doc/man/inet.html#posix-error-codes) is the list of all possible error reasons.\n\n### Options\n\nThere are a number of supported options(*not to be confused with the HTTP options method*), documented [here](https://hexdocs.pm/httpoison/HTTPoison.html#request/5), 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](https://erlang.org/doc/man/ssl.html), and `:recv_timeout` sets a timeout on receiving a response, the default is 5000ms.\n\n```elixir\ntoken = \"some_token_from_another_request\"\nurl = \"https://example.com/api/endpoint_that_needs_a_bearer_token\"\nheaders = [\"Authorization\": \"Bearer #{token}\", \"Accept\": \"Application/json; Charset=utf-8\"]\noptions = [ssl: [{:versions, [:'tlsv1.2']}], recv_timeout: 500]\n{:ok, response} = HTTPoison.get(url, headers, options)\n```\n\nAnd the example below shows the use of the `:ssl` options for a post request to an api that requires a client certification.\n\n```elixir\nurl = \"https://example.org/api/endpoint_that_needs_client_cert\"\noptions = [ssl: [certfile: \"certs/client.crt\"]]\n{:ok, response} = HTTPoison.post(url, [], options)\n```\n\n### Wrapping `HTTPoison.Base`\n\nYou can also use the `HTTPoison.Base` module in your modules in order to make\ncool API clients or something. The following example wraps `HTTPoison.Base` in\norder to build a client for the GitHub API\n([Poison](https://github.com/devinus/poison) is used for JSON decoding):\n\n```elixir\ndefmodule GitHub do\n  use HTTPoison.Base\n\n  @expected_fields ~w(\n    login id avatar_url gravatar_id url html_url followers_url\n    following_url gists_url starred_url subscriptions_url\n    organizations_url repos_url events_url received_events_url type\n    site_admin name company blog location email hireable bio\n    public_repos public_gists followers following created_at updated_at\n  )\n\n  def process_request_url(url) do\n    \"https://api.github.com\" \u003c\u003e url\n  end\n\n  def process_response_body(body) do\n    body\n    |\u003e Poison.decode!\n    |\u003e Map.take(@expected_fields)\n    |\u003e Enum.map(fn({k, v}) -\u003e {String.to_atom(k), v} end)\n  end\nend\n```\n\n```elixir\niex\u003e GitHub.start\niex\u003e GitHub.get!(\"/users/myfreeweb\").body[:public_repos]\n37\n```\n\nIt's possible to extend the functions listed below:\n\n```elixir\ndef process_request_body(body), do: body\n\ndef process_request_headers(headers) when is_map(headers) do\n  Enum.into(headers, [])\nend\n\ndef process_request_headers(headers), do: headers\n\ndef process_request_options(options), do: options\n\ndef process_request_url(url), do: url\n\ndef process_response_body(body), do: body\n\ndef process_response_chunk(chunk), do: chunk\n\ndef process_response_headers(headers), do: headers\n\ndef process_response_status_code(status_code), do: status_code\n```\n\n### Async requests\n\nHTTPoison now comes with async requests!\n\n```elixir\niex\u003e HTTPoison.get! \"https://github.com/\", %{}, stream_to: self\n%HTTPoison.AsyncResponse{id: #Reference\u003c0.0.0.1654\u003e}\niex\u003e flush\n%HTTPoison.AsyncStatus{code: 200, id: #Reference\u003c0.0.0.1654\u003e}\n%HTTPoison.AsyncHeaders{headers: %{\"Connection\" =\u003e \"keep-alive\", ...}, id: #Reference\u003c0.0.0.1654\u003e}\n%HTTPoison.AsyncChunk{chunk: \"\u003c!DOCTYPE html\u003e...\", id: #Reference\u003c0.0.0.1654\u003e}\n%HTTPoison.AsyncEnd{id: #Reference\u003c0.0.0.1654\u003e}\n:ok\n```\n\n**Warning: this option can flood a receiver in messages.**\n\nIf a server may send very large messages the `async: :once` option should be used.\nThis will send only a single chunk at a time the receiver can call `HTTPoison.stream_next/1` to indicate ability to process more chunks.\n\n### Cookies\n\nHTTPoison allows you to send cookies:\n\n```elixir\niex\u003e HTTPoison.get!(\"https://postman-echo.com/cookies\", %{}, hackney: [cookie: [\"session=a933ec1dd923b874e691; logged_in=true\"]])\n%HTTPoison.Response{\n  status_code: 200,\n  body: \"{\\n  \\\"cookies\\\": {\\n    \\\"session\\\": \\\"a933ec1dd923b874e691\\\",\\n    \\\"logged_in\\\": \\\"true\\\"\\n  }\\n}\",\n  headers: [ ... ]\n}\n\n```\n\nYou can also receive cookies from the server by reading the `\"set-cookie\"` headers in the response:\n\n```elixir\niex(1)\u003e response = HTTPoison.get!(\"https://postman-echo.com/cookies/set?foo=1\")\niex(2)\u003e cookies = Enum.filter(response.headers, fn\n...(2)\u003e {key, _} -\u003e String.match?(key, ~r/\\Aset-cookie\\z/i)\n...(2)\u003e end)\n[ {\"set-cookie\", \"foo=1; Path=/\"}, ...]\n```\n\nYou can see more usage examples in the test files (located in the\n[`test/`](test)) directory.\n\n### Connection Pools\n\nNormally **hackney** [opens and closes connections on demand](https://github.com/benoitc/hackney#reuse-a-connection), but it also creates a [default pool](https://github.com/benoitc/hackney#use-the-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.\n\nTo use the default pool, you can just declare it as an option:\n\n```elixir\nHTTPoison.get(\"httpbin.org/get\", [], hackney: [pool: :default])\n```\n\nIt is possible to use different pools for different purposes when a more fine grained allocation of resources is necessary.\n\n#### Simple pool declaration\n\nThe 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.\n\n```elixir\nHTTPoison.get(\"httpbin.org/get\", [], hackney: [pool: :first_pool])\nHTTPoison.get(\"httpbin.org/get\", [], hackney: [pool: :second_pool])\n```\n\n#### Explicit pool creation\n\nIf you want to use different configuration options you can create a pool manually [when your app starts](https://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html#the-application-callback) with `:hackney_pool.start_pool/2`.\n\n```elixir\n:ok = :hackney_pool.start_pool(:first_pool, [timeout: 15000, max_connections: 100])\n```\n\nFrom the already linked [hackney's readme](https://github.com/benoitc/hackney#use-the-default-pool):\n\n\u003e `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.\n\n#### Disabling pool\n\nIf you don't want to use a pool for a single http request, you can do it by passing an option:\n```elixir\nHTTPoison.get(\"httpbin.org/get\", [], hackney: [pool: false])\n```\n\nIf you want to disable the usage of the pool for every request you can do it by adding this to your environment configuration:\n```elixir\nconfig :hackney, use_default_pool: false\n```\n\nYou can find a little explanation here [hackney's readme](https://github.com/benoitc/hackney#use-the-default-pool).\n\n#### Pools as supervised processes\n\nA third option is to add the pool as part of your supervision tree:\n\n```elixir\nchildren = [\n  :hackney_pool.child_spec(:first_pool, [timeout: 15000, max_connections: 100])\n]\n```\n\nAdd that to the application supervisor and `first_pool` will be available to be used by HTTPoison/hackney.\n\n### Multipart\n\n#### Request\n\nHTTPoison supports making `multipart` requests. E.g. with a local file:\n\n```elixir\nHTTPoison.post(\"https://myurl.php\", {:multipart, [{:file, \"test.txt\", {\"form-data\", [{\"name\", \"mytest\"}, {\"filename\", \"test.txt\"}]}, []}]})\n```\n\nSometimes you may already have the file contents in memory and want to upload\nit elsewhere. A common example is fetching the file from a service like S3 and\nuploading it somewhere else. There is no need to persist the file locally, you\ncan do the below:\n\n```elixir\nbinary_file_content = \"Something you fetched and now have it in memory\"\ntoken = \"some_token_from_another_request\"\nheaders = [{\"Authorization\", \"Bearer #{token}\"}, {\"Content-Type\", \"multipart/form-data\"}]\noptions = [ssl: [{:versions, [:'tlsv1.2']}], recv_timeout: 500]\n\nHTTPoison.request(\n  :post,\n  \"https://myurl.com\",\n  {:multipart,\n   [{:file, binary_file_content, {\"form-data\", [name: \"file\", filename: \"a_file_name.txt\"]}, []}]},\n  headers,\n  options\n)\n```\n\nFurther examples of `multipart` requests can be found [in the issues](https://github.com/edgurgel/httpoison/issues?utf8=%E2%9C%93\u0026q=is%3Aissue+multipart) (e.g.: [here](https://github.com/edgurgel/httpoison/issues/144#issue-160035453) and [here](https://github.com/edgurgel/httpoison/issues/237#issuecomment-313132804)).\n\nFor more complex queries regarding multipart requests, you should follow the [hackney docs for the `multipart` API](https://github.com/benoitc/hackney#send-a-body).\n\n#### Response\n\nHTTPoison supports parsing `multipart` responses. E.g.:\n\n```elixir\niex(1)\u003e response = %HTTPoison.Response{\n...(1)\u003e   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\",\n...(1)\u003e   headers: [{\"Content-Type\", \"multipart/mixed;boundary=123\"}],\n...(1)\u003e   request_url: \"http://localhost\",\n...(1)\u003e   status_code: 200\n...(1)\u003e }\n%HTTPoison.Response{\n  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\",\n  headers: [{\"Content-Type\", \"multipart/mixed;boundary=123\"}],\n  request_url: \"http://localhost\",\n  status_code: 200\n}\n\niex(2)\u003e HTTPoison.Handlers.Multipart.decode_body(response)\n[\n  {[{\"Content-Type\", \"application/json\"}], \"{\\\"1\\\": \\\"first\\\"}\"},\n  {[{\"Content-Type\", \"application/json\"}], \"{\\\"2\\\": \\\"second\\\"}\"}\n]\n```\n\nFor more complex queries regarding multipart response parsing, you should follow the [hackney docs for the `hackney_multipart` API](https://github.com/benoitc/hackney/blob/master/doc/hackney_multipart.md).\n\n### Logging\n\nIf you're running on top of hackney (which you probably are) there's a handy way to get detailed request logging:\n\n```elixir\n# Add :runtime_tools to :extra_applications in mix.exs\ndef application do\n  [extra_applications: [:logger, :runtime_tools]]\nend\n```\n\n```\niex(1)\u003e :hackney_trace.enable(:max, :io)\n```\n\nJust throw this in your code before your HTTPoison call and you'll get low-level log output.\n\n## License\n\n    Copyright © 2013-present Eduardo Gurgel \u003ceduardo@gurgel.me\u003e\n\n    This work is free. You can redistribute it and/or modify it under the\n    terms of the MIT License. See the LICENSE file for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fhttpoison","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgurgel%2Fhttpoison","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fhttpoison/lists"}