golang 使用mime/multipart传输附件
client部分
package main
import (
"io"
"os"
"fmt"
"log"
"bytes"
"path/filepath"
"net/http"
"mime/multipart"
"crypto/md5"
"encoding/hex"
)
func main() {
bodyBuffer := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuffer)
if err := attachField(bodyWriter, "key1", "value1"); err != nil {
return
}
if err := attachField(bodyWriter, "key2", "value2"); err != nil {
return
}
if err := attachFile(bodyWriter, "fileform1", "a1.txt"); err != nil {
return
}
if err := attachFile(bodyWriter, "fileform1", "a2.txt"); err != nil {
return
}
if err := attachField(bodyWriter, "key3", "value3"); err != nil {
return
}
if err := attachFile(bodyWriter, "fileform2", "a2.txt"); err != nil {
return
}
if err := attachFile(bodyWriter, "fileform1", "a3.txt"); err != nil {
return
}
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
uri := fmt.Sprintf("http://localhost:%d/multipart", 8082)
req, err := http.NewRequest("POST", uri, bodyBuffer)
if err != nil {
log.Printf("Cannot NewRequest: %s , err: %v", uri, err)
return
}
//req.SetBasicAuth(c.user, c.passwd)
req.Header.Set("Content-Type", contentType)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Cannot client.Do, err: %v", err)
return
}
defer resp.Body.Close()
//dumpResponse(resp)
}
func attachField(bodyWriter * multipart.Writer, keyname, keyvalue string) error {
if err := bodyWriter.WriteField(keyname, keyvalue); err != nil {
log.Printf("Cannot WriteField: %s, err: %v", keyname, err)
return err
}
return nil
}
func attachFile(bodyWriter * multipart.Writer, formname, filename string) error {
fullname := filepath.Join(".", filename)
file, err := os.Open(fullname)
if err != nil {
log.Printf("Cannot open file: %s , err: %v", fullname, err)
return err
}
defer file.Close()
// MD5
md5hash := md5.New()
if _, err = io.Copy(md5hash, file); err != nil {
log.Printf("Cannot open md5 hash: %s , err: %v", fullname, err)
return err
}
keyname := filename + ".md5cksum"
keyvalue := hex.EncodeToString(md5hash.Sum(nil)[:16])
if err = attachField(bodyWriter, keyname, keyvalue); err != nil {
log.Printf("Cannot WriteField: %s, err: %v", keyname, err)
return err
}
// file
part, err := bodyWriter.CreateFormFile(formname, filename)
if err != nil {
log.Printf("Cannot CreateFormFile for: %s , err: %v", filename, err)
return err
}
_, err = io.Copy(part, file)
if err != nil {
log.Printf("Cannot Copy file: %s , err: %v", fullname, err)
return err
}
return nil
}
server部分
package main
import (
"io"
"os"
"fmt"
"log"
"net/http"
"io/ioutil"
"encoding/json"
"github.com/gorilla/mux"
)
func main() {
address := fmt.Sprintf("%s:%d", "0.0.0.0", 8082)
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/multipart", testHandle)
err := http.ListenAndServe(address, router)
if err != nil {
log.Panic("ListenAndServe err:", err)
}
}
func writeErrorResponse(code int, err error, w http.ResponseWriter) {
response := map[string]string {"error": err.Error()}
writeResponse(code, response, w)
}
func writeResponse(code int, jsonres interface{}, w http.ResponseWriter) {
b, err := json.Marshal(jsonres)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(b)
}
}
func testHandle(w http.ResponseWriter, r *http.Request) {
log.Printf("Entry of handle\n");
reader, err := r.MultipartReader()
if err != nil {
writeErrorResponse(http.StatusBadRequest, err, w)
return
}
for {
part, err := reader.NextPart()
if err == io.EOF {
log.Printf("==== EOF\n");
break
} else if err != nil {
log.Printf("==== ERROR\n");
writeErrorResponse(http.StatusBadRequest, err, w)
return
}
filename := part.FileName()
formname := part.FormName()
log.Printf("==== filename=[%s], forname=[%s]\n", filename, formname);
if filename != "" {
dst, err := os.Create(filename + ".new")
if err != nil {
writeErrorResponse(http.StatusBadRequest, err, w)
return
}
defer dst.Close()
_, err = io.Copy(dst, part)
if err != nil {
dst.Close()
writeErrorResponse(http.StatusBadRequest, err, w)
return
}
} else if formname == "key1" {
data, err := ioutil.ReadAll(part)
if err != nil {
writeErrorResponse(http.StatusBadRequest, err, w)
return
}
log.Printf("forname[%s]=[%s]\n", formname, string(data))
}
}
writeResponse(http.StatusAccepted, nil, w)
}
运行结果server端输出:
2019/09/05 05:14:16 Entry of handle
2019/09/05 05:14:16 ==== filename=[], forname=[key1]
2019/09/05 05:14:16 ==== filename=[], forname=[key2]
2019/09/05 05:14:16 ==== filename=[], forname=[a1.txt.md5cksum]
2019/09/05 05:14:16 ==== filename=[a1.txt], forname=[fileform1]
2019/09/05 05:14:16 ==== filename=[], forname=[a2.txt.md5cksum]
2019/09/05 05:14:16 ==== filename=[a2.txt], forname=[fileform1]
2019/09/05 05:14:16 ==== filename=[], forname=[key3]
2019/09/05 05:14:16 ==== filename=[], forname=[a2.txt.md5cksum]
2019/09/05 05:14:16 ==== filename=[a2.txt], forname=[fileform2]
2019/09/05 05:14:16 ==== filename=[], forname=[a3.txt.md5cksum]
2019/09/05 05:14:16 ==== filename=[a3.txt], forname=[fileform1]
2019/09/05 05:14:16 ==== EOF
我们可以观察到:
- 文件form包含文件名,而普通field form没有文件名
- 所有form的排序和加入时是一致的,就是按照加入的先后顺序读出,不区分是字段form还是文件form,也不按照字段的名字排序。
有疑问加站长微信联系(非本文作者)