3 Commits

Author SHA1 Message Date
2f43f2d934 Strip leading slashes from requests to download map
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-04-02 01:21:42 -04:00
a571832239 Add support for region paths
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-04-01 00:04:27 -04:00
20ae76ff06 Fix distance sorting, add env variable for local IP to test
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-31 22:43:18 -04:00
5 changed files with 98 additions and 44 deletions

View File

@ -46,6 +46,17 @@ func reloadConfig() {
// Reload server list // Reload server list
reloadServers() reloadServers()
// Create mirror map
mirrors := make(map[string][]*Server)
for _, server := range servers {
mirrors[server.Continent] = append(mirrors[server.Continent], server)
}
mirrors["default"] = append(mirrors["NA"], mirrors["EU"]...)
mirrorMap = mirrors
// Check top choices size // Check top choices size
if topChoices > len(servers) { if topChoices > len(servers) {
topChoices = len(servers) topChoices = len(servers)

80
http.go
View File

@ -3,9 +3,11 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/jmcvetta/randutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path" "path"
"strings" "strings"
) )
@ -18,21 +20,19 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) { func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
mirrors := make(map[string][]string) mirrorOutput := make(map[string][]string)
for _, server := range servers { for region, mirrors := range mirrorMap {
u := &url.URL{ list := make([]string, len(mirrors))
Scheme: r.URL.Scheme,
Host: server.Host, for i, mirror := range mirrors {
Path: server.Path, list[i] = r.URL.Scheme + "://" + mirror.Host + "/" + strings.TrimLeft(mirror.Path, "/")
} }
mirrors[server.Continent] = append(mirrors[server.Continent], u.String()) mirrorOutput[region] = list
} }
mirrors["default"] = append(mirrors["NA"], mirrors["EU"]...) json.NewEncoder(w).Encode(mirrorOutput)
json.NewEncoder(w).Encode(mirrors)
} }
func mirrorsHandler(w http.ResponseWriter, r *http.Request) { func mirrorsHandler(w http.ResponseWriter, r *http.Request) {
@ -50,18 +50,59 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
ip := net.ParseIP(ipStr) ip := net.ParseIP(ipStr)
// TODO: This is temporary to allow testing on private addresses. if ip.IsLoopback() || ip.IsPrivate() {
if ip.IsPrivate() { overrideIP := os.Getenv("OVERRIDE_IP")
ip = net.ParseIP("1.1.1.1")
if overrideIP == "" {
overrideIP = "1.1.1.1"
} }
server, distance, err := servers.Closest(ip) ip = net.ParseIP(overrideIP)
}
var server *Server
var distance float64
if strings.HasPrefix(r.URL.Path, "/region") {
parts := strings.Split(r.URL.Path, "/")
// region = parts[2]
if mirrors, ok := mirrorMap[parts[2]]; ok {
choices := make([]randutil.Choice, len(mirrors))
for i, item := range mirrors {
if !item.Available {
continue
}
choices[i] = randutil.Choice{
Weight: item.Weight,
Item: item,
}
}
choice, err := randutil.WeightedChoice(choices)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
server = choice.Item.(*Server)
r.URL.Path = strings.Join(parts[3:], "/")
}
}
if server == nil {
server, distance, err = servers.Closest(ip)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
scheme := r.URL.Scheme scheme := r.URL.Scheme
if scheme == "" { if scheme == "" {
@ -71,13 +112,7 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
redirectPath := path.Join(server.Path, r.URL.Path) redirectPath := path.Join(server.Path, r.URL.Path)
if dlMap != nil { if dlMap != nil {
p := r.URL.Path if newPath, exists := dlMap[strings.TrimLeft(r.URL.Path, "/")]; exists {
if p[0] != '/' {
p = "/" + p
}
if newPath, exists := dlMap[p]; exists {
downloadsMapped.Inc() downloadsMapped.Inc()
redirectPath = path.Join(server.Path, newPath) redirectPath = path.Join(server.Path, newPath)
} }
@ -96,7 +131,10 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
server.Redirects.Inc() server.Redirects.Inc()
redirectsServed.Inc() redirectsServed.Inc()
if distance > 0 {
w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance)) w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance))
}
w.Header().Set("Location", u.String()) w.Header().Set("Location", u.String())
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
} }

View File

@ -20,6 +20,7 @@ import (
var ( var (
db *maxminddb.Reader db *maxminddb.Reader
servers ServerList servers ServerList
mirrorMap map[string][]*Server
dlMap map[string]string dlMap map[string]string
@ -110,6 +111,7 @@ func main() {
r.Use(RealIPMiddleware) r.Use(RealIPMiddleware)
r.Use(logger.Logger("router", log.StandardLogger())) r.Use(logger.Logger("router", log.StandardLogger()))
r.Head("/status", statusHandler)
r.Get("/status", statusHandler) r.Get("/status", statusHandler)
r.Get("/mirrors", legacyMirrorsHandler) r.Get("/mirrors", legacyMirrorsHandler)
r.Get("/mirrors.json", mirrorsHandler) r.Get("/mirrors.json", mirrorsHandler)

3
map.go
View File

@ -4,6 +4,7 @@ import (
"encoding/csv" "encoding/csv"
"io" "io"
"os" "os"
"strings"
) )
func loadMap(file string) (map[string]string, error) { func loadMap(file string) (map[string]string, error) {
@ -32,7 +33,7 @@ func loadMap(file string) (map[string]string, error) {
return nil, err return nil, err
} }
m[row[0]] = row[1] m[strings.TrimLeft(row[0], "/")] = strings.TrimLeft(row[1], "/")
} }
return m, nil return m, nil

View File

@ -123,19 +123,6 @@ type ComputedDistance struct {
// DistanceList is a list of Computed Distances with an easy "Choices" func // DistanceList is a list of Computed Distances with an easy "Choices" func
type DistanceList []ComputedDistance type DistanceList []ComputedDistance
func (d DistanceList) Choices() []randutil.Choice {
c := make([]randutil.Choice, len(d))
for i, item := range d {
c[i] = randutil.Choice{
Weight: item.Server.Weight,
Item: item,
}
}
return c
}
// Closest will use GeoIP on the IP provided and find the closest servers. // Closest will use GeoIP on the IP provided and find the closest servers.
// When we have a list of x servers closest, we can choose a random or weighted one. // When we have a list of x servers closest, we can choose a random or weighted one.
// Return values are the closest server, the distance, and if an error occurred. // Return values are the closest server, the distance, and if an error occurred.
@ -157,18 +144,33 @@ func (s ServerList) Closest(ip net.IP) (*Server, float64, error) {
continue continue
} }
distance := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude)
c[i] = ComputedDistance{ c[i] = ComputedDistance{
Server: server, Server: server,
Distance: Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude), Distance: distance,
} }
} }
// Sort by distance // Sort by distance
sort.Slice(s, func(i int, j int) bool { sort.Slice(c, func(i int, j int) bool {
return c[i].Distance < c[j].Distance return c[i].Distance < c[j].Distance
}) })
choiceInterface = c[0:topChoices].Choices() choices := make([]randutil.Choice, topChoices)
for i, item := range c[0:topChoices] {
if item.Server == nil {
continue
}
choices[i] = randutil.Choice{
Weight: item.Server.Weight,
Item: item,
}
}
choiceInterface = choices
serverCache.Add(ip.String(), choiceInterface) serverCache.Add(ip.String(), choiceInterface)
} }