go语言实现视频上传下载

用go语言实现视频的上传和下载

项目结构

main.go

package main

import (
	"github.com/julienschmidt/httprouter"
	"log"
	"net/http"
)

type MiddlewareHandler struct {
	r *httprouter.Router
	l *ConnLimiter
}

func NewMiddlewareHandler(r *httprouter.Router, concurentLimit int) http.Handler {
	h := MiddlewareHandler{r: r, l: NewConnLimiter(concurentLimit)}
	return h
}
func (m MiddlewareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if !m.l.GetConn() {
		sendErrorResponse(w, "Two many request", http.StatusTooManyRequests)
		return
	}
	m.r.ServeHTTP(w, r)
	defer m.l.ReleaseConn()
}
func streamingHandlers() *httprouter.Router {
	router := httprouter.New()
	router.GET("/video/:video_id", StreamHandler)
	router.POST("/upload/:video_id", UploadHandler)
	router.GET("/test_page", TestPageHandler)
	return router
}
func main() {
	h := streamingHandlers()
	m := NewMiddlewareHandler(h, 2)
	log.Fatal(http.ListenAndServe(":9000", m))
}

defs.go

package main

import (
	"log"
	"path/filepath"
)

const (
	MAX_UPLOAD_SIZE = 1024 * 1024 * 1024 * 2 //2G
)

// 可执行文件所在目录的videos文件夹
var VIDEO_DIR = filepath.Join(GetCurrPath(), "videos")

func init() {
	err := CreateDirIfNotExists(VIDEO_DIR)
	if err != nil {
		log.Printf("create video dir failed: %v", err)
	}
}

handlers.go

package main

import (
	"github.com/julienschmidt/httprouter"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

func StreamHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	video_id := p.ByName("video_id")
	video_filename := filepath.Join(VIDEO_DIR, video_id)
	video, err := os.Open(video_filename)
	if err != nil {
		log.Printf("Error when trying to open file: %v", err)
		sendErrorResponse(w, "Internal Error", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-type", "video/mp4")
	http.ServeContent(w, r, "", time.Now(), video)

	defer video.Close()

}

func UploadHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	r.Body = http.MaxBytesReader(w, r.Body, MAX_UPLOAD_SIZE)
	if err := r.ParseMultipartForm(MAX_UPLOAD_SIZE); err != nil {
		sendErrorResponse(w, "the file is too big", http.StatusBadRequest)
		return
	}
	file, _, err := r.FormFile("file")
	if err != nil {
		sendErrorResponse(w, "internal error", http.StatusInternalServerError)
		return
	}
	data, err := ioutil.ReadAll(file)
	if err != nil {
		log.Printf("read file error: %v", err)
		sendErrorResponse(w, "internal error", http.StatusInternalServerError)
		return
	}
	video_id := p.ByName("video_id")
	video_filename := filepath.Join(VIDEO_DIR, video_id)
	err = ioutil.WriteFile(video_filename, data, 0666)
	if err != nil {
		log.Printf("error when write file: %v", err)
		sendErrorResponse(w, "interl error", http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	io.WriteString(w, "uplaod successfully")
}
func TestPageHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	t, _ := template.ParseFiles(filepath.Join(GetCurrPath(), "templates", "test_upload.html"))
	t.Execute(w, nil)
}

limiter.go

package main

import "log"

type ConnLimiter struct {
	concurrentConn int
	bucket         chan int
}

func NewConnLimiter(concurrentConn int) *ConnLimiter {
	return &ConnLimiter{concurrentConn: concurrentConn, bucket: make(chan int, concurrentConn)}
}

func (cl *ConnLimiter) GetConn() bool {
	if len(cl.bucket) >= cl.concurrentConn {
		log.Printf("reach the rate limitation: %d", cl.concurrentConn)
		return false
	}
	cl.bucket <- 1
	log.Printf("Get Conn, bucket len:%d", len(cl.bucket))
	return true
}

func (cl *ConnLimiter) ReleaseConn() {
	_ = <-cl.bucket
	log.Printf("Release Conn, bucket len: %d", len(cl.bucket))
}

response.go

package main

import (
	"io"
	"net/http"
)

func sendErrorResponse(w http.ResponseWriter, error string, status_code int) {
	w.WriteHeader(status_code)
	io.WriteString(w, error)
}

utils.go

package main

import (
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func GetCurrPath() string {
	file, _ := exec.LookPath(os.Args[0])
	path, _ := filepath.Abs(file)
	index := strings.LastIndex(path, string(os.PathSeparator))
	ret := path[:index]
	return ret
}

func CreateDirIfNotExists(path string) error {
	// 文件夹如果存在直接返回, 如果不存在递归创建
	_, err := os.Stat(path)
	if err == nil {
		log.Printf("%s exists, ok\n", path)
		return nil
	}
	log.Printf("%s does not exits, create\n", path)
	if !os.IsNotExist(err) {
		return err
	}
	err = os.MkdirAll(path, os.ModePerm)
	if err != nil {
		log.Printf("create %s failed\n", path)
		return err
	}
	log.Printf("created, ok\n")
	return nil
}

test_page.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传视频</title>
</head>
<body>
<form action="/upload/ddd" enctype="multipart/form-data" method="post">

    <input type="file" name="file">
    <input type="submit" value="上传视频">
</form>
</body>
</html>