解决golang post文件时Content-Type出现的问题


Posted in Golang onMay 02, 2021

同事用php写了一个接口,要上传文件,让我做下测试,直接用curl命令调用成功,然后想用golang写个示例,

源码如下:

package main 
import (
    "bytes" 
    "fmt" 
    "io/ioutil" 
    "mime/multipart" 
    "net/http" 
)
 
func main() { 
    uri := "http://xxxxxxxxxxxx/api/fileattr" //URL地址 xxxxxxxxxxxx由商务提供 
    name := "xxxxxxxxxxxx" //用户名 
    pass := "xxxxxxxxxxxx" //密码 
    fn := "xxxxxxxxxxxx.txt" //文件路径
 
    //读出文本文件数据 
    file_data, _ := ioutil.ReadFile(fn) 
    body := new(bytes.Buffer) 
    w := multipart.NewWriter(body)
 
    //取出内容类型 
    content_type := w.FormDataContentType() 
    //将文件数据写入 
    pa, _ := w.CreateFormFile("file", fn) 
    pa.Write(file_data) 
    //设置用户名密码 
    w.WriteField("name", name) 
    w.WriteField("pass", pass) 
    w.Close() 
    //开始提交
 
    req, _ := http.NewRequest("POST", uri, body) 
    req.Header.Set("Content-Type", content_type) 
    resp, _ := http.DefaultClient.Do(req) 
    data, _ := ioutil.ReadAll(resp.Body) 
    resp.Body.Close() 
    fmt.Println(resp.StatusCode) 
    fmt.Printf("%s", data) 
}

发现总是调用失败,返回文件类型不对,询问后得知,同事做了判断,文件只能为text/plain类型,抓包发现,我提交时的文件类型为:application/octet-stream,仔细查看golang源码:mime/multipart/write.go,CreateFormFile的源码是这样的:

func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) { 
    h := make(textproto.MIMEHeader) 
    h.Set("Content-Disposition", 
        fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 
            escapeQuotes(fieldname), escapeQuotes(filename))) 
    h.Set("Content-Type", "application/octet-stream") 
    return w.CreatePart(h) 
}

可以得知Content-Type被固定为了application/octet-stream,知道原因了,问题就好解决了。

第一种方法

就是直接修改CreateFormFile,或者加个CreateFormFile2命令,这种方法将来golang升级后可能会出问题。

第二种方法

可以自己来CreatePart:

h := make(textproto.MIMEHeader)
    h.Set("Content-Disposition",
        fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
            escapeQuotes(fieldname), escapeQuotes(filename)))
    h.Set("Content-Type", "text/plain")

再用 w.CreatePart(h)得到io.Writer,问题解决!这种方法不侵入golang源代码,最终代码如下:

package main 
import (
    "bytes"
    "fmt"
    "io/ioutil"
    "mime/multipart"
    "net/http"
    "net/textproto"
)
 
func main() {
    uri := "http://xxxxxxxxxxxx/api/fileattr" //URL地址 xxxxxxxxxxxx由商务提供
    name := "xxxxxxxxxx"                      //用户名
    pass := "xxxxxxx"                         //密码
    fn := "x:/xxx/xxx.txt"                    //文件路径
 
    //读出文本文件数据
    file_data, _ := ioutil.ReadFile(fn)
 
    body := new(bytes.Buffer)
    w := multipart.NewWriter(body)
 
    //取出内容类型
    content_type := w.FormDataContentType()
 
    //将文件数据写入
    h := make(textproto.MIMEHeader)
    h.Set("Content-Disposition",
        fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
            "file", //参数名为file
            fn))
    h.Set("Content-Type", "text/plain") //设置文件格式
    pa, _ := w.CreatePart(h)
    pa.Write(file_data)
 
    //设置用户名密码
    w.WriteField("name", name)
    w.WriteField("pass", pass)
 
    w.Close() 
    //开始提交
    req, _ := http.NewRequest("POST", uri, body)
    req.Header.Set("Content-Type", content_type)
    resp, _ := http.DefaultClient.Do(req)
    data, _ := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    fmt.Println(resp.StatusCode)
    fmt.Printf("%s", data)
}

补充:用go来玩最简单的web服务器------顺便说说Content-Type字段

web服务端代码s.go:

package main 
import (
    "io"
    "log"
    "net/http"
)
 
func handlerHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello girls")
}
 
func main() {
    http.HandleFunc("/hello", handlerHello)     // 注册   
    err := http.ListenAndServe("localhost:8080", nil)
    if err != nil {
        log.Println(err)
    }
}

go run s.go一下,跑起来, 然后在浏览器执行http://127.0.0.1:8080/hello (或者在命令行用curl发http请求也可以), 浏览器上的结果为:

hello girls

好简单。可以在客户端或者服务端抓包看下, 很典型的http req和rsp.

我们再来看一个有趣的问题, 修改s.go为:

package main 
import (
    "io"
    "log"
    "net/http"
)
 
func handlerHello(w http.ResponseWriter, r *http.Request) {
    str := `
        table border="1">
        <tr>
        <td>row 1, cell 1</td>
        <td>row 1, cell 2</td>
        </tr>
        <tr>
        <td>row 2, cell 1</td>
        <td>row 2, cell 2</td>
        </tr>
        </table>
        ` 
    io.WriteString(w, str)
}
 
