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> 高级选项</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.mod
和 go.sum
文件。
运行程序:
go run main.go
访问测试
http://localhost:8080
打开浏览器,访问 http://localhost:8080
,即可看到简易 Postman 工具的界面。在输入框中输入要拨测的 URL 和请求方法,并且可以自定义请求体内容,点击 “拨测一下” 按钮,即可发送请求并查看响应的状态码和延迟时间。
总结
通过本项目,我们成功实现了一个简易的接口测试工具,支持基本的 HTTP 拨测功能。项目采用了前后端分离的架构,前端使用 HTML、CSS 和 JavaScript 实现用户界面,后端使用 Go 语言和 Gin 框架实现 HTTP 服务器和拨测逻辑。在实际开发中,我们可以进一步扩展该工具,支持更多的协议类型(如 TCP、ICMP、SSL 等),添加更多的请求参数(如请求头、请求体等),以及优化用户界面和错误处理逻辑。这样可以让工具更加完善,满足更多的测试需求。