Skip to content

Commit

Permalink
Caching database
Browse files Browse the repository at this point in the history
  • Loading branch information
fiorix committed Mar 6, 2014
1 parent 2e3a62e commit 88a9408
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 54 deletions.
147 changes: 147 additions & 0 deletions cache.go
@@ -0,0 +1,147 @@
// Copyright 2013-2014 Alexandre Fiori
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package main

import (
"database/sql"
"log"
)

type Cache struct {
Country map[string]string
Region map[RegionKey]string
City map[int]Location
}

type RegionKey struct {
CountryCode,
RegionCode *string
}

type Location struct {
CountryCode,
RegionCode,
CityName,
ZipCode string
Latitude,
Longitude float32
MetroCode,
AreaCode string
}

func NewCache(db *sql.DB) *Cache {
cache := &Cache{
Country: make(map[string]string),
Region: make(map[RegionKey]string),
City: make(map[int]Location),
}

var (
row *sql.Rows
err error
)

// Load list of countries.
if row, err = db.Query(`
SELECT
country_code,
country_name
FROM
country_blocks
`); err != nil {
log.Fatal("Failed to load countries from db:", err)
}

var country_code, region_code, name string
for row.Next() {
if err = row.Scan(
&country_code,
&name,
); err != nil {
log.Fatal("Failed to load country from db:", err)
}

cache.Country[country_code] = name
}

row.Close()

// Load list of regions.
if row, err = db.Query(`
SELECT
country_code,
region_code,
region_name
FROM
region_names
`); err != nil {
log.Fatal("Failed to load regions from db:", err)
}

for row.Next() {
if err = row.Scan(
&country_code,
&region_code,
&name,
); err != nil {
log.Fatal("Failed to load region from db:", err)
}

cache.Region[RegionKey{&country_code, &region_code}] = name
}

row.Close()

// Load list of city locations.
if row, err = db.Query("SELECT * FROM city_location"); err != nil {
log.Fatal("Failed to load cities from db:", err)
}

var (
locId int
loc Location
)

for row.Next() {
if err = row.Scan(
&locId,
&loc.CountryCode,
&loc.RegionCode,
&loc.CityName,
&loc.ZipCode,
&loc.Latitude,
&loc.Longitude,
&loc.MetroCode,
&loc.AreaCode,
); err != nil {
log.Fatal("Failed to load city from db:", err)
}

cache.City[locId] = loc
}

row.Close()

return cache
}

func (cache *Cache) Update(geoip *GeoIP, locId int) {
city, ok := cache.City[locId]
if !ok {
return
}

geoip.CountryName = cache.Country[city.CountryCode]
geoip.RegionName = cache.Region[RegionKey{
&city.CountryCode,
&city.RegionCode,
}]
geoip.CityName = &city.CityName
geoip.ZipCode = &city.ZipCode
geoip.Latitude = &city.Latitude
geoip.Longitude = &city.Longitude
geoip.MetroCode = &city.MetroCode
geoip.AreaCode = &city.AreaCode
}
22 changes: 5 additions & 17 deletions freegeoip.go
Expand Up @@ -128,11 +128,14 @@ func LookupHandler() http.HandlerFunc {
log.Fatal(err)
}

stmt, err := db.Prepare(query)
stmt, err := db.Prepare(ipdb_query)
if err != nil {
log.Fatal(err)
}

log.Println("Caching database, please wait...")
cache := NewCache(db)

//defer stmt.Close()

var quota Quota
Expand Down Expand Up @@ -236,7 +239,7 @@ func LookupHandler() http.HandlerFunc {
}

// Query the db.
geoip, err := lookup(stmt, queryIP, nqueryIP)
geoip, err := ipdb_lookup(stmt, cache, queryIP, nqueryIP)
if err != nil {
http.NotFound(w, r)
return
Expand Down Expand Up @@ -347,21 +350,6 @@ func logger(r *http.Request, created time.Time, status, bytes int) {
}
}

