golang (*bufio.Reader)的读取方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang (*bufio.Reader)的读取方法相关的知识,希望对你有一定的参考价值。

参考技术A bufReader.ReadBytes('\n')和 bufReader.ReadString('\n')在读到文件最后一行时,会同时返回内容line和io.EOF。而bufReader.Read()读取到末尾时,会先返回内容,然后再下一次迭代时才返回io.EOF

从 bufio.Reader 创建请求

【中文标题】从 bufio.Reader 创建请求【英文标题】:creating a request from 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 上发现了这种模式。

【问题讨论】:

【参考方案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 时,不会打印输出(错误)。

【讨论】:

以上是关于golang (*bufio.Reader)的读取方法的主要内容,如果未能解决你的问题,请参考以下文章

Go标准库-带缓冲的IO(bufio)

golang Golang中多次读取锁定的示例

golang读取email

GoLang,切片读取错误

golang 在#golang中逐行读取文件

golang Golang示例:逐行读取文件。