Skip to content

Commit 6a6b1a8

Browse files
committed
feat(pkg/publicip/info): add ip2location.io provider
1 parent 346f4aa commit 6a6b1a8

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

pkg/publicip/info/ip2location.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package info
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/netip"
9+
)
10+
11+
func newIP2Location(client *http.Client) *ip2Location {
12+
return &ip2Location{
13+
client: client,
14+
}
15+
}
16+
17+
type ip2Location struct {
18+
client *http.Client
19+
}
20+
21+
func (p *ip2Location) get(ctx context.Context, ip netip.Addr) (
22+
result Result, err error) {
23+
result.Source = string(Ipinfo)
24+
25+
url := "https://api.ip2location.io/"
26+
if ip.IsValid() {
27+
url += "?ip=" + ip.String()
28+
}
29+
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
30+
if err != nil {
31+
return result, fmt.Errorf("creating request: %w", err)
32+
}
33+
34+
response, err := p.client.Do(request)
35+
if err != nil {
36+
return result, fmt.Errorf("doing request: %w", err)
37+
}
38+
39+
switch response.StatusCode {
40+
case http.StatusOK:
41+
case http.StatusForbidden, http.StatusTooManyRequests:
42+
bodyString := bodyToSingleLine(response.Body)
43+
_ = response.Body.Close()
44+
return result, fmt.Errorf("%w (%s)", ErrTooManyRequests, bodyString)
45+
default:
46+
bodyString := bodyToSingleLine(response.Body)
47+
_ = response.Body.Close()
48+
return result, fmt.Errorf("%w: %d %s (%s)", ErrBadHTTPStatus,
49+
response.StatusCode, response.Status, bodyString)
50+
}
51+
52+
decoder := json.NewDecoder(response.Body)
53+
var data struct {
54+
IP netip.Addr `json:"ip"`
55+
RegionName string `json:"region_name"`
56+
CountryName string `json:"country_name"`
57+
CityName string `json:"city_name"`
58+
// More fields available see https://www.ip2location.io/ip2location-documentation
59+
}
60+
err = decoder.Decode(&data)
61+
if err != nil {
62+
return result, fmt.Errorf("decoding JSON response: %w", err)
63+
}
64+
65+
result.IP = data.IP
66+
if data.RegionName != "" {
67+
result.Region = stringPtr(data.RegionName)
68+
}
69+
if data.CityName != "" {
70+
result.City = stringPtr(data.CityName)
71+
}
72+
if data.CountryName != "" {
73+
country := countryCodeToName(data.CountryName)
74+
result.Country = stringPtr(country)
75+
}
76+
77+
return result, nil
78+
}

pkg/publicip/info/provider.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import (
1111
type Provider string
1212

1313
const (
14-
Ipinfo Provider = "ipinfo"
14+
Ipinfo Provider = "ipinfo"
15+
IP2Location Provider = "ip2location"
1516
)
1617

1718
func ListProviders() []Provider {
1819
return []Provider{
1920
Ipinfo,
21+
IP2Location,
2022
}
2123
}
2224

@@ -39,6 +41,8 @@ func newProvider(providerName Provider, client *http.Client) provider { //nolint
3941
switch providerName {
4042
case Ipinfo:
4143
return newIpinfo(client)
44+
case IP2Location:
45+
return newIP2Location(client)
4246
default:
4347
panic(fmt.Sprintf("provider %s not implemented", providerName))
4448
}

0 commit comments

Comments
 (0)