Go 与 Gin 搭建简易 Postman:实现基础 HTTP 拨测的详细指南

发布于:2025-05-20 ⋅ 阅读:(11) ⋅ 点赞:(0)

Go 与 Gin 搭建简易 Postman:实现基础 HTTP 拨测的详细指南

项目简介

在这里插入图片描述

在日常的开发和测试工作中,网络接口的稳定性和正确性至关重要。为了确保各种网络接口能够正常工作,我们常常需要对其进行测试。Postman 作为一款广受欢迎的工具,能够帮助我们方便地发送 HTTP 请求并查看响应结果。不过,有时候我们可能需要一个更轻量级、定制化的工具来满足特定需求。本项目旨在实现一个简易的 Postman 工具,支持基本的 HTTP 拨测功能。用户可以通过该工具输入 URL 和请求方法,发送请求并查看响应的状态码和延迟时间。

代码结构

├── main.go          # 主程序入口
├── handlers         # HTTP处理器
│   └── probe.go
├── probe            # 拨测核心逻辑
│   ├── http_probe.go
│   ├── result.go
├── static           # 前端静态文件
│   ├── app.js
├── templates
│   ├──index.html
└── go.mod

各部分代码功能详细说明

  • main.go:作为主程序入口,负责初始化 Gin 框架。它设置了静态文件路由,将 /static 路径映射到 ./static 目录,这样前端页面就可以访问静态资源。同时,它还配置了 API 路由,定义了根路径 //api/probe 的处理函数。最后,启动 HTTP 服务器,监听 :8080 端口。
  • handlers/probe.go:处理拨测请求,它会解析客户端发送的请求参数,根据协议类型调用相应的拨测函数,并将拨测结果返回给客户端。
  • probe 目录:包含拨测的核心逻辑。其中 http_probe.go 实现了 HTTP 协议的拨测功能,result.go 定义了统一的响应结构体和创建错误结果的工具函数。
  • static 目录:存放前端静态文件,index.html 是前端页面的主体,style.css 用于设置页面的样式,app.js 实现了前端的交互逻辑。
  • go.mod:Go 模块文件,用于管理项目的依赖。

代码实现:

main.go

// 后端代码 main.go
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"http/probe"
	"net/http"
	"time"
)

func main() {
	r := gin.Default()

	// 静态文件服务
	r.Static("/static", "./static")
	r.LoadHTMLGlob("templates/index.html")

	// 路由配置
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	r.POST("/api/probe", probeHandler)
	//增加提示信息
	fmt.Printf("\n\033[1;36m%s\033[0m\n", "-----------------------------------------------")
	fmt.Printf("\033[1;32m服务已启动!请访问:\033[0m \033[4;35mhttp://localhost:8080\033[0m")
	fmt.Printf("\n\033[1;36m%s\033[0m\n\n", "-----------------------------------------------")

	r.Run(":8080")
}

// handlers/probe_handler.go
type ProbeRequest struct {
	Method  string            `json:"method"`
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers"`
	Body    string            `json:"body"`
	Timeout int               `json:"timeout"`
}

func probeHandler(c *gin.Context) {
	var req ProbeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	config := probe.HTTPConfig{
		Method:  req.Method,
		URL:     req.URL,
		Headers: req.Headers,
		Body:    req.Body,
		Timeout: time.Duration(req.Timeout) * time.Second,
	}

	result := probe.HTTPProbe(config)
	c.JSON(http.StatusOK, result)
}

代码解释
  • 导入了 github.com/gin-gonic/gin 包用于构建 HTTP 服务器,time 包用于处理时间相关操作。
  • 定义了 ProbeRequest 结构体,用于接收客户端发送的请求参数。
  • main 函数中,初始化 Gin 引擎 r,并设置静态文件路由和 API 路由。
  • probeHandler 函数负责处理 /api/probe 的 POST 请求,它会将客户端发送的 JSON 数据绑定到 ProbeRequest 结构体上,如果绑定失败则返回 400 错误。然后,根据请求参数创建 probe.HTTPConfig 对象,并调用 probe.HTTPProbe 函数进行拨测,最后将结果以 JSON 格式返回给客户端。

handlers/probe.go

package handlers

import (
	"net/http"
	"strings"
	"time"

	"http/probe"

	"github.com/gin-gonic/gin"
)

type ProbeRequest struct {
	Protocol string `json:"protocol"`
	Method   string `json:"method"`
	URL      string `json:"url"`
	Timeout  int    `json:"timeout"`
}

