Skip to content

Replace Hackney with Req as default HTTP client - #1

Merged
bglusman merged 4 commits into
masterfrom
port-pr-165-replace-hackney-with-req
Aug 20, 2026
Merged

Replace Hackney with Req as default HTTP client#1
bglusman merged 4 commits into
masterfrom
port-pr-165-replace-hackney-with-req

Conversation

@bglusman

@bglusman bglusman commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Purpose

Port and adapt lau/tzdata#165 onto this fork so Req becomes tzdata's default HTTP client and Hackney is no longer a required production dependency. This prepares SCHIP for a follow-up change that can update its tzdata ref and remove Hackney separately.

The original commit by Ryan Spore, co-authored by Matt Wynne, is preserved in this branch's history.

Implementation

  • Adds Tzdata.HTTPClient.Req and makes it the default adapter.
  • Supports Req >= 0.6.1 and < 1.0.0, including SCHIP's locked Req 0.6.2, while excluding vulnerable Req 0.5 releases.
  • Preserves the existing HTTP-client tuple contract, redirect behavior for GET and HEAD, repeated response headers, custom headers, and transport errors.
  • Removes the unused extra Tzdata.Finch pool; Req supervises and uses its own pool.
  • Keeps the Hackney adapter available only when explicitly configured and Hackney is present at runtime, without retaining a Hackney dependency edge.
  • Raises the minimum Elixir version to 1.15 and aligns GitHub Actions/Travis configuration with the fully patched Req/Finch/Mint/HPAX stack.
  • Refreshes the lock to Req 0.7.3, Finch 0.23.0, Mint 1.9.3, and HPAX 1.0.4, and removes an unused require that prevented warnings-as-errors compilation on the supported runtime.
  • Replaces network-bound HTTPBin unit tests with deterministic Req.Test coverage; real IANA requests remain available as explicitly included integration tests.

Validation

  • mix format --check-formatted on all changed Elixir files — passed.
  • mix deps.unlock --check-unused — passed.
  • mix test test/tzdata/http_client/req_test.exs — 7/7 passed with SCHIP's Req 0.6.2 and locked Req 0.7.3 (the adapter was also validated earlier against Req 0.5.17 before the vulnerable line was excluded).
  • mix test test/integration/req_download_test.exs --include integration — 5/5 passed against IANA with SCHIP's Req 0.6.2 and locked Req 0.7.3.
  • mix test --exclude doctest — 49 passed, 5 skipped, 22 excluded.
  • mix deps.tree --only prod — Req/Finch production tree contains no Hackney.
  • Full mix test under local Elixir 1.20/OTP 29 — 66 passed, 5 skipped, 5 integration tests excluded. A stale Madrid transition value in an existing doctest was refreshed to match the bundled timezone data.
  • mix compile --force --warnings-as-errors — passed after removing the pre-existing unused require Tzdata.Util.
  • GitHub Actions on Elixir 1.15.8 / OTP 26.2 — passed dependency, unused-lock, warnings-as-errors, and full-test checks.

Change Impact

Scope: diff
Files changed: 14
Functions changed: 8
Cross-app: no
Depth reached: 3
Test files identified: 2

Affected Areas

  • Tzdata.DataLoader.http_client/0 changes the default adapter used by downloads, HEAD metadata, and file-size checks.
  • Tzdata.HTTPClient.Req.get/3, head/3, and request/4 provide the new default Req implementation.
  • Tzdata.HTTPClient.Hackney.get/3, head/3, and ensure_hackney!/0 preserve Hackney as an explicitly configured legacy adapter with runtime dependency checks.
  • Tzdata.ReleaseUpdater and Tzdata.DataBuilder are downstream automatic IANA polling, download, and release-loading paths.
  • Tzdata.Mixfile changes the dependency, configured adapter, and supported Elixir baseline.
  • Tzdata.Parser has a build-only cleanup of an unused require so warnings-as-errors remains effective on Elixir 1.15+.
  • Tzdata.periods/1 documentation has its Madrid example synchronized with the bundled timezone data.
  • test/tzdata/http_client/req_test.exs and test/integration/req_download_test.exs cover the new adapter.

Category Hints

  • None.

Risk Assessment

Risk level: medium

Rationale: This changes an existing shared runtime path for every consumer using tzdata's default automatic-update behavior, including its external IANA integration and supported Elixir baseline. Focused unit and tagged integration coverage keep the change below high risk.

Key Factors

  • Default HTTP behavior changes from Hackney to Req across download and metadata-check paths.
  • Dependency and minimum-runtime changes can affect downstream consumers at compile or deployment time.
  • Cicada traced the affected path through Tzdata.DataLoader, Tzdata.ReleaseUpdater, and Tzdata.DataBuilder.
  • Seven deterministic adapter tests cover responses, redirects, repeated/custom headers, and transport errors; five opt-in integration tests exercise IANA.
  • Hackney remains explicitly configurable for backward compatibility.

