chore(publicip): less coupling with ipinfo.io
This commit is contained in:
47
internal/publicip/api/api.go
Normal file
47
internal/publicip/api/api.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type API interface {
|
||||
FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
result models.PublicIP, err error)
|
||||
}
|
||||
|
||||
type Provider string
|
||||
|
||||
const (
|
||||
IPInfo Provider = "ipinfo"
|
||||
)
|
||||
|
||||
func New(provider Provider, client *http.Client, token string) ( //nolint:ireturn
|
||||
a API, err error) {
|
||||
switch provider {
|
||||
case IPInfo:
|
||||
return newIPInfo(client, token), nil
|
||||
default:
|
||||
panic("provider not valid: " + provider)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrProviderNotValid = errors.New("API name is not valid")
|
||||
)
|
||||
|
||||
func ParseProvider(s string) (provider Provider, err error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "ipinfo":
|
||||
return IPInfo, nil
|
||||
default:
|
||||
return "", fmt.Errorf(`%w: %q can only be "ipinfo" or "ip2location"`,
|
||||
ErrProviderNotValid, s)
|
||||
}
|
||||
}
|
||||
9
internal/publicip/api/errors.go
Normal file
9
internal/publicip/api/errors.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package api
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrTokenNotValid = errors.New("token is not valid")
|
||||
ErrTooManyRequests = errors.New("too many requests sent for this month")
|
||||
ErrBadHTTPStatus = errors.New("bad HTTP status received")
|
||||
)
|
||||
13
internal/publicip/api/interfaces.go
Normal file
13
internal/publicip/api/interfaces.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type Fetcher interface {
|
||||
FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
result models.PublicIP, err error)
|
||||
}
|
||||
@@ -1,40 +1,34 @@
|
||||
package ipinfo
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type Fetch struct {
|
||||
type ipInfo struct {
|
||||
client *http.Client
|
||||
token string
|
||||
}
|
||||
|
||||
func New(client *http.Client, token string) *Fetch {
|
||||
return &Fetch{
|
||||
func newIPInfo(client *http.Client, token string) *ipInfo {
|
||||
return &ipInfo{
|
||||
client: client,
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrTokenNotValid = errors.New("token is not valid")
|
||||
ErrTooManyRequests = errors.New("too many requests sent for this month")
|
||||
ErrBadHTTPStatus = errors.New("bad HTTP status received")
|
||||
)
|
||||
|
||||
// FetchInfo obtains information on the ip address provided
|
||||
// using the ipinfo.io API. If the ip is the zero value, the public IP address
|
||||
// of the machine is used as the IP.
|
||||
func (f *Fetch) FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
result Response, err error) {
|
||||
func (i *ipInfo) FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
result models.PublicIP, err error) {
|
||||
url := "https://ipinfo.io/"
|
||||
switch {
|
||||
case ip.Is6():
|
||||
@@ -47,15 +41,15 @@ func (f *Fetch) FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
request.Header.Set("Authorization", "Bearer "+f.token)
|
||||
request.Header.Set("Authorization", "Bearer "+i.token)
|
||||
|
||||
response, err := f.client.Do(request)
|
||||
response, err := i.client.Do(request)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if f.token != "" && response.StatusCode == http.StatusUnauthorized {
|
||||
if i.token != "" && response.StatusCode == http.StatusUnauthorized {
|
||||
return result, fmt.Errorf("%w: %s", ErrTokenNotValid, response.Status)
|
||||
}
|
||||
|
||||
@@ -70,15 +64,37 @@ func (f *Fetch) FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(&result); err != nil {
|
||||
var data struct {
|
||||
IP netip.Addr `json:"ip,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Loc string `json:"loc,omitempty"`
|
||||
Org string `json:"org,omitempty"`
|
||||
Postal string `json:"postal,omitempty"`
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
return result, fmt.Errorf("decoding response: %w", err)
|
||||
}
|
||||
|
||||
countryCode := strings.ToLower(result.Country)
|
||||
countryCode := strings.ToLower(data.Country)
|
||||
country, ok := constants.CountryCodes()[countryCode]
|
||||
if ok {
|
||||
result.Country = country
|
||||
data.Country = country
|
||||
}
|
||||
|
||||
result = models.PublicIP{
|
||||
IP: data.IP,
|
||||
Region: data.Region,
|
||||
Country: data.Country,
|
||||
City: data.City,
|
||||
Hostname: data.Hostname,
|
||||
Location: data.Loc,
|
||||
Organization: data.Org,
|
||||
PostalCode: data.Postal,
|
||||
Timezone: data.Timezone,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package ipinfo
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
// FetchMultiInfo obtains the public IP address information for every IP
|
||||
@@ -11,13 +13,13 @@ import (
|
||||
// If an error is encountered, all the operations are canceled and
|
||||
// an error is returned, so the results returned should be considered
|
||||
// incomplete in this case.
|
||||
func (f *Fetch) FetchMultiInfo(ctx context.Context, ips []netip.Addr) (
|
||||
results []Response, err error) {
|
||||
func FetchMultiInfo(ctx context.Context, fetcher Fetcher, ips []netip.Addr) (
|
||||
results []models.PublicIP, err error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
type asyncResult struct {
|
||||
index int
|
||||
result Response
|
||||
result models.PublicIP
|
||||
err error
|
||||
}
|
||||
resultsCh := make(chan asyncResult)
|
||||
@@ -27,12 +29,12 @@ func (f *Fetch) FetchMultiInfo(ctx context.Context, ips []netip.Addr) (
|
||||
aResult := asyncResult{
|
||||
index: index,
|
||||
}
|
||||
aResult.result, aResult.err = f.FetchInfo(ctx, ip)
|
||||
aResult.result, aResult.err = fetcher.FetchInfo(ctx, ip)
|
||||
resultsCh <- aResult
|
||||
}(i, ip)
|
||||
}
|
||||
|
||||
results = make([]Response, len(ips))
|
||||
results = make([]models.PublicIP, len(ips))
|
||||
for i := 0; i < len(ips); i++ {
|
||||
aResult := <-resultsCh
|
||||
if aResult.err != nil {
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type Fetcher interface {
|
||||
FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||
result ipinfo.Response, err error)
|
||||
result models.PublicIP, err error)
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package ipinfo
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
IP netip.Addr `json:"ip,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Loc string `json:"loc,omitempty"`
|
||||
Org string `json:"org,omitempty"`
|
||||
Postal string `json:"postal,omitempty"`
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Response) ToPublicIPModel() (model models.PublicIP) {
|
||||
return models.PublicIP{
|
||||
IP: r.IP,
|
||||
Region: r.Region,
|
||||
Country: r.Country,
|
||||
City: r.City,
|
||||
Hostname: r.Hostname,
|
||||
Location: r.Loc,
|
||||
Organization: r.Org,
|
||||
PostalCode: r.Postal,
|
||||
Timezone: r.Timezone,
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||
"github.com/qdm12/gluetun/internal/publicip/api"
|
||||
)
|
||||
|
||||
type Loop struct {
|
||||
@@ -103,7 +103,7 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
||||
lastFetch = l.timeNow()
|
||||
timerIsReadyToReset = l.updateTimer(*l.settings.Period, lastFetch, timer, timerIsReadyToReset)
|
||||
|
||||
if errors.Is(err, ipinfo.ErrTooManyRequests) {
|
||||
if errors.Is(err, api.ErrTooManyRequests) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
||||
l.logger.Info(message)
|
||||
|
||||
l.ipDataMutex.Lock()
|
||||
l.ipData = result.ToPublicIPModel()
|
||||
l.ipData = result
|
||||
l.ipDataMutex.Unlock()
|
||||
|
||||
filepath := *l.settings.IPFilepath
|
||||
@@ -123,7 +123,7 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loop) fetchIPData(ctx context.Context) (result ipinfo.Response, err error) {
|
||||
func (l *Loop) fetchIPData(ctx context.Context) (result models.PublicIP, err error) {
|
||||
// keep retrying since settings updates won't change the
|
||||
// behavior of the following code.
|
||||
const defaultBackoffTime = 5 * time.Second
|
||||
@@ -135,7 +135,7 @@ func (l *Loop) fetchIPData(ctx context.Context) (result ipinfo.Response, err err
|
||||
return result, nil
|
||||
case ctx.Err() != nil:
|
||||
return result, err
|
||||
case errors.Is(err, ipinfo.ErrTooManyRequests):
|
||||
case errors.Is(err, api.ErrTooManyRequests):
|
||||
l.logger.Warn(err.Error() + "; not retrying.")
|
||||
return result, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user