func main() {
    http.HandleFunc("/hello", handlerHello)     // 注册   
    err := http.ListenAndServe("localhost:8080", nil)
    if err != nil {
        log.Println(err)
    }
}

再次重启服务并发请求, 浏览器上显示的内容是:

table border="1">
 <tr>
	 <td>row 1, cell 1</td>
	 <td>row 1, cell 2</td>
 </tr>
 <tr>
	 <td>row 2, cell 1</td>
	 <td>row 2, cell 2</td>
 </tr>
</table>

抓包看一下, 发现有:Content-Type: text/plain; charset=utf-8

因此, 浏览器需要根据纯文本显示。 注意到, 上述的table左边少了一个"<". 我们加上后,

s.go的代码如下:

package main 
import (
    "io"
    "log"
    "net/http"
)
 
func handlerHello(w http.ResponseWriter, r *http.Request) {
    str := `
        <table border="1">
        <tr>
        <td>row 1, cell 1</td>
        <td>row 1, cell 2</td>
        </tr>
        <tr>
        <td>row 2, cell 1</td>
        <td>row 2, cell 2</td>
        </tr>
        </table>
        ` 
    io.WriteString(w, str)
}
 
func main() {
    http.HandleFunc("/hello", handlerHello)     // 注册   
    err := http.ListenAndServe("localhost:8080", nil)
    if err != nil {
        log.Println(err)
    }
}

再次重启服务,发请求,浏览器端的显示是:

row 1, cell 1 row 1, cell 2
row 2, cell 1 row 2, cell 2

抓包看, 有Content-Type: text/html; charset=utf-8

可见, 服务端会判断str的格式,来确定Content-Type的类型, 从而决定了浏览器端的展示方式。服务端的自动判断行为, 有点意思。 在我看来, 这样不太好,应该让程序员来指定Content-Type.

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
goland 清除所有的默认设置操作
Apr 28 Golang
golang 生成对应的数据表struct定义操作
Apr 28 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
golang 实现Location跳转方式
May 02 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
go web 预防跨站脚本的实现方式
Jun 11 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
简单聊聊Golang中defer预计算参数
Mar 25 Golang
Golang数据类型和相互转换
Apr 12 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 Golang
GO语言异常处理分析 err接口及defer延迟
Apr 14 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 Golang
对Golang中的FORM相关字段理解
May 02 #Golang
解决go在函数退出后子协程的退出问题
Apr 30 #Golang
Go语言 go程释放操作(退出/销毁)
golang DNS服务器的简单实现操作
golang slice元素去重操作
Apr 30 #Golang
Golang中interface{}转为数组的操作
Apr 30 #Golang
解决Go gorm踩过的坑
Apr 30 #Golang
You might like
初品cakephp 入门基础
2012/02/16 PHP
那些年一起学习的PHP(三)
2012/03/22 PHP
thinkPHP中U方法加密传递参数功能示例
2018/05/29 PHP
JavaScript 使用技巧精萃(.net html
2009/04/25 Javascript
jQuery入门知识简介
2010/03/04 Javascript
jquery选择器需要注意的问题
2014/11/26 Javascript
js的toLowerCase方法用法实例
2015/01/27 Javascript
jQuery使用drag效果实现自由拖拽div
2015/06/11 Javascript
JavaScript生成二维码图片小结
2015/12/27 Javascript
Google 地图事件实例讲解
2016/08/06 Javascript
etmvc+jQuery EasyUI+combobox多值操作实现角色授权实例
2016/11/09 Javascript
基于react框架使用的一些细节要点的思考
2017/05/31 Javascript
利用JavaScript对中文(汉字)进行排序实例详解
2017/06/18 Javascript
vue 内置过滤器的使用总结(附加自定义过滤器)
2018/12/11 Javascript
详细介绍解决vue和jsp结合的方法
2020/02/06 Javascript
[02:17]快乐加倍!DOTA2食人魔魔法师至宝+迎霜节活动上线
2019/12/22 DOTA
从CentOS安装完成到生成词云python的实例
2017/12/01 Python
Python实现基本数据结构中队列的操作方法示例
2017/12/04 Python
对python3新增的byte类型详解
2018/12/04 Python
将python文件打包成EXE应用程序的方法
2019/05/22 Python
Python使用matplotlib绘制三维参数曲线操作示例
2019/09/10 Python
pip 安装库比较慢的解决方法(国内镜像)
2019/10/06 Python
Django单元测试中Fixtures的使用方法
2020/02/26 Python
手把手教你从PyCharm安装到激活(最新激活码),亲测有效可激活至2089年
2020/11/25 Python
html5 兼容IE6结构的实现代码
2012/05/14 HTML / CSS
boostrap modal 闪现问题的解决方法
2020/09/01 HTML / CSS
美国第一大药店连锁机构:Walgreens(沃尔格林)
2019/10/10 全球购物
通信工程专业女生个人求职信
2013/09/21 职场文书
艺术爱好者的自我评价分享
2013/10/08 职场文书
财务管理专业自荐信范文
2013/12/24 职场文书
绿化先进工作者事迹材料
2014/01/30 职场文书
大学生会计职业生涯规划范文
2014/02/28 职场文书
2015年护士工作总结范文
2015/03/31 职场文书
运动会开幕式新闻稿
2015/07/17 职场文书
《平行四边形的面积》教学反思
2016/02/16 职场文书
三八红旗手先进事迹材料(2016推荐版)
2016/02/25 职场文书