mirror of
https://github.com/skidoodle/ipinfo.git
synced 2025-02-15 08:29:17 +01:00
Refactor IP data lookup with caching and enable gzip compression
This commit is contained in:
parent
ff2dee80ee
commit
bc9f481717
2 changed files with 69 additions and 33 deletions
18
readme.md
18
readme.md
|
@ -1,7 +1,9 @@
|
||||||
# ipinfo
|
# ipinfo
|
||||||
|
|
||||||
`ipinfo` is a powerful and efficient IP information service written in Go. It fetches GeoIP data to provide detailed information about an IP address, including geographical location, ASN, and related network details. The service automatically updates its GeoIP databases to ensure accuracy and reliability.
|
`ipinfo` is a powerful and efficient IP information service written in Go. It fetches GeoIP data to provide detailed information about an IP address, including geographical location, ASN, and related network details. The service automatically updates its GeoIP databases to ensure accuracy and reliability.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **IP Geolocation**: Provides city, region, country, continent, and coordinates for any IP address.
|
- **IP Geolocation**: Provides city, region, country, continent, and coordinates for any IP address.
|
||||||
- **ASN Information**: Includes autonomous system number and organization.
|
- **ASN Information**: Includes autonomous system number and organization.
|
||||||
- **Hostname Lookup**: Retrieves the hostname associated with the IP address.
|
- **Hostname Lookup**: Retrieves the hostname associated with the IP address.
|
||||||
|
@ -9,7 +11,9 @@
|
||||||
- **JSONP Support**: Allows JSONP responses for cross-domain requests.
|
- **JSONP Support**: Allows JSONP responses for cross-domain requests.
|
||||||
|
|
||||||
## Example Endpoints
|
## Example Endpoints
|
||||||
|
|
||||||
### Get information about an IP address
|
### Get information about an IP address
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ curl https://ip.albert.lol/9.9.9.9
|
$ curl https://ip.albert.lol/9.9.9.9
|
||||||
{
|
{
|
||||||
|
@ -25,14 +29,18 @@ $ curl https://ip.albert.lol/9.9.9.9
|
||||||
"loc": "37.8767,-122.2676"
|
"loc": "37.8767,-122.2676"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get specific information (e.g., city) about an IP address
|
### Get specific information (e.g., city) about an IP address
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ curl https://ip.albert.lol/9.9.9.9/city
|
$ curl https://ip.albert.lol/9.9.9.9/city
|
||||||
{
|
{
|
||||||
"city": "Berkeley"
|
"city": "Berkeley"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use JSONP callback function
|
### Use JSONP callback function
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ curl https://test.albert.lol/9.9.9.9?callback=Quad9
|
$ curl https://test.albert.lol/9.9.9.9?callback=Quad9
|
||||||
/**/ typeof Quad9 === 'function' && Quad9({
|
/**/ typeof Quad9 === 'function' && Quad9({
|
||||||
|
@ -48,6 +56,7 @@ $ curl https://test.albert.lol/9.9.9.9?callback=Quad9
|
||||||
"loc": "37.8767,-122.2676"
|
"loc": "37.8767,-122.2676"
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script>
|
<script>
|
||||||
let Quad9 = function(data) {
|
let Quad9 = function(data) {
|
||||||
|
@ -58,7 +67,9 @@ let Quad9 = function(data) {
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running Locally
|
## Running Locally
|
||||||
|
|
||||||
### With Docker
|
### With Docker
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/skidoodle/ipinfo
|
git clone https://github.com/skidoodle/ipinfo
|
||||||
cd ipinfo
|
cd ipinfo
|
||||||
|
@ -71,7 +82,9 @@ docker run \
|
||||||
-e GEOIPUPDATE_DB_DIR=<> \
|
-e GEOIPUPDATE_DB_DIR=<> \
|
||||||
ipinfo:main
|
ipinfo:main
|
||||||
```
|
```
|
||||||
|
|
||||||
### Without Docker
|
### Without Docker
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/skidoodle/ipinfo
|
git clone https://github.com/skidoodle/ipinfo
|
||||||
cd ipinfo
|
cd ipinfo
|
||||||
|
@ -79,7 +92,9 @@ go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploying
|
## Deploying
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
ipinfo:
|
ipinfo:
|
||||||
|
@ -100,7 +115,9 @@ services:
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Run
|
### Docker Run
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run \
|
docker run \
|
||||||
-d \
|
-d \
|
||||||
|
@ -115,4 +132,5 @@ docker run \
|
||||||
```
|
```
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
||||||
[GPL-3.0](https://github.com/skidoodle/ipinfo/blob/main/license)
|
[GPL-3.0](https://github.com/skidoodle/ipinfo/blob/main/license)
|
||||||
|
|
84
server.go
84
server.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
@ -11,7 +12,6 @@ import (
|
||||||
|
|
||||||
var invalidIPBytes = []byte("Please provide a valid IP address.")
|
var invalidIPBytes = []byte("Please provide a valid IP address.")
|
||||||
|
|
||||||
// Struct to hold IP data
|
|
||||||
type dataStruct struct {
|
type dataStruct struct {
|
||||||
IP *string `json:"ip"`
|
IP *string `json:"ip"`
|
||||||
Hostname *string `json:"hostname"`
|
Hostname *string `json:"hostname"`
|
||||||
|
@ -36,9 +36,27 @@ func startServer() {
|
||||||
log.Fatal(http.ListenAndServe(":3000", nil))
|
log.Fatal(http.ListenAndServe(":3000", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gzipResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
Writer *gzip.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
return w.Writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
requestedThings := strings.Split(r.URL.Path, "/")
|
requestedThings := strings.Split(r.URL.Path, "/")
|
||||||
|
|
||||||
|
// Enable gzip compression if requested
|
||||||
|
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
gz := gzip.NewWriter(w)
|
||||||
|
defer gz.Close()
|
||||||
|
w = &gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract IP and field
|
||||||
var IPAddress, field string
|
var IPAddress, field string
|
||||||
if len(requestedThings) > 1 && net.ParseIP(requestedThings[1]) != nil {
|
if len(requestedThings) > 1 && net.ParseIP(requestedThings[1]) != nil {
|
||||||
IPAddress = requestedThings[1]
|
IPAddress = requestedThings[1]
|
||||||
|
@ -46,19 +64,23 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
field = requestedThings[2]
|
field = requestedThings[2]
|
||||||
}
|
}
|
||||||
} else if len(requestedThings) > 1 {
|
} else if len(requestedThings) > 1 {
|
||||||
field = requestedThings[1]
|
IPAddress = requestedThings[1] // This might be an invalid IP
|
||||||
}
|
}
|
||||||
|
|
||||||
if IPAddress == "" || IPAddress == "self" {
|
// Check if the IP is the client's IP
|
||||||
|
if IPAddress == "" {
|
||||||
IPAddress = getRealIP(r)
|
IPAddress = getRealIP(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the IP address
|
||||||
ip := net.ParseIP(IPAddress)
|
ip := net.ParseIP(IPAddress)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
// Send 400 Bad Request with invalid IP message
|
||||||
w.Write(invalidIPBytes)
|
http.Error(w, string(invalidIPBytes), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the IP is a bogon IP
|
||||||
if isBogon(ip) {
|
if isBogon(ip) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
bogonData := bogonDataStruct{
|
bogonData := bogonDataStruct{
|
||||||
|
@ -69,13 +91,15 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up IP data
|
||||||
data := lookupIPData(ip)
|
data := lookupIPData(ip)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
// Send 400 Bad Request with invalid IP message
|
||||||
w.Write(invalidIPBytes)
|
http.Error(w, string(invalidIPBytes), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle specific field requests
|
||||||
if field != "" {
|
if field != "" {
|
||||||
value := getField(data, field)
|
value := getField(data, field)
|
||||||
if value != nil {
|
if value != nil {
|
||||||
|
@ -83,12 +107,14 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]*string{field: value})
|
json.NewEncoder(w).Encode(map[string]*string{field: value})
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
// Handle invalid field request
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
json.NewEncoder(w).Encode(map[string]*string{field: nil})
|
json.NewEncoder(w).Encode(map[string]*string{field: nil})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no specific field is requested, return the whole data
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
callback := r.URL.Query().Get("callback")
|
callback := r.URL.Query().Get("callback")
|
||||||
enableJSONP := callback != "" && len(callback) < 2000 && callbackJSONP.MatchString(callback)
|
enableJSONP := callback != "" && len(callback) < 2000 && callbackJSONP.MatchString(callback)
|
||||||
|
@ -106,30 +132,22 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get specific field from dataStruct
|
var fieldMap = map[string]func(*dataStruct) *string{
|
||||||
func getField(data *dataStruct, field string) *string {
|
"ip": func(d *dataStruct) *string { return d.IP },
|
||||||
switch field {
|
"hostname": func(d *dataStruct) *string { return d.Hostname },
|
||||||
case "ip":
|
"asn": func(d *dataStruct) *string { return d.ASN },
|
||||||
return data.IP
|
"org": func(d *dataStruct) *string { return d.Org },
|
||||||
case "hostname":
|
"city": func(d *dataStruct) *string { return d.City },
|
||||||
return data.Hostname
|
"region": func(d *dataStruct) *string { return d.Region },
|
||||||
case "asn":
|
"country": func(d *dataStruct) *string { return d.Country },
|
||||||
return data.ASN
|
"continent": func(d *dataStruct) *string { return d.Continent },
|
||||||
case "org":
|
"timezone": func(d *dataStruct) *string { return d.Timezone },
|
||||||
return data.Org
|
"loc": func(d *dataStruct) *string { return d.Loc },
|
||||||
case "city":
|
}
|
||||||
return data.City
|
|
||||||
case "region":
|
func getField(data *dataStruct, field string) *string {
|
||||||
return data.Region
|
if f, ok := fieldMap[field]; ok {
|
||||||
case "country":
|
return f(data)
|
||||||
return data.Country
|
}
|
||||||
case "continent":
|
return nil
|
||||||
return data.Continent
|
|
||||||
case "timezone":
|
|
||||||
return data.Timezone
|
|
||||||
case "loc":
|
|
||||||
return data.Loc
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue