解决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原生库的中bytes.Buffer用法
Apr 25 Golang
golang 实现两个结构体复制字段
Apr 28 Golang
修改并编译golang源码的操作步骤
Jul 25 Golang
go使用Gin框架利用阿里云实现短信验证码功能
Aug 04 Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 Golang
Go 通过结构struct实现接口interface的问题
Oct 05 Golang
Go语言基础函数基本用法及示例详解
Nov 17 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
GO语言异常处理分析 err接口及defer延迟
Apr 14 Golang
Golang bufio详细讲解
Apr 21 Golang
Go 内联优化让程序员爱不释手
Jun 21 Golang
Go gorilla securecookie库的安装使用详解
Aug 14 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 变量定义方法
2009/06/14 PHP
PHP执行linux系统命令的常用函数使用说明
2010/04/27 PHP
PHP数组及条件,循环语句学习
2012/11/11 PHP
PHP中empty和isset对于参数结构的判断及empty()和isset()的区别
2015/11/15 PHP
Laravel框架模板加载,分配变量及简单路由功能示例
2018/06/11 PHP
基于jQuery实现点击同时更改两个iframe的网址
2010/07/01 Javascript
js截取函数(indexOf,join等)
2010/09/01 Javascript
Jsonp 跨域的原理以及Jquery的解决方案
2011/06/27 Javascript
jQuery实现当按下回车键时绑定点击事件
2014/01/28 Javascript
JavaScript中停止执行setInterval和setTimeout事件的方法
2015/05/14 Javascript
深入理解JavaScript中的对象
2015/06/04 Javascript
jQuery实现大转盘抽奖活动仿QQ音乐代码分享
2015/08/21 Javascript
AngularJS 自定义指令详解及示例代码
2016/08/17 Javascript
微信小程序 解决请求服务器手机预览请求不到数据的方法
2017/01/04 Javascript
css配合JavaScript实现tab标签切换效果
2018/10/11 Javascript
微信小程序实现弹出层效果
2020/05/26 Javascript
Javascript实现html转pdf高清版(提高分辨率)
2020/02/19 Javascript
Javascript摸拟自由落体与上抛运动原理与实现方法详解
2020/04/08 Javascript
react-native 实现购物车滑动删除效果的示例代码
2021/01/15 Javascript
[03:40]2014DOTA2国际邀请赛 B神专访:躲箭真的很难
2014/07/13 DOTA
python实现基于两张图片生成圆角图标效果的方法
2015/03/26 Python
举例讲解Python面向对象编程中类的继承
2016/06/17 Python
Python利用turtle库绘制彩虹代码示例
2017/12/20 Python
python 实现倒排索引的方法
2018/12/25 Python
django组合搜索实现过程详解(附代码)
2019/08/06 Python
TensorFlow设置日志级别的几种方式小结
2020/02/04 Python
Python实现队列的方法示例小结【数组,链表】
2020/02/22 Python
selenium+python配置chrome浏览器的选项的实现
2020/03/18 Python
Python内置方法和属性应用:反射和单例(推荐)
2020/06/19 Python
python实现企业微信定时发送文本消息的示例代码
2020/11/24 Python
热能动力工程毕业生自荐信
2013/11/07 职场文书
幼儿园教师师德师风演讲稿:我自豪我是一名幼师
2014/09/10 职场文书
2015年度销售个人工作总结
2015/03/31 职场文书
2015年市场营销工作总结
2015/07/23 职场文书
SQL Server基本使用和简单的CRUD操作
2021/04/05 SQL Server
LyScript实现绕过反调试保护的示例详解
2022/08/14 Python