从 bufio.Reader 创建请求
creating a request from bufio.Reader
我正在尝试实现一个接受多部分混合的批处理程序。
我目前有点幼稚的实现如下所示。稍后我会尝试聚合响应并发送多部分响应。
我目前的问题是无法将各个部分的 body 解析为新请求。
func handleBatchPost(w http.ResponseWriter, r *http.Request) {
// read the multipart body
reader, err := r.MultipartReader()
if err != nil {
http.Error(w, fmt.Sprintf("could not read multipart %v\n", err), http.StatusBadRequest)
}
// read each part
for {
part, err := reader.NextPart()
if err == io.EOF {
break
} else if err != nil {
http.Error(w, fmt.Sprintf("could not read next part %v\n", err), http.StatusBadRequest)
return
}
// check if content type is http
if part.Header.Get("Content-Type") != "application/http" {
http.Error(w, fmt.Sprintf("part has wrong content type: %s\n", part.Header.Get("Content-Type")), http.StatusBadRequest)
return
}
// parse the body of the part into a request
req, err := http.ReadRequest(bufio.NewReader(part))
if err != nil {
http.Error(w, fmt.Sprintf("could not create request: %s\n", err), http.StatusBadRequest)
return
}
// handle the request
router.ServeHTTP(w, req)
}
}
func handleItemPost(w http.ResponseWriter, r *http.Request) {
var item map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, fmt.Sprintf("could not decode item json: %v\n", err), http.StatusBadRequest)
return
}
w.Write([]byte(`{"success": true}`))
}
我收到来自服务器的错误响应。似乎 ReadRequest
没有阅读 body 而只是 headers(方法、url 等)。
could not decode item json: EOF
这是我发送的负载。
POST /batch HTTP/1.1
Host: localhost:8080
Content-Type: multipart/mixed; boundary=boundary
--boundary
Content-Type: application/http
Content-ID: <item1>
POST /items HTTP/1.1
Content-Type: application/json
{ "name": "batch1", "description": "batch1 description" }
--boundary
Content-Type: application/http
Content-ID: <item2>
POST /items HTTP/1.1
Content-Type: application/json
{ "name": "batch2", "description": "batch2 description" }
--boundary--
我在 gmail api 文档 https://developers.google.com/gmail/api/guides/batch 上找到了这个模式。
主要问题是您的负载没有为 sub-requests 指定 Content-Length
header。如果缺少 Content-Length
header,http.ReadRequest()
将假定没有 body,将不会读取和显示实际的 body,这就是为什么会出现 EOF 错误.
所以首先提供缺失的Content-Length
headers:
POST /batch HTTP/1.1
Host: localhost:8080
Content-Type: multipart/mixed; boundary=boundary
--boundary
Content-Type: application/http
Content-ID: <item1>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch1", "description": "batch1 description" }
--boundary
Content-Type: application/http
Content-ID: <item2>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch2", "description": "batch2 description" }
--boundary--
这样它应该可以工作,但请注意,由于您在同一个循环中处理零件,并在最后调用 router.ServeHTTP(w, req)
,因此您 重用了 w
作家。这是什么意思?如果 handleItemPost()
向输出写入任何内容,则对 handleItemPost()
的后续调用无法收回。
例如如果 handleItemPost()
失败,它会以 HTTP 错误响应(这意味着设置响应状态并写入 body)。后续的handleItemPost()
不能再报错(headers已经commit),而且如果报成功,错误header已经发送,只能写进一步的消息到错误 body.
例如,如果我们将 handleItemPost()
修改为:
func handleItemPost(w http.ResponseWriter, r *http.Request) {
var item map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
fmt.Printf("JSON decode error: %v\n", err)
return
}
fmt.Printf("Success, item: %v\n", item)
}
并执行以下 curl
命令:
curl localhost:8080/batch -X POST \
-H "Content-Type: multipart/mixed; boundary=boundary" \
-d '--boundary
Content-Type: application/http
Content-ID: <item1>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch1", "description": "batch1 description" }
--boundary
Content-Type: application/http
Content-ID: <item2>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch2", "description": "batch2 description" }
--boundary--'
我们将看到以下输出:
Success, item: map[description:batch1 description name:batch1]
Success, item: map[description:batch2 description name:batch2]
请注意,如果 handleItemPost()
需要保持完整的功能并可自行调用(以处理请求并产生响应),则不能对它的所有调用使用相同的 http.ResponseWriter
.
在这种情况下,您可以为每个调用创建和使用单独的 http.ResponseWriter
。标准库有一个实现 http.ResponseWriter
的 httptest.ResponseRecorder
类型。它主要用于测试目的,但您也可以在这里使用它。它记录了书面答复,因此您可以在通话后查看它。
例如:
w2 := httptest.NewRecorder()
router.ServeHTTP(w2, req)
if w2.Code != http.StatusOK {
fmt.Printf("handleItemPost returned non-OK status: %v\n", w2.Code)
fmt.Printf("\terror body: %v\n", w2.Body.String())
}
运行 这与您的原始请求(未指定 Content-Length
),输出将是:
handleItemPost returned non-OK status: 400
error body: could not decode item json: EOF
handleItemPost returned non-OK status: 400
error body: could not decode item json: EOF
但是当你指定sub-requests的Content-Length
时,没有打印输出(错误)。
我正在尝试实现一个接受多部分混合的批处理程序。
我目前有点幼稚的实现如下所示。稍后我会尝试聚合响应并发送多部分响应。
我目前的问题是无法将各个部分的 body 解析为新请求。
func handleBatchPost(w http.ResponseWriter, r *http.Request) {
// read the multipart body
reader, err := r.MultipartReader()
if err != nil {
http.Error(w, fmt.Sprintf("could not read multipart %v\n", err), http.StatusBadRequest)
}
// read each part
for {
part, err := reader.NextPart()
if err == io.EOF {
break
} else if err != nil {
http.Error(w, fmt.Sprintf("could not read next part %v\n", err), http.StatusBadRequest)
return
}
// check if content type is http
if part.Header.Get("Content-Type") != "application/http" {
http.Error(w, fmt.Sprintf("part has wrong content type: %s\n", part.Header.Get("Content-Type")), http.StatusBadRequest)
return
}
// parse the body of the part into a request
req, err := http.ReadRequest(bufio.NewReader(part))
if err != nil {
http.Error(w, fmt.Sprintf("could not create request: %s\n", err), http.StatusBadRequest)
return
}
// handle the request
router.ServeHTTP(w, req)
}
}
func handleItemPost(w http.ResponseWriter, r *http.Request) {
var item map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, fmt.Sprintf("could not decode item json: %v\n", err), http.StatusBadRequest)
return
}
w.Write([]byte(`{"success": true}`))
}
我收到来自服务器的错误响应。似乎 ReadRequest
没有阅读 body 而只是 headers(方法、url 等)。
could not decode item json: EOF
这是我发送的负载。
POST /batch HTTP/1.1
Host: localhost:8080
Content-Type: multipart/mixed; boundary=boundary
--boundary
Content-Type: application/http
Content-ID: <item1>
POST /items HTTP/1.1
Content-Type: application/json
{ "name": "batch1", "description": "batch1 description" }
--boundary
Content-Type: application/http
Content-ID: <item2>
POST /items HTTP/1.1
Content-Type: application/json
{ "name": "batch2", "description": "batch2 description" }
--boundary--
我在 gmail api 文档 https://developers.google.com/gmail/api/guides/batch 上找到了这个模式。
主要问题是您的负载没有为 sub-requests 指定 Content-Length
header。如果缺少 Content-Length
header,http.ReadRequest()
将假定没有 body,将不会读取和显示实际的 body,这就是为什么会出现 EOF 错误.
所以首先提供缺失的Content-Length
headers:
POST /batch HTTP/1.1
Host: localhost:8080
Content-Type: multipart/mixed; boundary=boundary
--boundary
Content-Type: application/http
Content-ID: <item1>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch1", "description": "batch1 description" }
--boundary
Content-Type: application/http
Content-ID: <item2>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch2", "description": "batch2 description" }
--boundary--
这样它应该可以工作,但请注意,由于您在同一个循环中处理零件,并在最后调用 router.ServeHTTP(w, req)
,因此您 重用了 w
作家。这是什么意思?如果 handleItemPost()
向输出写入任何内容,则对 handleItemPost()
的后续调用无法收回。
例如如果 handleItemPost()
失败,它会以 HTTP 错误响应(这意味着设置响应状态并写入 body)。后续的handleItemPost()
不能再报错(headers已经commit),而且如果报成功,错误header已经发送,只能写进一步的消息到错误 body.
例如,如果我们将 handleItemPost()
修改为:
func handleItemPost(w http.ResponseWriter, r *http.Request) {
var item map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
fmt.Printf("JSON decode error: %v\n", err)
return
}
fmt.Printf("Success, item: %v\n", item)
}
并执行以下 curl
命令:
curl localhost:8080/batch -X POST \
-H "Content-Type: multipart/mixed; boundary=boundary" \
-d '--boundary
Content-Type: application/http
Content-ID: <item1>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch1", "description": "batch1 description" }
--boundary
Content-Type: application/http
Content-ID: <item2>
POST /items HTTP/1.1
Content-Type: application/json
Content-length: 58
{ "name": "batch2", "description": "batch2 description" }
--boundary--'
我们将看到以下输出:
Success, item: map[description:batch1 description name:batch1]
Success, item: map[description:batch2 description name:batch2]
请注意,如果 handleItemPost()
需要保持完整的功能并可自行调用(以处理请求并产生响应),则不能对它的所有调用使用相同的 http.ResponseWriter
.
在这种情况下,您可以为每个调用创建和使用单独的 http.ResponseWriter
。标准库有一个实现 http.ResponseWriter
的 httptest.ResponseRecorder
类型。它主要用于测试目的,但您也可以在这里使用它。它记录了书面答复,因此您可以在通话后查看它。
例如:
w2 := httptest.NewRecorder()
router.ServeHTTP(w2, req)
if w2.Code != http.StatusOK {
fmt.Printf("handleItemPost returned non-OK status: %v\n", w2.Code)
fmt.Printf("\terror body: %v\n", w2.Body.String())
}
运行 这与您的原始请求(未指定 Content-Length
),输出将是:
handleItemPost returned non-OK status: 400
error body: could not decode item json: EOF
handleItemPost returned non-OK status: 400
error body: could not decode item json: EOF
但是当你指定sub-requests的Content-Length
时,没有打印输出(错误)。