Skip to content

Latest commit

 

History

History
237 lines (203 loc) · 6.58 KB

File metadata and controls

237 lines (203 loc) · 6.58 KB

4.5 处理文件上传

要是表单能够上传文件,需为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调用之后,后续的调用将不会生效

上述示例可以看出上传文件处理主要分为三步:

  1. 表单中添加enctype="multipart/form-data"
  2. 服务端调用r.ParseMultipartForm,将上传的文件存储在内存和临时文件中
  3. 使用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可以写入其他普通字段