背景

简单需要一个文件服务器来传递数据,只要两个功能,一个上传接口,一个下载接口。

选用go http模块实现,比nginx、ftp等更方便快捷。

需求整理
  • 上传接口"/v1/file_upload/"
  • 上传接口增加简单BasicAuth鉴权
  • 上传成功返回下载URL
    json格式返回
电梯直达

只想文件上传服务器测试接口,以下电梯直达即可
CSDN 5积分下载
白嫖

测试效果

在这里插入图片描述在这里插入图片描述

GO代码实现
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

type ViewFunc func(http.ResponseWriter, *http.Request)

func http_resp(code int, msg string, w http.ResponseWriter) {
	var Result map[string]interface{}
	Result = make(map[string]interface{})

	Result["code"] = code
	Result["msg"] = msg

	data, err := json.Marshal(Result)
	if err != nil {
		log.Printf("%v\n", err)
	}

	w.Write([]byte(string(data)))
}

func upload(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("upload.html")
		t.Execute(w, nil)
		return
	}
	
	contentType := r.Header.Get("content-type")
	contentLen := r.ContentLength

	if !strings.Contains(contentType, "multipart/form-data") {
		http_resp(-1001, "The content-type must be multipart/form-data", w)
		return
	}
	//限制最大文件大小
	if contentLen >= 50*1024*1024 {
		http_resp(-1002, "Failed to large,limit 50MB", w)
		return
	}

	err := r.ParseMultipartForm(50 * 1024 * 1024)
	if err != nil {
		http_resp(-1003, "Failed to ParseMultipartForm", w)
		return
	}

	if len(r.MultipartForm.File) == 0 {
		http_resp(-1004, "File is NULL", w)
		return
	}

	var Result map[string]interface{}
	Result = make(map[string]interface{})

	Result["code"] = 0
	DownLoadUrl := "http://192.168.10.9:8080/"

	FileCount := 0
	for _, headers := range r.MultipartForm.File {
		for _, header := range headers {
			log.Printf("recv file: %s\n", header.Filename)

			filePath := filepath.Join("./upload", header.Filename)
			dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
			if err != nil {
				log.Printf("Create file %s error: %s\n", filePath, err)
				return
			}
			srcFile, err := header.Open()
			if err != nil {
				log.Printf("Open header failed: %s\n", err)
			}
			_, err = io.Copy(dstFile, srcFile)
			if err != nil {
				log.Printf("Write file %s error: %s\n", filePath, err)
			}
			_, _ = srcFile.Close(), dstFile.Close()
			FileCount++
			name := fmt.Sprintf("file%d_url", FileCount)
			Result[name] = (DownLoadUrl + header.Filename)
		}
	}
	data, err := json.Marshal(Result)
	if err != nil {
		log.Printf("%v \n", err)
	}
	w.Write([]byte(string(data)))
}

func BasicAuth(f ViewFunc, user, passwd []byte) ViewFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		basicAuthPrefix := "Basic "

		// 获取 request header
		auth := r.Header.Get("Authorization")
		// 如果是 http basic auth
		if strings.HasPrefix(auth, basicAuthPrefix) {
			// 解码认证信息
			payload, err := base64.StdEncoding.DecodeString(
				auth[len(basicAuthPrefix):],
			)
			if err == nil {
				pair := bytes.SplitN(payload, []byte(":"), 2)
				if len(pair) == 2 && bytes.Equal(pair[0], user) &&
					bytes.Equal(pair[1], passwd) {
					// 执行被装饰的函数
					f(w, r)
					return
				}
			}
		}

		// 认证失败,提示 401 Unauthorized
		// Restricted 可以改成其他的值
		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
		// 401 状态码
		w.WriteHeader(http.StatusUnauthorized)
	}
}

func main() {
	addr := flag.String("addr", "0.0.0.0:8080", "服务绑定地址端口")
	user := flag.String("user", "", "http认证用户名(如果用户名为空,则不需要认证)")
	psw := flag.String("psw", "", "http认证密码")
	root := flag.String("root", "./upload/", "文件服务根目录")
	flag.Parse()

	os.Mkdir(*root, 0777)

	http_user := []byte(*user)
	http_psw := []byte(*psw)

	log.Printf("http server listen on %v \n", *addr)

	if *user != "" {
		http.HandleFunc("/v1/file_upload", BasicAuth(upload, http_user, http_psw))
	} else {
		http.HandleFunc("/v1/file_upload", upload)
	}

	http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(*root))))
	err := http.ListenAndServe(*addr, nil)
	if err != nil {
		log.Printf("%v", err)
	}
}
网页上传

新建文件upload.html放到http服务器root目录。例如upload文件夹

<!DOCTYPE html>
 <html>
      <head>
      <meta charset="utf-8">
      <title>go 上传test</title>
     </head>
<body>
<form method="POST" action="/v1/file_upload" enctype="multipart/form-data" >
<input type="file" name="uploadfile" />
<input type="submit" value="上传">
</form>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

参考