{"id":46839945,"url":"https://github.com/codeadict/gen_http","last_synced_at":"2026-03-10T12:37:12.219Z","repository":{"id":340400303,"uuid":"846632683","full_name":"codeadict/gen_http","owner":"codeadict","description":"HTTP client for Erlang  with support for HTTP/1 and HTTP/2 (Inspired by Mint)","archived":false,"fork":false,"pushed_at":"2026-02-24T20:46:57.000Z","size":89,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-24T21:00:21.235Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codeadict.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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-23T16:09:16.000Z","updated_at":"2026-02-24T20:47:00.000Z","dependencies_parsed_at":"2026-02-24T21:00:24.544Z","dependency_job_id":null,"html_url":"https://github.com/codeadict/gen_http","commit_stats":null,"previous_names":["codeadict/gen_http"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/codeadict/gen_http","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeadict%2Fgen_http","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeadict%2Fgen_http/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeadict%2Fgen_http/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeadict%2Fgen_http/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codeadict","download_url":"https://codeload.github.com/codeadict/gen_http/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeadict%2Fgen_http/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30333628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T05:25:20.737Z","status":"ssl_error","status_checked_at":"2026-03-10T05:25:17.430Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":"2026-03-10T12:37:11.673Z","updated_at":"2026-03-10T12:37:12.209Z","avatar_url":"https://github.com/codeadict.png","language":"Erlang","readme":"# gen_http\n\n[![CI](https://github.com/codeadict/gen_http/actions/workflows/ci.yml/badge.svg)](https://github.com/codeadict/gen_http/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/github/codeadict/gen_http/graph/badge.svg?token=Y07BA8DQ6T)](https://codecov.io/github/codeadict/gen_http)\n[![HTTP/2 Compliance](https://github.com/codeadict/gen_http/actions/workflows/h2-compliance.yml/badge.svg)](https://github.com/codeadict/gen_http/actions/workflows/h2-compliance.yml)\n[![RFC 7540](https://img.shields.io/badge/RFC%207540-compliant-brightgreen)](https://datatracker.ietf.org/doc/html/rfc7540)\n[![RFC 7541](https://img.shields.io/badge/RFC%207541-compliant-brightgreen)](https://datatracker.ietf.org/doc/html/rfc7541)\n\nA minimal, low-level HTTP client for Erlang.\n\nHTTP/1.1 and HTTP/2 support. Pure Erlang. Zero dependencies.\n\n## Why?\n\nFast. Small API. Proper HTTP/1.1 and HTTP/2 support. Works with both protocols transparently.\n\nBuilt to replace `httpc` with better performance and cleaner code.\n\n## HTTP/2 Compliance\n\n**156 tests** covering RFC 7540 (HTTP/2) and RFC 7541 (HPACK). All frame types, stream states, flow control, priority handling, HPACK compression, and error conditions.\n\nTested against [h2-client-test-harness](https://github.com/nomadlabsinc/h2-client-test-harness). **100% pass rate**.\n\n## Quick Start\n\n```erlang\n%% HTTP/1.1\n{ok, Conn} = gen_http:connect(http, \"httpbin.org\", 80),\n{ok, Conn2, Ref} = gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/get\"\u003e\u003e, [], \u003c\u003c\u003e\u003e),\n\n%% Collect response in active mode (default)\nreceive\n    Msg -\u003e\n        case gen_http:stream(Conn2, Msg) of\n            {ok, Conn3, [{status, Ref, 200}, {headers, Ref, Headers}, {data, Ref, Body}, {done, Ref}]} -\u003e\n                io:format(\"Body: ~s~n\", [Body])\n        end\nend.\n\n%% HTTP/2 (automatic via ALPN)\n{ok, Conn} = gen_http:connect(https, \"google.com\", 443),\n{ok, Conn2, Ref} = gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/\"\u003e\u003e, [], \u003c\u003c\u003e\u003e),\n%% Same API, different protocol\n```\n\n## Installation\n\nAdd to your `rebar.config`:\n\n```erlang\n{deps, [\n    {gen_http, {git, \"https://github.com/codeadict/gen_http.git\", {branch, \"main\"}}}\n]}.\n```\n\n## Examples\n\n### Simple GET Request\n\n```erlang\n{ok, Conn} = gen_http:connect(http, \"httpbin.org\", 80),\n{ok, Conn2, Ref} = gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/get\"\u003e\u003e, [], \u003c\u003c\u003e\u003e),\n\n%% Active mode - receive messages\nreceive Msg -\u003e\n    {ok, Conn3, Responses} = gen_http:stream(Conn2, Msg),\n    io:format(\"Responses: ~p~n\", [Responses])\nend.\n```\n\n### POST with Body\n\n```erlang\n{ok, Conn} = gen_http:connect(http, \"httpbin.org\", 80),\n\nHeaders = [{\u003c\u003c\"content-type\"\u003e\u003e, \u003c\u003c\"application/json\"\u003e\u003e}],\nBody = \u003c\u003c\"{\\\"hello\\\": \\\"world\\\"}\"\u003e\u003e,\n\n{ok, Conn2, Ref} = gen_http:request(Conn, \u003c\u003c\"POST\"\u003e\u003e, \u003c\u003c\"/post\"\u003e\u003e, Headers, Body).\n```\n\n### HTTPS with HTTP/2\n\n```erlang\n%% ALPN automatically negotiates HTTP/2 if available\n{ok, Conn} = gen_http:connect(https, \"www.google.com\", 443),\n{ok, Conn2, Ref} = gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/\"\u003e\u003e, [], \u003c\u003c\u003e\u003e).\n```\n\n### Passive Mode (Blocking)\n\n```erlang\n{ok, Conn} = gen_http:connect(http, \"httpbin.org\", 80, #{mode =\u003e passive}),\n{ok, Conn2, Ref} = gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/get\"\u003e\u003e, [], \u003c\u003c\u003e\u003e),\n\n%% Blocking receive\n{ok, Conn3, Responses} = gen_http:recv(Conn2, 0, 5000),\nio:format(\"Responses: ~p~n\", [Responses]).\n```\n\n### Connection Reuse\n\n```erlang\n{ok, Conn} = gen_http:connect(http, \"httpbin.org\", 80),\n\n%% First request\n{ok, Conn2, Ref1} = gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/get\"\u003e\u003e, [], \u003c\u003c\u003e\u003e),\n%% ... handle response ...\n\n%% Second request on same connection\n{ok, Conn3, Ref2} = gen_http:request(Conn2, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/headers\"\u003e\u003e, [], \u003c\u003c\u003e\u003e),\n%% ... handle response ...\n\n{ok, _} = gen_http:close(Conn3).\n```\n\n### Error Handling\n\n```erlang\ncase gen_http:connect(http, \"example.com\", 80) of\n    {ok, Conn} -\u003e\n        case gen_http:request(Conn, \u003c\u003c\"GET\"\u003e\u003e, \u003c\u003c\"/\"\u003e\u003e, [], \u003c\u003c\u003e\u003e) of\n            {ok, Conn2, Ref} -\u003e\n                handle_success(Conn2, Ref);\n            {error, Conn2, Reason} -\u003e\n                %% Structured errors: {transport_error, _}, {protocol_error, _}, {application_error, _}\n                case gen_http:is_retriable_error(Reason) of\n                    true -\u003e retry_request();\n                    false -\u003e handle_permanent_error(Reason)\n                end\n        end;\n    {error, Reason} -\u003e\n        io:format(\"Connection failed: ~p~n\", [Reason])\nend.\n```\n\n## Testing\n\nSSL/H2/ALPN tests need `nghttpx` (from nghttp2). Install on macOS with `brew install nghttp2`. Suites that need it skip gracefully if it's not installed.\n\n```bash\n# Run all tests\nrebar3 test\n\n# Run specific test types\nrebar3 eunit           # Unit tests\nrebar3 ct              # Integration tests\nrebar3 proper          # Property-based tests\n\n# HTTP/2 compliance tests (excluded by default, requires separate Docker harness)\nrebar3 ct --suite=h2_compliance_SUITE\n```\n\n## Project Status\n\nEarly development. API may change.\n\n## Inspiration\n\n- [Mint](https://github.com/elixir-mint/mint) - HTTP client for Elixir\n- [httpcore](https://github.com/encode/httpcore) - Minimal HTTP client for Python\n- [gun](https://github.com/ninenines/gun) - HTTP client for Erlang\n\n## License\n\nApache 2.0\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeadict%2Fgen_http","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodeadict%2Fgen_http","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeadict%2Fgen_http/lists"}