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

2 回复

问题 #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 man_facepalming 并且我还需要添加描述。

虽然不在原始帖子中,但我意识到我需要为分配任务添加一个结构体:

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.Printflog.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
}

关键修改:

  1. 使用正确的JSON结构:{"body": "your message here"}
  2. 使用base64.StdEncoding而不是base64.URLEncoding
  3. 添加了调试函数来查看实际发送的请求体内容
  4. 根据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内容。

回到顶部