用go处理文件上传 [英] Handle file uploading with go
问题描述
我最近刚开始玩,所以我仍然是一个小菜鸟,抱歉,如果我犯了太多错误。我一直试图解决这个问题很长一段时间,但我只是不明白发生了什么。在我的main.go文件中,我有一个主要函数:
$ p $ func main(){
http.HandleFunc( /,处理程序)
http.HandleFunc(/ submit /,submit)
log.Fatal(http.ListenAndServe(:8080,nil))
}
处理函数如下所示:
func处理程序(w http.ResponseWriter,r * http.Request){
data,_:= ioutil.ReadFile(web / index.html)
w.Write数据)
}
我知道这不是提供网站$ b的最佳方式$ b submit函数如下所示:
func submit(w http.ResponseWriter,r * http.Request){
log.Println(METHOD IS+ r.Method +AND CONTENT-TYPE IS+ r.Header.Get(Content-Type))
r.ParseMultipartForm(32 <20 )
file,header,err:= r.FormFile(uploadFile)
if err!= nil {
json.NewEncoder(w).Encode(Response {err.Error() ,true})
返回
}
defer file.Close()
$ b $ out,err:= os.Create(/ tmp / file_+ time.Now()。String()+.png)
if err!= nil {
json.NewEncoder(w).Encode(Response {err.Error(),true})
return
}
推迟.Close()
_,err = io.Copy(out,file)
if err!= nil {
json.NewEncoder(w).Encode(Response {err .Error(),true})
return
}
json.NewEncoder(w).Encode(Response {File'+ header.Filename +'''submited successfully ,false})
}
问题是执行submit函数时, r.Method
是 GET
和 r.Header.Get(Content-Type)
是一个空字符串,那么它会一直持续到第一个,如果r.FormFile返回以下错误:
请求Content-Type不是multipart / form-data
我不明白为什么r.Method总是GET并且没有Content-Type。我试图以许多不同的方式来执行index.html,但r.Method总是GET并且Content-Type是空的。这是index.html中上传文件的函数:
function upload(){
var formData = new FormData ();
formData.append('uploadFile',document.querySelector('#file-input')。files [0]);
fetch('/ submit',{
method:'post',
headers:{
Content-Type:multipart / form-data
},
body:formData
})。then(function json(response){
return response.json()
})。then(function(data){
window.console.log('JSON响应请求成功',data);
})。catch(function(error){
window.console.log('Request failed',error) ;
});
}
以下是HTML:
< input id =file-inputtype =filename =uploadFile/>
请注意,标签不在标签内,我认为这可能是问题所以我改变了两个函数和HTML如下所示:
function upload(){
fetch('/ submit', {
method:'post',
headers:{
Content-Type:multipart / form-data
},
body:new FormData document.querySelector('#form')
})。then(function json(response){
return response.json()
})。then(function(data){
window.console.log('JSON响应请求成功',data);
})。catch(function(error){
window.console.log('Request failed',error) ;
});
}
< form id =formmethod =postenctype =multipart / form-dataaction =/ submit> ;< input id =file-inputtype =filename =uploadFile/>< / form>
但是,这也没有奏效。我已经用Google搜索了如何使用fetch()以及如何从go上接收文件上传,我已经看到它们与我的很相似,我不知道我在做什么错误。
更新:
使用 curl -v -F'uploadFile = @ \C:/ Users / raul - / Desktop / test.png \'http:// localhost:8080 / submit
我得到以下输出:
*尝试:: 1 ...
*连接到本地主机(:: 1)端口8080(#0)
> POST /提交HTTP / 1.1
>主机:localhost:8080
> User-Agent:curl / 7.45.0
>接受:* / *
> Content-Length:522
>预计:100-继续
>内容类型:multipart / form-data; boundary = --------------------------- a17d4e54fcec53f8
>
< HTTP / 1.1 301永久移动
<位置:/ submit /
<日期:2015年11月18日星期三14:48:38 GMT
< Content-Length:0
<内容类型:text / plain; charset = utf-8
*发送结束前发生HTTP错误,停止发送
<
*关闭连接0
我运行的控制台运行main.go
使用curl时不会输出任何内容。
我设法解决了我的问题,所以在这里以防万一别人需要它。感谢@JiangYD使用curl来测试服务器。
TL; DR
- 我写了
http.HandleFunc(/ submit /,submit)
但我正在向/ submit
(注意缺少的斜线)<<这一点很重要,因为重定向
- 不要自己指定Content-Type,浏览器会为你做这件事
LONG ANSWER
我做了@JiangYD的说法并使用 curl来测试服务器,我更新了答案响应。我发现奇怪的是有一个301重定向,因为我没有把它放在那里,我决定使用下面的curl命令
curl -v -F'uploadFile = @ \C:/Users/raul-/Desktop/test.png\'-L http:// localhost:8080 / submit
(注意 -L )curl遵循重定向,尽管它再次失败,因为重定向时curl切换从POST到GET,但是通过该响应,我发现将 / submit
的请求重定向到 / submit /
我记得这就是我在 main
函数中编写它的方式。
修复后仍然失败,响应是 http:没有这样的文件
,通过查看 net / http
代码,我发现它意味着该字段没有'所以我做了一个快速测试,对所有获得的字段名进行迭代:
for k,_:= range r。 MultipartForm.File {
log.Println(k)
}
我将'uploadFile
作为字段名称,I删除了curl命令中的单引号,现在它完美地上传了文件。
但是它并没有在这里结束,我现在知道服务器工作正常,因为我可以上传一个使用 curl
的文件,但是当我尝试通过托管网页上传时,出现错误: Content-Type中没有multipart边界参数
$ b因此,我发现我假设在标题中包含边界,我将fetch更改为如下所示:
fetch('/ submit',{
method:'post',
headers:{
Content-Type :multipart / form-data; border = ------------------------+ boundary
},body:formData})
我这样计算边界:
var boundary = Math.random()。toString()。substr(2);
出现错误:
multipart:NextPart:EOF
那么您如何计算边界?我阅读spec https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm ,发现边界是由编码文件的算法计算出来的,在我的例子中是FormData,FormData API没有公开获得边界的方法,但是我发现浏览器添加了Content-如果你没有指定它,自动输入multipart / form-data
和边界,所以我从fetch $ c中删除了标题对象$ c>呼叫,现在它终于起作用了!
I've started playing with go very recently so I'm still a noob, sorry if I make too many mistakes. I've been trying to fix this for a long time but I just don't understand what's going on. In my main.go file I have a main function:
func main() { http.HandleFunc("/", handler) http.HandleFunc("/submit/", submit) log.Fatal(http.ListenAndServe(":8080", nil)) }
The handler function looks like this:
func handler(w http.ResponseWriter, r *http.Request) { data, _ := ioutil.ReadFile("web/index.html") w.Write(data) }
I know this is not the best way to serve a website The submit function looks like this:
func submit(w http.ResponseWriter, r *http.Request) { log.Println("METHOD IS " + r.Method + " AND CONTENT-TYPE IS " + r.Header.Get("Content-Type")) r.ParseMultipartForm(32 << 20) file, header, err := r.FormFile("uploadFile") if err != nil { json.NewEncoder(w).Encode(Response{err.Error(), true}) return } defer file.Close() out, err := os.Create("/tmp/file_" + time.Now().String() + ".png") if err != nil { json.NewEncoder(w).Encode(Response{err.Error(), true}) return } defer out.Close() _, err = io.Copy(out, file) if err != nil { json.NewEncoder(w).Encode(Response{err.Error(), true}) return } json.NewEncoder(w).Encode(Response{"File '" + header.Filename + "' submited successfully", false}) }
The problem is when the submit function is executed,
r.Method
isGET
andr.Header.Get("Content-Type")
is an empty string, then it continues until the first if where r.FormFile returns the following error:request Content-Type isn't multipart/form-data
I don't understand why r.Method is always GET and there's no Content-Type. I've tried to do the index.html in many different ways but r.Method is always GET and Content-Type is empty. Here's the function in index.html that uploads a file:function upload() { var formData = new FormData(); formData.append('uploadFile', document.querySelector('#file-input').files[0]); fetch('/submit', { method: 'post', headers: { "Content-Type": "multipart/form-data" }, body: formData }).then(function json(response) { return response.json() }).then(function(data) { window.console.log('Request succeeded with JSON response', data); }).catch(function(error) { window.console.log('Request failed', error); }); }
And here's the HTML:
<input id="file-input" type="file" name="uploadFile" />
Note that the tag is not inside a tag, I thought that could be the problem so I changed both the function and the HTML to something like this:
function upload() { fetch('/submit', { method: 'post', headers: { "Content-Type": "multipart/form-data" }, body: new FormData(document.querySelector('#form') }).then(function json(response) { return response.json() }).then(function(data) { window.console.log('Request succeeded with JSON response', data); }).catch(function(error) { window.console.log('Request failed', error); }); } <form id="form" method="post" enctype="multipart/form-data" action="/submit"><input id="file-input" type="file" name="uploadFile" /></form>
But that didn't work neither. I've searched with Google how to use fetch() and how to receive a file upload from go and I've seen that they are pretty similar to mine, I don't know what I'm doing wrong.
UPDATE: After using
curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit
I get the following output:* Trying ::1... * Connected to localhost (::1) port 8080 (#0) > POST /submit HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.45.0 > Accept: */* > Content-Length: 522 > Expect: 100-continue > Content-Type: multipart/form-data; boundary=---------------------------a17d4e54fcec53f8 > < HTTP/1.1 301 Moved Permanently < Location: /submit/ < Date: Wed, 18 Nov 2015 14:48:38 GMT < Content-Length: 0 < Content-Type: text/plain; charset=utf-8 * HTTP error before end of send, stop sending < * Closing connection 0
The console where I'm running
go run main.go
outputs nothing when using curl.解决方案I managed to solve my problem, so here it is in case someone else needs it. And thanks @JiangYD for the tip of using curl to test the server.
TL;DR
- I wrote
http.HandleFunc("/submit/", submit)
but I was making a POST request to/submit
(note the missing slash) << This is important because of redirections - Don't specify the Content-Type yourself, the browser will do it for you
LONG ANSWER
I did as @JiangYD said and used curl to test the server, I updated my answer with the response. I found odd that there was a 301 Redirect since I didn't put it there, I decided to use the following curl command
curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' -L http://localhost:8080/submit
(note the -L) That way curl followed the redirect, though it failed again because, when redirecting, curl switched from POST to GET but with that response I found out that the request to /submit
was being redirected to /submit/
and I remembered that's how I wrote it in the main
function.
After fixing that it still failed, the response was http: no such file
and by looking at the net/http
code I found that it meant the field didn't exist, so I did a quick test iterating over all the field names obtained:
for k, _ := range r.MultipartForm.File {
log.Println(k)
}
I was getting 'uploadFile
as the field name, I removed the single quotes in the curl command and now it uploaded the file perfectly
But it doesn't end here, I now knew the server was working correctly because I could upload a file using curl
but when I tried uploading it through the hosted web page I got an error: no multipart boundary param in Content-Type
.
So I found out I was suppose to include the boundary in the header, I changed fetch to something like this:
fetch('/submit', {
method: 'post',
headers: {
"Content-Type": "multipart/form-data; boundary=------------------------" + boundary
}, body: formData})
I calculate the boundary like this:
var boundary = Math.random().toString().substr(2);
But I still got an error: multipart: NextPart: EOF
So how do you calculate the boundary? I read the spec https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm and found out the boundary is calculated by the algorithm that encodes the file, which in my case is FormData, the FormData API doesn't expose a way to get that boundary but I found out that the browser adds the Content-Type with multipart/form-data
and the boundary automatically if you don't specify it so I removed the headers object from the fetch
call and now it finally works!
这篇关于用go处理文件上传的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!