
本文详解 go 中使用 `net/http` 发送标准 `application/x-www-form-urlencoded` 类型 post 请求的正确方式,指出常见错误(如缺失 content-type、误用缓冲区),并提供可直接运行的健壮示例代码。
在 go 中模拟 curl -X POST -d “key=val” … 的行为,关键在于两点:正确设置请求头 Content-Type 和 以 URL 编码格式提交请求体。原代码中虽调用了 data.Encode() 生成了 api_token=MY_KEY&action=list_projects 字符串,但遗漏了至关重要的 Content-Type: application/x-www-form-urlencoded 头,导致服务端无法识别表单数据,从而返回“无 POST 数据”的错误。
以下是推荐的两种实现方式:
✅ 方式一:使用 http.PostForm(最简洁,推荐初学者)
package main import ( "fmt" "io/ioutil" "net/http" "net/url" ) func main() { apiUrl := "https://example.com/api/" data := url.Values{ "api_token": {"MY_KEY"}, "action": {"list_projects"}, } resp, err := http.PostForm(apiUrl, data) if err != nil { fmt.Printf("请求失败: %vn", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("读取响应失败: %vn", err) return } fmt.Println("HTTP 状态:", resp.Status) fmt.Println("响应内容:", string(body)) }
http.PostForm 内部自动设置 Content-Type 并编码表单,语义清晰、不易出错。
✅ 方式二:手动构造 http.Request(更灵活,适合进阶控制)
package main import ( "bytes" "fmt" "io/ioutil" "net/http" "net/url" ) func main() { apiUrl := "https://example.com/api/" data := url.Values{} data.Set("api_token", "MY_KEY") data.Set("action", "list_projects") // 注意:必须显式设置 Content-Type req, err := http.NewRequest("POST", apiUrl, bytes.NewBufferString(data.Encode())) if err != nil { fmt.Printf("创建请求失败: %vn", err) return } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Printf("发送请求失败: %vn", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("读取响应失败: %vn", err) return } fmt.Println("HTTP 状态:", resp.Status) fmt.Println("响应内容:", string(body)) }
⚠️ 关键注意事项
- 永远检查 err:网络请求可能因超时、dns 失败、连接拒绝等失败,忽略 err 会导致程序静默崩溃或逻辑错误。
- defer resp.Body.Close() 必须在 err == nil 后调用:否则当 resp 为 nil 时会 panic。
- 避免使用已弃用的 ioutil(Go 1.16+):生产环境建议升级至 io.ReadAll(需 import “io”),ioutil 已被标记为 deprecated。
- 不要用 strings.NewReader 替代 bytes.NewBufferString:二者功能等价,但 bytes.NewBufferString 更常用于此场景;重点在于 Content-Type 设置,而非 Reader 类型。
掌握这两种方式后,你就能可靠地与绝大多数基于表单的 rest api(如 php 后端、传统 Web 框架)完成交互。