download.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package task
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "net"
  7. "net/http"
  8. "sort"
  9. "strconv"
  10. "time"
  11. "github.com/XIU2/CloudflareSpeedTest/utils"
  12. "github.com/VividCortex/ewma"
  13. )
  14. const (
  15. bufferSize = 1024
  16. defaultURL = "https://cf.xiu2.xyz/url"
  17. defaultTimeout = 10 * time.Second
  18. defaultDisableDownload = false
  19. defaultTestNum = 10
  20. defaultMinSpeed float64 = 0.0
  21. )
  22. var (
  23. URL = defaultURL
  24. Timeout = defaultTimeout
  25. Disable = defaultDisableDownload
  26. TestCount = defaultTestNum
  27. MinSpeed = defaultMinSpeed
  28. )
  29. func checkDownloadDefault() {
  30. if URL == "" {
  31. URL = defaultURL
  32. }
  33. if Timeout <= 0 {
  34. Timeout = defaultTimeout
  35. }
  36. if TestCount <= 0 {
  37. TestCount = defaultTestNum
  38. }
  39. if MinSpeed <= 0.0 {
  40. MinSpeed = defaultMinSpeed
  41. }
  42. }
  43. func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSet) {
  44. checkDownloadDefault()
  45. if Disable {
  46. return utils.DownloadSpeedSet(ipSet)
  47. }
  48. if len(ipSet) <= 0 { // IP数组长度(IP数量) 大于 0 时才会继续下载测速
  49. fmt.Println("\n\033[33m[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。\033[0m")
  50. return
  51. }
  52. testNum := TestCount
  53. if len(ipSet) < TestCount || MinSpeed > 0 { // 如果IP数组长度(IP数量) 小于下载测速数量(-dn),则次数修正为IP数
  54. testNum = len(ipSet)
  55. }
  56. if testNum < TestCount {
  57. TestCount = testNum
  58. }
  59. fmt.Printf("\033[34m开始下载测速(下限:%.2f MB/s, 数量:%d, 队列:%d)\033[0m\n", MinSpeed, TestCount, testNum)
  60. // 控制 下载测速进度条 与 延迟测速进度条 长度一致(强迫症)
  61. bar_a := len(strconv.Itoa(len(ipSet)))
  62. bar_b := " "
  63. for i := 0; i < bar_a; i++ {
  64. bar_b += " "
  65. }
  66. bar := utils.NewBar(TestCount, bar_b, "")
  67. for i := 0; i < testNum; i++ {
  68. speed, colo := downloadHandler(ipSet[i].IP)
  69. ipSet[i].DownloadSpeed = speed
  70. if ipSet[i].Colo == "" { // 只有当 Colo 是空的时候,才写入,否则代表之前是 httping 测速并获取过了
  71. ipSet[i].Colo = colo
  72. }
  73. // 在每个 IP 下载测速后,以 [下载速度下限] 条件过滤结果
  74. if speed >= MinSpeed*1024*1024 {
  75. bar.Grow(1, "")
  76. speedSet = append(speedSet, ipSet[i]) // 高于下载速度下限时,添加到新数组中
  77. if len(speedSet) == TestCount { // 凑够满足条件的 IP 时(下载测速数量 -dn),就跳出循环
  78. break
  79. }
  80. }
  81. }
  82. bar.Done()
  83. if utils.Debug && len(speedSet) == 0 { // 调试模式下,没有满足速度限制的数据,返回所有测速数据供用户查看当前的测速结果,以便适当调低预期测速条件
  84. fmt.Println("\033[33m[调试] 没有满足 下载速度下限 条件的 IP,忽略条件返回所有测速数据(方便下次测速时调整条件)。\033[0m")
  85. speedSet = utils.DownloadSpeedSet(ipSet)
  86. }
  87. // 按速度排序
  88. sort.Sort(speedSet)
  89. return
  90. }
  91. func getDialContext(ip *net.IPAddr) func(ctx context.Context, network, address string) (net.Conn, error) {
  92. var fakeSourceAddr string
  93. if isIPv4(ip.String()) {
  94. fakeSourceAddr = fmt.Sprintf("%s:%d", ip.String(), TCPPort)
  95. } else {
  96. fakeSourceAddr = fmt.Sprintf("[%s]:%d", ip.String(), TCPPort)
  97. }
  98. return func(ctx context.Context, network, address string) (net.Conn, error) {
  99. return (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr)
  100. }
  101. }
  102. // return download Speed
  103. func downloadHandler(ip *net.IPAddr) (float64, string) {
  104. client := &http.Client{
  105. Transport: &http.Transport{DialContext: getDialContext(ip)},
  106. Timeout: Timeout,
  107. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  108. if len(via) > 10 { // 限制最多重定向 10 次
  109. if utils.Debug { // 调试模式下,输出更多信息
  110. fmt.Printf("\033[31m[调试] IP: %s, 下载测速地址重定向次数过多,终止测速,下载测速地址: %s\033[0m\n", ip.String(), req.URL.String())
  111. }
  112. return http.ErrUseLastResponse
  113. }
  114. if req.Header.Get("Referer") == defaultURL { // 当使用默认下载测速地址时,重定向不携带 Referer
  115. req.Header.Del("Referer")
  116. }
  117. return nil
  118. },
  119. }
  120. req, err := http.NewRequest("GET", URL, nil)
  121. if err != nil {
  122. if utils.Debug { // 调试模式下,输出更多信息
  123. fmt.Printf("\033[31m[调试] IP: %s, 下载测速请求创建失败,错误信息: %v, 下载测速地址: %s\033[0m\n", ip.String(), err, URL)
  124. }
  125. return 0.0, ""
  126. }
  127. req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36")
  128. response, err := client.Do(req)
  129. if err != nil {
  130. if utils.Debug { // 调试模式下,输出更多信息
  131. finalURL := URL // 默认的最终 URL,这样当 response 为空时也能输出
  132. if response != nil && response.Request != nil && response.Request.URL != nil { // 如果 response 和 URL 存在,则获取最终 URL
  133. finalURL = response.Request.URL.String()
  134. }
  135. fmt.Printf("\033[31m[调试] IP: %s, 下载测速失败,错误信息: %v, 下载测速地址: %s, 最终地址(如有重定向): %s\033[0m\n", ip.String(), err, URL, finalURL)
  136. }
  137. return 0.0, ""
  138. }
  139. defer response.Body.Close()
  140. if response.StatusCode != 200 {
  141. if utils.Debug { // 调试模式下,输出更多信息
  142. fmt.Printf("\033[31m[调试] IP: %s, 下载测速终止,HTTP 状态码: %d, 下载测速地址: %s, 最终地址(如有重定向): %s\033[0m\n", ip.String(), response.StatusCode, URL, response.Request.URL.String())
  143. }
  144. return 0.0, ""
  145. }
  146. // 通过头部参数获取地区码
  147. colo := getHeaderColo(response.Header)
  148. timeStart := time.Now() // 开始时间(当前)
  149. timeEnd := timeStart.Add(Timeout) // 加上下载测速时间得到的结束时间
  150. contentLength := response.ContentLength // 文件大小
  151. buffer := make([]byte, bufferSize)
  152. var (
  153. contentRead int64 = 0
  154. timeSlice = Timeout / 100
  155. timeCounter = 1
  156. lastContentRead int64 = 0
  157. )
  158. var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
  159. e := ewma.NewMovingAverage()
  160. // 循环计算,如果文件下载完了(两者相等),则退出循环(终止测速)
  161. for contentLength != contentRead {
  162. currentTime := time.Now()
  163. if currentTime.After(nextTime) {
  164. timeCounter++
  165. nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
  166. e.Add(float64(contentRead - lastContentRead))
  167. lastContentRead = contentRead
  168. }
  169. // 如果超出下载测速时间,则退出循环(终止测速)
  170. if currentTime.After(timeEnd) {
  171. break
  172. }
  173. bufferRead, err := response.Body.Read(buffer)
  174. if err != nil {
  175. if err != io.EOF { // 如果文件下载过程中遇到报错(如 Timeout),且并不是因为文件下载完了,则退出循环(终止测速)
  176. break
  177. } else if contentLength == -1 { // 文件下载完成 且 文件大小未知,则退出循环(终止测速),例如:https://speed.cloudflare.com/__down?bytes=200000000 这样的,如果在 10 秒内就下载完成了,会导致测速结果明显偏低甚至显示为 0.00(下载速度太快时)
  178. break
  179. }
  180. // 获取上个时间片
  181. last_time_slice := timeStart.Add(timeSlice * time.Duration(timeCounter-1))
  182. // 下载数据量 / (用当前时间 - 上个时间片/ 时间片)
  183. e.Add(float64(contentRead-lastContentRead) / (float64(currentTime.Sub(last_time_slice)) / float64(timeSlice)))
  184. }
  185. contentRead += int64(bufferRead)
  186. }
  187. return e.Value() / (Timeout.Seconds() / 120), colo
  188. }