完1
This commit is contained in:
@@ -1,45 +1,142 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func handleDns(c *gin.Context, url string, params map[string]interface{}) {
|
||||
// 执行DNS查询
|
||||
start := time.Now()
|
||||
ips, err := net.LookupIP(url)
|
||||
lookupTime := time.Since(start).Milliseconds()
|
||||
// 获取seq参数
|
||||
seq := ""
|
||||
if seqVal, ok := params["seq"].(string); ok {
|
||||
seq = seqVal
|
||||
}
|
||||
|
||||
// 获取dig类型参数
|
||||
digType := ""
|
||||
if dt, ok := params["dt"].(string); ok {
|
||||
digType = dt
|
||||
}
|
||||
|
||||
// 获取DNS服务器参数
|
||||
dnsServer := ""
|
||||
if ds, ok := params["ds"].(string); ok {
|
||||
dnsServer = ds
|
||||
}
|
||||
|
||||
// 解析URL,提取hostname
|
||||
hostname := url
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
|
||||
parts := strings.Split(url, "//")
|
||||
if len(parts) > 1 {
|
||||
hostParts := strings.Split(parts[1], "/")
|
||||
hostname = hostParts[0]
|
||||
if idx := strings.Index(hostname, ":"); idx != -1 {
|
||||
hostname = hostname[:idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 准备结果
|
||||
result := map[string]interface{}{
|
||||
"seq": seq,
|
||||
"type": "ceDns",
|
||||
"requrl": hostname,
|
||||
"ips": []interface{}{},
|
||||
}
|
||||
|
||||
// 构建dig命令
|
||||
args := []string{hostname}
|
||||
if digType != "" {
|
||||
args = append([]string{"-t", digType}, args...)
|
||||
}
|
||||
if dnsServer != "" {
|
||||
args = append([]string{"@" + dnsServer}, args...)
|
||||
}
|
||||
|
||||
cmd := exec.Command("dig", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
outputStr := string(output)
|
||||
|
||||
// 编码完整输出为base64(header字段)
|
||||
result["header"] = base64.StdEncoding.EncodeToString([]byte(outputStr))
|
||||
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"type": "ceDns",
|
||||
"url": url,
|
||||
"error": err.Error(),
|
||||
})
|
||||
result["error"] = err.Error()
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化IP列表
|
||||
// 解析dig输出
|
||||
lines := strings.Split(outputStr, "\n")
|
||||
inAnswerSection := false
|
||||
ipList := make([]map[string]interface{}, 0)
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.Contains(line, "ANSWER SECTION") {
|
||||
inAnswerSection = true
|
||||
continue
|
||||
}
|
||||
if inAnswerSection {
|
||||
if line == "" {
|
||||
break
|
||||
}
|
||||
// 解析dig输出行,格式如:example.com. 300 IN A 192.168.1.1
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 5 {
|
||||
recordType := parts[3] // IN
|
||||
recordClass := parts[4] // A, AAAA, CNAME等
|
||||
recordValue := ""
|
||||
if len(parts) > 5 {
|
||||
recordValue = strings.Join(parts[5:], " ")
|
||||
}
|
||||
|
||||
// 只处理A和AAAA记录
|
||||
if recordClass == "A" || recordClass == "AAAA" {
|
||||
ipItem := map[string]interface{}{
|
||||
"url": parts[0],
|
||||
"type": recordClass,
|
||||
"ip": recordValue,
|
||||
}
|
||||
ipList = append(ipList, ipItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有从dig输出解析到IP,尝试使用net.LookupIP
|
||||
if len(ipList) == 0 {
|
||||
start := time.Now()
|
||||
ips, err := net.LookupIP(hostname)
|
||||
lookupTime := time.Since(start)
|
||||
|
||||
if err == nil {
|
||||
for _, ip := range ips {
|
||||
ipType := "A"
|
||||
if ip.To4() == nil {
|
||||
ipType = "AAAA"
|
||||
}
|
||||
ipList = append(ipList, map[string]interface{}{
|
||||
ipItem := map[string]interface{}{
|
||||
"url": hostname,
|
||||
"type": ipType,
|
||||
"ip": ip.String(),
|
||||
})
|
||||
}
|
||||
ipList = append(ipList, ipItem)
|
||||
}
|
||||
// 更新header,包含lookup时间信息
|
||||
lookupInfo := fmt.Sprintf("Lookup time: %v\n", lookupTime)
|
||||
result["header"] = base64.StdEncoding.EncodeToString([]byte(outputStr + lookupInfo))
|
||||
} else {
|
||||
result["error"] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"type": "ceDns",
|
||||
"url": url,
|
||||
"ips": ipList,
|
||||
"lookup_time": lookupTime,
|
||||
})
|
||||
result["ips"] = ipList
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,496 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func handleGet(c *gin.Context, url string, params map[string]interface{}) {
|
||||
// TODO: 实现HTTP GET测试
|
||||
// 这里先返回一个简单的响应
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
// timingTransport 用于跟踪HTTP请求的各个阶段时间
|
||||
type timingTransport struct {
|
||||
transport http.RoundTripper
|
||||
nameLookup time.Duration
|
||||
connect time.Duration
|
||||
startTransfer time.Duration
|
||||
total time.Duration
|
||||
primaryIP string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newTimingTransport() *timingTransport {
|
||||
return &timingTransport{
|
||||
transport: &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *timingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
host := req.URL.Hostname()
|
||||
port := req.URL.Port()
|
||||
if port == "" {
|
||||
if req.URL.Scheme == "https" {
|
||||
port = "443"
|
||||
} else {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
|
||||
// DNS查询时间
|
||||
dnsStart := time.Now()
|
||||
ips, err := net.LookupIP(host)
|
||||
dnsTime := time.Since(dnsStart)
|
||||
|
||||
t.mu.Lock()
|
||||
t.nameLookup = dnsTime
|
||||
if len(ips) > 0 {
|
||||
// 优先使用IPv4
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil {
|
||||
t.primaryIP = ip.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.primaryIP == "" && len(ips) > 0 {
|
||||
t.primaryIP = ips[0].String()
|
||||
}
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TCP连接时间(如果已知IP)
|
||||
var connectTime time.Duration
|
||||
if t.primaryIP != "" {
|
||||
connectStart := time.Now()
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(t.primaryIP, port), 5*time.Second)
|
||||
connectTime = time.Since(connectStart)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 执行HTTP请求
|
||||
httpStart := time.Now()
|
||||
resp, err := t.transport.RoundTrip(req)
|
||||
httpTime := time.Since(httpStart)
|
||||
totalTime := time.Since(start)
|
||||
|
||||
t.mu.Lock()
|
||||
if connectTime > 0 {
|
||||
t.connect = connectTime
|
||||
} else {
|
||||
// 如果没有单独测量连接时间,使用HTTP请求时间的一部分
|
||||
t.connect = httpTime / 3
|
||||
}
|
||||
t.total = totalTime
|
||||
if resp != nil {
|
||||
// 首字节时间 = DNS + 连接 + HTTP请求开始到响应头的时间
|
||||
t.startTransfer = dnsTime + connectTime + (httpTime / 2)
|
||||
if t.startTransfer > totalTime {
|
||||
t.startTransfer = totalTime * 2 / 3
|
||||
}
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func handleGet(c *gin.Context, urlStr string, params map[string]interface{}) {
|
||||
// 获取seq参数
|
||||
seq := ""
|
||||
if seqVal, ok := params["seq"].(string); ok {
|
||||
seq = seqVal
|
||||
}
|
||||
|
||||
// 解析URL
|
||||
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
|
||||
urlStr = "http://" + urlStr
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"seq": seq,
|
||||
"type": "ceGet",
|
||||
"url": url,
|
||||
"statuscode": 200,
|
||||
"totaltime": time.Since(time.Now()).Milliseconds(),
|
||||
"response": "OK",
|
||||
"url": urlStr,
|
||||
"error": "URL格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func handlePost(c *gin.Context, url string, params map[string]interface{}) {
|
||||
// TODO: 实现HTTP POST测试
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
// 准备结果
|
||||
result := map[string]interface{}{
|
||||
"seq": seq,
|
||||
"type": "ceGet",
|
||||
"url": urlStr,
|
||||
}
|
||||
|
||||
// 创建自定义Transport用于时间跟踪
|
||||
timingTransport := newTimingTransport()
|
||||
|
||||
// 创建HTTP客户端
|
||||
client := &http.Client{
|
||||
Transport: timingTransport,
|
||||
Timeout: 15 * time.Second,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
// 跟随重定向,最多20次
|
||||
if len(via) >= 20 {
|
||||
return fmt.Errorf("重定向次数过多")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
req, err := http.NewRequest("GET", urlStr, nil)
|
||||
if err != nil {
|
||||
result["error"] = err.Error()
|
||||
result["ip"] = "访问失败"
|
||||
result["totaltime"] = "*"
|
||||
result["downtime"] = "*"
|
||||
result["downsize"] = "*"
|
||||
result["downspeed"] = "*"
|
||||
result["firstbytetime"] = "*"
|
||||
result["conntime"] = "*"
|
||||
result["size"] = "*"
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置User-Agent
|
||||
userAgents := []string{
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) AppleWebKit/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.1; EML-L29 Build/HUAWEIEML-L29) AppleWebKit/537.36",
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgents[0])
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
|
||||
// 执行请求
|
||||
startTime := time.Now()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// 错误处理
|
||||
errMsg := err.Error()
|
||||
if strings.Contains(errMsg, "no such host") {
|
||||
result["ip"] = "域名无法解析"
|
||||
} else if strings.Contains(errMsg, "connection refused") || strings.Contains(errMsg, "timeout") {
|
||||
result["ip"] = "无法连接"
|
||||
} else if strings.Contains(errMsg, "deadline exceeded") || strings.Contains(errMsg, "timeout") {
|
||||
result["ip"] = "访问超时"
|
||||
} else {
|
||||
result["ip"] = "访问失败"
|
||||
}
|
||||
result["error"] = errMsg
|
||||
result["totaltime"] = "*"
|
||||
result["downtime"] = "*"
|
||||
result["downsize"] = "*"
|
||||
result["downspeed"] = "*"
|
||||
result["firstbytetime"] = "*"
|
||||
result["conntime"] = "*"
|
||||
result["size"] = "*"
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 获取时间信息
|
||||
timingTransport.mu.Lock()
|
||||
nameLookupTime := timingTransport.nameLookup
|
||||
connectTime := timingTransport.connect
|
||||
firstByteTime := timingTransport.startTransfer
|
||||
totalTime := timingTransport.total
|
||||
primaryIP := timingTransport.primaryIP
|
||||
timingTransport.mu.Unlock()
|
||||
|
||||
// 如果primaryIP为空,尝试从URL获取
|
||||
if primaryIP == "" {
|
||||
host := parsedURL.Hostname()
|
||||
if net.ParseIP(host) != nil {
|
||||
primaryIP = host
|
||||
}
|
||||
}
|
||||
|
||||
// 构建header字符串(base64编码)
|
||||
headerBuilder := strings.Builder{}
|
||||
headerBuilder.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))
|
||||
for k, v := range resp.Header {
|
||||
headerBuilder.WriteString(fmt.Sprintf("%s: %s\r\n", k, strings.Join(v, ", ")))
|
||||
}
|
||||
headerBuilder.WriteString("\r\n")
|
||||
headerBytes := []byte(headerBuilder.String())
|
||||
result["header"] = base64.StdEncoding.EncodeToString(headerBytes)
|
||||
|
||||
// 读取响应体(限制大小)
|
||||
bodyReader := io.LimitReader(resp.Body, 1024*1024) // 限制1MB
|
||||
bodyStartTime := time.Now()
|
||||
body, err := io.ReadAll(bodyReader)
|
||||
bodyReadTime := time.Now().Sub(bodyStartTime)
|
||||
if err != nil && err != io.EOF {
|
||||
result["error"] = err.Error()
|
||||
}
|
||||
|
||||
downloadSize := int64(len(body))
|
||||
statusCode := resp.StatusCode
|
||||
|
||||
// 如果首字节时间为0,使用连接时间
|
||||
if firstByteTime == 0 {
|
||||
firstByteTime = connectTime
|
||||
}
|
||||
|
||||
// 总时间 = 实际请求时间
|
||||
if totalTime == 0 {
|
||||
totalTime = time.Since(startTime)
|
||||
}
|
||||
|
||||
// 计算下载时间(使用实际读取时间)
|
||||
downloadTime := bodyReadTime
|
||||
if downloadTime <= 0 {
|
||||
downloadTime = totalTime - firstByteTime
|
||||
if downloadTime < 0 {
|
||||
downloadTime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 计算下载速度(字节/秒)
|
||||
var downloadSpeed float64
|
||||
if downloadTime > 0 {
|
||||
downloadSpeed = float64(downloadSize) / downloadTime.Seconds()
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
sizeStr := formatSize(downloadSize)
|
||||
downSizeStr := formatSizeKB(downloadSize)
|
||||
|
||||
// 填充结果
|
||||
result["ip"] = primaryIP
|
||||
result["statuscode"] = statusCode
|
||||
result["nslookuptime"] = roundFloat(nameLookupTime.Seconds(), 3)
|
||||
result["conntime"] = roundFloat(connectTime.Seconds(), 3)
|
||||
result["firstbytetime"] = roundFloat(firstByteTime.Seconds(), 3)
|
||||
result["totaltime"] = roundFloat(totalTime.Seconds(), 3)
|
||||
result["downtime"] = roundFloat(downloadTime.Seconds(), 6)
|
||||
result["downsize"] = downSizeStr
|
||||
result["downspeed"] = downloadSpeed
|
||||
result["size"] = sizeStr
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func handlePost(c *gin.Context, urlStr string, params map[string]interface{}) {
|
||||
// 获取seq参数
|
||||
seq := ""
|
||||
if seqVal, ok := params["seq"].(string); ok {
|
||||
seq = seqVal
|
||||
}
|
||||
|
||||
// 解析URL
|
||||
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
|
||||
urlStr = "http://" + urlStr
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"seq": seq,
|
||||
"type": "cePost",
|
||||
"url": url,
|
||||
"statuscode": 200,
|
||||
"totaltime": time.Since(time.Now()).Milliseconds(),
|
||||
"response": "OK",
|
||||
"url": urlStr,
|
||||
"error": "URL格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 准备结果
|
||||
result := map[string]interface{}{
|
||||
"seq": seq,
|
||||
"type": "cePost",
|
||||
"url": urlStr,
|
||||
}
|
||||
|
||||
// 获取POST数据
|
||||
postData := "abc=123"
|
||||
if data, ok := params["data"].(string); ok && data != "" {
|
||||
postData = data
|
||||
}
|
||||
|
||||
// 创建自定义Transport用于时间跟踪
|
||||
timingTransport := newTimingTransport()
|
||||
|
||||
// 创建HTTP客户端
|
||||
client := &http.Client{
|
||||
Transport: timingTransport,
|
||||
Timeout: 15 * time.Second,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 20 {
|
||||
return fmt.Errorf("重定向次数过多")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// 创建POST请求
|
||||
req, err := http.NewRequest("POST", urlStr, strings.NewReader(postData))
|
||||
if err != nil {
|
||||
result["error"] = err.Error()
|
||||
result["ip"] = "访问失败"
|
||||
result["totaltime"] = "*"
|
||||
result["downtime"] = "*"
|
||||
result["downsize"] = "*"
|
||||
result["downspeed"] = "*"
|
||||
result["firstbytetime"] = "*"
|
||||
result["conntime"] = "*"
|
||||
result["size"] = "*"
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38")
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
|
||||
// 执行请求
|
||||
startTime := time.Now()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
errMsg := err.Error()
|
||||
if strings.Contains(errMsg, "no such host") {
|
||||
result["ip"] = "域名无法解析"
|
||||
} else if strings.Contains(errMsg, "connection refused") || strings.Contains(errMsg, "timeout") {
|
||||
result["ip"] = "无法连接"
|
||||
} else if strings.Contains(errMsg, "deadline exceeded") || strings.Contains(errMsg, "timeout") {
|
||||
result["ip"] = "访问超时"
|
||||
} else {
|
||||
result["ip"] = "访问失败"
|
||||
}
|
||||
result["error"] = errMsg
|
||||
result["totaltime"] = "*"
|
||||
result["downtime"] = "*"
|
||||
result["downsize"] = "*"
|
||||
result["downspeed"] = "*"
|
||||
result["firstbytetime"] = "*"
|
||||
result["conntime"] = "*"
|
||||
result["size"] = "*"
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 获取时间信息
|
||||
timingTransport.mu.Lock()
|
||||
nameLookupTime := timingTransport.nameLookup
|
||||
connectTime := timingTransport.connect
|
||||
firstByteTime := timingTransport.startTransfer
|
||||
totalTime := timingTransport.total
|
||||
primaryIP := timingTransport.primaryIP
|
||||
timingTransport.mu.Unlock()
|
||||
|
||||
// 如果primaryIP为空,尝试从URL获取
|
||||
if primaryIP == "" {
|
||||
host := parsedURL.Hostname()
|
||||
if net.ParseIP(host) != nil {
|
||||
primaryIP = host
|
||||
}
|
||||
}
|
||||
|
||||
// 构建header字符串(base64编码)
|
||||
headerBuilder := strings.Builder{}
|
||||
headerBuilder.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))
|
||||
for k, v := range resp.Header {
|
||||
headerBuilder.WriteString(fmt.Sprintf("%s: %s\r\n", k, strings.Join(v, ", ")))
|
||||
}
|
||||
headerBuilder.WriteString("\r\n")
|
||||
headerBytes := []byte(headerBuilder.String())
|
||||
result["header"] = base64.StdEncoding.EncodeToString(headerBytes)
|
||||
|
||||
// 读取响应体(限制大小)
|
||||
bodyReader := io.LimitReader(resp.Body, 1024*1024)
|
||||
bodyStartTime := time.Now()
|
||||
body, err := io.ReadAll(bodyReader)
|
||||
bodyReadTime := time.Since(bodyStartTime)
|
||||
if err != nil && err != io.EOF {
|
||||
result["error"] = err.Error()
|
||||
}
|
||||
|
||||
downloadSize := int64(len(body))
|
||||
statusCode := resp.StatusCode
|
||||
|
||||
// 如果首字节时间为0,使用连接时间
|
||||
if firstByteTime == 0 {
|
||||
firstByteTime = connectTime
|
||||
}
|
||||
|
||||
// 总时间 = 实际请求时间
|
||||
if totalTime == 0 {
|
||||
totalTime = time.Since(startTime)
|
||||
}
|
||||
|
||||
// 计算下载时间(使用实际读取时间)
|
||||
downloadTime := bodyReadTime
|
||||
if downloadTime <= 0 {
|
||||
downloadTime = totalTime - firstByteTime
|
||||
if downloadTime < 0 {
|
||||
downloadTime = 0
|
||||
}
|
||||
}
|
||||
|
||||
var downloadSpeed float64
|
||||
if downloadTime > 0 {
|
||||
downloadSpeed = float64(downloadSize) / downloadTime.Seconds()
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
sizeStr := formatSize(downloadSize)
|
||||
downSizeStr := formatSizeKB(downloadSize)
|
||||
|
||||
// 填充结果
|
||||
result["ip"] = primaryIP
|
||||
result["statuscode"] = statusCode
|
||||
result["nslookuptime"] = roundFloat(nameLookupTime.Seconds(), 3)
|
||||
result["conntime"] = roundFloat(connectTime.Seconds(), 3)
|
||||
result["firstbytetime"] = roundFloat(firstByteTime.Seconds(), 3)
|
||||
result["totaltime"] = roundFloat(totalTime.Seconds(), 3)
|
||||
result["downtime"] = roundFloat(downloadTime.Seconds(), 6)
|
||||
result["downsize"] = downSizeStr
|
||||
result["downspeed"] = downloadSpeed
|
||||
result["size"] = sizeStr
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func roundFloat(val float64, precision int) float64 {
|
||||
multiplier := 1.0
|
||||
for i := 0; i < precision; i++ {
|
||||
multiplier *= 10
|
||||
}
|
||||
return float64(int(val*multiplier+0.5)) / multiplier
|
||||
}
|
||||
|
||||
func formatSize(bytes int64) string {
|
||||
if bytes < 1024 {
|
||||
return fmt.Sprintf("%dB", bytes)
|
||||
}
|
||||
kb := float64(bytes) / 1024
|
||||
if kb < 1024 {
|
||||
return fmt.Sprintf("%.3fKB", kb)
|
||||
}
|
||||
mb := kb / 1024
|
||||
return fmt.Sprintf("%.3fMB", mb)
|
||||
}
|
||||
|
||||
func formatSizeKB(bytes int64) string {
|
||||
kb := float64(bytes) / 1024
|
||||
return fmt.Sprintf("%.3fKB", kb)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -10,47 +12,114 @@ import (
|
||||
)
|
||||
|
||||
func handlePing(c *gin.Context, url string, params map[string]interface{}) {
|
||||
// 获取seq参数
|
||||
seq := ""
|
||||
if seqVal, ok := params["seq"].(string); ok {
|
||||
seq = seqVal
|
||||
}
|
||||
|
||||
// 解析URL,提取hostname
|
||||
hostname := url
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
|
||||
// 从URL中提取hostname
|
||||
parts := strings.Split(url, "//")
|
||||
if len(parts) > 1 {
|
||||
hostParts := strings.Split(parts[1], "/")
|
||||
hostname = hostParts[0]
|
||||
// 移除端口号
|
||||
if idx := strings.Index(hostname, ":"); idx != -1 {
|
||||
hostname = hostname[:idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行ping命令
|
||||
cmd := exec.Command("ping", "-c", "4", url)
|
||||
cmd := exec.Command("ping", "-c", "10", "-i", "0.5", hostname)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"type": "cePing",
|
||||
"url": url,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
outputStr := string(output)
|
||||
|
||||
// 解析ping输出
|
||||
result := parsePingOutput(string(output), url)
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func parsePingOutput(output, url string) map[string]interface{} {
|
||||
// 准备结果
|
||||
result := map[string]interface{}{
|
||||
"seq": seq,
|
||||
"type": "cePing",
|
||||
"url": url,
|
||||
"ip": "",
|
||||
}
|
||||
|
||||
// 解析IP地址
|
||||
lines := strings.Split(output, "\n")
|
||||
// 编码完整输出为base64(header字段)
|
||||
result["header"] = base64.StdEncoding.EncodeToString([]byte(outputStr))
|
||||
|
||||
if err != nil {
|
||||
result["error"] = err.Error()
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析ping输出
|
||||
lines := strings.Split(outputStr, "\n")
|
||||
|
||||
// 解析IP地址(从PING行)
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "PING") {
|
||||
// 提取IP地址
|
||||
// 提取IP地址,格式如:PING example.com (192.168.1.1) 56(84) bytes of data.
|
||||
re := regexp.MustCompile(`\(([0-9.]+)\)`)
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if len(matches) > 1 {
|
||||
result["ip"] = matches[1]
|
||||
} else {
|
||||
// 尝试直接解析IP
|
||||
parts := strings.Fields(line)
|
||||
for _, part := range parts {
|
||||
if net.ParseIP(part) != nil {
|
||||
if ip := net.ParseIP(part); ip != nil {
|
||||
result["ip"] = part
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 解析包大小(bytes字段)
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "bytes of data") {
|
||||
// 提取bytes,格式如:64 bytes from ...
|
||||
re := regexp.MustCompile(`(\d+)\s+bytes`)
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if len(matches) > 1 {
|
||||
result["bytes"] = matches[1]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 解析统计信息
|
||||
for _, line := range lines {
|
||||
// 解析丢包率和包统计
|
||||
if strings.Contains(line, "packets transmitted") {
|
||||
// 解析丢包率
|
||||
// 格式如:10 packets transmitted, 10 received, 0% packet loss
|
||||
re := regexp.MustCompile(`(\d+)\s+packets\s+transmitted[,\s]+(\d+)\s+received[,\s]+(\d+(?:\.\d+)?)%`)
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if len(matches) >= 4 {
|
||||
result["packets_total"] = matches[1]
|
||||
result["packets_recv"] = matches[2]
|
||||
if loss, err := strconv.ParseFloat(matches[3], 64); err == nil {
|
||||
result["packets_losrat"] = loss
|
||||
}
|
||||
} else {
|
||||
// 备用解析方式
|
||||
parts := strings.Fields(line)
|
||||
for i, part := range parts {
|
||||
if part == "packets" && i+1 < len(parts) {
|
||||
if total, err := strconv.Atoi(parts[i-1]); err == nil {
|
||||
result["packets_total"] = strconv.Itoa(total)
|
||||
}
|
||||
}
|
||||
if part == "received" && i-1 >= 0 {
|
||||
if recv, err := strconv.Atoi(parts[i-1]); err == nil {
|
||||
result["packets_recv"] = strconv.Itoa(recv)
|
||||
}
|
||||
}
|
||||
if part == "packet" && i+2 < len(parts) {
|
||||
if loss, err := strconv.ParseFloat(strings.Trim(parts[i+1], "%"), 64); err == nil {
|
||||
result["packets_losrat"] = loss
|
||||
@@ -58,8 +127,25 @@ func parsePingOutput(output, url string) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "min/avg/max") {
|
||||
// 解析延迟统计
|
||||
}
|
||||
|
||||
// 解析时间统计(min/avg/max)
|
||||
if strings.Contains(line, "min/avg/max") || strings.Contains(line, "rtt min/avg/max") {
|
||||
// 格式如:rtt min/avg/max/mdev = 10.123/12.456/15.789/2.345 ms
|
||||
re := regexp.MustCompile(`=\s*([0-9.]+)/([0-9.]+)/([0-9.]+)`)
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if len(matches) >= 4 {
|
||||
if min, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||
result["time_min"] = min
|
||||
}
|
||||
if avg, err := strconv.ParseFloat(matches[2], 64); err == nil {
|
||||
result["time_avg"] = avg
|
||||
}
|
||||
if max, err := strconv.ParseFloat(matches[3], 64); err == nil {
|
||||
result["time_max"] = max
|
||||
}
|
||||
} else {
|
||||
// 备用解析方式
|
||||
parts := strings.Fields(line)
|
||||
for _, part := range parts {
|
||||
if strings.Contains(part, "/") {
|
||||
@@ -79,7 +165,7 @@ func parsePingOutput(output, url string) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
@@ -10,22 +10,66 @@ import (
|
||||
)
|
||||
|
||||
func handleSocket(c *gin.Context, url string, params map[string]interface{}) {
|
||||
// 解析host:port格式
|
||||
parts := strings.Split(url, ":")
|
||||
if len(parts) != 2 {
|
||||
c.JSON(200, gin.H{
|
||||
"type": "ceSocket",
|
||||
"url": url,
|
||||
"error": "格式错误,需要 host:port",
|
||||
})
|
||||
return
|
||||
// 获取seq参数
|
||||
seq := ""
|
||||
if seqVal, ok := params["seq"].(string); ok {
|
||||
seq = seqVal
|
||||
}
|
||||
|
||||
host := parts[0]
|
||||
portStr := parts[1]
|
||||
port, err := strconv.Atoi(portStr)
|
||||
// 解析host:port格式
|
||||
var host, portStr string
|
||||
var port int
|
||||
|
||||
// 尝试从URL中解析
|
||||
if strings.Contains(url, ":") {
|
||||
parts := strings.Split(url, ":")
|
||||
if len(parts) >= 2 {
|
||||
host = parts[0]
|
||||
portStr = parts[len(parts)-1]
|
||||
}
|
||||
} else {
|
||||
// 尝试从params中获取
|
||||
if hostVal, ok := params["host"].(string); ok {
|
||||
host = hostVal
|
||||
}
|
||||
if portVal, ok := params["port"].(string); ok {
|
||||
portStr = portVal
|
||||
} else if portVal, ok := params["port"].(float64); ok {
|
||||
portStr = strconv.Itoa(int(portVal))
|
||||
}
|
||||
}
|
||||
|
||||
// 如果host为空,尝试从URL解析
|
||||
if host == "" {
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
|
||||
parts := strings.Split(url, "//")
|
||||
if len(parts) > 1 {
|
||||
hostParts := strings.Split(parts[1], "/")
|
||||
hostPort := hostParts[0]
|
||||
if idx := strings.Index(hostPort, ":"); idx != -1 {
|
||||
host = hostPort[:idx]
|
||||
portStr = hostPort[idx+1:]
|
||||
} else {
|
||||
host = hostPort
|
||||
if strings.HasPrefix(url, "https://") {
|
||||
portStr = "443"
|
||||
} else {
|
||||
portStr = "80"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
host = url
|
||||
portStr = "80"
|
||||
}
|
||||
}
|
||||
|
||||
// 解析端口
|
||||
var err error
|
||||
port, err = strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"seq": seq,
|
||||
"type": "ceSocket",
|
||||
"url": url,
|
||||
"error": "端口格式错误",
|
||||
@@ -33,27 +77,56 @@ func handleSocket(c *gin.Context, url string, params map[string]interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// 执行TCP连接测试
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, portStr), 5*time.Second)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
// 准备结果
|
||||
result := map[string]interface{}{
|
||||
"seq": seq,
|
||||
"type": "ceSocket",
|
||||
"url": url,
|
||||
"host": host,
|
||||
"port": port,
|
||||
"ip": "",
|
||||
"result": "false",
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// 解析域名或IP
|
||||
var ip string
|
||||
parsedIP := net.ParseIP(host)
|
||||
if parsedIP != nil {
|
||||
ip = host
|
||||
} else {
|
||||
// DNS解析
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
result["ip"] = ""
|
||||
result["result"] = "域名无法解析"
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
if len(ips) > 0 {
|
||||
ip = ips[0].String()
|
||||
}
|
||||
}
|
||||
|
||||
result["ip"] = ip
|
||||
|
||||
// 检查IP是否有效
|
||||
if ip == "" || ip == "0.0.0.0" || ip == "127.0.0.0" {
|
||||
result["result"] = "false"
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行TCP连接测试
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, portStr), 5*time.Second)
|
||||
if err != nil {
|
||||
result["result"] = "false"
|
||||
if err.Error() != "" {
|
||||
result["error"] = err.Error()
|
||||
}
|
||||
c.JSON(200, result)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"type": "ceSocket",
|
||||
"url": url,
|
||||
"host": host,
|
||||
"port": port,
|
||||
"result": "true",
|
||||
})
|
||||
result["result"] = "true"
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,31 @@ import (
|
||||
)
|
||||
|
||||
func handleTrace(c *gin.Context, url string, params map[string]interface{}) {
|
||||
// 获取seq参数
|
||||
seq := ""
|
||||
if seqVal, ok := params["seq"].(string); ok {
|
||||
seq = seqVal
|
||||
}
|
||||
|
||||
// 解析URL,提取hostname
|
||||
hostname := url
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
|
||||
parts := strings.Split(url, "//")
|
||||
if len(parts) > 1 {
|
||||
hostParts := strings.Split(parts[1], "/")
|
||||
hostname = hostParts[0]
|
||||
if idx := strings.Index(hostname, ":"); idx != -1 {
|
||||
hostname = hostname[:idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行traceroute命令
|
||||
cmd := exec.Command("traceroute", url)
|
||||
cmd := exec.Command("traceroute", "-m", "30", "-n", hostname)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"seq": seq,
|
||||
"type": "ceTrace",
|
||||
"url": url,
|
||||
"error": err.Error(),
|
||||
@@ -31,6 +51,7 @@ func handleTrace(c *gin.Context, url string, params map[string]interface{}) {
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"seq": seq,
|
||||
"type": "ceTrace",
|
||||
"url": url,
|
||||
"trace_result": traceResult,
|
||||
|
||||
Reference in New Issue
Block a user