Skills Used

  • opening-pull-requests v1.1.0
  • analyzing-change-impact (unversioned; last reviewed 2026-06-04)
  • developing-with-tdd (unversioned; last reviewed 2026-06-01)
  • searching-elixir-code (unversioned; last reviewed 2026-01-24)
  • writing-elixir-code (unversioned; last reviewed 2026-06-01)

Summary by Sourcery

Replace Hackney with Req as tzdata’s default HTTP client while preserving explicit Hackney compatibility and validating the new adapter against local and IANA requests.

New Features:

  • Use Req via Finch as the default HTTP client for timezone database downloads and metadata requests.
  • Retain Hackney as an explicitly configurable legacy HTTP client with runtime dependency validation.

Bug Fixes:

  • Preserve HTTP response handling, redirects, headers, and transport errors while replacing the default client.

Enhancements:

  • Raise the supported minimum Elixir version to 1.15 and remove Hackney from the required production dependency tree.
  • Update user-facing HTTP client configuration guidance and synchronize outdated timezone documentation data.

Build:

  • Update project dependencies to the supported Req, Finch, Mint, and HPAX versions and remove the unused parser requirement.

CI:

  • Align GitHub Actions and Travis CI with Elixir 1.15.8 and OTP 26.2, removing obsolete older-runtime coverage.

Documentation:

  • Document Req as the default HTTP client and explain how to configure Hackney for backward compatibility.

Tests:

  • Replace network-dependent adapter tests with deterministic Req.Test coverage and add opt-in IANA integration tests.

Chores:

  • Exclude integration tests from the default test helper run.

ry4n1m3 and others added 2 commits August 19, 2026 10:34
Addresses security concerns with Hackney (CVE-2018-1000007,
AIKIDO-2026-10122). Req provides a modern, actively maintained
HTTP client built on Mint.

Changes:
- Add Req as required dependency, remove Hackney
- Implement Tzdata.HTTPClient.Req with redirect support
- Set Req as default HTTP client in configuration
- Hackney implementation remains available for backward compatibility
- Update README with migration information

Co-Authored-By: Matt Wynne <[email protected]>
@bglusman bglusman added the risk:medium Moderate runtime, dependency, or compatibility risk label Aug 19, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR switches tzdata’s default HTTP client from Hackney to Req, updates dependencies and minimum Elixir version to align with the Req/Finch/Mint stack, keeps a legacy Hackney adapter available only when explicitly configured, and adds focused unit and integration tests for the new Req adapter while cleaning up CI and a few docs/build details.

Sequence diagram for Req HTTP client request flow

sequenceDiagram
  participant DataLoader as Tzdata_DataLoader
  participant HTTPClient as Tzdata_HTTPClient_Req
  participant ReqLib as Req

  DataLoader->>HTTPClient: get(url, headers, options)
  HTTPClient->>HTTPClient: request(:get, url, headers, options)
  HTTPClient->>ReqLib: Req.request(req_options)
  ReqLib-->>HTTPClient: {:ok, %Req.Response{status, body}}
  HTTPClient-->>DataLoader: {:ok, {status, Req.get_headers_list(response), body}}
Loading

File-Level Changes

Change Details Files
Introduce Req-based HTTP client adapter and make it the default for tzdata downloads and metadata requests.
  • Add Tzdata.HTTPClient.Req implementing the existing Tzdata.HTTPClient behaviour using Req.request/1.
  • Use Req.Response data to return the existing tuple contract of {status, headers, body} for GET and {status, headers} for HEAD.
  • Preserve redirect handling via a :follow_redirect option mapped to Req’s :redirect option and disable body decoding to keep raw responses.
  • Wire the new adapter as the default client in Tzdata.DataLoader.http_client/0 and application config defaults.
lib/tzdata/http_client/req.ex
lib/tzdata/data_loader.ex
mix.exs
Retain Hackney support as an optional, explicitly configured legacy adapter without a compile-time dependency.
  • Remove Hackney from mix dependencies while preserving Tzdata.HTTPClient.Hackney module.
  • Replace compile-time Code.ensure_loaded? branching with a runtime ensure_hackney!/0 that raises a clear message if Hackney is not present.
  • Wrap Hackney calls in apply/3 to avoid compile-time references and keep the adapter usable when Hackney is available.
  • Update README to describe Req as the default HTTP client and document how to configure Hackney explicitly, including dependency constraints and config settings.
