用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>