Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
2f43f2d934 | |||
a571832239 | |||
20ae76ff06 | |||
b4ed1fc1a3 |
11
config.go
11
config.go
@ -46,6 +46,17 @@ func reloadConfig() {
|
||||
// Reload server list
|
||||
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
|
||||
if topChoices > len(servers) {
|
||||
topChoices = len(servers)
|
||||
|
93
http.go
93
http.go
@ -3,10 +3,13 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/jmcvetta/randutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -17,21 +20,19 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
mirrors := make(map[string][]string)
|
||||
mirrorOutput := make(map[string][]string)
|
||||
|
||||
for _, server := range servers {
|
||||
u := &url.URL{
|
||||
Scheme: r.URL.Scheme,
|
||||
Host: server.Host,
|
||||
Path: server.Path,
|
||||
for region, mirrors := range mirrorMap {
|
||||
list := make([]string, len(mirrors))
|
||||
|
||||
for i, mirror := range mirrors {
|
||||
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(mirrors)
|
||||
json.NewEncoder(w).Encode(mirrorOutput)
|
||||
}
|
||||
|
||||
func mirrorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -49,16 +50,57 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
|
||||
// TODO: This is temporary to allow testing on private addresses.
|
||||
if ip.IsPrivate() {
|
||||
ip = net.ParseIP("1.1.1.1")
|
||||
if ip.IsLoopback() || ip.IsPrivate() {
|
||||
overrideIP := os.Getenv("OVERRIDE_IP")
|
||||
|
||||
if overrideIP == "" {
|
||||
overrideIP = "1.1.1.1"
|
||||
}
|
||||
|
||||
ip = net.ParseIP(overrideIP)
|
||||
}
|
||||
|
||||
server, distance, err := servers.Closest(ip)
|
||||
var server *Server
|
||||
var distance float64
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
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 {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
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
|
||||
@ -70,18 +112,16 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
redirectPath := path.Join(server.Path, r.URL.Path)
|
||||
|
||||
if dlMap != nil {
|
||||
p := r.URL.Path
|
||||
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
|
||||
if newPath, exists := dlMap[p]; exists {
|
||||
if newPath, exists := dlMap[strings.TrimLeft(r.URL.Path, "/")]; exists {
|
||||
downloadsMapped.Inc()
|
||||
redirectPath = path.Join(server.Path, newPath)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/") && !strings.HasSuffix(redirectPath, "/") {
|
||||
redirectPath += "/"
|
||||
}
|
||||
|
||||
u := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: server.Host,
|
||||
@ -91,7 +131,10 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
server.Redirects.Inc()
|
||||
redirectsServed.Inc()
|
||||
|
||||
w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance))
|
||||
if distance > 0 {
|
||||
w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance))
|
||||
}
|
||||
|
||||
w.Header().Set("Location", u.String())
|
||||
w.WriteHeader(http.StatusFound)
|
||||
}
|
||||
|
6
main.go
6
main.go
@ -18,8 +18,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
db *maxminddb.Reader
|
||||
servers ServerList
|
||||
db *maxminddb.Reader
|
||||
servers ServerList
|
||||
mirrorMap map[string][]*Server
|
||||
|
||||
dlMap map[string]string
|
||||
|
||||
@ -110,6 +111,7 @@ func main() {
|
||||
r.Use(RealIPMiddleware)
|
||||
r.Use(logger.Logger("router", log.StandardLogger()))
|
||||
|
||||
r.Head("/status", statusHandler)
|
||||
r.Get("/status", statusHandler)
|
||||
r.Get("/mirrors", legacyMirrorsHandler)
|
||||
r.Get("/mirrors.json", mirrorsHandler)
|
||||
|
3
map.go
3
map.go
@ -4,6 +4,7 @@ import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func loadMap(file string) (map[string]string, error) {
|
||||
@ -32,7 +33,7 @@ func loadMap(file string) (map[string]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m[row[0]] = row[1]
|
||||
m[strings.TrimLeft(row[0], "/")] = strings.TrimLeft(row[1], "/")
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
34
servers.go
34
servers.go
@ -123,19 +123,6 @@ type ComputedDistance struct {
|
||||
// DistanceList is a list of Computed Distances with an easy "Choices" func
|
||||
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.
|
||||
// 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.
|
||||
@ -157,18 +144,33 @@ func (s ServerList) Closest(ip net.IP) (*Server, float64, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
distance := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude)
|
||||
|
||||
c[i] = ComputedDistance{
|
||||
Server: server,
|
||||
Distance: Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude),
|
||||
Distance: 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
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user