Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish to Hex.pm
name: CI

on:
push:
Expand All @@ -13,31 +13,48 @@ jobs:
name: Test
runs-on: ${{ matrix.os }}
strategy:
# Let every OTP release report its own result instead of cancelling
# the remaining jobs as soon as one of them fails.
fail-fast: false
matrix:
# Each OTP release is paired with the newest rebar3 release that
# supports it: 3.24 is the last one for OTP 25, 3.25 the last one
# for OTP 26, and 3.28 covers OTP 27 and later.
#
# OTP 29 is not listed yet: the test dependency meck 0.9.2 does not
# compile there (its bare `catch` is deprecated in OTP 29), and the
# meck releases that do compile dropped OTP 25.
#
# ubuntu-latest resolves to Ubuntu 26.04, for which no OTP 25 builds
# are published (erlef/setup-beam supports OTP 26+ there and 24.3+ on
# ubuntu-24.04), so the OTP 25 job runs on ubuntu-24.04.
include:
- otp: '25'
rebar3: '3.24'
os: ubuntu-24.04
- otp: '26'
rebar3: '3.25'
os: ubuntu-latest
- otp: '27'
rebar3: '3.28'
os: ubuntu-latest
- otp: '28'
rebar3: '3.28'
os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
rebar3-version: '3.24'
rebar3-version: ${{ matrix.rebar3 }}

- name: Restore dependencies cache
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: _build
key: ${{ runner.os }}-otp-${{ matrix.otp }}-build-${{ hashFiles('rebar.config') }}
restore-keys: ${{ runner.os }}-otp-${{ matrix.otp }}-build-
key: ${{ runner.os }}-otp-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-build-${{ hashFiles('rebar.config') }}
restore-keys: ${{ runner.os }}-otp-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-build-

- name: Compile
run: rebar3 compile
Expand All @@ -59,19 +76,19 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: erlef/setup-beam@v1
with:
otp-version: '27'
rebar3-version: '3.24'
rebar3-version: '3.28'

- name: Restore dependencies cache
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: _build
key: ${{ runner.os }}-otp-27-build-${{ hashFiles('rebar.config') }}
restore-keys: ${{ runner.os }}-otp-27-build-
key: ${{ runner.os }}-otp-27-rebar3-3.28-build-${{ hashFiles('rebar.config') }}
restore-keys: ${{ runner.os }}-otp-27-rebar3-3.28-build-

- name: Publish to Hex.pm
run: rebar3 hex publish --repo hexpm --yes
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,25 @@ maps:get(<<"country_name">>, Result).
%% Access response fields
maps:get(<<"ip">>, Result). %% => <<"8.8.8.8">>
maps:get(<<"country_name">>, Result). %% => <<"United States">>
maps:get(<<"city">>, Result). %% => <<"Ashburn">>
maps:get(<<"latitude">>, Result). %% => 39.03
maps:get(<<"longitude">>, Result). %% => -77.5
maps:get(<<"country_code">>, Result). %% => <<"US">>

%% Fields the API has no data for are null. 8.8.8.8 is an anycast address
%% located at country level only, so it has no city and country-level coordinates.
maps:get(<<"city">>, Result). %% => null
maps:get(<<"latitude">>, Result). %% => 39.7599983215332
maps:get(<<"longitude">>, Result). %% => -98.5

%% Nested data
ASN = maps:get(<<"asn">>, Result).
maps:get(<<"name">>, ASN). %% => <<"Google LLC">>

Threat = maps:get(<<"threat">>, Result).
maps:get(<<"is_vpn">>, Threat). %% => false
maps:get(<<"is_vpn">>, Threat). %% => false
maps:get(<<"is_datacenter">>, Threat). %% => true

%% Flags the API includes only when they are true: read them with a default
maps:get(<<"is_anycast">>, Result, false). %% => true
maps:get(<<"is_satellite">>, Result, false). %% => false

TimeZone = maps:get(<<"time_zone">>, Result).
maps:get(<<"name">>, TimeZone). %% => <<"America/New_York">>
```

### Filter Response Fields
Expand Down Expand Up @@ -144,7 +146,7 @@ end.

## Connection reuse

The `ipdata` application owns a dedicated `httpc` profile, also named `ipdata`, that keeps persistent HTTPS connections (HTTP/1.1 keep-alive) to the API for as long as the application runs. Start the application once per node and keep it running: every lookup from every process then goes out on an already established connection instead of paying a new TCP and TLS handshake (roughly 660 to 980 ms for a cold connection to api.ipdata.co against 220 to 300 ms for a request on a reused one). Idle connections are closed after 60 seconds. HTTP/2 is not available with `httpc`, so the saving is the TLS handshake, not multiplexing; an HTTP/2 client such as `gun` would need a connection-owner process and a new client lifecycle, which is out of scope for this library. TLS 1.3 session tickets are enabled so that a connection opened after the idle timeout can resume the previous TLS session.
The `ipdata` application owns a dedicated `httpc` profile, also named `ipdata`, that keeps persistent HTTPS connections (HTTP/1.1 keep-alive) to the API for as long as the application runs. Start the application once per node and keep it running: every lookup from every process then goes out on an already established connection. A new connection pays a TCP and TLS handshake; a request on a reused connection does not. Idle connections are closed after 60 seconds. HTTP/2 is not available with `httpc`, so the saving is the TLS handshake, not multiplexing; an HTTP/2 client such as `gun` would need a connection-owner process and a new client lifecycle, which is out of scope for this library. TLS 1.3 session tickets are enabled so that a connection opened after the idle timeout can resume the previous TLS session.

A client map returned by `ipdata:new/1,2` is plain immutable data: it holds no process, socket or other resource, so it can be shared freely between processes, kept in application state or rebuilt at any time, and there is nothing to close. `application:stop(ipdata)` stops the profile and closes its connections. If a request is made before the application has been started, the profile is started on demand (`inets` and `ssl` must be running).

Expand Down
Loading