【问题标题】:creating a request from bufio.Reader从 bufio.Reader 创建请求
【发布时间】:2022-01-21 23:45:39
【问题描述】:

我正在尝试实现一个接受多部分混合的批处理处理程序。

我目前有点幼稚的实现如下所示。稍后我将尝试汇总响应并发送多部分响应。

我当前的问题是我无法将各个部分的正文解析为新请求。

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 似乎没有读取正文,而只是读取标题(方法、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 上发现了这种模式。

【问题讨论】:

    标签: http go


    【解决方案1】:

    主要问题是您的有效负载没有为子请求指定Content-Length 标头。如果缺少 Content-Length 标头,http.ReadRequest() 将假定没有正文,不会读取和呈现实际正文,这就是您收到 EOF 错误的原因。

    所以首先提供缺少的Content-Length 标头:

    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 错误响应(这意味着设置响应状态并写入正文)。随后的handleItemPost() 不能再次报告错误(标头已提交),并且如果它报告成功,则错误标头已经发送,只能将进一步的消息写入错误正文。

    例如,如果我们将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.ResponseWriterhttptest.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
    

    但是当您指定子请求的Content-Length 时,不会打印输出(错误)。

    【讨论】:

      猜你喜欢
      • 2014-05-22
      • 1970-01-01
      • 1970-01-01
      • 2016-02-23
      • 2016-11-07
      • 2018-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多