func ProbeHandler(c *gin.Context) {
	var req ProbeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 自动补全协议头
	if !strings.Contains(req.URL, "://") {
		req.URL = "http://" + req.URL
	}

	start := time.Now()
	var result probe.ProbeResult

	switch req.Protocol {
	case "HTTP":
		result = probe.HTTPProbe(req.Method, req.URL, req.Timeout)
	//case "TCP":
	//	result = probe.TCPProbe(req.URL, req.Timeout)
	//case "ICMP":
	//	result = probe.ICMPProbe(strings.Split(req.URL, "://")[1], 4)
	//case "SSL":
	//	result = probe.SSLProbe(req.URL, req.Timeout)
	default:
		c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的协议类型"})
		return
	}

	result.Duration = time.Since(start).Milliseconds()
	c.JSON(http.StatusOK, result)
}

代码解释
  • 定义了 ProbeRequest 结构体,用于接收客户端发送的拨测请求参数,包括协议类型、请求方法、URL 和超时时间。
  • ProbeHandler 函数是 /api/probe 接口的处理函数,它首先尝试将客户端发送的 JSON 数据绑定到 ProbeRequest 结构体上,如果绑定失败则返回 400 错误。
  • 自动补全 URL 的协议头,如果 URL 中不包含 ://,则默认添加 http://
  • 记录开始时间,根据协议类型调用相应的拨测函数,目前只实现了 HTTP 协议的拨测。
  • 计算拨测的延迟时间,并将其赋值给 result.Duration
  • 最后返回拨测结果给客户端。

probe/result.go

// probe/result.go
package probe

type ProbeResult struct {
	Endpoint   string            `json:"endpoint"`
	StatusCode int               `json:"status_code"`
	Duration   int64             `json:"duration"`
	Protocol   string            `json:"protocol"`
	Error      string            `json:"error,omitempty"`
	Response   string            `json:"response,omitempty"`
	Headers    map[string]string `json:"headers,omitempty"`
}
代码解释
  • 定义了 ProbeResult 结构体,作为统一的拨测响应结构体,包含探测地址、状态码、延迟时间、协议类型、错误信息、响应内容和响应头。

probe/http_probe.go

// probe/http_probe.go
package probe

import (
	"bytes"
	"io"
	"net/http"
	"strings"
	"time"
)

type HTTPConfig struct {
	Method  string
	URL     string
	Headers map[string]string
	Body    string
	Timeout time.Duration
}

