-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.go
173 lines (147 loc) · 3.94 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package speedtest
import (
"bytes"
"encoding/xml"
"fmt"
"io/ioutil"
"math"
"net/http"
"sort"
"strconv"
"errors"
"time"
)
// Server information
type Server struct {
URL string `xml:"url,attr"`
Lat string `xml:"lat,attr"`
Lon string `xml:"lon,attr"`
Name string `xml:"name,attr"`
Country string `xml:"country,attr"`
Sponsor string `xml:"sponsor,attr"`
ID string `xml:"id,attr"`
URL2 string `xml:"url2,attr"`
Host string `xml:"host,attr"`
Distance float64
Latency time.Duration
DLSpeed float64
ULSpeed float64
}
// ServerList list of Server
type ServerList struct {
Servers []*Server `xml:"servers>server"`
}
// Servers for sorting servers.
type Servers []*Server
// ByDistance for sorting servers.
type ByDistance struct {
Servers
}
// Len finds length of servers. For sorting servers.
func (svrs Servers) Len() int {
return len(svrs)
}
// Swap swaps i-th and j-th. For sorting servers.
func (svrs Servers) Swap(i, j int) {
svrs[i], svrs[j] = svrs[j], svrs[i]
}
// Less compares the distance. For sorting servers.
func (b ByDistance) Less(i, j int) bool {
return b.Servers[i].Distance < b.Servers[j].Distance
}
// FetchServerList retrieves a list of available servers
func FetchServerList(user *User) (ServerList, error) {
// Fetch xml server data
resp, err := http.Get("https://speedtest.noia.network/servers.xml")
if err != nil {
return ServerList{}, errors.New("failed to retrieve speedtest servers")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ServerList{}, errors.New("failed to read response body")
}
defer resp.Body.Close()
if len(body) == 0 {
resp, err = http.Get("https://speedtest.noia.network/servers.xml")
if err != nil {
errors.New("failed to retrieve alternate speedtest servers")
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return ServerList{}, errors.New("failed to read response body")
}
defer resp.Body.Close()
}
// Decode xml
decoder := xml.NewDecoder(bytes.NewReader(body))
list := ServerList{}
for {
t, _ := decoder.Token()
if t == nil {
break
}
switch se := t.(type) {
case xml.StartElement:
decoder.DecodeElement(&list, &se)
}
}
// Calculate distance
for i := range list.Servers {
server := list.Servers[i]
sLat, _ := strconv.ParseFloat(server.Lat, 64)
sLon, _ := strconv.ParseFloat(server.Lon, 64)
uLat, _ := strconv.ParseFloat(user.Lat, 64)
uLon, _ := strconv.ParseFloat(user.Lon, 64)
server.Distance = distance(sLat, sLon, uLat, uLon)
}
// Sort by distance
sort.Sort(ByDistance{list.Servers})
if len(list.Servers) <= 0 {
return list, errors.New("unable to retrieve server list")
}
return list, nil
}
func distance(lat1 float64, lon1 float64, lat2 float64, lon2 float64) float64 {
radius := 6378.137
a1 := lat1 * math.Pi / 180.0
b1 := lon1 * math.Pi / 180.0
a2 := lat2 * math.Pi / 180.0
b2 := lon2 * math.Pi / 180.0
x := math.Sin(a1)*math.Sin(a2) + math.Cos(a1)*math.Cos(a2)*math.Cos(b2-b1)
return radius * math.Acos(x)
}
// FindServer finds server by serverID
func (l *ServerList) FindServer(serverID []int) (Servers, error) {
servers := Servers{}
if len(l.Servers) <= 0 {
return servers, errors.New("no servers available")
}
for _, sid := range serverID {
for _, s := range l.Servers {
id, _ := strconv.Atoi(s.ID)
if sid == id {
servers = append(servers, s)
}
}
}
if len(servers) == 0 {
servers = append(servers, l.Servers[0])
}
return servers, nil
}
// String representation of ServerList
func (l *ServerList) String() string {
slr := ""
for _, s := range l.Servers {
slr += s.String()
}
return slr
}
// String representation of Server
func (s *Server) String() string {
return fmt.Sprintf("[%4s] %8.2fkm \n%s (%s) by %s\n", s.ID, s.Distance, s.Name, s.Country, s.Sponsor)
}
// CheckResultValid checks that results are logical given UL and DL speeds
func (s Server) CheckResultValid() bool {
return !(s.DLSpeed*100 < s.ULSpeed) || !(s.DLSpeed > s.ULSpeed*100)
}