使用Golang的net/http包通过cURL表单发送文件

使用Golang的net/http包通过cURL表单发送文件 你好!

我正在尝试发起一个等效于 curl -d "file=@filename" https://xxx/upload 的 POST 请求。

我尝试了以下方法:

formData := url.Values{
		"file": {"@" + filename},
	}
	//req, err := http.NewRequest("POST", url, strings.NewReader(form.Encode()))
	resp, err := http.PostForm("https://api.anonfiles.com/upload", formData)

我认为我应该使用 CreateFormFile,但它的文档有些令人困惑。

任何帮助都将不胜感激!


更多关于使用Golang的net/http包通过cURL表单发送文件的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

这取决于相关缓冲区的实现。

虽然实现通常力求以不需要在内存中保存完整内容的方式进行,除非直接从内存读取或写入内存。

更多关于使用Golang的net/http包通过cURL表单发送文件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,Sean!非常感谢你的回复。

我忘记更新这个帖子了,但我最终用一个与你展示的方法类似的方式解决了问题:

mpb := bytes.NewBuffer(nil)
mw := multipart.NewWriter(mpb)

partWriter, err := mw.CreateFormFile("file", filename)
checkErr(err)
fileReader, err := os.Open(filename)
checkErr(err)
io.Copy(partWriter, fileReader)
mw.Close()

不过,我对此还有一个疑问。io.Copy 是否真的将文件内容复制到了计算机的 RAM 中?如果是的话,是否有更好的方法来上传大文件?

再次感谢你抽出时间!

你好,Vinicius,

那个 file=@filename 的写法是 curl 特有的。在 Go 中,你可能需要自己处理读取数据。我想下面的代码可以做到(或者至少能让你开始):

func POSTFile(filename string) (*http.Response, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return POSTData(f)
}

func POSTData(r io.Reader) (*http.Response, error) {
    sb := strings.Builder{}
    if _, err := io.Copy(sb, r); err != nil {
        return nil, err
    }
    formData := url.Values{
        "file": []string{sb.String()},
    }
    return http.PostForm("https://api.anonfiles.com/upload", formData)
}

请注意,这只在你的文件是文本文件时才有效,但我想这与 curl 的 -d, --data 参数的使用方式是匹配的。

要使用Golang的net/http包发送文件,你需要使用multipart/form-data编码,而不是简单的application/x-www-form-urlencoded。以下是正确的实现方式:

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

func uploadFile(url, filename string) error {
    // 打开文件
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // 创建multipart writer
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    
    // 创建表单文件字段
    part, err := writer.CreateFormFile("file", filename)
    if err != nil {
        return err
    }
    
    // 复制文件内容到表单
    _, err = io.Copy(part, file)
    if err != nil {
        return err
    }
    
    // 关闭writer以完成multipart消息
    err = writer.Close()
    if err != nil {
        return err
    }

    // 创建请求
    req, err := http.NewRequest("POST", url, body)
    if err != nil {
        return err
    }
    
    // 设置Content-Type头
    req.Header.Set("Content-Type", writer.FormDataContentType())

    // 发送请求
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // 读取响应
    respBody, err := io.ReadAll(resp.Body)
    if err != nil {
        return err
    }
    
    fmt.Printf("Response: %s\n", respBody)
    return nil
}

func main() {
    err := uploadFile("https://api.anonfiles.com/upload", "example.txt")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
}

如果你想要更简洁的写法,也可以使用http.Post

func uploadFileSimple(url, filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    part, _ := writer.CreateFormFile("file", filename)
    io.Copy(part, file)
    writer.Close()

    resp, err := http.Post(url, writer.FormDataContentType(), body)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)
    fmt.Printf("Response: %s\n", respBody)
    return nil
}

关键点:

  1. 使用multipart.NewWriter创建multipart writer
  2. 使用CreateFormFile创建文件字段,第一个参数是表单字段名(对应cURL中的file=),第二个参数是文件名
  3. 必须设置正确的Content-Type头:multipart/form-data,并包含boundary
  4. 这完全等同于curl -F "file=@filename"命令
回到顶部