func HTTPProbe(config HTTPConfig) ProbeResult {
	start := time.Now()
	result := ProbeResult{
		Protocol: "HTTP",
		Endpoint: config.URL,
	}

	// 协议头处理
	if !strings.Contains(config.URL, "://") {
		config.URL = "http://" + config.URL
	}

	// 创建请求
	var bodyReader io.Reader
	if config.Body != "" {
		bodyReader = bytes.NewBufferString(config.Body)
	}

	req, err := http.NewRequest(config.Method, config.URL, bodyReader)
	if err != nil {
		result.Error = "创建请求失败: " + err.Error()
		return result
	}

	// 设置请求头
	for k, v := range config.Headers {
		req.Header.Set(k, v)
	}
	if config.Body != "" && req.Header.Get("Content-Type") == "" {
		req.Header.Set("Content-Type", "application/json")
	}

	// 配置客户端
	client := &http.Client{
		Timeout: config.Timeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	// 执行请求
	resp, err := client.Do(req)
	if err != nil {
		result.Error = err.Error()
		return result
	}
	defer resp.Body.Close()

	// 读取响应
	bodyBytes, _ := io.ReadAll(resp.Body)

	result.StatusCode = resp.StatusCode
	result.Duration = time.Since(start).Milliseconds()
	result.Response = string(bodyBytes)
	result.Headers = make(map[string]string)
	for k, v := range resp.Header {
		if len(v) > 0 {
			result.Headers[k] = v[0]
		}
	}

	return result
}
代码解释:
  • 定义结构体HTTPConfig 结构体用于存储 HTTP 请求的配置信息,包括请求方法、URL、请求头、请求体和超时时间。
  • HTTPProbe 函数:
    • 记录开始时间,创建 ProbeResult 对象并初始化协议类型和探测地址。
    • 自动补全 URL 的协议头。
    • 根据请求体内容创建请求体读取器,并创建 HTTP 请求。
    • 设置请求头,如果请求体不为空且没有设置 Content-Type,则默认设置为 application/json
    • 配置 HTTP 客户端,设置超时时间和重定向策略。
    • 执行 HTTP 请求,如果请求过程中出现错误,将错误信息赋值给 result.Error 并返回结果。
    • 读取响应内容,将状态码、延迟时间、响应内容和响应头信息赋值给 result 对象,并返回结果。

index.html

<!-- 前端代码 templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>是垚不是土网络测试工具</title>
    <link rel="stylesheet" href="/static/style.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<div class="container">
    <div class="tabs">
        <button class="active">HTTP</button>
        <button>ICMP</button>
        <button>TCP</button>
        <button>SSL</button>
    </div>

    <div class="input-group">
        <select id="method">
            <option>GET</option>
            <option>POST</option>
            <option>PUT</option>
            <option>DELETE</option>
        </select>
        <input type="text" id="url" placeholder="请输入端点,如:https://github.com">
        <button id="probe-btn">拨测一下<i class="fas fa-arrow-right"></i></button>
    </div>

    <div class="advanced-options">
        <button> <i class="fas fa-chevron-right"></i>&nbsp;&nbsp;&nbsp;&nbsp;高级选项</button>
    </div>

    <div>
        <div class="input-group">
            <textarea id="request-body" placeholder="请求体内容 (支持JSON/文本)"></textarea>
        </div>
        <div class="input-group">
            <input type="number" id="timeout" min="1" max="60" value="10">
            <label>超时时间()</label>
        </div>
    </div>

    <table>
        <thead>
        <tr>
            <th class="th1">端点</th>
            <th>状态码</th>
            <th>延迟</th>
            <th class="th2">响应预览</th>
        </tr>
        </thead>
        <tbody id="results"></tbody>
    </table>
</div>

<script src="/static/app.js"></script>
</body>
</html>
代码解释
  • 前端页面使用 HTML、CSS 和 JavaScript 实现。
  • HTML 部分构建了页面的结构,包括选项卡、输入框、拨测按钮和结果表格。
  • 引入了外部的 CSS 样式文件 style.css 和字体图标库 font-awesome
  • 通过 <script> 标签引入了 app.js 文件,实现了前端的交互逻辑。

style.css

/* static/style.css */
body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: #f8f9fa;
    margin: 0;
    padding: 20px;
}

.container {
    background: white;
    border-radius: 8px; /* 修改圆角 */
    box-shadow: 0 2px 12px rgba(0,0,0,0.1); /* 修改阴影 */
    max-width: 800px; /* 修改最大宽度 */
    margin: 20px auto;
    padding: 24px;
}

.tabs {
    display: flex;
    gap: 8px; /* 修改间距 */
    margin-bottom: 16px;
}

button {
    background: none;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.2s;
}

button.active {
    /* background: #e9ecef; 修改激活状态背景 */
    border-bottom: 2px solid #007bff; /* 修改激活状态下划线 */
}

.input-group {
    display: flex;
    gap: 12px;
    margin: 16px 0;
    align-items: center;
}

#method {
    width: 80px; /* 修改宽度 */
    padding: 8px;
    border: 1px solid #f8f7f7;
    border-radius: 6px;
    background-color:rgba(226, 223, 223, 0.3);
}

#url {
    flex: 1;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-family: monospace;
}

#probe-btn {
    background: white;
    color: #007bff;
    border: 1px solid #ddd;
    padding: 10px 24px;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.2s;
}

#probe-btn:hover {
    background:rgba(226, 223, 223, 0.3);
}

.advanced-options {
    width: 100%;
    height: 15%;
    background-color: rgba(226, 223, 223, 0.3);
    border: 2px solid rgba(226, 223, 223, 0.5);
    border-radius: 10px;
}
.input-group {
    display: flex;
    gap: 12px;
    margin: 16px 0;
    align-items: center;
}
#request-body {
    width: 100%;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 8px;
    min-height: 100px;
    font-family: monospace;
}
#timeout{
    border-radius: 8px;
    border: 2px solid #ddd;
}
table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
}

td {
    padding: 12px;
    border: 1px solid #eee;
    word-break: break-all;
}

th {
    padding: 12px;
    background: #f8f9fa;
    font-weight: 500;
    word-break: break-all;
}
.th1{
    border-radius: 15px 0 0 0;
}

.th2{
    border-radius: 0 15px 0 0;
}
.status-ok {
    color: #28a745;
    font-weight: bold;
}

.status-error {
    color: #dc3545;
    font-weight: bold;
}

