要是表单能够上传文件,需为form添加enctype属性:
- application/x-www-form-urlencoded 发送前编码所有字符
- multipart/form-data 不对字符编码
- text/plain 空格转换为"+",但不对特殊字符编码
创建新表单如下:
<html>
<head>
<title>Upload Files</title>
</head>
<body>
<form action="/upload" enctype="multipart/form-data" method="POST">
<input type="file" name="uploadFile">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="upload">
</form>
</body>
</html>
服务端代码如下:
//my_server4.5.go
package myserver
import (
"GoWebAppNotes/src/myutils"
"crypto/md5"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"text/template"
"time"
)
func upload(rw http.ResponseWriter, r *http.Request) {
log.Println("HTTP Method : ", r.Method)
//handle GET Method
if r.Method == "GET" {
//Get current time
currentTime := time.Now().Unix()
//create a new Hash to compute md5
h := md5.New()
//write current time into Hash
io.WriteString(h, strconv.FormatInt(currentTime, 10))
//compute the hash
token := fmt.Sprintf("%x", h.Sum(nil))
t, err := template.ParseFiles("./resources/html/upload.html")
myutils.HasError(err, "Template parseFiles error")
t.Execute(rw, token)
} else { //handle POST Method
//Parse request body as multipart/form date
//max memory is 32 MiB
r.ParseMultipartForm(32 << 20)
//get the first file for key 'uploadfile'
file, fileHandler, err := r.FormFile("uploadFile")
myutils.HasError(err, "parse form file error")
//close the file
defer file.Close()
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
fmt.Fprintf(rw, "%v", fileHandler.Header)
f, err := os.OpenFile("./recources/tmp/"+fileHandler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
myutils.HasError(err, "Open file error")
defer f.Close()
//copy file to f
io.Copy(f, file)
}
}
func StartMyServer45() {
log.Println("Starting server ...")
http.HandleFunc("/upload", upload)
err := http.ListenAndServe(":9090", nil)
myutils.HasError(err, "ListenAndServe error")
}
//main.go
package main
import "GoWebAppNotes/src/myserver"
func main() {
myserver.StartMyServer45()
}
有上述代码可见,处理上传的文件需要调用r.ParseMultipartForm(maxmemory int64)
,其中maxMemory,调用ParseMultipartForm之后会将上传的文件存储在大小为maxMemory byte的内存中,若文件大小超过最大设置内存大小,剩下部分将会存储在系统的临时文件中。可通过调用r.FormFile(key string)
获取文件句柄,之后使用io.Copy(dst,src)
来存储文件
注意:ParseMultipartForm调用之后,后续的调用将不会生效
上述示例可以看出上传文件处理主要分为三步:
- 表单中添加enctype="multipart/form-data"
- 服务端调用r.ParseMultipartForm,将上传的文件存储在内存和临时文件中
- 使用r.FormFile获取文件句柄,后续即可对文件进行操作
示例中文件句柄fileHandler的类型为*multipart.FileHeader
// A FileHeader describes a file part of a multipart request.
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
Size int64
content []byte
tmpfile string
}
Golang支持模拟客户端文件上传,示例如下:
////my_server4.5.go
package myserver
import (
"GoWebAppNotes/src/myutils"
"bytes"
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"strconv"
"strings"
"text/template"
"time"
)
func upload(rw http.ResponseWriter, r *http.Request) {
log.Println("HTTP Method : ", r.Method)
//handle GET Method
if r.Method == "GET" {
//Get current time
currentTime := time.Now().Unix()
//create a new Hash to compute md5
h := md5.New()
//write current time into Hash
io.WriteString(h, strconv.FormatInt(currentTime, 10))
//compute the hash
token := fmt.Sprintf("%x", h.Sum(nil))
t, err := template.ParseFiles("./resources/html/upload.html")
myutils.HasError(err, "Template parseFiles error")
t.Execute(rw, token)
} else { //handle POST Method
//Parse request body as multipart/form date
//max memory is 32 MiB
r.ParseMultipartForm(32 << 20)
//get the first file for key 'uploadFile'
file, fileHandler, err := r.FormFile("uploadFile")
myutils.HasError(err, "parse form file error")
//close the file
defer file.Close()
//write file header into rw
fmt.Fprintf(rw, "%v", fileHandler.Header)
//Open file
f, err := os.OpenFile("./resources/tmp/"+fileHandler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
myutils.HasError(err, "Open file error")
defer f.Close()
//copy file to f
_, err = io.Copy(f, file)
myutils.HasError(err, "Copy file error")
log.Print("Copy File Done")
}
}
func uploadFile(rw http.ResponseWriter, r *http.Request) {
log.Print("Start to uploading file ")
postFile("./resources/upload/tmp1.txt", "http://localhost:9090/upload")
fmt.Fprint(rw, "Upload Done")
}
func postFile(filePath string, targetURL string) {
//create a bytes buffer
bodyBuf := &bytes.Buffer{}
//create a new multipart writer writing to bodybuf
bodyWriter := multipart.NewWriter(bodyBuf)
//get file name from file path
arr := strings.Split(filePath, "/")
fileName := arr[len(arr)-1]
//create form-data header with provided field name and file name
fileWriter, err := bodyWriter.CreateFormFile("uploadFile", fileName)
myutils.HasError(err, "Create Form File error")
//open file that will be posted
fh, err := os.Open(filePath)
myutils.HasError(err, "Open file error")
defer fh.Close()
//copy opened file to fileWriter
_, err = io.Copy(fileWriter, fh)
myutils.HasError(err, "Copy file error")
//get content type of fileWriter
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
//send http post request to upload file
resp, err := http.Post(targetURL, contentType, bodyBuf)
myutils.HasError(err, "Post file error")
defer resp.Body.Close()
//read response body
resp_body, err := ioutil.ReadAll(resp.Body)
myutils.HasError(err, "Read file error")
log.Print(resp.Status)
log.Print(string(resp_body))
}
func StartMyServer45() {
log.Println("Starting server ...")
http.HandleFunc("/upload", upload)
http.HandleFunc("/uploadFile", uploadFile)
err := http.ListenAndServe(":9090", nil)
myutils.HasError(err, "ListenAndServe error")
}
上述示例中,客户端通过multipart.Writer将文件写入缓存中,并发送http的POST请求将缓存发往服务器
Tips:multipart.Writer.WriteField可以写入其他普通字段