这是一段旅程学习的地狱,这是一个值得的困难,因为我非常习惯JavaScript和PHP的松散性质(我已经写了大约3年的语言)。在过去的几个月中,Go的严格性质是我刚刚与之相关的事情,并且一定会喜欢它。这是一种很棒的语言。
这篇文章的原因是试图通过业余爱好者的眼光解释文件上传,所以如果我的写作非常冗长,请赦免我,因为我会尝试像我自己一样解释这一点。因此,让我们挖掘。
我们将作为表单主体的一部分以及处理多个文件来处理文件上传。
首先,让我们创建样板,创建一个文件夹并创建一个新的main.go文件。
package main
func uploadHandler(w http.ResponseWriter, r *http.Request){
}
func main(){
http.HandleFunc("/upload", uploadHandler)
log.Fatal(http.ListenAndServe(":9000", nil))
}
接下来,让我们创建一个简单的index.html文件,我们将在其中从
中进行提交
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go file upload</title>
</head>
<body>
<form action="/upload" method="post">
<input type="file" name="image" accept="image/*">
<button type="submit">Submit</button>
</form>
</body>
</html>
让我们还提供index.html文件。
package main
func uploadHandler(w http.ResponseWriter, r *http.Request){
}
func indexHandler(res http.ResponseWriter, r *http.Request) {
res.Header().Add("Content-Type", "text/html")
http.ServeFile(res, r, "index.html")
}
func main(){
http.HandleFunc("/", indexHandler)
http.HandleFunc("/upload", uploadHandler)
log.Fatal(http.ListenAndServe(":9000", nil))
}
我确实建议您在使用index.html文件进行测试时添加enctype =“ multipart/form-data”,因为浏览器将不会在不在表单标签的情况下提交文件,并且会导致错误,否则您可以使用Postman,直到我们转到解析多部分形式。
接下来,我们继续在请求中处理表格,首先,我们必须解析表格
func uploadHandler(w http.ResponseWriter, r *http.Request){
// this neccessary else you will not be able to access other properties needed to get the file
// this method returns an err
err:=r.ParseForm()
// always handle error
if err != nil{
// of course we should use a better error message
http.Error(w, "Unable to parse form", http.StatusInternalServerError)
return
}
}
解析表格的原因是我们需要调用 r.formfile()方法,而该方法以及 r.formvalue()方法是直到解析表格为止。
在这种情况下,我们将使用 r.formfile()方法,此方法返回三个参数:
第一个参数是 multipart.file类型。
第二个参数是 multipart.fileheader 类型这些参数包含文件的元详细信息,例如其名称,大小和文件本身。”
第三个也是最后一个论点是错误。
话虽如此,请跳进去看看代码的添加行
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// this neccessary else you will not be able to access other properties needed to get the file
// this method returns an err
err:=r.ParseForm()
// always handle error
if err != nil{
http.Error(w, "Unable to parse form", http.StatusInternalServerError)
return
}
file, fileHeader, err := r.FormFile("image")
// get the file by entering its form name.
// handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//postpone closing the file until the function is complete
defer file.Close()
在上一步之后,现在是时候在本地服务器上创建文件了,我们将使用当前时间作为文件的名称,并使用 filepath.ext提取文件扩展名( )方法。
func uploadHandler(w http.ResponseWriter, r *http.Request) {
/*
reduced for brevity
*/
// create file on the local server, filepath.Ext() will get the extension out of the filename
localfile, err := os.Create(time.Now().UTC().String()+filepath.Ext(fileHeader.Filename))
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
现在我们可以将上传的文件复制到新创建的文件中。
/*
reduced for brevity
*/
// create file on the local server, filepath.Ext() will get the extension out of the filename
localfile, err := os.Create(time.Now().UTC().String()+filepath.Ext(fileHeader.Filename))
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// use the io.Copy() method to copy bytes to the localbyte from the uploaded file,
// this method returns the number of bytes copied and an error, we would be ignoring the bytes
_, err = io.Copy(localfile, file)
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//after which we can simply respond back to the client
fmt.Fprint(w, "File upload successful")
}
就是这样,就是上传文件所需的一切。
解析多部分形式
如果提交的表格是多部分形式,则解析表格与正常形式略有不同,但仅通过一行。
//change this line
err:=r.ParseForm()
//to, r.ParseMultipartForm() takes an argument (maxMemory), maxMemory indicates how much of the file size should be kept in memory, while any extra will be kept in a temp folder on the disc
err:=r.ParseMultipartForm(300)
上传多个文件
上传多个文件与上传单个文件一样简单,主要区别在于您如何处理文件并通过它们循环,让我们查看。
// create a new function for uploading multiple files
func uploadMultipleFiles(w http.ResponseWriter, r *http.Request){
r.ParseMultipartForm(1024 * 1024 *10) //maxMemory if 10mb
}
让我们也在主机上处理。
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/upload", uploadHandler)
http.HandleFunc("/uploadmultiple", uploadMultipleFiles)
http.ListenAndServe(":9000", nil)
}
解析表格后,我们现在可以使用r.multipartform.file [“ images”] slice获取文件。
func uploadMultipleFiles(w http.ResponseWriter, r *http.Request){
r.ParseMultipartForm(1024 * 1024 *10) //maxMemory if 10mb
//we get the slice of files
files:= r.MultipartForm.File["images"]
}
我们需要像以前一样循环浏览文件,以便能够上传每个文件,唯一的区别是我们不会使用实际的 multipart.file type但是使用 multipart.fileheader 类型。但不要担心 multipart.fileheader 也包含文件本身。
func uploadMultipleFiles(w http.ResponseWriter, r *http.Request){
r.ParseMultipartForm(1024 * 1024 *10) //maxMemory if 10mb
//we get the slice of files
files:= r.MultipartForm.File["images"]
// looping through the files to upload each file individually
for _, file := range files {
uploadFile, err := file.Open()
// handle error as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// postpone file closure until the function is complete
defer uploadFile.Close()
//create a new localfile
localfile, err := os.Create(time.Now().UTC().String() + filepath.Ext(file.Filename))
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = io.Copy(localfile, uploadFile)
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
//after which we can simply respond back to the client
fmt.Fprint(w, "File upload successful")
}
额外
设置最大文件上传大小
在您要限制文件上传大小的情况下,我们只需将文件大小等同于字节:
// add where neccessary in your code
const MAX_UPLOAD_SIZE int64 = 1024 * 1024 * 10 //maximum of 10mb per file
// if this was for a single file upload it would be fileHeader.Size, because it is the
// multipart.FileHeader type that has Size a property
if file.Size > MAX_UPLOAD_SIZE{
http.Error(w, "file sizes cannot be bigger than 10mb", http.StatusBadRequest)
return
}
跟踪文件上传进度
跟进文件上传,以检查文件上传进度是否有点棘手,但并不复杂。首先,我们需要创建一种新的结构类型,其属性将是文件的总大小( totalbytestoread ),并且读取了多少个字节( totalbytesread )。结构还需要满足 io.writer 接口,好吧,让我们查看一些代码。
type Progress struct{
TotalBytesToRead int64
TotalBytesRead int64
}
// create the write method so satisfy the i0.Writer interface
func (p *Progress) Write(pb []byte) (int, error) {
n := len(pb) //length of the file read so far
// set error to nil since no error will be handled
p.TotalBytesRead = int64(n)
if p.TotalBytesRead == p.TotalBytesToRead {
fmt.Println("Done")
return n, nil
}
//this will be printed on your terminals
fmt.Printf("File upload still in progress -- %d\n", p.TotalBytesRead)
return n, nil
}
// now in use with the file upload
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// this neccessary else you will not be able to access other properties needed to get the file
// this method returns an err
err:=r.ParseForm()
// always handle error
if err != nil{
http.Error(w, "Unable to parse form", http.StatusInternalServerError)
return
}
file, fileHeader, err := r.FormFile("image")
// get the file by entering its form name.
// handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//postpone closing the file until the function is complete
defer file.Close()
//create a new localfile
localfile, err := os.Create(time.Now().UTC().String()+filepath.Ext(file.Filename))
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Create a new instance of progress
progress := &Progress{
TotalBytesToRead : fileHeader.Size,
}
//instead of coppying directly we would use the io.TeeReader() method
// this method will allow us to write the amount of bytes read into the progress.Write method
_, err := io.Copy(localfile, io.TeeReader(file, progress))
//handle errors as neccessary
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
就是这样,这就是跟踪文件上传进度所需的一切。
在这一点上,我希望您现在是文件上传艺术及其所有警告的主人。感谢您的阅读,希望这很有帮助。请关注我。
可以找到完整的代码here