type GeoIP struct {
XMLName xml.Name `json:"-" xml:"Response"`
Ip string `json:"ip"`
CountryCode string `json:"country_code"`
CountryName string `json:"country_name"`
RegionCode string `json:"region_code"`
RegionName string `json:"region_name"`
CityName string `json:"city" xml:"City"`
ZipCode string `json:"zipcode"`
Latitude float32 `json:"latitude"`
Longitude float32 `json:"longitude"`
MetroCode string `json:"metro_code"`
AreaCode string `json:"areacode"`
}

type ConfigFile struct {
XMLName xml.Name `xml:"Server"`
Debug bool `xml:"debug,attr"`
Expand Down
75 changes: 38 additions & 37 deletions ipdb.go
Expand Up @@ -6,41 +6,52 @@ package main

import (
"database/sql"
"encoding/xml"
"net"
)

func lookup(stmt *sql.Stmt, IP net.IP, nIP uint32) (*GeoIP, error) {
func ipdb_lookup(stmt *sql.Stmt, cache *Cache, IP net.IP, nIP uint32) (*GeoIP, error) {
var reserved bool
for _, net := range reservedIPs {
if net.Contains(IP) {
reserved = true
break
}
}
geoip := GeoIP{Ip: IP.String()}

geoip := &GeoIP{Ip: IP.String()}
if reserved {
geoip.CountryCode = "RD"
geoip.CountryCode = &reservedCountryCode
geoip.CountryName = "Reserved"
} else {
if err := stmt.QueryRow(nIP).Scan(
&geoip.CountryCode,
&geoip.CountryName,
&geoip.RegionCode,
&geoip.RegionName,
&geoip.CityName,
&geoip.ZipCode,
&geoip.Latitude,
&geoip.Longitude,
&geoip.MetroCode,
&geoip.AreaCode,
); err != nil {
var locId int
if err := stmt.QueryRow(nIP).Scan(&locId); err != nil {
return nil, err
}

cache.Update(geoip, locId)
}
return &geoip, nil

return geoip, nil
}

type GeoIP struct {
XMLName xml.Name `json:"-" xml:"Response"`
Ip string `json:"ip"`
CountryCode *string `json:"country_code"`
CountryName string `json:"country_name"`
RegionCode *string `json:"region_code"`
RegionName string `json:"region_name"`
CityName *string `json:"city" xml:"City"`
ZipCode *string `json:"zipcode"`
Latitude *float32 `json:"latitude"`
Longitude *float32 `json:"longitude"`
MetroCode *string `json:"metro_code"`
AreaCode *string `json:"areacode"`
}

// http://en.wikipedia.org/wiki/Reserved_IP_addresses
var reservedCountryCode = "RD"
var reservedIPs = []net.IPNet{
{net.IPv4(0, 0, 0, 0), net.IPv4Mask(255, 0, 0, 0)},
{net.IPv4(10, 0, 0, 0), net.IPv4Mask(255, 0, 0, 0)},
Expand All @@ -61,24 +72,14 @@ var reservedIPs = []net.IPNet{
}

// SQLite query.
const query = `SELECT
city_location.country_code,
country_blocks.country_name,
city_location.region_code,
region_names.region_name,
city_location.city_name,
city_location.postal_code,
city_location.latitude,
city_location.longitude,
city_location.metro_code,
city_location.area_code
FROM city_blocks
NATURAL JOIN city_location
INNER JOIN country_blocks ON
city_location.country_code = country_blocks.country_code
LEFT OUTER JOIN region_names ON
city_location.country_code = region_names.country_code
AND
city_location.region_code = region_names.region_code
WHERE city_blocks.ip_start <= ?
ORDER BY city_blocks.ip_start DESC LIMIT 1`
const ipdb_query = `
SELECT
loc_id
FROM
city_blocks
WHERE
ip_start <= ?
ORDER BY
ip_start DESC
LIMIT 1
`

0 comments on commit 88a9408

Please sign in to comment.