Golang 多部分文件表单请求

2023-12-01

我正在针对 Mapbox 编写一个 API 客户端,将一批 svg 图像上传到自定义地图。他们为此提供的 api 记录了一个运行良好的 cUrl 调用示例:

curl -F images=@include/mapbox/sprites_dark/aubergine_selected.svg "https://api.mapbox.com/styles/v1/<my_company>/<my_style_id>/sprite?access_token=$MAPBOX_API_KEY" --trace-ascii /dev/stdout

当尝试从 golang 执行相同操作时,我很快发现多形式库非常有限,并编写了一些代码来发出类似于上面提到的 cUrl 请求的请求。

func createMultipartFormData(fileMap map[string]string) (bytes.Buffer, *multipart.Writer) {
    var b bytes.Buffer
    var err error
    w := multipart.NewWriter(&b)
    var fw io.Writer
    for fileName, filePath := range fileMap {

        h := make(textproto.MIMEHeader)
        h.Set("Content-Disposition",
            fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "images", fileName))
        h.Set("Content-Type", "image/svg+xml")

        if fw, err = w.CreatePart(h); err != nil {
            fmt.Printf("Error creating form File %v, %v", fileName, err)
            continue
        }

        fileContents, err := ioutil.ReadFile(filePath)
        fileContents = bytes.ReplaceAll(fileContents, []byte("\n"), []byte("."))

        blockSize := 64
        remainder := len(fileContents) % blockSize
        iterations := (len(fileContents) - remainder) / blockSize

        newBytes := []byte{}
        for i := 0; i < iterations; i++ {
            start := i * blockSize
            end := i*blockSize + blockSize
            newBytes = append(newBytes, fileContents[start:end]...)
            newBytes = append(newBytes, []byte("\n")...)
        }

        if remainder > 0 {
            newBytes = append(newBytes, fileContents[iterations*blockSize:]...)
            newBytes = append(newBytes, []byte("\n")...)
        }

        if err != nil {
            fmt.Printf("Error reading svg file: %v: %v", filePath, err)
            continue
        }

        _, err = fw.Write(newBytes)

        if err != nil {
            log.Debugf("Could not write file to multipart: %v, %v", fileName, err)
            continue
        }
    }

    w.Close()

    return b, w
}

除了在实际请求中设置标头之外:

    bytes, formWriter := createMultipartFormData(filesMap)

    req, err := http.NewRequest("Post", fmt.Sprintf("https://api.mapbox.com/styles/v1/%v/%v/sprite?access_token=%v", "my_company", styleID, os.Getenv("MAPBOX_API_KEY")), &bytes)

    if err != nil {
        return err
    }

    req.Header.Set("User-Agent", "curl/7.64.1")
    req.Header.Set("Accept", "*/*")
    req.Header.Set("Content-Length", fmt.Sprintf("%v", len(bytes.Bytes())))
    req.Header.Set("Content-Type", formWriter.FormDataContentType())

    byts, _ := httputil.DumpRequest(req, true)
    fmt.Println(string(byts))

    res, err := http.DefaultClient.Do(req)

甚至想尽可能限制行长度并复制 cUrl 使用的编码,但到目前为止还没有运气。有经验的人知道为什么这在 cUrl 中有效但在 golang 中无效吗?


好吧,我承认解决你的任务的“难题”的所有部分都可以在网上找到,这有两个问题:

  • 他们经常错过某些有趣的细节。
  • 有时,他们给出的建议完全不正确。

所以,这是一个可行的解决方案。

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "mime"
    "mime/multipart"
    "net/http"
    "net/textproto"
    "net/url"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

