geolocation

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2025 License: MIT Imports: 12 Imported by: 0

README

go-geolocation

CI codecov

A framework-agnostic Go module for geolocation, inspired by php-geolocation. Provides core geolocation features and adapters for popular Go web frameworks.

Features

  • Extracts country from Cloudflare headers
  • Parses browser, OS, device, and language from standard headers
  • Local development simulation - Fake Cloudflare headers for testing without production setup
  • Auto-detection of local environments (localhost, local IPs, missing Cloudflare headers)
  • Advanced language negotiation - matches browser and available site languages for multi-language countries
  • Comprehensive client info - browser, OS, device type (including tablet), screen resolution
  • Built-in country data for 8 countries (US, CA, GB, DE, FR, JP, AU, BR)
  • Middleware/adapters for net/http, Gin, Echo, Fiber
  • Testable, modular design
  • High test coverage and CI integration

Installation

go get github.com/rumendamyanov/go-geolocation

Usage

Core Usage
import "github.com/rumendamyanov/go-geolocation"

// In your handler:
loc := geolocation.FromRequest(r)
client := geolocation.ParseClientInfo(r)
languages := geolocation.ParseLanguageInfo(r)
net/http Example

See: examples/nethttp.go

Gin Example

See: examples/gin.go

Echo Example

See: examples/echo.go

Fiber Example

See: examples/fiber.go

Example Output

IP: 1.2.3.4
Country: BG
Browser: Chrome 123.0.0.0
OS: Windows NT 10.0
Device: Desktop
DefaultLang: en-US
AllLangs: [en-US en bg de]

Local Development Simulation

When developing locally where Cloudflare is not available, you can simulate its functionality:

Quick Simulation
// Create a simulated request for a specific country
req := geolocation.Simulate("DE", nil)
info := geolocation.GetGeoInfo(req)
fmt.Printf("Country: %s, IP: %s\n", info.CountryCode, info.IP)
Advanced Simulation
// Custom simulation options
req := geolocation.Simulate("JP", &geolocation.SimulationOptions{
    UserAgent: "Custom User Agent",
    Languages: []string{"ja", "en"},
})
Auto-Detection of Local Environment
req := getRequest() // your HTTP request
if geolocation.IsLocalDevelopment(req) {
    fmt.Println("Running in local development mode")
}
Available Countries for Simulation
countries := geolocation.GetAvailableCountries()
// Returns: ["US", "CA", "GB", "DE", "FR", "JP", "AU", "BR"]

randomCountry := geolocation.RandomCountry()

Advanced Features

Comprehensive Client Information
info := geolocation.GetGeoInfo(req)
fmt.Printf("Country: %s\n", info.CountryCode)
fmt.Printf("IP: %s\n", info.IP)
fmt.Printf("Browser: %s %s\n", info.Browser, info.BrowserVersion)
fmt.Printf("OS: %s\n", info.OS)
fmt.Printf("Device: %s\n", info.Device) // Desktop, Mobile, Tablet
fmt.Printf("Languages: %v\n", info.AllLanguages)
fmt.Printf("Resolution: %dx%d\n", info.Resolution.Width, info.Resolution.Height)
Advanced Language Negotiation
cfg := &geolocation.Config{
    DefaultLanguage: "en",
    CountryToLanguageMap: map[string][]string{
        "CA": {"en", "fr"}, // Canada: English (default), French
        "CH": {"de", "fr", "it"}, // Switzerland: German, French, Italian
    },
}

// Sophisticated language selection based on:
// 1. Browser preferred language matching country languages and available site languages
// 2. All browser languages for matches with available languages
// 3. First country language as fallback
availableSiteLanguages := []string{"en", "fr", "de", "es"}
bestLang := geolocation.GetLanguageForCountry(req, cfg, "CH", availableSiteLanguages)

// Check if language cookie should be set
if geolocation.ShouldSetLanguage(req, "lang") {
    // Set language in your application
    geolocation.SetCookie(w, "lang", bestLang, &http.Cookie{MaxAge: 86400 * 30})
}
Screen Resolution Detection
// Frontend JavaScript would set these headers:
// req.Header.Set("X-Screen-Width", "1920")
// req.Header.Set("X-Screen-Height", "1080")

resolution := geolocation.GetResolution(req)
fmt.Printf("Screen: %dx%d\n", resolution.Width, resolution.Height)

Testing

Run all tests:

go test ./...

Check coverage:

go test -cover ./...

Note on Coverage:

All error branches and edge cases in the core package are thoroughly tested. Due to Go's coverage tool behavior, a few lines in LoadConfig may not be counted as covered, even though all real error paths (file not found, invalid JSON/YAML, unsupported extension) are exercised in tests. The code is idiomatic and robust; further refactoring for the sake of 100% coverage is not recommended.

