将 os.Stdin 通过 HTTP POST 发送,而无需将文件加载到内存中。

huangapple go评论101阅读模式
英文:

Send os.Stdin via http POST without loading file into memory

问题

我正在尝试通过POST请求将文件发送到服务器。为了实现这一目标,我使用以下代码:

func newfileUploadRequest(uri string) (*http.Request, error) {
    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile("file", "file")
    if err != nil {
        return nil, err
    }
    io.Copy(part, os.Stdin)

    err = writer.Close()
    if err != nil {
        return nil, err
    }

    request, err := http.NewRequest("POST", uri, body)
    if err != nil {
        return nil, err
    }
    request.Header.Set("Content-Type", writer.FormDataContentType())
    return request, nil
}

func main() {
    r, err := newfileUploadRequest("http://localhost:8080/")
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(r)
    if err != nil {
        panic(err)
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    print(string(body))
}

虽然这段代码运行良好,但我了解到io.Copy会在发送POST请求之前将整个文件复制到内存中。大文件(多个GB)会导致问题。有没有办法避免这种情况?我找到了这个链接,但它只是说要使用io.Copy

英文:

I am trying to send a file to a server via a POST request. To accomplish this, I use the following code:

func newfileUploadRequest(uri string) (*http.Request, error) {

    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile("file", "file")
    if err != nil {
        return nil, err
    }
    io.Copy(part, os.Stdin)

    err = writer.Close()
    if err != nil {
        return nil, err
    }

    request, err := http.NewRequest("POST", uri, body)
    if err != nil {
        return nil, err
    }
    request.Header.Set("Content-Type", writer.FormDataContentType())
    return request, nil
}


func main() {
    r, err := newfileUploadRequest("http://localhost:8080/")
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(r)
    if err != nil {
        panic(err)
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    print(string(body))
}

While this works well, it is my understanding that io.Copy will copy the entire file into memory before the POST request is sent. Large files (multiple GB) will create issues. Is there a way to prevent this? I found this, but that simply says to use io.Copy.

答案1

得分: 2

你可以通过使用 io.Pipe 和一个将文件从管道复制到内存的 goroutine 来避免复制内存中的数据:

func newfileUploadRequest(uri string) (*http.Request, error) {
  r, w := io.Pipe()
  writer := multipart.NewWriter(w)
  go func() {
    part, err := writer.CreateFormFile("file", "file")
    if err != nil {
      w.CloseWithError(err)
      return
    }
    _, err = io.Copy(part, os.Stdin)
    if err != nil {
      w.CloseWithError(err)
      return
    }
    err = writer.Close()
    if err != nil {
      w.CloseWithError(err)
      return
    }
  }()

  request, err := http.NewRequest("POST", uri, r)
  if err != nil {
    return nil, err
  }
  request.Header.Set("Content-Type", writer.FormDataContentType())
  return request, nil
}

这段代码使用了 io.Pipe 和 goroutine 来实现了文件的上传。在 newfileUploadRequest 函数中,我们创建了一个管道 r, w := io.Pipe(),然后使用 multipart.NewWriter(w) 创建了一个 multipart.Writer 对象。接下来,我们启动了一个 goroutine,该 goroutine 在管道中创建了一个表单文件,并将文件内容从标准输入流 os.Stdin 复制到表单文件中。最后,我们关闭了 multipart.Writer 并创建了一个 http.Request 对象,将管道作为请求的主体,并设置了正确的请求头。

英文:

You can avoid copying the data in memory by using an io.Pipe and a goroutine that copies from the file to the pipe:

func newfileUploadRequest(uri string) (*http.Request, error) {
  r, w := io.Pipe()
  writer := multipart.NewWriter(w)
  go func() {
	part, err := writer.CreateFormFile("file", "file")
	if err != nil {
		w.CloseWithError(err)
		return
	}
	_, err = io.Copy(part, os.Stdin)
	if err != nil {
		w.CloseWithError(err)
		return
	}
	err = writer.Close()
	if err != nil {
		w.CloseWithError(err)
		return
	}
  }()

  request, err := http.NewRequest("POST", uri, r)
  if err != nil {
	return nil, err
  }
  request.Header.Set("Content-Type", writer.FormDataContentType())
  return request, nil
}

答案2

得分: 1

使用io.Pipe() - https://golang.org/pkg/io/#Pipe

你需要使用一个 goroutine,但这并不难做到。如果你需要一个具体的示例,请留下评论,我会为你提供一个。

英文:

Use io.Pipe() - https://golang.org/pkg/io/#Pipe

You will need to use a goroutine but it isn't too hard to do. If you need a specific example leave a comment and I'll get one for you.

huangapple
  • 本文由 发表于 2017年2月16日 06:37:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/42261421.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定