解决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 相关文章推荐
golang中实现给gif、png、jpeg图片添加文字水印
Apr 26 Golang
golang中的空slice案例
Apr 27 Golang
golang json数组拼接的实例
Apr 28 Golang
Go 实现英尺和米的简单单位换算方式
Apr 29 Golang
golang 接口嵌套实现复用的操作
Apr 29 Golang
golang slice元素去重操作
Apr 30 Golang
go web 预防跨站脚本的实现方式
Jun 11 Golang
Go 语言中 20 个占位符的整理
Oct 16 Golang
golang操作redis的客户端包有多个比如redigo、go-redis
Apr 14 Golang
golang使用map实现去除重复数组
Apr 14 Golang
Golang jwt身份认证
Apr 20 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 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优化需要注意的地方
2014/11/27 PHP
PHP基于反射机制实现插件的可插拔设计详解
2016/11/10 PHP
php获取excel文件数据
2017/04/21 PHP
JQUERY 对象与DOM对象之两者相互间的转换
2009/04/27 Javascript
JavaScript DOM 学习第九章 选取范围的介绍
2010/02/19 Javascript
jquery中交替点击事件toggle方法的使用示例
2013/12/08 Javascript
jQuery学习笔记之jQuery原型属性和方法
2014/06/09 Javascript
基于javascript的COOkie的操作实现只能点一次
2014/12/26 Javascript
浅谈重写window对象的方法
2014/12/29 Javascript
jQuery使用slideUp方法实现控制元素缓慢收起
2015/03/27 Javascript
JS实现兼容性好,带缓冲的动感网页右键菜单效果
2015/09/18 Javascript
利用vue实现模态框组件
2016/12/19 Javascript
详解React Native网络请求fetch简单封装
2017/08/10 Javascript
AngularJS模态框模板ngDialog的使用详解
2018/05/11 Javascript
详解使用VueJS开发项目中的兼容问题
2018/08/02 Javascript
Vue 通过自定义指令回顾v-内置指令(小结)
2018/09/03 Javascript
Vue实现本地购物车功能
2018/12/05 Javascript
关于Vue Router中路由守卫的应用及在全局导航守卫中检查元字段的方法
2018/12/09 Javascript
Vue通过配置WebSocket并实现群聊功能
2019/12/31 Javascript
原生JS实现无缝轮播图片
2020/06/24 Javascript
[01:13]这,就是刀塔
2014/07/16 DOTA
Python高效编程技巧
2013/01/07 Python
跟老齐学Python之用while来循环
2014/10/02 Python
HTML中使用python屏蔽一些基本功能的方法
2017/07/07 Python
python打包生成的exe文件运行时提示缺少模块的解决方法
2018/10/31 Python
修改 CentOS 6.x 上默认Python的方法
2019/09/06 Python
解决django框架model中外键不落实到数据库问题
2020/05/20 Python
Python如何实现爬取B站视频
2020/05/20 Python
CSS实现半透明边框与多重边框的场景分析
2019/11/13 HTML / CSS
中国排名第一的外贸销售网站:LightInTheBox.com(兰亭集势)
2016/10/28 全球购物
瑞典多品牌连锁店:Johnells
2021/01/13 全球购物
英语专业毕业生求职信
2014/05/24 职场文书
三严三实对照检查材料
2014/09/22 职场文书
三人合伙协议书范本
2014/10/29 职场文书
php7中停止php-fpm服务的方法详解
2021/05/09 PHP
一篇带你入门Java垃圾回收器
2021/06/16 Java/Android