CI & Coverage

  • GitHub Actions for CI
  • Codecov integration for test coverage

License

MIT

Advanced Usage

Combining Geolocation, Client Info, and Language

You can extract all available information in a single handler:

import (
    "fmt"
    "net/http"
    "github.com/rumendamyanov/go-geolocation"
)

func handler(w http.ResponseWriter, r *http.Request) {
    loc := geolocation.FromRequest(r)
    info := geolocation.ParseClientInfo(r)
    lang := geolocation.ParseLanguageInfo(r)
    fmt.Fprintf(w, "IP: %s\nCountry: %s\nBrowser: %s %s\nOS: %s\nDevice: %s\nDefaultLang: %s\nAllLangs: %v\n",
        loc.IP, loc.Country, info.BrowserName, info.BrowserVersion, info.OS, info.Device, lang.Default, lang.Supported)
}
Custom Middleware Example (net/http)

You can create your own middleware to attach all info to the request context:

import (
    "context"
    "net/http"
    "github.com/rumendamyanov/go-geolocation"
)

type contextKey struct{}

func Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        loc := geolocation.FromRequest(r)
        info := geolocation.ParseClientInfo(r)
        lang := geolocation.ParseLanguageInfo(r)
        ctx := context.WithValue(r.Context(), contextKey{}, struct {
            *geolocation.Location
            *geolocation.ClientInfo
            *geolocation.LanguageInfo
        }{loc, info, lang})
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
Using in API Responses

You can return all extracted info as JSON in your API endpoints for debugging or analytics:

import (
    "encoding/json"
    "net/http"
    "github.com/rumendamyanov/go-geolocation"
)

