Lambda中从SQS事件构建HTTP请求时无法读取请求体的问题排查
问题
我正在Lambda中使用Go语言代码从SQS事件构建HTTP请求。目前我能获取到请求头,但请求体是空的。如果原始请求带有Content-Length头,请求体里就有内容,但这个请求是外部系统生成的,并不包含这个头。请问我哪里操作出错了?
代码示例
package main import ( "bufio" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" ) func main() { sqsEvent := `{ "Type" : "Notification", "MessageId" : "d66493a6-7e62-5104-9e8f-c8de60b5fb85", "TopicArn" : "arn:aws:sns:us-east-1:1234567890:msgbridge-my-sns-topic", "Message" : "POST /endpoint HTTP/1.1\r\nAccept: */*\r\nAccept-Encoding: deflate, gzip\r\nContent-Type: application/json\r\nX-Amzn-Trace-Id: Root=1-62fe1c96-098f3f26680ffa54742d9aa2\r\nX-Forwarded-For: 173.252.127.12\r\nX-Forwarded-Port: 443\r\nX-Forwarded-Proto: https\r\n\r\n{\"status\":\"ok\"}", "Timestamp" : "2022-08-18T11:03:51.069Z", "SignatureVersion" : "1", "Signature" : "s+M8QtMEYC5XfBIFIokrI8+ChHSTAzp7i+/1+hSkVQx/0vzQA7g6GQle5jrYPclrob2oIWy7FeEs+EGoKPZQ8YuxdZf6xrGIpV7qyVUcrLWpxm0HyYViJ3q/qbcjZPWmVxaYrOV30m1CBkpDcwYvFGHl0neFefkOpybZAdRgEXkbChAlanAiIgh91Sm1IWD4J1Doth1UF84afMov9TfSZMSgGQ+e7LCnNMtcSpUT34aDQDUxOB51NsLd9+tmYSzdqIanUQItcaB2zGJFp/yk2u6pJUjLJ1oybh3olgjhrmVQrO8mBTOVbMH3i/TyZUl1aogikhe+3hKQoDoCuMc/KQ==" }` var req struct { Message string `json:"Message"` } if err := json.Unmarshal([]byte(sqsEvent), &req); err != nil { fmt.Printf("error unmarshalling; error=%s\n", err) return } hreq, err := http.ReadRequest(bufio.NewReader(strings.NewReader(req.Message))) if err != nil { fmt.Printf("failed to convert message body to http request, error=%s\n", err) return } fmt.Printf("X-Amzn-Trace-Id=%s\n", hreq.Header.Get("X-Amzn-Trace-Id")) body, err := ioutil.ReadAll(hreq.Body) if err != nil { fmt.Printf("failed to read body, error=%s\n", err) return } fmt.Printf("Body = %s\n", string(body)) }
运行输出
go run .
X-Amzn-Trace-Id=Root=1-62fe1c96-098f3f26680ffa54742d9aa2
Body =
解决方案
这个问题我之前也碰到过,核心原因是Go标准库的http.ReadRequest严格遵循HTTP/1.1规范:当请求里没有Content-Length或者Transfer-Encoding头时,它会默认认为请求没有体部,直接跳过后面的内容读取。你的外部系统生成的请求虽然有正确的\r\n\r\n分界符,但因为缺少Content-Length,所以体部被忽略了。
解决办法很直接,我们手动拆分请求的头部和体部,再把体部内容赋值给HTTP请求对象即可:
修改后的完整代码
package main import ( "bufio" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" ) func main() { sqsEvent := `{ "Type" : "Notification", "MessageId" : "d66493a6-7e62-5104-9e8f-c8de60b5fb85", "TopicArn" : "arn:aws:sns:us-east-1:1234567890:msgbridge-my-sns-topic", "Message" : "POST /endpoint HTTP/1.1\r\nAccept: */*\r\nAccept-Encoding: deflate, gzip\r\nContent-Type: application/json\r\nX-Amzn-Trace-Id: Root=1-62fe1c96-098f3f26680ffa54742d9aa2\r\nX-Forwarded-For: 173.252.127.12\r\nX-Forwarded-Port: 443\r\nX-Forwarded-Proto: https\r\n\r\n{\"status\":\"ok\"}", "Timestamp" : "2022-08-18T11:03:51.069Z", "SignatureVersion" : "1", "Signature" : "s+M8QtMEYC5XfBIFIokrI8+ChHSTAzp7i+/1+hSkVQx/0vzQA7g6GQle5jrYPclrob2oIWy7FeEs+EGoKPZQ8YuxdZf6xrGIpV7qyVUcrLWpxm0HyYViJ3q/qbcjZPWmVxaYrOV30m1CBkpDcwYvFGHl0neFefkOpybZAdRgEXkbChAlanAiIgh91Sm1IWD4J1Doth1UF84afMov9TfSZMSgGQ+e7LCnNMtcSpUT34aDQDUxOB51NsLd9+tmYSzdqIanUQItcaB2zGJFp/yk2u6pJUjLJ1oybh3olgjhrmVQrO8mBTOVbMH3i/TyZUl1aogikhe+3hKQoDoCuMc/KQ==" }` var req struct { Message string `json:"Message"` } if err := json.Unmarshal([]byte(sqsEvent), &req); err != nil { fmt.Printf("error unmarshalling; error=%s\n", err) return } // 拆分请求的头部和体部,HTTP规范中用\r\n\r\n作为分界 requestParts := strings.SplitN(req.Message, "\r\n\r\n", 2) if len(requestParts) != 2 { fmt.Println("invalid request format: missing body separator") return } // 先解析请求头部部分(保留分界符让ReadRequest正常处理) httpReq, err := http.ReadRequest(bufio.NewReader(strings.NewReader(requestParts[0] + "\r\n\r\n"))) if err != nil { fmt.Printf("failed to parse request headers, error=%s\n", err) return } // 手动设置请求体和Content-Length信息 bodyContent := requestParts[1] httpReq.Body = ioutil.NopCloser(strings.NewReader(bodyContent)) httpReq.ContentLength = int64(len(bodyContent)) httpReq.Header.Set("Content-Length", fmt.Sprintf("%d", len(bodyContent))) // 验证结果 fmt.Printf("X-Amzn-Trace-Id=%s\n", httpReq.Header.Get("X-Amzn-Trace-Id")) body, err := ioutil.ReadAll(httpReq.Body) if err != nil { fmt.Printf("failed to read body, error=%s\n", err) return } fmt.Printf("Body = %s\n", string(body)) }
关键修改点说明
- 用
strings.SplitN把SQS消息里的HTTP请求拆成头部和体部两部分,分界符是HTTP标准的\r\n\r\n - 解析请求头部时,只传头部内容加分界符,让
http.ReadRequest正常解析头部信息,不会因为缺少Content-Length而报错 - 手动把体部内容赋值给
httpReq.Body,同时设置ContentLength字段和对应的请求头,确保后续读取体部的逻辑能正常工作
现在运行修改后的代码,就能正确输出请求体内容了。
内容的提问来源于stack exchange,提问作者Arvind
相关产品推荐
相关产品推荐

