https://github.com/474420502/gcurl
一个在 Go 服务中安全、完整执行 curl 命令的利器
Github地址:https://github.com/474420502/gcurl
1. 为什么需要 gcurl?
先说这个业务是什么,我正在做一个服务监控系统,需要监控应用程序相关服务、服务器相关状态等,其实是有做相关后台管理页面,会展展示一些上报的数据,但是对于内部一些开发者而言,可能需要通过CLI更快的去找到相关问题,所以需要做一个支持curl相关的服务,这里就使用一个很完善的curl解析库gcurl来实现
原生 Go 的 net/http 只能手动拼 Header、Body、Method......
而用户最熟悉的表达方式就是 curl 命令。
gcurl 的使命:把 99% 真实的 curl 命令完整解析并在 Go 中原生执行,包括但不限于:
| curl 功能 | gcurl 是否支持 |
|---|---|
-X POST/PUT/DELETE... |
支持 |
-H "Header: value" 多条 |
支持 |
-d 'data' / --data-raw |
支持 |
--data-binary @file |
支持(v0.2.5+) |
-F "key=@file.txt" 文件上传 |
支持 |
--compressed 自动解压 gzip |
支持 |
-L 自动跳转 |
支持 |
-i 显示响应头 |
支持 |
--head / -I 只发 HEAD |
支持 |
-u user:pass Basic Auth |
支持 |
--insecure 跳过 TLS 验证 |
支持 |
-m 30 / --max-time 超时 |
支持 |
-x http://proxy 代理 |
支持 |
-b "cookie=1" / -c file |
部分支持(v0.3+) |
2. 核心用法
go
package main
import (
"github.com/474420502/gcurl"
"github.com/gin-gonic/gin"
)
type Req struct {
Cmd string `json:"cmd"`
}
func RunCurl(c *gin.Context) {
var req Req
if err := c.ShouldBindJSON(&req); err != nil || req.Cmd == "" {
c.String(400, "empty cmd")
return
}
// 1. 解析 curl 命令
curlCmd, err := gcurl.Parse(req.Cmd)
if err != nil {
c.String(400, "parse error: %v", err)
return
}
// 2. 创建会话(可复用)
session := curlCmd.CreateSession()
// 可选:全局超时(覆盖 -m 参数)
session.Config().SetTimeout(60 * time.Second)
// 3. 执行请求
resp, err := curlCmd.CreateRequest(session).Execute()
if err != nil {
c.String(500, "execute error: %v", err)
return
}
// 4. 判断是否需要显示 header(-i / --include / -I)
showHeader := curlCmd.Include || curlCmd.Method == "HEAD"
body := resp.Content()
if showHeader {
// 手动拼出和 curl 一模一样的响应头格式
var buf bytes.Buffer
fmt.Fprintf(&buf, "HTTP/%d.%d %d %s\r\n",
resp.ProtoMajor, resp.ProtoMinor,
resp.StatusCode, http.StatusText(resp.StatusCode))
for k, vs := range resp.Header {
for _, v := range vs {
fmt.Fprintf(&buf, "%s: %s\r\n", k, v)
}
}
buf.WriteString("\r\n")
if curlCmd.Method != "HEAD" {
buf.Write(body)
}
body = buf.Bytes()
}
// 5. 返回纯文本,和终端 curl 输出完全一致
c.Data(200, "text/plain; charset=utf-8", body)
}
请求示例:
bash
curl -XPOST http://localhost:8080/run -d '{"cmd":"curl -i https://httpbin.org/json"}'
返回:
HTTP/1.1 200 OK
Content-Type: application/json
...
{"slideshow": {...}}
3. 关键 API 详解
3.1 gcurl.Parse(string) → *gcurl.CURL
解析 curl 命令,返回一个不可变的 CURL 对象。
go
curlCmd, err := gcurl.Parse(`curl -X POST https://api.com -H "Token: 123" -d '{"a":1}'`)
3.2 curlCmd.CreateSession() → *gcurl.Session
可复用的会话,内部持有 cookie jar、自定义 Transport 等。
go
session := curlCmd.CreateSession()
// 可以手动设置代理、跳过 TLS 等
session.Config().SetInsecureSkipVerify(true) // 等价 --insecure
session.Config().SetProxy("http://127.0.0.1:8080")
3.3 curlCmd.CreateRequest(session) → *gcurl.Request → Execute()
真正执行请求。
go
resp, err := curlCmd.CreateRequest(session).Execute()
resp 是标准 *http.Response 的超集:
go
resp.StatusCode
resp.Header
resp.Content() // []byte,已经自动解压 gzip
resp.GetResponse() // 原生 *http.Response
4. 生产环境安全建议(非常重要)
这个功能一旦暴露到公网,等同于给黑客一个远程 curl 执行器,必须做好以下限制:
go
// 推荐加一个白名单中间件
func CurlWhitelist() gin.HandlerFunc {
allowHosts := map[string]bool{
"httpbin.org": true,
"api.github.com": true,
// 只允许你自己的域名或测试域名
}
return func(c *gin.Context) {
var req Req
if c.ShouldBindJSON(&req) != nil {
return
}
cmd, _ := gcurl.Parse(req.Cmd)
host := strings.ToLower(cmd.URL.Host)
if !allowHosts[host] {
c.String(403, "host %s not allowed", host)
c.Abort()
return
}
c.Next()
}
}
额外建议:
- 限制最大超时 ≤ 30s
- 限制响应体大小(gcurl 默认不限制,建议自己读 resp.Body 时 io.LimitReader)
- 禁止本地地址(127.0.0.1、10.0.0.0/8 等)
- 记录所有执行的 curl 命令
总结
gcurl 是目前 Go 生态里 最接近原生 curl 行为 的解析执行库,你就能在任何 Go Web 项目里实现一个「和终端 curl 输出 99% 一致」的在线执行接口。
用gcurl可以避免自己写过多的代码,来进行处理这些命令,若以上内容有错误之处,可提出,感谢观看。