func apiHandler(w http.ResponseWriter, r *http.Request) {
    resp := struct {
        *geolocation.Location
        *geolocation.ClientInfo
        *geolocation.LanguageInfo
    }{
        geolocation.FromRequest(r),
        geolocation.ParseClientInfo(r),
        geolocation.ParseLanguageInfo(r),
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

Documentation

For comprehensive documentation and additional examples:

Contributing

We welcome contributions! Please see our Contributing Guidelines for details on how to submit pull requests, report issues, and contribute to the project.

Support

If you find this project helpful, please consider:

  • ⭐ Starring the repository
  • 📝 Reporting issues or suggesting features
  • 💝 Supporting via GitHub Sponsors

For detailed support information, see FUNDING.md.

Documentation

Overview

Package geolocation provides framework-agnostic geolocation extraction from Cloudflare headers and user agent parsing for browser, OS, device, and language information.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddCountryData

func AddCountryData(countryCode string, data CountryData)

AddCountryData adds custom country data for simulation.

func FakeCloudflareHeaders

func FakeCloudflareHeaders(countryCode string, options *SimulationOptions) map[string]string

FakeCloudflareHeaders generates fake Cloudflare headers for a specific country.

func GetAvailableCountries

func GetAvailableCountries() []string

GetAvailableCountries returns a list of available country codes for simulation.

func GetCookie

func GetCookie(r *http.Request, name string) string

GetCookie retrieves a named cookie value from the request. Returns empty string if not found.

func GetLanguageForCountry

func GetLanguageForCountry(r *http.Request, cfg *Config, countryCode string, availableSiteLanguages []string) string

GetLanguageForCountry returns the best language for a given country code, considering browser languages and available site languages.

Logic: 1. If browser preferred language matches a country language and is available, use it 2. Check all browser languages for a match with available languages 3. Use the first country language as fallback 4. Returns empty string if no match found

func HTTPMiddleware

func HTTPMiddleware(next http.Handler) http.Handler

HTTPMiddleware attaches geolocation info to the request context.

func IsLocalDevelopment

func IsLocalDevelopment(r *http.Request) bool

IsLocalDevelopment checks if we're in a local development environment. Returns true for localhost, local IPs, or missing Cloudflare headers.

func RandomCountry

func RandomCountry() string

RandomCountry returns a random country code for simulation.

func SetCookie

func SetCookie(w http.ResponseWriter, name, value string, opts *http.Cookie)

SetCookie sets a cookie with the given name and value on the response writer. You can pass additional options via the http.Cookie struct.

func ShouldSetLanguage

func ShouldSetLanguage(r *http.Request, cookieName string) bool

ShouldSetLanguage returns true if the language cookie should be set (i.e., if no language cookie exists).

func Simulate

func Simulate(countryCode string, options *SimulationOptions) *http.Request

Simulate creates a geolocation-enabled HTTP request with simulated Cloudflare headers for local development and testing.

Example:

req := geolocation.Simulate("DE", &geolocation.SimulationOptions{
	UserAgent: "Custom Test Agent",
})
info := geolocation.GetGeoInfo(req)

func SimulateRequest

func SimulateRequest(countryCode string, options *SimulationOptions) *http.Request

SimulateRequest creates a new HTTP request with simulated Cloudflare headers.

Types

type ClientInfo

type ClientInfo struct {
	BrowserName    string // e.g., Chrome, Firefox
	BrowserVersion string // e.g., 123.0.0.0
	OS             string // e.g., Windows NT 10.0
	Device         string // e.g., Mobile, Desktop, Tablet
}

ClientInfo holds browser, OS, and device information parsed from the User-Agent header.

func ParseClientInfo

func ParseClientInfo(r *http.Request) *ClientInfo

ParseClientInfo parses the User-Agent header for browser, OS, and device info.

Example:

info := geolocation.ParseClientInfo(r)
fmt.Println(info.BrowserName, info.OS, info.Device)

type Config

type Config struct {
	DefaultLanguage      string              `json:"default_language" yaml:"default_language"`
	CountryToLanguageMap map[string][]string `json:"country_to_language_map" yaml:"country_to_language_map"`
	CookieName           string              `json:"cookie_name" yaml:"cookie_name"`
}

Config holds module configuration, including country-to-language mapping, defaults, and cookie name.

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig loads configuration from a JSON or YAML file. The format is determined by the file extension (.json or .yaml/.yml).

func (*Config) ActiveLanguage

func (c *Config) ActiveLanguage(country string) string

ActiveLanguage returns the first language for a given country code, or the default if not mapped.

func (*Config) ActiveLanguages

func (c *Config) ActiveLanguages(country string) []string

ActiveLanguages returns the list of languages for a given country code, or the default if not mapped.

type CountryData

type CountryData struct {
	Country   string   `json:"country"`
	IPRanges  []string `json:"ip_ranges"`
	Languages []string `json:"languages"`
	Timezone  string   `json:"timezone"`
}

CountryData holds simulation data for a specific country.

type GeoInfo

type GeoInfo struct {
	CountryCode       string     `json:"country_code"`
	IP                string     `json:"ip"`
	PreferredLanguage string     `json:"preferred_language"`
	AllLanguages      []string   `json:"all_languages"`
	OS                string     `json:"os"`
	Browser           string     `json:"browser"`
	BrowserVersion    string     `json:"browser_version"`
	Device            string     `json:"device"`
	Resolution        Resolution `json:"resolution"`
}

GeoInfo holds all geolocation and client information.

func GetGeoInfo

func GetGeoInfo(r *http.Request) *GeoInfo

GetGeoInfo returns all geolocation and client information in a single struct.

Example:

info := geolocation.GetGeoInfo(r)
fmt.Printf("Country: %s, Browser: %s, Device: %s", info.CountryCode, info.Browser, info.Device)

type GeolocationSimulator

type GeolocationSimulator struct{}

GeolocationSimulator provides methods to simulate Cloudflare geolocation headers for local development when actual Cloudflare infrastructure is not available.

type LanguageInfo

type LanguageInfo struct {
	Default   string   // The default (first) language
	Supported []string // All languages in order of preference
}

LanguageInfo holds the user's preferred and supported languages from Accept-Language.

func ParseLanguageInfo

func ParseLanguageInfo(r *http.Request) *LanguageInfo

ParseLanguageInfo parses the Accept-Language header for language preferences.

Example:

lang := geolocation.ParseLanguageInfo(r)
fmt.Println(lang.Default, lang.Supported)

type Location

type Location struct {
	IP      string // The user's public IP address (from CF-Connecting-IP)
	Country string // The user's country code (from CF-IPCountry)
}

Location represents a geolocation result, typically extracted from Cloudflare headers.

func FromContext

func FromContext(ctx context.Context) *Location

FromContext retrieves the Location from context.

func FromRequest

func FromRequest(r *http.Request) *Location

FromRequest extracts geolocation info from Cloudflare headers in the request.

Example:

loc := geolocation.FromRequest(r)
fmt.Println(loc.IP, loc.Country)

func LookupIP

func LookupIP(ip string) (*Location, error)

LookupIP is deprecated: use FromRequest for Cloudflare geolocation.

type Resolution

type Resolution struct {
	Width  int // Screen width in pixels
	Height int // Screen height in pixels
}

Resolution holds screen resolution information.

func GetResolution

func GetResolution(r *http.Request) Resolution

GetResolution retrieves screen resolution from custom headers (if set by frontend JS).

type SimulationOptions

type SimulationOptions struct {
	UserAgent  string   `json:"user_agent"`
	ServerName string   `json:"server_name"`
	IPRange    string   `json:"ip_range"`
	Languages  []string `json:"languages"`
}

SimulationOptions holds additional options for customizing simulation.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL