使用 go 处理文件上传
Handle file uploading with go
最近才开始玩围棋,所以我还是菜鸟,如果我犯了太多错误,请原谅。很长一段时间以来,我一直试图解决这个问题,但我只是不明白发生了什么。在我的 main.go 文件中,我有一个主要功能:
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/submit/", submit)
log.Fatal(http.ListenAndServe(":8080", nil))
}
处理函数如下所示:
func handler(w http.ResponseWriter, r *http.Request) {
data, _ := ioutil.ReadFile("web/index.html")
w.Write(data)
}
我知道这不是为网站提供服务的最佳方式
提交函数如下所示:
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})
}
问题是执行提交函数的时候,r.Method
是GET
,r.Header.Get("Content-Type")
是一个空字符串,然后一直持续到第一个if where r.FormFile returns 出现以下错误:
request Content-Type isn't 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('Request succeeded with JSON response', data);
}).catch(function(error) {
window.console.log('Request failed', error);
});
}
这里是 HTML:
<input id="file-input" type="file" name="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('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>
但这也没有用。我用 Google 搜索了如何使用 fetch() 以及如何从 go 接收文件上传,我发现它们与我的非常相似,我不知道我做错了什么。
更新:
使用 curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit
后,我得到以下输出:
* 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
我所在的控制台 运行 go run main.go
使用 curl 时不输出任何内容。
我设法解决了我的问题,所以在这里以防其他人需要它。并感谢@JiangYD 提供使用 curl 测试服务器的提示。
TL;DR
- 我写了
http.HandleFunc("/submit/", submit)
但我正在向 /submit
发出 POST 请求(注意缺少的斜杠)<< 这很重要,因为重定向
- 不要自己指定 Content-Type,浏览器会为您指定
长答案
我按照@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: no such file
,通过查看net/http
代码,我发现这意味着该字段不存在,所以我做了一个快速测试迭代在获得的所有字段名称上:
for k, _ := range r.MultipartForm.File {
log.Println(k)
}
我得到 'uploadFile
作为字段名称,我删除了 curl 命令中的单引号,现在它完美地上传了文件
但这还不止于此,我现在知道服务器工作正常,因为我可以使用 curl
上传文件,但是当我尝试通过托管网页上传文件时,出现错误:no multipart boundary param in Content-Type
.
所以我发现我应该在 header 中包含边界,我将 fetch 更改为如下内容:
fetch('/submit', {
method: 'post',
headers: {
"Content-Type": "multipart/form-data; boundary=------------------------" + boundary
}, body: formData})
我这样计算边界:
var boundary = Math.random().toString().substr(2);
但是我还是报错了:multipart: NextPart: EOF
那么边界是怎么计算的呢?我阅读了规范 https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm 并发现边界是由编码文件的算法计算的,在我的例子中是 FormData,FormData API 没有公开获取该边界的方法,但我发现浏览器会自动添加带有 multipart/form-data
的 Content-Type 和边界,如果您不指定它,所以我从 [=26= 中删除了 headers object ] 打电话,现在终于可以用了!
完全删除 header 实际上有效。特别是通过 fetch 或 axios 发送请求时。
axios.post(
endpoint + "/api/v1/personalslip",
{
newSlip
},
{
}
)
.then(res => {
console.log(res);
});
最近才开始玩围棋,所以我还是菜鸟,如果我犯了太多错误,请原谅。很长一段时间以来,我一直试图解决这个问题,但我只是不明白发生了什么。在我的 main.go 文件中,我有一个主要功能:
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/submit/", submit)
log.Fatal(http.ListenAndServe(":8080", nil))
}
处理函数如下所示:
func handler(w http.ResponseWriter, r *http.Request) {
data, _ := ioutil.ReadFile("web/index.html")
w.Write(data)
}
我知道这不是为网站提供服务的最佳方式 提交函数如下所示:
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})
}
问题是执行提交函数的时候,r.Method
是GET
,r.Header.Get("Content-Type")
是一个空字符串,然后一直持续到第一个if where r.FormFile returns 出现以下错误:
request Content-Type isn't 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('Request succeeded with JSON response', data);
}).catch(function(error) {
window.console.log('Request failed', error);
});
}
这里是 HTML:
<input id="file-input" type="file" name="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('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>
但这也没有用。我用 Google 搜索了如何使用 fetch() 以及如何从 go 接收文件上传,我发现它们与我的非常相似,我不知道我做错了什么。
更新:
使用 curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit
后,我得到以下输出:
* 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
我所在的控制台 运行 go run main.go
使用 curl 时不输出任何内容。
我设法解决了我的问题,所以在这里以防其他人需要它。并感谢@JiangYD 提供使用 curl 测试服务器的提示。
TL;DR
- 我写了
http.HandleFunc("/submit/", submit)
但我正在向/submit
发出 POST 请求(注意缺少的斜杠)<< 这很重要,因为重定向 - 不要自己指定 Content-Type,浏览器会为您指定
长答案
我按照@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: no such file
,通过查看net/http
代码,我发现这意味着该字段不存在,所以我做了一个快速测试迭代在获得的所有字段名称上:
for k, _ := range r.MultipartForm.File {
log.Println(k)
}
我得到 'uploadFile
作为字段名称,我删除了 curl 命令中的单引号,现在它完美地上传了文件
但这还不止于此,我现在知道服务器工作正常,因为我可以使用 curl
上传文件,但是当我尝试通过托管网页上传文件时,出现错误:no multipart boundary param in Content-Type
.
所以我发现我应该在 header 中包含边界,我将 fetch 更改为如下内容:
fetch('/submit', {
method: 'post',
headers: {
"Content-Type": "multipart/form-data; boundary=------------------------" + boundary
}, body: formData})
我这样计算边界:
var boundary = Math.random().toString().substr(2);
但是我还是报错了:multipart: NextPart: EOF
那么边界是怎么计算的呢?我阅读了规范 https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm 并发现边界是由编码文件的算法计算的,在我的例子中是 FormData,FormData API 没有公开获取该边界的方法,但我发现浏览器会自动添加带有 multipart/form-data
的 Content-Type 和边界,如果您不指定它,所以我从 [=26= 中删除了 headers object ] 打电话,现在终于可以用了!
完全删除 header 实际上有效。特别是通过 fetch 或 axios 发送请求时。
axios.post(
endpoint + "/api/v1/personalslip",
{
newSlip
},
{
}
)
.then(res => {
console.log(res);
});