Golang中如何在PUT请求的Body中添加Reader
Golang中如何在PUT请求的Body中添加Reader
我在向FreshService发送PUT请求为工单添加私有备注时遇到了一些问题。每次尝试发送PUT或POST请求时,请求体都会引入某种Reader:键。根据他们的文档,请求体应该只包含备注本身,这尤其令人困扰:https://api.freshservice.com/#create_a_note。
以下是我收到的具体错误信息:
{"code":"invalid_json","message":"Request body has invalid json format"}
以下是我正在使用的代码: https://play.golang.org/p/nte_4rYDcbb
package main
// PUTRequest sends a PUT request to /api/v2/ at the specified endpoint
func PUTRequest(endpoint, apiKey string, jsonStr []byte) ([]byte, error) {
url := "https://example.freshservice.com/api/v2/"
req, _ := http.NewRequest(http.MethodPut, url+endpoint, bytes.NewBuffer(jsonStr))
log.Printf("[req:] %+v\n", req)
key := base64.URLEncoding.EncodeToString([]byte(apiKey + ":X"))
req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", "Basic "+key)
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("could not client.Do:\n%v", err)
}
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
return body, nil
}
// CreateNote creates a private note on *TicketList with the message specified
func (ticketList *TicketList) CreateNote(message, apiKey string) ([]byte, error) {
payload, _ := json.Marshal(message)
var responses []byte
for i := range ticketList.Tickets {
ticketUrl := fmt.Sprintf("tickets/%v", ticketList.Tickets[i].ID)
resp, err := PUTRequest(ticketUrl, apiKey, payload)
if err != nil {
return nil, fmt.Errorf("could not update ticket:\n%v", err)
}
responses = append(responses, resp...)
}
return responses, nil
}
func main() {
response, err := ticketList.CreateNote("This is a test", "<ApiKey>")
if err != nil {
log.Fatal(err)
}
log.Printf("[response:] %s", response)
}
使用log.Printf打印的输出如下:
2021/09/03 09:20:07 [resp:] &{Method:GET URL:https://example.freshservice.com/api/v2/tickets/<Ticket#>/requested_items Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Authorization:[Basic <base64Key>=] Content-Type:[application/json]] Body:{} GetBody:0x11f37c0 ContentLength:0 TransferEncoding:[] Close:false Host:example.freshservice.com Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr: RequestURI: TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00001e0b8}
2021/09/03 09:20:07 [reqlist:] {RequestedItems:[{OnboardingItems:{StartDate:2021-08-25 FirstName:test LastName:UserMan Address:1234 Main ST, nowhere USA PhoneNumber:123-456-7890 OfficeLocation:Remote Department:Engineering LaptopType:Mac LaptopExceptions: ReportingManagerEmail:manager.name@example.com IsManager:false IsContractor:false Monitor:false KeyboardAndMouse:false Mouse:false}}]}
2021/09/03 09:20:07 [req:] &{Method:PUT URL:https://example.freshservice.com/api/v2/tickets/<ticket#> Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[] Body:{Reader:"This is a test"} GetBody:0x11dc8a0 ContentLength:16 TransferEncoding:[] Close:false Host:example.freshservice.com Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr: RequestURI: TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00001e0b8}
2021/09/03 09:20:07 [response:] {"code":"invalid_json","message":"Request body has invalid json format"}
正如你在第3行([req:])看到的,请求体不知何故变成了Body:{Reader:"This is a test"}。我只想发送Body{"this is a test"}。我目前的推测是,在http.NewRequest中,它使用了body io.Reader,而io.Reader不知何故将Reader添加到了请求体中。
如果我在FreshService中创建用户(使用POST请求),这并没有成为问题,尽管它也包含了Body: {Reader: {key:value}}。但对于PUT请求,不知为何,Body:{Reader:}似乎成了一个问题。
我也尝试过在PUTRequest函数中运行:
payload, _ := json.Marshal(map[string]string{"body": message})
然而,无论怎样,这仍然是个问题。以下是它发送的请求:
Body:{Reader:{"body":"This is a test"}}
但是,仍然收到相同的错误:
{"code":"invalid_json","message":"Request body has invalid json format"}
请告诉我还可以尝试什么,我仍在学习Go的过程中。
更多关于Golang中如何在PUT请求的Body中添加Reader的实战教程也可以访问 https://www.itying.com/category-94-b0.html
问题 #1:我无法创建备注 解决方案: 错误的端点和方法。
在第 41 和 42 行,我有:
ticketUrl := fmt.Sprintf("tickets/%v", ticketList.Tickets[i].ID)
resp, err := PUTRequest(ticketUrl, apiKey, payload)
这应该是:
ticketUrl := fmt.Sprintf("tickets/%v/notes", ticketList.Tickets[i].ID)
resp, err := MethodRequest("POST", ticketUrl, apiKey, payload)
注意末尾添加的 /notes。我还需要发起 POST 请求而不是 PUT。
问题 #2: 我无法分配任务
解决方案: 我忘记在有效载荷中添加 responder_id
并且我还需要添加描述。
虽然不在原始帖子中,但我意识到我需要为分配任务添加一个结构体:
type AssignTasks struct {
ResponderID int `json:"responder_id"`
Description string `json:"description"`
}
然后这在我的方法/函数中用于分配任务:
payload, _ := json.Marshal(&AssignTasks{
ResponderID: agentsList.Agents[0].Id,
Description: "new hire",
})
我已在此处添加了更改:https://play.golang.org/p/8Kx4NFCF5Ki 没有包含分配任务的信息。如果有兴趣,我可以更新我为此制作的几个函数/方法。
编辑:
忘记包含其他一些东西 -
请求体仍然是 Body:{Reader},但似乎没有影响响应。
以下是发送的创建备注请求:
POST URL:https://example.freshservice.com/api/v2/tickets/<ticket#>/notes Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Authorization:[Basic <base64apiKey>=] Content-Type:[application/json]] Body:{Reader:{"body":"This is a test"}} GetBody:0x11dc8a0 ContentLength:25 TransferEncoding:[] Close:false Host:example.freshservice.com Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr: RequestURI: TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00001e0b8
以及发送的分配工单请求:
{Method:PUT URL:https://example.freshservice.com/api/v2/tickets/<ticket#> Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Authorization:[Basic <base64apiKey>=] Content-Type:[application/json]] Body:{Reader:} GetBody:0x11dc8a0 ContentLength:53 TransferEncoding:[] Close:false example.freshservice.com Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr: RequestURI: TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00001e0b8
两者都响应正常。所以 Reader 被添加到 Body 并不是真正的问题,真正的问题是错误的端点以及忘记添加正确的有效载荷。
更多关于Golang中如何在PUT请求的Body中添加Reader的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
问题在于你打印请求对象的方式。http.Request.Body字段是一个io.ReadCloser接口,当你使用fmt.Printf或log.Printf打印时,它会显示内部结构,但这并不代表实际发送的数据。
实际发送的数据是正确的。问题很可能出在JSON格式上。根据FreshService API文档,创建备注的请求体应该是包含body字段的JSON对象,而不是纯字符串。
以下是修正后的代码:
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
// PUTRequest sends a PUT request to /api/v2/ at the specified endpoint
func PUTRequest(endpoint, apiKey string, jsonStr []byte) ([]byte, error) {
url := "https://example.freshservice.com/api/v2/"
req, err := http.NewRequest(http.MethodPut, url+endpoint, bytes.NewBuffer(jsonStr))
if err != nil {
return nil, fmt.Errorf("could not create request: %v", err)
}
key := base64.StdEncoding.EncodeToString([]byte(apiKey + ":X"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Basic "+key)
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("could not client.Do: %v", err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %v", err)
}
return body, nil
}
// CreateNote creates a private note on *TicketList with the message specified
func (ticketList *TicketList) CreateNote(message, apiKey string) ([]byte, error) {
// 创建符合API要求的JSON结构
note := struct {
Body string `json:"body"`
}{
Body: message,
}
payload, err := json.Marshal(note)
if err != nil {
return nil, fmt.Errorf("could not marshal note: %v", err)
}
// 调试:打印实际发送的JSON
log.Printf("Sending JSON payload: %s", string(payload))
var responses []byte
for i := range ticketList.Tickets {
ticketUrl := fmt.Sprintf("tickets/%d/notes", ticketList.Tickets[i].ID)
resp, err := PUTRequest(ticketUrl, apiKey, payload)
if err != nil {
return nil, fmt.Errorf("could not update ticket: %v", err)
}
responses = append(responses, resp...)
}
return responses, nil
}
// 调试函数:打印实际请求内容
func debugRequest(req *http.Request) {
// 读取并打印请求体
if req.Body != nil {
bodyBytes, _ := ioutil.ReadAll(req.Body)
log.Printf("Request body content: %s", string(bodyBytes))
// 重新设置请求体,因为ReadAll消耗了它
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
}
}
// 修改PUTRequest函数以包含调试
func PUTRequestWithDebug(endpoint, apiKey string, jsonStr []byte) ([]byte, error) {
url := "https://example.freshservice.com/api/v2/"
req, err := http.NewRequest(http.MethodPut, url+endpoint, bytes.NewBuffer(jsonStr))
if err != nil {
return nil, fmt.Errorf("could not create request: %v", err)
}
// 调试:打印实际请求内容
debugRequest(req)
key := base64.StdEncoding.EncodeToString([]byte(apiKey + ":X"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Basic "+key)
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("could not client.Do: %v", err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %v", err)
}
return body, nil
}
关键修改:
- 使用正确的JSON结构:
{"body": "your message here"} - 使用
base64.StdEncoding而不是base64.URLEncoding - 添加了调试函数来查看实际发送的请求体内容
- 根据API文档,端点应该是
tickets/{id}/notes而不是tickets/{id}
实际发送的请求体应该是:
{"body":"This is a test"}
而不是:
"This is a test"
或者:
{"body":{"body":"This is a test"}}
Body:{Reader:"This is a test"}只是http.Request对象的字符串表示形式,不是实际发送的数据。使用debugRequest函数可以查看实际发送的JSON内容。

