mirror of
https://github.com/XIU2/CloudflareSpeedTest.git
synced 2026-03-20 06:19:49 +08:00
新增 支持显示地区码(机场三字码,仅限 Cloudflare、AWS CloudFront,HTTPing 和 下载测速(无论是哪个测速模式)过程中都会自动获取);
新增 调试模式运行参数(-debug 方便排查下载测速过程中遇到的问题); 新增 彩色输出内容; 调整 当没找到符合速度条件的 IP 时,默认不再直接忽略条件输出所有 IP 测速结果了,而是只有在调试模式下才会输出;
This commit is contained in:
@@ -54,7 +54,7 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe
|
||||
return utils.DownloadSpeedSet(ipSet)
|
||||
}
|
||||
if len(ipSet) <= 0 { // IP数组长度(IP数量) 大于 0 时才会继续下载测速
|
||||
fmt.Println("\n[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。")
|
||||
fmt.Println("\n\033[33m[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。\033[0m")
|
||||
return
|
||||
}
|
||||
testNum := TestCount
|
||||
@@ -65,7 +65,7 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe
|
||||
TestCount = testNum
|
||||
}
|
||||
|
||||
fmt.Printf("开始下载测速(下限:%.2f MB/s, 数量:%d, 队列:%d)\n", MinSpeed, TestCount, testNum)
|
||||
fmt.Printf("\033[34m开始下载测速(下限:%.2f MB/s, 数量:%d, 队列:%d)\033[0m\n", MinSpeed, TestCount, testNum)
|
||||
// 控制 下载测速进度条 与 延迟测速进度条 长度一致(强迫症)
|
||||
bar_a := len(strconv.Itoa(len(ipSet)))
|
||||
bar_b := " "
|
||||
@@ -74,8 +74,11 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe
|
||||
}
|
||||
bar := utils.NewBar(TestCount, bar_b, "")
|
||||
for i := 0; i < testNum; i++ {
|
||||
speed := downloadHandler(ipSet[i].IP)
|
||||
speed, colo := downloadHandler(ipSet[i].IP)
|
||||
ipSet[i].DownloadSpeed = speed
|
||||
if ipSet[i].Colo == "" { // 只有当 Colo 是空的时候,才写入,否则代表之前是 httping 测速并获取过了
|
||||
ipSet[i].Colo = colo
|
||||
}
|
||||
// 在每个 IP 下载测速后,以 [下载速度下限] 条件过滤结果
|
||||
if speed >= MinSpeed*1024*1024 {
|
||||
bar.Grow(1, "")
|
||||
@@ -86,7 +89,8 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe
|
||||
}
|
||||
}
|
||||
bar.Done()
|
||||
if len(speedSet) == 0 { // 没有符合速度限制的数据,返回所有测试数据
|
||||
if utils.Debug && len(speedSet) == 0 { // 调试模式下,没有满足速度限制的数据,返回所有测速数据供用户查看当前的测速结果,以便适当调低预期测速条件
|
||||
fmt.Println("\033[33m[调试] 没有满足 下载速度下限 条件的 IP,忽略条件返回所有测速数据(方便下次测速时调整条件)。\033[0m")
|
||||
speedSet = utils.DownloadSpeedSet(ipSet)
|
||||
}
|
||||
// 按速度排序
|
||||
@@ -107,12 +111,15 @@ func getDialContext(ip *net.IPAddr) func(ctx context.Context, network, address s
|
||||
}
|
||||
|
||||
// return download Speed
|
||||
func downloadHandler(ip *net.IPAddr) float64 {
|
||||
func downloadHandler(ip *net.IPAddr) (float64, string) {
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{DialContext: getDialContext(ip)},
|
||||
Timeout: Timeout,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) > 10 { // 限制最多重定向 10 次
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
fmt.Printf("\033[31m[调试] IP: %s, 下载测速地址重定向次数过多,终止测速,URL: %s\033[0m\n", ip.String(), req.URL.String())
|
||||
}
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
if req.Header.Get("Referer") == defaultURL { // 当使用默认下载测速地址时,重定向不携带 Referer
|
||||
@@ -123,19 +130,31 @@ func downloadHandler(ip *net.IPAddr) float64 {
|
||||
}
|
||||
req, err := http.NewRequest("GET", URL, nil)
|
||||
if err != nil {
|
||||
return 0.0
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
fmt.Printf("\033[31m[调试] IP: %s, 下载测速请求创建失败,错误信息: %v, URL: %s\033[0m\n", ip.String(), err, URL)
|
||||
}
|
||||
return 0.0, ""
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0.0
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
fmt.Printf("\033[31m[调试] IP: %s, 下载测速失败,错误信息: %v, URL: , 最终URL: %s%s\033[0m\n", ip.String(), err, URL, response.Request.URL.String())
|
||||
}
|
||||
return 0.0, ""
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return 0.0
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
fmt.Printf("\033[31m[调试] IP: %s, 下载测速终止,HTTP 状态码: %d, URL: %s, 最终URL: %s\033[0m\n", ip.String(), response.StatusCode, URL, response.Request.URL.String())
|
||||
}
|
||||
return 0.0, ""
|
||||
}
|
||||
// 通过头部 Server 值判断是 Cloudflare 还是 AWS CloudFront 并设置 cfRay 为各自的机场地区码完整内容
|
||||
colo := getHeaderColo(response.Header)
|
||||
|
||||
timeStart := time.Now() // 开始时间(当前)
|
||||
timeEnd := timeStart.Add(Timeout) // 加上下载测速时间得到的结束时间
|
||||
|
||||
@@ -179,5 +198,5 @@ func downloadHandler(ip *net.IPAddr) float64 {
|
||||
}
|
||||
contentRead += int64(bufferRead)
|
||||
}
|
||||
return e.Value() / (Timeout.Seconds() / 120)
|
||||
return e.Value() / (Timeout.Seconds() / 120), colo
|
||||
}
|
||||
|
||||
102
task/httping.go
102
task/httping.go
@@ -18,11 +18,11 @@ var (
|
||||
HttpingStatusCode int
|
||||
HttpingCFColo string
|
||||
HttpingCFColomap *sync.Map
|
||||
OutRegexp = regexp.MustCompile(`[A-Z]{3}`)
|
||||
ColoRegexp = regexp.MustCompile(`[A-Z]{3}`)
|
||||
)
|
||||
|
||||
// pingReceived pingTotalTime
|
||||
func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration) {
|
||||
func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration, string) {
|
||||
hc := http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
Transport: &http.Transport{
|
||||
@@ -35,84 +35,79 @@ func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration) {
|
||||
}
|
||||
|
||||
// 先访问一次获得 HTTP 状态码 及 Cloudflare Colo
|
||||
var colo string
|
||||
{
|
||||
requ, err := http.NewRequest(http.MethodHead, URL, nil)
|
||||
request, err := http.NewRequest(http.MethodHead, URL, nil)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
return 0, 0, ""
|
||||
}
|
||||
requ.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")
|
||||
resp, err := hc.Do(requ)
|
||||
request.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")
|
||||
response, err := hc.Do(request)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
return 0, 0, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer response.Body.Close()
|
||||
|
||||
//fmt.Println("IP:", ip, "StatusCode:", resp.StatusCode, resp.Request.URL)
|
||||
//fmt.Println("IP:", ip, "StatusCode:", response.StatusCode, response.Request.URL)
|
||||
// 如果未指定的 HTTP 状态码,或指定的状态码不合规,则默认只认为 200、301、302 才算 HTTPing 通过
|
||||
if HttpingStatusCode == 0 || HttpingStatusCode < 100 && HttpingStatusCode > 599 {
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 301 && resp.StatusCode != 302 {
|
||||
return 0, 0
|
||||
if response.StatusCode != 200 && response.StatusCode != 301 && response.StatusCode != 302 {
|
||||
return 0, 0, ""
|
||||
}
|
||||
} else {
|
||||
if resp.StatusCode != HttpingStatusCode {
|
||||
return 0, 0
|
||||
if response.StatusCode != HttpingStatusCode {
|
||||
return 0, 0, ""
|
||||
}
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
io.Copy(io.Discard, response.Body)
|
||||
|
||||
// 只有指定了地区才匹配机场三字码
|
||||
// 通过头部 Server 值判断是 Cloudflare 还是 AWS CloudFront 并设置 cfRay 为各自的机场地区码完整内容
|
||||
colo = getHeaderColo(response.Header)
|
||||
|
||||
// 只有指定了地区才匹配机场地区码
|
||||
if HttpingCFColo != "" {
|
||||
// 通过头部 Server 值判断是 Cloudflare 还是 AWS CloudFront 并设置 cfRay 为各自的机场三字码完整内容
|
||||
cfRay := func() string {
|
||||
if resp.Header.Get("Server") == "cloudflare" {
|
||||
return resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC
|
||||
}
|
||||
return resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1
|
||||
}()
|
||||
colo := p.getColo(cfRay)
|
||||
if colo == "" { // 没有匹配到三字码或不符合指定地区则直接结束该 IP 测试
|
||||
return 0, 0
|
||||
// 判断是否匹配指定的地区码
|
||||
colo = p.filterColo(colo)
|
||||
if colo == "" { // 没有匹配到地区码或不符合指定地区则直接结束该 IP 测试
|
||||
return 0, 0, ""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 循环测速计算延迟
|
||||
success := 0
|
||||
var delay time.Duration
|
||||
for i := 0; i < PingTimes; i++ {
|
||||
requ, err := http.NewRequest(http.MethodHead, URL, nil)
|
||||
request, err := http.NewRequest(http.MethodHead, URL, nil)
|
||||
if err != nil {
|
||||
log.Fatal("意外的错误,情报告:", err)
|
||||
return 0, 0
|
||||
return 0, 0, ""
|
||||
}
|
||||
requ.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")
|
||||
request.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")
|
||||
if i == PingTimes-1 {
|
||||
requ.Header.Set("Connection", "close")
|
||||
request.Header.Set("Connection", "close")
|
||||
}
|
||||
startTime := time.Now()
|
||||
resp, err := hc.Do(requ)
|
||||
response, err := hc.Do(request)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
success++
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
io.Copy(io.Discard, response.Body)
|
||||
_ = response.Body.Close()
|
||||
duration := time.Since(startTime)
|
||||
delay += duration
|
||||
|
||||
}
|
||||
|
||||
return success, delay
|
||||
|
||||
return success, delay, colo
|
||||
}
|
||||
|
||||
func MapColoMap() *sync.Map {
|
||||
if HttpingCFColo == "" {
|
||||
return nil
|
||||
}
|
||||
// 将参数指定的地区三字码转为大写并格式化
|
||||
// 将 -cfcolo 参数指定的地区地区码转为大写并格式化
|
||||
colos := strings.Split(strings.ToUpper(HttpingCFColo), ",")
|
||||
colomap := &sync.Map{}
|
||||
for _, colo := range colos {
|
||||
@@ -121,21 +116,36 @@ func MapColoMap() *sync.Map {
|
||||
return colomap
|
||||
}
|
||||
|
||||
func (p *Ping) getColo(b string) string {
|
||||
if b == "" {
|
||||
// 从响应头中获取 地区码 值
|
||||
func getHeaderColo(header http.Header) (colo string) {
|
||||
// 如果是 Cloudflare 的服务器,则获取 CF-RAY 头部
|
||||
if header.Get("Server") == "cloudflare" {
|
||||
colo = header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC
|
||||
} else { // 如果是 AWS CloudFront 的服务器,则获取 X-Amz-Cf-Pop 头部
|
||||
colo = header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1
|
||||
}
|
||||
|
||||
// 如果没有获取到头部信息,说明不是 Cloudflare 和 AWS CloudFront,则直接返回空字符串
|
||||
if colo == "" {
|
||||
return ""
|
||||
}
|
||||
// 正则匹配并返回 机场三字码
|
||||
out := OutRegexp.FindString(b)
|
||||
// 正则匹配并返回 机场地区码
|
||||
return ColoRegexp.FindString(colo)
|
||||
}
|
||||
|
||||
// 处理地区码
|
||||
func (p *Ping) filterColo(colo string) string {
|
||||
if colo == "" {
|
||||
return ""
|
||||
}
|
||||
// 如果没有指定 -cfcolo 参数,则直接返回
|
||||
if HttpingCFColomap == nil {
|
||||
return out
|
||||
return colo
|
||||
}
|
||||
// 匹配 机场三字码 是否为指定的地区
|
||||
_, ok := HttpingCFColomap.Load(out)
|
||||
// 匹配 机场地区码 是否为指定的地区
|
||||
_, ok := HttpingCFColomap.Load(colo)
|
||||
if ok {
|
||||
return out
|
||||
return colo
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ func (p *Ping) Run() utils.PingDelaySet {
|
||||
return p.csv
|
||||
}
|
||||
if Httping {
|
||||
fmt.Printf("开始延迟测速(模式:HTTP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate)
|
||||
fmt.Printf("\033[34m开始延迟测速(模式:HTTP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\033[0m\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate)
|
||||
} else {
|
||||
fmt.Printf("开始延迟测速(模式:TCP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate)
|
||||
fmt.Printf("\033[34m开始延迟测速(模式:TCP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\033[0m\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate)
|
||||
}
|
||||
for _, ip := range p.ips {
|
||||
p.wg.Add(1)
|
||||
@@ -104,11 +104,12 @@ func (p *Ping) tcping(ip *net.IPAddr) (bool, time.Duration) {
|
||||
}
|
||||
|
||||
// pingReceived pingTotalTime
|
||||
func (p *Ping) checkConnection(ip *net.IPAddr) (recv int, totalDelay time.Duration) {
|
||||
func (p *Ping) checkConnection(ip *net.IPAddr) (recv int, totalDelay time.Duration, colo string) {
|
||||
if Httping {
|
||||
recv, totalDelay = p.httping(ip)
|
||||
recv, totalDelay, colo = p.httping(ip)
|
||||
return
|
||||
}
|
||||
colo = "" // TCPing 不获取 colo
|
||||
for i := 0; i < PingTimes; i++ {
|
||||
if ok, delay := p.tcping(ip); ok {
|
||||
recv++
|
||||
@@ -128,7 +129,7 @@ func (p *Ping) appendIPData(data *utils.PingData) {
|
||||
|
||||
// handle tcping
|
||||
func (p *Ping) tcpingHandler(ip *net.IPAddr) {
|
||||
recv, totalDlay := p.checkConnection(ip)
|
||||
recv, totalDlay, colo := p.checkConnection(ip)
|
||||
nowAble := len(p.csv)
|
||||
if recv != 0 {
|
||||
nowAble++
|
||||
@@ -142,6 +143,7 @@ func (p *Ping) tcpingHandler(ip *net.IPAddr) {
|
||||
Sended: PingTimes,
|
||||
Received: recv,
|
||||
Delay: totalDlay / time.Duration(recv),
|
||||
Colo: colo,
|
||||
}
|
||||
p.appendIPData(data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user