func main() {
    const (
        dst   = "https://api.mapbox.com/styles/v1/AcmeInc/Style_001/sprite"
        fname = "path/to/a/sprite/image.svg"
        token = "an_invalid_token"
    )

    err := post(dst, fname, token)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func post(dst, fname, token string) error {
    u, err := url.Parse(dst)
    if err != nil {
        return fmt.Errorf("failed to parse destination url: %w", err)
    }

    form, err := makeRequestBody(fname)
    if err != nil {
        return fmt.Errorf("failed to prepare request body: %w", err)
    }

    q := u.Query()
    q.Set("access_token", token)
    u.RawQuery = q.Encode()

    hdr := make(http.Header)
    hdr.Set("Content-Type", form.contentType)
    req := http.Request{
        Method:        "POST",
        URL:           u,
        Header:        hdr,
        Body:          ioutil.NopCloser(form.body),
        ContentLength: int64(form.contentLen),
    }

    resp, err := http.DefaultClient.Do(&req)
    if err != nil {
        return fmt.Errorf("failed to perform http request: %w", err)
    }
    defer resp.Body.Close()

    _, _ = io.Copy(os.Stdout, resp.Body)

    return nil
}

type form struct {
    body        *bytes.Buffer
    contentType string
    contentLen  int
}

func makeRequestBody(fname string) (form, error) {
    ct, err := getImageContentType(fname)
    if err != nil {
        return form{}, fmt.Errorf(
            `failed to get content type for image file "%s": %w`,
            fname, err)
    }

    fd, err := os.Open(fname)
    if err != nil {
        return form{}, fmt.Errorf("failed to open file to upload: %w", err)
    }
    defer fd.Close()

    stat, err := fd.Stat()
    if err != nil {
        return form{}, fmt.Errorf("failed to query file info: %w", err)
    }

    hdr := make(textproto.MIMEHeader)
    cd := mime.FormatMediaType("form-data", map[string]string{
        "name":     "images",
        "filename": fname,
    })
    hdr.Set("Content-Disposition", cd)
    hdr.Set("Contnt-Type", ct)
    hdr.Set("Content-Length", strconv.FormatInt(stat.Size(), 10))

    var buf bytes.Buffer
    mw := multipart.NewWriter(&buf)

    part, err := mw.CreatePart(hdr)
    if err != nil {
        return form{}, fmt.Errorf("failed to create new form part: %w", err)
    }

    n, err := io.Copy(part, fd)
    if err != nil {
        return form{}, fmt.Errorf("failed to write form part: %w", err)
    }

    if int64(n) != stat.Size() {
        return form{}, fmt.Errorf("file size changed while writing: %s", fd.Name())
    }

    err = mw.Close()
    if err != nil {
        return form{}, fmt.Errorf("failed to prepare form: %w", err)
    }

    return form{
        body:        &buf,
        contentType: mw.FormDataContentType(),
        contentLen:  buf.Len(),
    }, nil
}

var imageContentTypes = map[string]string{
    "png":  "image/png",
    "jpg":  "image/jpeg",
    "jpeg": "image/jpeg",
    "svg":  "image/svg+xml",
}

func getImageContentType(fname string) (string, error) {
    ext := filepath.Ext(fname)
    if ext == "" {
        return "", fmt.Errorf("file name has no extension: %s", fname)
    }

    ext = strings.ToLower(ext[1:])
    ct, found := imageContentTypes[ext]
    if !found {
        return "", fmt.Errorf("unknown file name extension: %s", ext)
    }

    return ct, nil
}

一些关于实现的随机注释可以帮助您理解这些概念:

  • 为了构建请求的有效负载(主体),我们使用bytes.Buffer实例。
    它有一个很好的属性,即指向它的指针(*bytes.Buffer)同时实现io.Writer and io.Reader因此可以很容易地与处理 I/O 的 Go stdlib 的其他部分组合。
  • 当准备发送多部分表单时,我们不会将整个文件的内容放入内存中,而是将它们直接“管道”到“多部分表单编写器”中。
  • 我们有一个查找表,它将要提交的文件名的扩展名映射到其 MIME 类型;我不知道 API 是否需要这个;如果不是真的需要,准备包含文件的表单字段的代码部分可以简化很多,但 cURL 会发送它,我们也是如此。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang 多部分文件表单请求 的相关文章

  • 通过执行本机操作系统的命令(例如curl)通过MySQL调用HTTP GET请求

    我使用的是在 32 位 Microsoft Windows XP 专业版 2002 Service Pack 3 上运行的 MySQL 5 6 11 我安装了MySQLsys exec https github com mysqludf l
  • 当变量更新时动态刷新模板的一部分golang

    在Golang中 当变量更新时可以刷新模板的一部分吗 例如 我们可以在 Angular js 中找到这一点 基本上在我的代码中 我通过 ajax 中的邮政编码查找地址 它显示我找到的该邮政编码的用户列表 Here is a sample o
  • 将cURL请求转换为node.js的请求

    这是我的有效 cURL 命令 curl https www example com api data jsonrpc 2 0 method getObjectsByFilter id 3 这是我在 Node js 中尝试过的 var url
  • Go MSSQL 连接

    如何提供 MSSQL 连接 它说它始终与代码相关 即使信息不正确 也不会报错 package main import database sql fmt github com denisenkom go mssqldb log var ser
  • Golang Appengine 项目无法构建

    我有一个使用 golang 的应用程序引擎项目 我已经大约一年没有碰过了 我现在无法让它在之前构建的机器上构建 我收到以下错误 go app builder 解析输入失败 解析器 src golang org x net internal
  • 如何在C++中使用Curl获取HTTP响应字符串

    我对 HTTP 命令和 libcurl 库非常陌生 我知道如何获取 HTTP 响应代码 但不知道如何获取 HTTP 响应字符串 以下是我为获取响应代码而编写的代码片段 任何有关如何获取响应字符串的帮助将不胜感激 curl easy seto
  • 文件再次获取内容不起作用替代方案

    我尝试了curl 因为file get contents在php中不起作用 任何人都可以给我解决方案 所以使用curl解决它 这里是代码 userData json decode file get contents https graph
  • 共享来自单独命令/进程的属性

    我提供带有多个命令和子命令的命令行工具 我使用cobra https github com spf13 cobra命令行 我有两个单独的命令首先是前提条件e 给其他人 例如第一个命令是通过创建临时文件夹并验证某些文件来首选环境 第二个命令应
  • 在函数中将通道作为参数传递的不同方法

    我正在阅读一些Go代码 并说了几种传递Go通道的不同方法 也许它们是相同的 但我想知道是否有任何区别 因为我无法在线找到文档 1 func serve ch lt chan interface do stuff 2 func serve c
  • 无法添加 laravel/homestead 盒子。 “SSL 证书问题..”。视窗

    我已经在另外两台机器上安装了 laravel homestead 以前从未见过这个问题 我搜索了又搜索 实施了大量建议的修复方案 但没有任何效果对我有用 我已经安装了 virtual box 和 vagrant 但我陷入了第一个障碍 vag
  • 在golang中获取TTFB(第一个字节的时间)值

    我正在尝试获取 TTFB 值和 Connect 值 c exec Command curl w Connect time connect TTFB time starttransfer Total time time total o dev
  • 如何从非英语字符串解析go中的月份

    我想将以下字符串解析为 go 中的日期 This item will be released on March 9 2014 我跟着this https stackoverflow com questions 14106541 go par
  • Go 的范围不能超过 (类型接口 {})

    我正处于尝试将我的注意力集中在 Go 上的婴儿阶段 目前 我正在模拟一个 API 请求 该请求返回包含对象数组的 JSON 格式的字符串 我试图找出迭代每个记录并访问每个字段的最合适的方法 最终 每个字段都将写入 Excel 电子表格 但现
  • 如何在 PHP 中使用 cURL 发出同时包含 GET 和 POST 参数的请求?

    其他人已经问过如何从 perl java bash 等执行此操作 但我需要在 PHP 中执行此操作 并且我没有看到任何已提出的专门与 PHP 相关的问题 或包含 PHP 的答案 My code ch curl init url curl s
  • 重新插入通道导致死锁

    我有稳定的入站 作业 流 将其输入到无缓冲通道中 我有一个for range循环来迭代项目并处理它们 如果处理该项目失败 我会将项目重新插入通道中 以便稍后重试 问题是当我将项目重新插入通道时 它陷入僵局 我明白为什么会发生这种情况 处理器
  • (转)如何使用toml文件?

    正如标题 我想知道如何使用 golang 中的 toml 文件 在此之前 我展示了我的 toml 示例 这样对吗 datatitle enable true userids 12345 67890 datatitle 12345 prop1
  • 编写每个处理程序中间件

    我希望从处理程序中提取一些重复的逻辑 并将其放入一些每个处理程序的中间件中 特别是 CSRF 检查 检查现有会话值 即身份验证或预览页面 等 我读了关于此的几篇文章 http justinas org writing http middle
  • GORM中的一对多递归关系

    我需要有一个Organization与父级有关系 像这样的事情 type Organization struct gorm Model Parent Organization gorm ForeignKey ParentId Name st
  • Go 编程语言中的“方法需要指针接收器”

    我刚刚看到了 Go 编程语言的演示 并想尝试写几行 一切工作正常 直到我尝试在这种情况下使用界面 我该如何解决这个问题 package main import fmt type entity float32 func e entity in
  • 如何使用 mediawiki 的 api、curl 和 bash 登录?

    我对流程的理解 来自 mediawikis 登录手册https www mediawiki org wiki API 登录 https www mediawiki org wiki API Login 使用 MediaWiki 的 Web

随机推荐

  • R:从 5 个元素组合的数据框中提取内部更高级别的组合(1、2、3 和 4 个元素的组)

    抱歉 我必须跟进另一个问题this one and 另一个 虽然第二个问题的答案完美地解决了 MWE 但在我的现实世界数据中 我需要以不同的方式做事 并且想知道是否有人可以提供帮助 所以这一次 我的起点是一个数据框 名为plusminus
  • Android 应用程序无法在模拟器上启动

    我只是按照这个设置eclipse开始android开发http developer android com sdk installing html 我的问题似乎与此类似 Android 应用程序无法在模拟器上启动 但该解决方案不起作用 我正
  • 使用 jackson 反序列化包含 @JsonFormat(shape=JsonFormat.Shape.ARRAY) 和自定义对象的 json

    我有一个自定义对象 public class Response JsonProperty values private List
  • 如何通过 Java 在 SSH 中运行多个命令?

    如何使用 Java 运行时在 SSH 中运行多个命令 使用较新的流程构建器类而不是Runtime exec 您可以通过指定程序及其参数列表来构造一个程序 如下面我的代码所示 您不需要在命令周围使用单引号 您还应该阅读 stdout 和 st
  • 检测链接是否已被单击,如果已单击则应用不同的 CSS 类

    所以我想做的是确定当前链接是否处于 活动 状态 即用户是否刚刚单击了该链接 如果是的话 那么我想应用class selected对此 li item 这就是我正在处理的事情 ul li a href Link 1 a li li a hre
  • ExtJS 4 / Sencha Touch 容器、组件、元素和面板

    Container Component Element Panel之间有何联系或区别 请帮忙 如果短暂 Element是 DOM 元素的包装器 成分是所有小部件的基本类 容器是组件的子类 它可以有 项目 即容器可以包含其他组件 Panel是
  • S3分块上传异常

    使用 S3 MultiPartAPI 我得到一个异常 您建议的上传内容小于允许的最小大小 我用 11MB 和 7MB 的 2 个文件进行了测试 1MB 就可以正常工作 FileStream fs new FileStream foo Fil
  • Django基于添加表单编辑表单?

    我制作了一个漂亮的表单 以及一个用于处理它的大而复杂的 添加 函数 事情是这样开始的 def add req if req method POST form ArticleForm req POST if form is valid art
  • 我们可以与 psql 脚本交互吗?

    我们可以做类似的事情吗 echo Type username to show its properties SELECT FROM mY users WHERE username echo End of script 在 psql 脚本文件
  • mysql 获取字符串的 ascii 代码转储

    在MySQL中 有没有一种简单的方法SELECT获取 a 中每个字符的 ASCII 代码 代码点序列varchar价值 我比较熟悉Oracle 它有DUMP可以用于此目的的函数 例如 select some function abcd 会返
  • 在 HTML 选项卡条中创建选定的选项卡

    2022 年 2 月 17 日 我花了一段时间 但我弄清楚发生了什么事 每个 TD 都有一个锚点 A 标签 并且 TD 和锚点都有类别 锚点只有一个类 但它是最后应用的 因此 TD 显然将该类作为其类列表的一部分 因此 当前类将应用于锚标记
  • 无法解析 styles.xml 中的符号“主题”(Android Studio)

    从今天起 Android Studio 无法在 styles xml 中找到 AppCompat 主题 但例如代码中的 AppCompatActivity 确实可以识别 我的 Android Studio 版本是 2 2 2 Build A
  • 具有单个全局变量的 cpp 文件将被忽略

    我正在尝试初始化全局地图 std map
  • awk 根据列合并行

    想要根据第一列合并行 1到行并格式化输出 打印时需要生成headerMax Unique count of first field 例如 安哥拉显示 count 3 巴西显示 count 5 赞比亚显示 count 1 字段 1 的最大唯一
  • NginX 友好的 PHP 框架

    我正在寻找一种 PHP 框架 如果幸运的话 它只能在 FastCGI 下的 nginx 中工作 否则 不需要太多调整 Symfony 1 4 与 nginx 非常棒 我已经完成了调整 这是我的生产配置的概括 我可以保证它适合生产使用 ser
  • 删除因子级别的“空单元格”

    我有一个数据框 其中有一列 列中有一些数据和一些空单元格 当我检查该列的级别时 它显示三个级别 因为它将空单元格作为一个级别 我想删除那个级别 假设我有 editor note starting from R 4 0 0 stringsAs
  • HTML输入24位格式的时间

    我正在使用下面的 HTML 标签
  • 黑莓条码扫描库?

    有人很好地掌握了条形码扫描库 可以根据数码相机的输入读取 UPC A EAN 13 或其他主要条形码格式吗 RIM 是否有可用于此的标准库 我知道 BlackBerry Messenger 内置了 2D 条码扫描功能 所以我猜测一定有可用的
  • 将上下文菜单添加到 Inno Setup 页面

    如何将一些上下文菜单添加到 Inno Setup 的特定页面 例如 在安装页面中 如果用户右键单击页面 他可以看到 取消 或 暂停 菜单项 可以执行一些操作 Inno Setup 没有上下文菜单 API 甚至没有用于处理鼠标点击的 API
  • Golang 多部分文件表单请求

    我正在针对 Mapbox 编写一个 API 客户端 将一批 svg 图像上传到自定义地图 他们为此提供的 api 记录了一个运行良好的 cUrl 调用示例 curl F images include mapbox sprites dark