helpers.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package web
  2. import (
  3. "crypto/rand"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "math"
  8. "net/http"
  9. "strconv"
  10. "strings"
  11. "github.com/librespeed/speedtest/config"
  12. "github.com/librespeed/speedtest/results"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. var (
  16. // get server location from ipinfo.io from start to minimize API access
  17. serverLat, serverLng = getServerLocation()
  18. // for testing
  19. // serverLat, serverLng = 22.7702, 112.9578
  20. // serverLat, serverLng = 23.018, 113.7487
  21. )
  22. func getRandomData(length int) []byte {
  23. data := make([]byte, length)
  24. if _, err := rand.Read(data); err != nil {
  25. log.Fatalf("Failed to generate random data: %s", err)
  26. }
  27. return data
  28. }
  29. func getIPInfoURL(address string) string {
  30. apiKey := config.LoadedConfig().IPInfoAPIKey
  31. ipInfoURL := `https://ipinfo.io/%s/json`
  32. if address != "" {
  33. ipInfoURL = fmt.Sprintf(ipInfoURL, address)
  34. } else {
  35. ipInfoURL = "https://ipinfo.io/json"
  36. }
  37. if apiKey != "" {
  38. ipInfoURL += "?token=" + apiKey
  39. }
  40. return ipInfoURL
  41. }
  42. func getIPInfo(addr string) results.IPInfoResponse {
  43. var ret results.IPInfoResponse
  44. resp, err := http.DefaultClient.Get(getIPInfoURL(addr))
  45. if err != nil {
  46. log.Errorf("Error getting response from ipinfo.io: %s", err)
  47. return ret
  48. }
  49. raw, err := ioutil.ReadAll(resp.Body)
  50. if err != nil {
  51. log.Errorf("Error reading response from ipinfo.io: %s", err)
  52. return ret
  53. }
  54. defer resp.Body.Close()
  55. if err := json.Unmarshal(raw, &ret); err != nil {
  56. log.Errorf("Error parsing response from ipinfo.io: %s", err)
  57. }
  58. return ret
  59. }
  60. func getServerLocation() (float64, float64) {
  61. conf := config.LoadedConfig()
  62. if conf.ServerLat > 0 && conf.ServerLng > 0 {
  63. log.Infof("Configured server coordinates: %.6f, %.6f", conf.ServerLat, conf.ServerLng)
  64. return conf.ServerLat, conf.ServerLng
  65. }
  66. var ret results.IPInfoResponse
  67. resp, err := http.DefaultClient.Get(getIPInfoURL(""))
  68. if err != nil {
  69. log.Errorf("Error getting repsonse from ipinfo.io: %s", err)
  70. return 0, 0
  71. }
  72. raw, err := ioutil.ReadAll(resp.Body)
  73. if err != nil {
  74. log.Errorf("Error reading response from ipinfo.io: %s", err)
  75. return 0, 0
  76. }
  77. defer resp.Body.Close()
  78. if err := json.Unmarshal(raw, &ret); err != nil {
  79. log.Errorf("Error parsing response from ipinfo.io: %s", err)
  80. return 0, 0
  81. }
  82. var lat, lng float64
  83. if ret.Location != "" {
  84. lat, lng = parseLocationString(ret.Location)
  85. }
  86. log.Infof("Fetched server coordinates: %.6f, %.6f", lat, lng)
  87. return lat, lng
  88. }
  89. func parseLocationString(location string) (float64, float64) {
  90. parts := strings.Split(location, ",")
  91. if len(parts) != 2 {
  92. log.Errorf("Unknown location format: %s", location)
  93. return 0, 0
  94. }
  95. lat, err := strconv.ParseFloat(parts[0], 64)
  96. if err != nil {
  97. log.Errorf("Error parsing latitude: %s", parts[0])
  98. return 0, 0
  99. }
  100. lng, err := strconv.ParseFloat(parts[1], 64)
  101. if err != nil {
  102. log.Errorf("Error parsing longitude: %s", parts[0])
  103. return 0, 0
  104. }
  105. return lat, lng
  106. }
  107. func calculateDistance(clientLocation string, unit string) string {
  108. clientLat, clientLng := parseLocationString(clientLocation)
  109. radlat1 := float64(math.Pi * serverLat / 180)
  110. radlat2 := float64(math.Pi * clientLat / 180)
  111. theta := float64(serverLng - clientLng)
  112. radtheta := float64(math.Pi * theta / 180)
  113. dist := math.Sin(radlat1)*math.Sin(radlat2) + math.Cos(radlat1)*math.Cos(radlat2)*math.Cos(radtheta)
  114. if dist > 1 {
  115. dist = 1
  116. }
  117. dist = math.Acos(dist)
  118. dist = dist * 180 / math.Pi
  119. dist = dist * 60 * 1.1515
  120. unitString := " mi"
  121. switch unit {
  122. case "km":
  123. dist = dist * 1.609344
  124. unitString = " km"
  125. case "NM":
  126. dist = dist * 0.8684
  127. unitString = " NM"
  128. }
  129. return fmt.Sprintf("%d%s", round(dist), unitString)
  130. }
  131. func round(v float64) int {
  132. r := int(math.Round(v))
  133. return 10 * ((r + 9) / 10)
  134. }