解决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 相关文章推荐
go语言-在mac下brew升级golang
Apr 25 Golang
用golang如何替换某个文件中的字符串
Apr 25 Golang
golang如何去除多余空白字符(含制表符)
Apr 25 Golang
Go语言中的UTF-8实现
Apr 26 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
go语言中GOPATH GOROOT的作用和设置方式
May 05 Golang
基于Golang 高并发问题的解决方案
May 08 Golang
go语言中http超时引发的事故解决
Jun 02 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
Go 通过结构struct实现接口interface的问题
Oct 05 Golang
在ubuntu下安装go开发环境的全过程
Aug 05 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
微盾PHP脚本加密专家php解密算法
2020/09/13 PHP
PHP CURL获取cookies模拟登录的方法
2013/11/04 PHP
php查看请求头信息获取远程图片大小的方法分享
2013/12/25 PHP
smarty模板中拼接字符串的方法
2014/02/14 PHP
PHP执行linux命令常用函数汇总
2016/02/02 PHP
PHP中isset、empty的用法与区别示例详解
2020/11/05 PHP
PHP实现倒计时功能
2020/11/16 PHP
ComboBox 和 DateField 在IE下消失的解决方法
2013/08/30 Javascript
jquery iframe操作详细解析
2013/11/20 Javascript
Node.js编写爬虫的基本思路及抓取百度图片的实例分享
2016/03/12 Javascript
jquery获取form表单input元素值的简单实例
2016/05/30 Javascript
jquery.validate表单验证插件使用方法解析
2016/11/07 Javascript
Easyui ueditor 整合解决不能编辑的问题(推荐)
2017/06/25 Javascript
Angular自定义组件实现数据双向数据绑定的实例
2017/12/11 Javascript
微信小程序实现点击图片旋转180度并且弹出下拉列表
2018/11/27 Javascript
关于vue状态过渡transition不起作用的原因解决
2019/04/09 Javascript
vue中使用props传值的方法
2019/05/08 Javascript
javascript设计模式 ? 命令模式原理与用法实例分析
2020/04/20 Javascript
原生微信小程序开发中 redux 的使用详解
2021/02/18 Javascript
[01:46]辉夜杯—打造中国DOTA新格局
2015/12/25 DOTA
Python中splitlines()方法的使用简介
2015/05/20 Python
Linux下通过python访问MySQL、Oracle、SQL Server数据库的方法
2016/04/23 Python
Python中functools模块函数解析
2017/03/12 Python
详解python实现读取邮件数据并下载附件的实例
2017/08/03 Python
12个步骤教你理解Python装饰器
2019/07/01 Python
Python Django框架防御CSRF攻击的方法分析
2019/10/18 Python
详解使用django-mama-cas快速搭建CAS服务的实现
2019/10/30 Python
PyCharm如何导入python项目的方法
2020/02/06 Python
python 写一个性能测试工具(一)
2020/10/24 Python
New Balance德国官方网站:购买鞋子和服装
2019/08/31 全球购物
班级安全教育实施方案
2014/02/23 职场文书
公司年底活动方案
2014/08/17 职场文书
考试作弊检讨书
2014/10/21 职场文书
《改造我们的学习》心得体会
2014/11/07 职场文书
python将图片转为矢量图的方法步骤
2021/03/30 Python
Python入门之基础语法详解
2021/05/11 Python