{"id":32168579,"url":"https://github.com/gsmlg-dev/http_fetch","last_synced_at":"2025-10-21T15:56:05.940Z","repository":{"id":307199575,"uuid":"1028565916","full_name":"gsmlg-dev/http_fetch","owner":"gsmlg-dev","description":"A modern HTTP client library for Elixir that provides a fetch API similar to web browsers, built on Erlang's built-in :httpc module.","archived":false,"fork":false,"pushed_at":"2025-08-12T17:00:16.000Z","size":115,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-12T13:10:29.134Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/gsmlg-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2025-07-29T18:07:39.000Z","updated_at":"2025-08-02T02:39:50.000Z","dependencies_parsed_at":"2025-07-30T01:40:17.681Z","dependency_job_id":"6ec1a171-c51a-4265-aee5-37f2dbac2f87","html_url":"https://github.com/gsmlg-dev/http_fetch","commit_stats":null,"previous_names":["gsmlg-dev/http_fetch"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gsmlg-dev/http_fetch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fhttp_fetch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fhttp_fetch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fhttp_fetch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fhttp_fetch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gsmlg-dev","download_url":"https://codeload.github.com/gsmlg-dev/http_fetch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fhttp_fetch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279017454,"owners_count":26086081,"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","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2025-10-21T15:56:04.686Z","updated_at":"2025-10-21T15:56:05.935Z","avatar_url":"https://github.com/gsmlg-dev.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HTTP Fetch\n\n [![Elixir CI](https://github.com/gsmlg-dev/http_fetch/actions/workflows/elixir.yml/badge.svg)](https://github.com/gsmlg-dev/http_fetch/actions/workflows/elixir.yml)\n [![Hex.pm](https://img.shields.io/hexpm/v/http_fetch.svg)](https://hex.pm/packages/phoenix_react_server)\n [![Hexdocs.pm](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/http_fetch/)\n [![Hex.pm](https://img.shields.io/hexpm/dt/http_fetch.svg)](https://hex.pm/packages/http_fetch)\n [![Hex.pm](https://img.shields.io/hexpm/dw/http_fetch.svg)](https://hex.pm/packages/http_fetch)\n\nA modern HTTP client library for Elixir that provides a fetch API similar to web browsers, built on Erlang's built-in `:httpc` module.\n\n## Features\n\n- **Browser-like API**: Familiar fetch interface with promises and async/await patterns\n- **Full HTTP support**: GET, POST, PUT, DELETE, PATCH, HEAD methods\n- **Complete httpc integration**: Support for all :httpc.request options\n- **Form data support**: HTTP.FormData for multipart/form-data and file uploads\n- **Streaming file uploads**: Efficient large file uploads using streams\n- **Type-safe configuration**: HTTP.FetchOptions for structured request configuration\n- **Promise-based**: Async operations with chaining support\n- **Request cancellation**: AbortController support for cancelling requests\n- **Automatic JSON parsing**: Built-in JSON response handling\n- **Zero dependencies**: Uses only Erlang/OTP built-in modules\n\n## Quick Start\n\n```elixir\n# Simple GET request\n{:ok, response} = \n  HTTP.fetch(\"https://jsonplaceholder.typicode.com/posts/1\")\n  |\u003e HTTP.Promise.await()\n\n# Get response data\nIO.puts(\"Status: #{response.status}\")\ntext = HTTP.Response.text(response)\n{:ok, json} = HTTP.Response.json(response)\n\n# Read response body as raw binary\nresponse = \n  HTTP.fetch(\"https://jsonplaceholder.typicode.com/posts/1\")\n  |\u003e HTTP.Promise.await()\n\n# response.body contains the raw binary data\nbinary_data = response.body\n\n# POST request with JSON\n{:ok, response} = \n  HTTP.fetch(\"https://jsonplaceholder.typicode.com/posts\", [\n    method: \"POST\",\n    headers: %{\"Content-Type\" =\u003e \"application/json\"},\n    body: JSON.encode\\!(%{title: \"Hello\", body: \"World\"})\n  ])\n  |\u003e HTTP.Promise.await()\n```\n\n# Form data with file upload\n\n```elixir\nfile_stream = File.stream!(\"document.pdf\")\nform = HTTP.FormData.new()\n       |\u003e HTTP.FormData.append_field(\"name\", \"John Doe\")\n       |\u003e HTTP.FormData.append_file(\"document\", \"document.pdf\", file_stream)\n\n{:ok, response} = \n  HTTP.fetch(\"https://api.example.com/upload\", [\n    method: \"POST\",\n    body: form\n  ])\n  |\u003e HTTP.Promise.await()\n```\n\n## API Reference\n\n### HTTP.fetch/2\nPerforms an HTTP request and returns a Promise.\n\n```elixir\npromise = HTTP.fetch(url, [\n  method: \"GET\",\n  headers: %{\"Accept\" =\u003e \"application/json\"},\n  body: \"request body\",\n  content_type: \"application/json\",\n  options: [timeout: 10_000],\n  signal: abort_controller\n])\n```\n\nSupports both string URLs and URI structs:\n\n```elixir\n# String URL\npromise = HTTP.fetch(\"https://api.example.com/data\")\n\n# URI struct\nuri = URI.parse(\"https://api.example.com/data\")\npromise = HTTP.fetch(uri)\n```\n\n### HTTP.Promise\nAsynchronous promise wrapper for HTTP requests.\n\n```elixir\n{:ok, response} = HTTP.Promise.await(promise)\n\n# Promise chaining\nHTTP.fetch(\"https://api.example.com/data\")\n|\u003e HTTP.Promise.then(fn response -\u003e HTTP.Response.json(response) end)\n|\u003e HTTP.Promise.await()\n```\n\n### HTTP.Response\nRepresents an HTTP response.\n\n```elixir\ntext = HTTP.Response.text(response)\n{:ok, json} = HTTP.Response.json(response)\n\n# Access raw response body as binary\nresponse = \n  HTTP.fetch(\"https://api.example.com/large-file\")\n  |\u003e HTTP.Promise.await()\n\n# response.body contains the raw binary response data\nbinary_data = response.body\n\n# Write response to file (supports both streaming and non-streaming)\n:ok = HTTP.Response.write_to(response, \"/tmp/downloaded-file.txt\")\n\n# Write large file downloads directly to disk\n{:ok, response} = \n  HTTP.fetch(\"https://example.com/large-file.zip\")\n  |\u003e HTTP.Promise.await()\n\n:ok = HTTP.Response.write_to(response, \"/tmp/large-file.zip\")\n```\n\n### HTTP.Headers\nHandle HTTP headers with utilities for parsing, normalizing, and manipulating headers.\n\n```elixir\n# Create headers\nheaders = HTTP.Headers.new([{\"Content-Type\", \"application/json\"}])\n\n# Get header value\ntype = HTTP.Headers.get(headers, \"content-type\")\n\n# Set header\nheaders = HTTP.Headers.set(headers, \"Authorization\", \"Bearer token\")\n\n# Set header only if not already present\nheaders = HTTP.Headers.set_default(headers, \"User-Agent\", \"CustomAgent/1.0\")\n\n# Access default user agent string\ndefault_ua = HTTP.Headers.user_agent()\n\n# Parse Content-Type\n{media_type, params} = HTTP.Headers.parse_content_type(\"application/json; charset=utf-8\")\n```\n\n### HTTP.Telemetry\nComprehensive telemetry and metrics for HTTP requests and responses.\n\n```elixir\n# All HTTP.fetch operations automatically emit telemetry events\n# No configuration required - just attach handlers\n\n:telemetry.attach_many(\n  \"my_handler\",\n  [\n    [:http_fetch, :request, :start],\n    [:http_fetch, :request, :stop],\n    [:http_fetch, :request, :exception]\n  ],\n  fn event_name, measurements, metadata, _config -\u003e\n    case event_name do\n      [:http_fetch, :request, :start] -\u003e\n        IO.puts(\"Starting request to #{metadata.url}\")\n      [:http_fetch, :request, :stop] -\u003e\n        IO.puts(\"Request completed: #{measurements.status} in #{measurements.duration}μs\")\n      [:http_fetch, :request, :exception] -\u003e\n        IO.puts(\"Request failed: #{inspect(metadata.error)}\")\n    end\n  end,\n  nil\n)\n\n# Manual telemetry events (for custom implementations)\nHTTP.Telemetry.request_start(\"GET\", URI.parse(\"https://example.com\"), %HTTP.Headers{})\nHTTP.Telemetry.request_stop(200, URI.parse(\"https://example.com\"), 1024, 1500)\nHTTP.Telemetry.request_exception(URI.parse(\"https://example.com\"), :timeout, 5000)\n```\n\n### HTTP.Request\nRequest configuration struct.\n\n```elixir\nrequest = %HTTP.Request{\n  method: :post,\n  url: URI.parse(\"https://api.example.com/data\"),\n  headers: [{\"Authorization\", \"Bearer token\"}],\n  body: \"data\",\n  http_options: [timeout: 10_000, connect_timeout: 5_000],\n  options: [sync: false, body_format: :binary]\n}\n```\n\n**Field Mapping to :httpc.request/4:**\n- `http_options`: 3rd argument (request-specific HTTP options)\n- `options`: 4th argument (client-specific options)\n\n### HTTP.FormData\nHandle form data and file uploads.\n\n```elixir\n# Regular form data\nform = HTTP.FormData.new()\n       |\u003e HTTP.FormData.append_field(\"name\", \"John\")\n       |\u003e HTTP.FormData.append_field(\"email\", \"john@example.com\")\n\n# File upload\nfile_stream = File.stream!(\"document.pdf\")\nform = HTTP.FormData.new()\n       |\u003e HTTP.FormData.append_field(\"name\", \"John\")\n       |\u003e HTTP.FormData.append_file(\"document\", \"document.pdf\", file_stream, \"application/pdf\")\n\n# Use in request\nHTTP.fetch(\"https://api.example.com/upload\", method: \"POST\", body: form)\n```\n\n### HTTP.AbortController\nRequest cancellation.\n\n```elixir\ncontroller = HTTP.AbortController.new()\nHTTP.AbortController.abort(controller)\n```\n\n## Error Handling\n\nThe library handles:\n- Network errors and timeouts\n- HTTP error status codes\n- JSON parsing errors\n- Invalid URLs\n- Cancelled requests\n\n## License\n\nMIT License\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgsmlg-dev%2Fhttp_fetch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgsmlg-dev%2Fhttp_fetch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgsmlg-dev%2Fhttp_fetch/lists"}