This commit is contained in:
2025-11-21 18:30:09 +08:00
parent e6a9a7a1f4
commit 27806421f7
5 changed files with 861 additions and 120 deletions

View File

@@ -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)
// 编码完整输出为base64header字段
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 _, ip := range ips {
ipType := "A"
if ip.To4() == nil {
ipType = "AAAA"
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)
}
}
}
ipList = append(ipList, map[string]interface{}{
"type": ipType,
"ip": ip.String(),
})
}
c.JSON(200, gin.H{
"type": "ceDns",
"url": url,
"ips": ipList,
"lookup_time": lookupTime,
})
}
// 如果没有从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"
}
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()
}
}
result["ips"] = ipList
c.JSON(200, result)
}

View File

@@ -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{
"type": "ceGet",
"url": url,
"statuscode": 200,
"totaltime": time.Since(time.Now()).Milliseconds(),
"response": "OK",
})
// 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 handlePost(c *gin.Context, url string, params map[string]interface{}) {
// TODO: 实现HTTP POST测试
c.JSON(http.StatusOK, gin.H{
"type": "cePost",
"url": url,
"statuscode": 200,
"totaltime": time.Since(time.Now()).Milliseconds(),
"response": "OK",
})
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": urlStr,
"error": "URL格式错误",
})
return
}
// 准备结果
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": 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)
}

View File

@@ -1,8 +1,10 @@
package handler
import (
"encoding/base64"
"net"
"os/exec"
"regexp"
"strconv"
"strings"
@@ -10,69 +12,154 @@ import (
)
func handlePing(c *gin.Context, url string, params map[string]interface{}) {
// 执行ping命令
cmd := exec.Command("ping", "-c", "4", url)
output, err := cmd.CombinedOutput()
if err != nil {
c.JSON(200, gin.H{
"type": "cePing",
"url": url,
"error": err.Error(),
})
return
// 获取seq参数
seq := ""
if seqVal, ok := params["seq"].(string); ok {
seq = seqVal
}
// 解析ping输出
result := parsePingOutput(string(output), url)
c.JSON(200, result)
}
// 解析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]
}
}
}
func parsePingOutput(output, url string) map[string]interface{} {
// 执行ping命令
cmd := exec.Command("ping", "-c", "10", "-i", "0.5", hostname)
output, err := cmd.CombinedOutput()
outputStr := string(output)
// 准备结果
result := map[string]interface{}{
"seq": seq,
"type": "cePing",
"url": url,
"ip": "",
}
// 解析IP地址
lines := strings.Split(output, "\n")
// 编码完整输出为base64header字段
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地址
parts := strings.Fields(line)
for _, part := range parts {
if net.ParseIP(part) != nil {
result["ip"] = part
break
// 提取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 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") {
// 解析丢包率
parts := strings.Fields(line)
for i, part := range parts {
if part == "packet" && i+2 < len(parts) {
if loss, err := strconv.ParseFloat(strings.Trim(parts[i+1], "%"), 64); err == nil {
result["packets_losrat"] = loss
// 格式如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
}
}
}
}
}
if strings.Contains(line, "min/avg/max") {
// 解析延迟统计
parts := strings.Fields(line)
for _, part := range parts {
if strings.Contains(part, "/") {
times := strings.Split(part, "/")
if len(times) >= 3 {
if min, err := strconv.ParseFloat(times[0], 64); err == nil {
result["time_min"] = min
}
if avg, err := strconv.ParseFloat(times[1], 64); err == nil {
result["time_avg"] = avg
}
if max, err := strconv.ParseFloat(times[2], 64); err == nil {
result["time_max"] = 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, "/") {
times := strings.Split(part, "/")
if len(times) >= 3 {
if min, err := strconv.ParseFloat(times[0], 64); err == nil {
result["time_min"] = min
}
if avg, err := strconv.ParseFloat(times[1], 64); err == nil {
result["time_avg"] = avg
}
if max, err := strconv.ParseFloat(times[2], 64); err == nil {
result["time_max"] = max
}
}
}
}
@@ -80,6 +167,5 @@ func parsePingOutput(output, url string) map[string]interface{} {
}
}
return result
c.JSON(200, result)
}

View File

@@ -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
}
// 准备结果
result := map[string]interface{}{
"seq": seq,
"type": "ceSocket",
"url": url,
"port": port,
"ip": "",
"result": "false",
}
// 解析域名或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(host, portStr), 5*time.Second)
conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, portStr), 5*time.Second)
if err != nil {
c.JSON(200, gin.H{
"type": "ceSocket",
"url": url,
"host": host,
"port": port,
"result": "false",
"error": err.Error(),
})
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)
}

View File

@@ -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,