.response-preview {
    max-width: 300px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
代码解释
  • style.css 文件用于设置页面的全局样式,包括背景颜色、字体、按钮样式、表格样式等。
  • 通过 CSS 选择器对不同的 HTML 元素进行样式设置,提高了页面的美观性和用户体验。

static/app.js

// static/app.js
document.addEventListener('DOMContentLoaded', () => {
    const probeBtn = document.getElementById('probe-btn');
    const results = document.getElementById('results');

    probeBtn.addEventListener('click', async () => {
        const config = {
            method: document.getElementById('method').value,
            url: document.getElementById('url').value,
            body: document.getElementById('request-body').value,
            timeout: parseInt(document.getElementById('timeout').value)
        };

        if (!config.url) {
            alert('请输入请求URL');
            return;
        }

        try {
            const response = await fetch('/api/probe', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    method: config.method,
                    url: config.url,
                    body: config.body,
                    timeout: config.timeout
                })
            });

            if (!response.ok) throw new Error(`HTTP错误 ${response.status}`);

            const data = await response.json();
            addResultRow(data);

        } catch (error) {
            addErrorRow(config.url, error.message);
        }
    });

    function addResultRow(data) {
        const row = document.createElement('tr');
        row.innerHTML = `
            <td>${data.endpoint}</td>
            <td class="${data.status_code >= 200 && data.status_code < 300 ? 'status-ok' : 'status-error'}">
                ${data.status_code || 'ERR'}
            </td>
            <td>${data.duration}ms</td>
            <td class="response-preview" title="${data.response || ''}">
                ${truncateText(data.response || '', 40)}
                ${renderHeaders(data.headers)}
            </td>
        `;
        results.prepend(row);
    }

    function addErrorRow(url, error) {
        const row = document.createElement('tr');
        row.innerHTML = `
            <td>${url}</td>
            <td class="status-error">ERR</td>
            <td>-</td>
            <td class="status-error">${error}</td>
        `;
        results.prepend(row);
    }

    function truncateText(text, maxLength) {
        return text.length > maxLength ?
            text.substring(0, maxLength) + '...' :
            text;
    }

    function renderHeaders(headers) {
        if (!headers) return '';
        return `<div class="header-info">${
            Object.entries(headers)
                .map(([k, v]) => `<div><b>${k}:</b> ${v}</div>`)
                .join('')
        }</div>`;
    }
});
  • JavaScript 逻辑:
    • 使用 document.addEventListener('DOMContentLoaded', ...) 确保页面加载完成后再执行代码。
    • 为拨测按钮添加点击事件监听器,当按钮被点击时,获取用户输入的请求方法、URL、请求体和超时时间。
    • 进行输入验证,如果用户没有输入 URL,则弹出提示框。
    • 使用 fetch API 发送 POST 请求到 /api/probe 接口,携带请求参数。
    • 如果请求成功,将响应数据解析为 JSON 格式,并调用 addResultRow 函数将拨测结果显示在表格顶部。
    • 如果请求失败,调用 addErrorRow 函数将错误信息显示在表格顶部。
    • addResultRow 函数创建一个新的表格行,并将拨测结果填充到表格行中。
    • addErrorRow 函数创建一个包含错误信息的表格行,并将其显示在表格顶部。
    • truncateText 函数用于截断文本,当文本长度超过指定长度时,在文本末尾添加省略号。
    • renderHeaders 函数用于渲染响应头信息。

代码构建

安装依赖

go mod tidy

go mod tidy 命令会自动下载项目所需的依赖包,并更新 go.modgo.sum 文件。

运行程序:

go run main.go

访问测试

http://localhost:8080
在这里插入图片描述
在这里插入图片描述

打开浏览器,访问 http://localhost:8080,即可看到简易 Postman 工具的界面。在输入框中输入要拨测的 URL 和请求方法,并且可以自定义请求体内容,点击 “拨测一下” 按钮,即可发送请求并查看响应的状态码和延迟时间。

在这里插入图片描述
在这里插入图片描述

总结

通过本项目,我们成功实现了一个简易的接口测试工具,支持基本的 HTTP 拨测功能。项目采用了前后端分离的架构,前端使用 HTML、CSS 和 JavaScript 实现用户界面,后端使用 Go 语言和 Gin 框架实现 HTTP 服务器和拨测逻辑。在实际开发中,我们可以进一步扩展该工具,支持更多的协议类型(如 TCP、ICMP、SSL 等),添加更多的请求参数(如请求头、请求体等),以及优化用户界面和错误处理逻辑。这样可以让工具更加完善,满足更多的测试需求。


网站公告

今日签到

点亮在社区的每一天
去签到