lib/tzdata/http_client/hackney.ex
README.md
mix.exs
Update CI, Travis, and README to require Elixir 1.15+ and align with the supported Req/Finch/Mint/HPAX stack.
  • Raise the required Elixir version in mix.exs from ~> 1.9 to ~> 1.15.
  • Update GitHub Actions matrix to run on Elixir 1.15.8 / OTP 26.2 and track the master branch.
  • Remove the legacy CI job that tested older Elixir versions from the GitHub Actions workflow.
  • Update Travis configuration to Elixir 1.15.8 / OTP 26.2 and simplify comments.
  • Refresh README “Getting started” section to state Elixir 1.15+ as the baseline.
mix.exs
.github/workflows/ci.yml
.travis.yml
README.md
Add deterministic unit tests and tagged integration tests for the Req adapter, and configure ExUnit to skip integration tests by default.
  • Add Tzdata.HTTPClient.ReqTest using Req.Test and Plug.Conn to validate successful responses, redirects, custom headers, and repeated response headers for GET, plus method and transport-error handling for HEAD.
  • Add Tzdata.Integration.ReqDownloadTest that performs real GET/HEAD requests against IANA’s tzdata archive to verify content-length, last-modified, gzip content, redirects, and custom headers.
  • Configure ExUnit to exclude :integration-tagged tests by default via test/test_helper.exs.
test/tzdata/http_client/req_test.exs
test/integration/req_download_test.exs
test/test_helper.exs
Tidy up miscellaneous runtime and documentation details impacted by the new stack.
  • Reformat Tzdata.App.start/2 child-supervision setup for clarity while preserving behavior.
  • Remove an unused require Tzdata.Util from Tzdata.Parser so warnings-as-errors compilation succeeds under Elixir 1.15+.
  • Update the Tzdata.periods/1 doctest example for Europe/Madrid to match the currently bundled timezone data values.
lib/tzdata/tzdata_app.ex
lib/tzdata/parser.ex
lib/tzdata.ex

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bglusman
bglusman marked this pull request as ready for review August 20, 2026 04:10
Copilot AI lite review requested due to automatic review settings August 20, 2026 04:10
@bglusman
bglusman merged commit 470f925 into master Aug 20, 2026
2 checks passed

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Fixed security issues:


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR switches tzdata’s default HTTP client from Hackney to Req (via Finch), keeping Hackney available only as an explicitly configured legacy adapter, and updates project docs/tests/CI to match the new baseline (Elixir 1.15+).

Changes:

  • Add a new Tzdata.HTTPClient.Req adapter and set it as the default HTTP client in config and Tzdata.DataLoader.
  • Make Hackney optional at runtime (no longer a production dependency) while preserving the existing adapter contract.
  • Replace network-bound unit tests with deterministic Req.Test coverage and add opt-in integration tests; update CI/Travis and documentation accordingly.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib/tzdata/http_client/req.ex Introduces the Req-based HTTP client adapter implementing the tzdata HTTP client behaviour.
lib/tzdata/http_client/hackney.ex Keeps Hackney adapter available with a runtime presence check and updated guidance message.
lib/tzdata/data_loader.ex Changes the default configured HTTP client to Req for download/metadata paths.
mix.exs Raises minimum Elixir version and swaps prod dependency from Hackney to Req (adds Plug for tests).
mix.lock Refreshes the dependency lockfile to reflect the Req/Finch/Mint/HPAX stack and removal of Hackney transitive deps.
README.md Updates docs to reflect Req as the default HTTP client and documents legacy Hackney configuration.
test/tzdata/http_client/req_test.exs Adds deterministic unit tests for the Req adapter (headers, redirects, errors).
test/integration/req_download_test.exs Adds opt-in integration tests that hit IANA using the Req adapter.
test/test_helper.exs Excludes :integration tests by default.
lib/tzdata/tzdata_app.ex Minor refactor/formatting in application start child selection.
lib/tzdata/parser.ex Removes an unused require to support warnings-as-errors.
lib/tzdata.ex Updates a doctest expected value to match current bundled timezone data.
.github/workflows/ci.yml Updates CI branch trigger and test matrix; removes older-Elixir job.
.travis.yml Updates Travis Elixir/OTP versions to match the new supported baseline.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/tzdata/data_loader.ex
Comment on lines 173 to 175
defp http_client() do
Application.get_env(:tzdata, :http_client, Tzdata.HTTPClient.Hackney)
Application.get_env(:tzdata, :http_client, Tzdata.HTTPClient.Req)
end
Comment on lines +16 to +20
content_length =
headers
|> Enum.find(fn {k, _v} -> String.downcase(k) == "content-length" end)
|> elem(1)
|> String.to_integer()
Comment on lines +9 to +10
Tzdata requires a HTTP client in order to automatically update timezone
database.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:medium Moderate runtime, dependency, or compatibility risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants