
本文深入探讨了从php curl迁移http post请求到go语言时可能遇到的问题,特别是请求体数据处理的差异。我们将分析php和go在构建post请求时的不同机制,并通过示例代码展示如何正确地在go中发送url编码或json格式的post数据,以避免常见的“无法授权请求”错误。
在现代服务开发中,不同编程语言间的API调用迁移是常见任务。然而,即使是看似简单的HTTP POST请求,也可能因语言特性和库设计的差异而导致意外行为。本文将以PHP的cURL和Go语言的net/http包为例,详细阐述在实现HTTP POST请求时,特别是处理请求体数据方面的关键区别,并提供正确的Go语言实现方案。
HTTP POST请求的核心在于将数据作为请求的“主体”(body)发送到服务器。与GET请求将数据附加在URL的查询字符串中不同,POST请求的数据通常不直接暴露在URL上,而是通过请求体传输。服务器在接收到POST请求时,会解析请求头中的Content-Type字段来确定如何解码请求体中的数据。
在PHP中,使用cURL库发送POST请求是一种常见做法。以下是一个典型的PHP cURL POST请求示例:
<?php
function api_query(array $req = array()) {
$key = '90294318da0162b082c3d27126be80c3873955f9';
$req['method'] = 'getinfo';
$req['nonce'] = 1394503747386411;
// 使用 http_build_query 生成 URL 编码的 POST 数据字符串
$post_data = http_build_query($req, '', '&');
$sign = '75da1e3ff750286bf73d03197f1b779fbfff963fd7402941ae326509a6615eacb839b44f236b4d5ee6cff39321e7b35e9563a9a2075e99df0f4ee3b732999348';
$headers = array(
'Sign: '.$sign,
'Key: '.$key,
);
static $ch = null;
if (is_null($ch)) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; Cryptsy API PHP client; '.php_uname('s').'; PHP/'.phpversion().')');
}
curl_setopt($ch, CURLOPT_URL, 'https://api.cryptsy.com/api');
// 关键:CURLOPT_POSTFIELDS 将数据作为请求体发送
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
$res = curl_exec($ch);
// ... 错误处理与结果解析
return json_decode($res, true);
}
api_query();
?>在上述PHP代码中,http_build_query($req, '', '&')函数负责将关联数组$req转换为URL编码的字符串(例如method=getinfo&nonce=1394503747386411)。随后,curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data)指令明确地告诉cURL将这个字符串作为HTTP POST请求的请求体发送。默认情况下,cURL会自动设置Content-Type头为application/x-www-form-urlencoded。
立即学习“PHP免费学习笔记(深入)”;
当尝试将上述PHP逻辑迁移到Go语言时,一个常见的错误是未能正确处理POST请求的数据体。以下是Go语言中一个错误的实现尝试:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
)
func PrivateCall(c appengine.Context) (map[string]interface{}, error) {
AuthAPI := "https://api.cryptsy.com/api"
APIKey := "90294318da0162b082c3d27126be80c3873955f9"
tr := urlfetch.Transport{Context: c} // App Engine specific transport
values := url.Values{}
values.Set("method", "getinfo")
values.Set("nonce", "1394503747386411")
signature := "75da1e3ff750286bf73d03197f1b779fbfff963fd7402941ae326509a6615eacb839b44f236b4d5ee6cff39321e7b35e9563a9a2075e99df0f4ee3b732999348"
// 错误点:将 POST 数据作为查询参数附加到 URL
req, err := http.NewRequest("POST", AuthAPI+"?"+values.Encode(), nil)
if err != nil {
c.Infof("API - Call - error 2 - %s", err.Error())
return nil, err
}
req.Header.Set("Key", APIKey)
req.Header.Set("Sign", signature)
// ... 后续请求发送和响应处理
resp, err := tr.RoundTrip(req)
if err != nil {
c.Errorf("API post error: %s", err)
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.Errorf("API read error: could not read body: %s", err)
return nil, err
}
result := make(map[string]interface{})
err = json.Unmarshal(body, &result)
if err != nil {
c.Infof("Unmarshal: %v", err)
c.Infof("%s", body)
return nil, err
}
return result, nil
}上述Go代码的问题在于:
这种错误会导致服务器收到一个空的请求体,从而无法找到预期的POST数据,进而返回“Unable to Authorize Request - Check Your Post Data”之类的错误信息。
在Go语言中,正确发送HTTP POST请求的关键在于将请求体数据封装成一个io.Reader,并将其作为http.NewRequest的第三个参数传入。同时,需要设置正确的Content-Type请求头,以告知服务器请求体的格式。
我们将提供两种常见的POST数据格式的实现方式:application/x-www-form-urlencoded(与PHP cURL行为一致)和application/json(API开发中常用)。
这种方式最直接地对应PHP http_build_query 和 CURLOPT_POSTFIELDS 的行为。
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings" // 引入 strings 包
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
)
// CorrectPrivateCallWithForm 演示如何发送 application/x-www-form-urlencoded 格式的 POST 请求
func CorrectPrivateCallWithForm(c appengine.Context) (map[string]interface{}, error) {
AuthAPI := "https://api.cryptsy.com/api"
APIKey := "90294318da0162b082c3d27126be80c3873955f9"
tr := urlfetch.Transport{Context: c}
values := url.Values{}
values.Set("method", "getinfo")
values.Set("nonce", "1394503747386411")
signature := "75da1e3ff750286bf73d03197f1b779fbfff963fd7402941ae326509a6615eacb839b44f236b4d5ee6cff39321e7b35e9563a9a2075e99df0f4ee3b732999348"
// 将 URL 编码的字符串作为请求体
postData := values.Encode()
req, err := http.NewRequest("POST", AuthAPI, strings.NewReader(postData)) // 使用 strings.NewReader
if err != nil {
c.Errorf("Failed to create request: %v", err)
return nil, err
}
// 设置必要的请求头
req.Header.Set("Key", APIKey)
req.Header.Set("Sign", signature)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // 明确指定 Content-Type
resp, err := tr.RoundTrip(req)
if err != nil {
c.Errorf("API post error: %s", err)
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.Errorf("API read error: could not read body: %s", err)
return nil, err
}
result := make(map[string]interface{})
err = json.Unmarshal(body, &result)
if err != nil {
c.Infof("Unmarshal error: %v, raw body: %s", err, body)
return nil, err
}
return result, nil
}关键点:
如果目标API支持JSON格式的POST数据,那么发送JSON数据是另一种常见的且通常更现代的方式。
package main
import (
"bytes" // 引入 bytes 包
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
)
// CorrectPrivateCallWithJSON 演示如何发送 application/json 格式的 POST 请求
func CorrectPrivateCallWithJSON(c appengine.Context) (map[string]interface{}, error) {
AuthAPI := "https://api.cryptsy.com/api"
APIKey := "90294318da0162b082c3d27126be80c3873955f9"
tr := urlfetch.Transport{Context: c}
// 定义一个匿名结构体来表示要发送的 JSON 数据
data := struct {
Method string `json:"method"`
Nonce string `json:"nonce"`
}{
"getinfo",
"1394503747386411",
}
signature := "75da1e3ff750286bf73d03197f1b779fbfff963fd7402941ae326509a6615eacb839b44f236b4d5ee6cff39321e7b35e9563a9a2075e99df0f4ee3b732999348"
// 将 Go 结构体编码为 JSON 字节数组
jsonData, err := json.Marshal(data)
if err != nil {
c.Errorf("Failed to marshal JSON data: %v", err)
return nil, err
}
// 使用 bytes.NewBuffer 将 JSON 字节数组包装成 io.Reader
req, err := http.NewRequest("POST", AuthAPI, bytes.NewBuffer(jsonData))
if err != nil {
c.Errorf("Failed to create request: %v", err)
return nil, err
}
// 设置必要的请求头
req.Header.Set("Key", APIKey)
req.Header.Set("Sign", signature)
req.Header.Set("Content-Type", "application/json") // 明确指定 Content-Type
resp, err := tr.RoundTrip(req)
if err != nil {
c.Errorf("API post error: %s", err)
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.Errorf("API read error: could not read body: %s", err)
return nil, err
}
result := make(map[string]interface{})
err = json.Unmarshal(body, &result)
if err != nil {
c.Infof("Unmarshal error: %v, raw body: %s", err, body)
return nil, err
}
return result, nil
}关键点:
从PHP cURL迁移HTTP POST请求到Go语言时,核心区别在于Go的net/http包要求将POST请求体数据作为io.Reader传入,并且需要显式设置Content-Type请求头。通过正确使用strings.NewReader或bytes.NewBuffer来构建请求体,并设置相应的Content-Type,可以避免因请求体解析失败导致的认证或数据处理错误。理解这些底层机制,是编写健壮、高效的跨语言HTTP客户端的关键。
以上就是Go语言中正确实现HTTP POST请求:从PHP cURL迁移的实践指南的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号