Skip to content

Commit

Permalink
Merge branch 'master' of github.com:fiorix/freegeoip
Browse files Browse the repository at this point in the history
  • Loading branch information
fiorix committed Feb 21, 2017
2 parents 8e536c3 + 9f235e4 commit 8704674
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ db.gz
*.csv
*.zip
*.sqlite
/vendor/*/
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ RUN cd /go/src/github.com/fiorix/freegeoip/cmd/freegeoip && go get && go install

ENTRYPOINT ["/go/bin/freegeoip"]

EXPOSE 8080

# CMD instructions:
# Add "-use-x-forwarded-for" if your server is behind a reverse proxy
# Add "-public", "/var/www" to enable the web front-end
Expand Down
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: freegeoip -http :${PORT} -use-x-forwarded-for -public /app/cmd/freegeoip/public -quota-backend map -quota-max 10000
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# freegeoip

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)

This is the source code of the freegeoip software. It contains both
the web server that empowers freegeoip.net, and a package for the
[Go](http://golang.org) programming language that enables any web server
Expand Down
24 changes: 20 additions & 4 deletions apiserver/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ import (
"github.com/go-web/httprl"
"github.com/go-web/httprl/memcacherl"
"github.com/go-web/httprl/redisrl"
newrelic "github.com/newrelic/go-agent"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/cors"

"github.com/fiorix/freegeoip"
)

type apiHandler struct {
db *freegeoip.DB
conf *Config
cors *cors.Cors
db *freegeoip.DB
conf *Config
cors *cors.Cors
nrapp newrelic.Application
}

// NewHandler creates an http handler for the freegeoip server that
Expand Down Expand Up @@ -83,6 +85,14 @@ func (f *apiHandler) config(mc *httpmux.Config) error {
}
mc.Use(rl.Handle)
}
if f.conf.NewrelicName != "" && f.conf.NewrelicKey != "" {
config := newrelic.NewConfig(f.conf.NewrelicName, f.conf.NewrelicKey)
app, err := newrelic.NewApplication(config)
if err != nil {
return fmt.Errorf("failed to create newrelic application: {name: %v, key: %v}", f.conf.NewrelicName, f.conf.NewrelicKey)
}
f.nrapp = app
}
return nil
}

Expand Down Expand Up @@ -126,7 +136,13 @@ func (f *apiHandler) metrics(next http.HandlerFunc) http.HandlerFunc {
type writerFunc func(w http.ResponseWriter, r *http.Request, d *responseRecord)

func (f *apiHandler) register(name string, writer writerFunc) http.HandlerFunc {
h := prometheus.InstrumentHandler(name, f.iplookup(writer))
var h http.Handler
if f.nrapp == nil {
h = prometheus.InstrumentHandler(name, f.iplookup(writer))
} else {
h = prometheus.InstrumentHandler(newrelic.WrapHandle(f.nrapp, name, f.iplookup(writer)))
}

return f.cors.Handler(h).ServeHTTP
}

Expand Down
4 changes: 4 additions & 0 deletions apiserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type Config struct {
LicenseKey string
UserID string
ProductID string
NewrelicName string
NewrelicKey string

errorLog *log.Logger
accessLog *log.Logger
Expand Down Expand Up @@ -122,6 +124,8 @@ func (c *Config) AddFlags(fs *flag.FlagSet) {
fs.StringVar(&c.LicenseKey, "license-key", c.LicenseKey, "MaxMind License Key (requires user-id)")
fs.StringVar(&c.UserID, "user-id", c.UserID, "MaxMind User ID (requires license-key)")
fs.StringVar(&c.ProductID, "product-id", c.ProductID, "MaxMind Product ID (e.g GeoIP2-City)")
fs.StringVar(&c.NewrelicName, "newrelic-name", c.NewrelicName, "Newrepic APM application name")
fs.StringVar(&c.NewrelicKey, "newrelic-key", c.NewrelicKey, "Nerelic API key")
}

func (c *Config) logWriter() io.Writer {
Expand Down
7 changes: 7 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "freegeoip",
"description": "IP geolocation web server",
"website": "https://github.com/fiorix/freegeoip",
"success_url": "/",
"keywords": ["golang", "geoip", "api"]
}
34 changes: 22 additions & 12 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var (
// DB is the IP geolocation database.
type DB struct {
file string // Database file name.
checksum string // MD5 of the unzipped database file
reader *maxminddb.Reader // Actual db object.
notifyQuit chan struct{} // Stop auto-update and watch goroutines.
notifyOpen chan string // Notify when a db file is open.
Expand Down Expand Up @@ -177,37 +178,39 @@ func (db *DB) watchEvents(watcher *fsnotify.Watcher) {
}

func (db *DB) openFile() error {
reader, err := db.newReader(db.file)
reader, checksum, err := db.newReader(db.file)
if err != nil {
return err
}
stat, err := os.Stat(db.file)
if err != nil {
return err
}
db.setReader(reader, stat.ModTime())
db.setReader(reader, stat.ModTime(), checksum)
return nil
}

func (db *DB) newReader(dbfile string) (*maxminddb.Reader, error) {
func (db *DB) newReader(dbfile string) (*maxminddb.Reader, string, error) {
f, err := os.Open(dbfile)
if err != nil {
return nil, err
return nil, "", err
}
defer f.Close()
gzf, err := gzip.NewReader(f)
if err != nil {
return nil, err
return nil, "", err
}
defer gzf.Close()
b, err := ioutil.ReadAll(gzf)
if err != nil {
return nil, err
return nil, "", err
}
return maxminddb.FromBytes(b)
checksum := fmt.Sprintf("%x", md5.Sum(b))
mmdb, err := maxminddb.FromBytes(b)
return mmdb, checksum, err
}

func (db *DB) setReader(reader *maxminddb.Reader, modtime time.Time) {
func (db *DB) setReader(reader *maxminddb.Reader, modtime time.Time, checksum string) {
db.mu.Lock()
defer db.mu.Unlock()
if db.closed {
Expand All @@ -219,6 +222,7 @@ func (db *DB) setReader(reader *maxminddb.Reader, modtime time.Time) {
}
db.reader = reader
db.lastUpdated = modtime.UTC()
db.checksum = checksum
select {
case db.notifyOpen <- db.file:
default:
Expand All @@ -228,6 +232,7 @@ func (db *DB) setReader(reader *maxminddb.Reader, modtime time.Time) {
func (db *DB) autoUpdate(url string) {
backoff := time.Second
for {
db.sendInfo("starting update")
err := db.runUpdate(url)
if err != nil {
bs := backoff.Seconds()
Expand All @@ -237,6 +242,7 @@ func (db *DB) autoUpdate(url string) {
} else {
backoff = db.updateInterval
}
db.sendInfo("finished update")
select {
case <-db.notifyQuit:
return
Expand All @@ -247,7 +253,6 @@ func (db *DB) autoUpdate(url string) {
}

func (db *DB) runUpdate(url string) error {
db.sendInfo("starting update")
yes, err := db.needUpdate(url)
if err != nil {
return err
Expand All @@ -264,7 +269,6 @@ func (db *DB) runUpdate(url string) error {
// Cleanup the tempfile if renaming failed.
os.RemoveAll(tmpfile)
}
db.sendInfo("finished update")
return err
}

Expand All @@ -273,19 +277,26 @@ func (db *DB) needUpdate(url string) (bool, error) {
if err != nil {
return true, nil // Local db is missing, must be downloaded.
}

resp, err := http.Head(url)
if err != nil {
return false, err
}
defer resp.Body.Close()

// Check X-Database-MD5 if it exists
headerMd5 := resp.Header.Get("X-Database-MD5")
if len(headerMd5) > 0 && db.checksum != headerMd5 {
return true, nil
}

if stat.Size() != resp.ContentLength {
return true, nil
}
return false, nil
}

func (db *DB) download(url string) (tmpfile string, err error) {
db.sendInfo("starting download")
resp, err := http.Get(url)
if err != nil {
return "", err
Expand All @@ -302,7 +313,6 @@ func (db *DB) download(url string) (tmpfile string, err error) {
if err != nil {
return "", err
}
db.sendInfo("finished download")
return tmpfile, nil
}

Expand Down
47 changes: 47 additions & 0 deletions db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,53 @@ func TestNeedUpdateSameFile(t *testing.T) {
}
}

func TestNeedUpdateSameMD5(t *testing.T) {
db := &DB{file: testFile}
_, checksum, err := db.newReader(db.file)
if err != nil {
t.Fatal(err)
}
db.checksum = checksum
mux := http.NewServeMux()
changeHeaderThenServe := func(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Database-MD5", checksum)
h.ServeHTTP(w, r)
}
}
mux.Handle("/testdata/", changeHeaderThenServe(http.FileServer(http.Dir("."))))
srv := httptest.NewServer(mux)
defer srv.Close()
yes, err := db.needUpdate(srv.URL + "/" + testFile)
if err != nil {
t.Fatal(err)
}
if yes {
t.Fatal("Unexpected: db is not supposed to need an update")
}
}

func TestNeedUpdateMD5(t *testing.T) {
mux := http.NewServeMux()
changeHeaderThenServe := func(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Database-MD5", "9823y5981y2398y1234")
h.ServeHTTP(w, r)
}
}
mux.Handle("/testdata/", changeHeaderThenServe(http.FileServer(http.Dir("."))))
srv := httptest.NewServer(mux)
defer srv.Close()
db := &DB{file: testFile}
yes, err := db.needUpdate(srv.URL + "/" + testFile)
if err != nil {
t.Fatal(err)
}
if !yes {
t.Fatal("Unexpected: db is supposed to need an update")
}
}

func TestNeedUpdate(t *testing.T) {
mux := http.NewServeMux()
mux.Handle("/testdata/", http.FileServer(http.Dir(".")))
Expand Down
Loading

0 comments on commit 8704674

Please sign in to comment.