refactor geoip (#1442)
- Introduce a new Client type to remove the global variables from the file - Use the sync package to prevent race conditions with the cache and enabled flag - Cache results for IPs, even if the result is nil There are still data races around the client.Geo variable, but that can be resolved in a future commit.
This commit is contained in:
parent
01b3489287
commit
12eb59f611
@ -38,6 +38,8 @@ type Server struct {
|
||||
|
||||
// unregister requests from clients.
|
||||
unregister chan uint // the ChatClient id
|
||||
|
||||
geoipClient *geoip.Client
|
||||
}
|
||||
|
||||
// NewChat will return a new instance of the chat server.
|
||||
@ -51,6 +53,7 @@ func NewChat() *Server {
|
||||
inbound: make(chan chatClientEvent),
|
||||
unregister: make(chan uint),
|
||||
maxSocketConnectionLimit: maximumConcurrentConnectionLimit,
|
||||
geoipClient: geoip.NewClient(),
|
||||
}
|
||||
|
||||
return server
|
||||
@ -117,7 +120,7 @@ func (s *Server) Addclient(conn *websocket.Conn, user *user.User, accessToken st
|
||||
|
||||
// Asynchronously, optionally, fetch GeoIP data.
|
||||
go func(client *Client) {
|
||||
client.Geo = geoip.GetGeoFromIP(ipAddress)
|
||||
client.Geo = s.geoipClient.GetGeoFromIP(ipAddress)
|
||||
}(client)
|
||||
|
||||
return client
|
||||
|
@ -6,14 +6,27 @@ package geoip
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var _geoIPCache = map[string]GeoDetails{}
|
||||
var _enabled = true // Try to use GeoIP support it by default.
|
||||
var geoIPDatabasePath = "data/GeoLite2-City.mmdb"
|
||||
const geoIPDatabasePath = "data/GeoLite2-City.mmdb"
|
||||
|
||||
// Client can look up geography information for IP addresses.
|
||||
type Client struct {
|
||||
cache sync.Map
|
||||
enabled int32
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
enabled: 1, // Try to use GeoIP support by default.
|
||||
}
|
||||
}
|
||||
|
||||
// GeoDetails stores details about a location.
|
||||
type GeoDetails struct {
|
||||
@ -24,9 +37,9 @@ type GeoDetails struct {
|
||||
|
||||
// GetGeoFromIP returns geo details associated with an IP address if we
|
||||
// have previously fetched it.
|
||||
func GetGeoFromIP(ip string) *GeoDetails {
|
||||
if cachedGeoDetails, ok := _geoIPCache[ip]; ok {
|
||||
return &cachedGeoDetails
|
||||
func (c *Client) GetGeoFromIP(ip string) *GeoDetails {
|
||||
if cachedGeoDetails, ok := c.cache.Load(ip); ok {
|
||||
return cachedGeoDetails.(*GeoDetails)
|
||||
}
|
||||
|
||||
if ip == "::1" || ip == "127.0.0.1" {
|
||||
@ -37,62 +50,50 @@ func GetGeoFromIP(ip string) *GeoDetails {
|
||||
}
|
||||
}
|
||||
|
||||
return fetchGeoForIP(ip)
|
||||
return c.fetchGeoForIP(ip)
|
||||
}
|
||||
|
||||
// fetchGeoForIP makes an API call to get geo details for an IP address.
|
||||
func fetchGeoForIP(ip string) *GeoDetails {
|
||||
func (c *Client) fetchGeoForIP(ip string) *GeoDetails {
|
||||
// If GeoIP has been disabled then don't try to access it.
|
||||
if !_enabled {
|
||||
if atomic.LoadInt32(&c.enabled) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't re-fetch if we already have it.
|
||||
if geoDetails, ok := _geoIPCache[ip]; ok {
|
||||
return &geoDetails
|
||||
}
|
||||
|
||||
db, err := geoip2.Open(geoIPDatabasePath)
|
||||
if err != nil {
|
||||
log.Traceln("GeoIP support is disabled. visit http://owncast.online/docs/geoip to learn how to enable.", err)
|
||||
_enabled = false
|
||||
log.Traceln("GeoIP support is disabled. visit https://owncast.online/docs/geoip to learn how to enable.", err)
|
||||
atomic.StoreInt32(&c.enabled, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer db.Close()
|
||||
|
||||
var response *GeoDetails
|
||||
ipObject := net.ParseIP(ip)
|
||||
|
||||
record, err := db.City(ipObject)
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
// If no country is available then exit
|
||||
// If we believe this IP to be anonymous then no reason to report it
|
||||
if record.Country.IsoCode != "" && !record.Traits.IsAnonymousProxy {
|
||||
var regionName = "Unknown"
|
||||
if len(record.Subdivisions) > 0 {
|
||||
if region, ok := record.Subdivisions[0].Names["en"]; ok {
|
||||
regionName = region
|
||||
}
|
||||
}
|
||||
|
||||
// If no country is available then exit
|
||||
if record.Country.IsoCode == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we believe this IP to be anonymous then no reason to report it
|
||||
if record.Traits.IsAnonymousProxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
var regionName = "Unknown"
|
||||
if len(record.Subdivisions) > 0 {
|
||||
if region, ok := record.Subdivisions[0].Names["en"]; ok {
|
||||
regionName = region
|
||||
response = &GeoDetails{
|
||||
CountryCode: record.Country.IsoCode,
|
||||
RegionName: regionName,
|
||||
TimeZone: record.Location.TimeZone,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Warnln(err)
|
||||
}
|
||||
|
||||
response := GeoDetails{
|
||||
CountryCode: record.Country.IsoCode,
|
||||
RegionName: regionName,
|
||||
TimeZone: record.Location.TimeZone,
|
||||
}
|
||||
c.cache.Store(ip, response)
|
||||
|
||||
_geoIPCache[ip] = response
|
||||
|
||||
return &response
|
||||
return response
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user