2 Star 3 Fork 2

leo / Share

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
main.go 7.71 KB
一键复制 编辑 原始数据 按行查看 历史
leo 提交于 2019-12-18 09:46 . 修改图标
package main
import (
"encoding/json"
"flag"
"fmt"
"html/template"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
rice "github.com/GeertJohan/go.rice"
)
// Configure 启动配置
type Configure struct {
Title string
Prefix string
Root string
Port int
}
// ParseConfigure 解析命令行的配置
func ParseConfigure() *Configure {
conf := &Configure{}
flag.StringVar(&conf.Title, "title", "File Server", "Title of this site.")
flag.StringVar(&conf.Prefix, "prefix", "", "Set the prefix URL when using behide NGINX")
flag.StringVar(&conf.Root, "root", "./", "Set the path for shared files")
flag.IntVar(&conf.Port, "port", 8088, "Listen port")
flag.Parse()
conf.Root = strings.TrimSuffix(conf.Root, "/")
conf.Prefix = strings.TrimSuffix(conf.Prefix, "/")
return conf
}
// FormatSize 生成可读的文件大小
func FormatSize(size int64) string {
if size < 1024 {
return fmt.Sprintf("%d B", size)
}
if size < 1024*1024 {
return fmt.Sprintf("%.03f KB", float32(size)/1024)
}
if size < 1024*1024*1024 {
return fmt.Sprintf("%.03f MB", float32(size)/(1024*1024))
}
return fmt.Sprintf("%.03f GB", float32(size)/(1024*1024*1024))
}
// FindFileIcon 根据后缀给出文件的图标
func FindFileIcon(name string) string {
ext := filepath.Ext(name)
switch ext {
case ".txt", ".json":
return "file-text-o"
case ".doc", ".docx":
return "file-word-o"
case ".ppt", ".pptx":
return "file-powerpoint-o"
case ".xsl", ".xslx":
return "file-excel-o"
case ".db", ".sql", ".pdb":
return "database"
case ".zip", ".rar", ".7z", ".bin", ".lzma", ".gz", ".tar":
return "file-archive-o"
case ".mp3", ".wav", ".ogg", ".snd":
return "file-audio-o"
case ".png", ".jpg", ".jpeg", ".bmp", ".tga", ".psd", ".ico":
return "file-image-o"
case ".pdf":
return "file-pdf-o"
case ".mp4", ".avi", ".rmvb", ".asf", ".mov", ".wmv", ".3gp", ".rm":
return "file-video-o"
case ".h", ".hpp", ".c", ".cpp", ".cc", ".cxx", ".cs", ".go", ".java", ".ts", ".tsx", ".py":
return "file-code-o"
case ".html", ".htm":
return "html5"
case ".css":
return "css3"
case ".js":
return "jsfiddle"
case ".sh":
return "linux"
case ".apk":
return "android"
case ".ipa":
return "apple"
case ".exe", ".dll":
return "windows"
case ".md":
return "book"
default:
return "file-o"
}
}
// Service HTTP文件服务
type Service struct {
conf *Configure
assets *rice.HTTPBox
page *template.Template
}
// Start 启动服务
func (s *Service) Start() {
s.conf = ParseConfigure()
s.assets = rice.MustFindBox("assets").HTTPBox()
s.page, _ = template.New("MAIN").Parse(s.assets.MustString("index.html"))
err := http.ListenAndServe(fmt.Sprintf(":%d", s.conf.Port), s)
if err != nil {
log.Fatalf("Service exit with error : %v\n", err)
}
}
// ServeHTTP 实现http.Handler
func (s *Service) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
req.URL.Path = strings.TrimPrefix(req.URL.Path, s.conf.Prefix)
log.Printf("%5s %s\n", req.Method, req.URL.Path)
switch req.Method {
case "GET":
s.serveContent(rsp, req)
case "POST":
s.serveAPI(rsp, req)
default:
rsp.WriteHeader(http.StatusMethodNotAllowed)
rsp.Header().Set("Content-Type", "text/html")
rsp.Write([]byte(`
<div style="margin-top: 10%; text-align: center; font-weight: bolder; font-size: 48px; color: lightgray">
:( 405 METHOD NOT ALLOWED
<br><hr>
</div>
`))
}
}
func (s *Service) serveContent(rsp http.ResponseWriter, req *http.Request) {
path := req.URL.Path
if strings.HasPrefix(path, "/-/assets/") {
req.URL.Path = strings.TrimPrefix(path, "/-/assets")
http.FileServer(s.assets).ServeHTTP(rsp, req)
return
}
file, err := os.Open(s.conf.Root + path)
if err != nil {
s.page.Execute(rsp, &map[string]interface{}{
"Title": s.conf.Title,
"Prefix": s.conf.Prefix,
"Path": path,
"IsError": true,
})
return
}
defer file.Close()
info, err := file.Stat()
if err != nil {
s.page.Execute(rsp, &map[string]interface{}{
"Title": s.conf.Title,
"Prefix": s.conf.Prefix,
"Path": path,
"IsError": true,
})
return
}
if info.IsDir() {
rsp.Header().Set("Content-Type", "text/html")
type Item struct {
Name string
Size string
IsDir bool
ModTime string
Path string
Icon string
}
readme := ""
dirs, err := file.Readdir(-1)
if err != nil {
dirs = []os.FileInfo{}
}
items := []*Item{}
for _, dir := range dirs {
item := &Item{
Name: dir.Name(),
IsDir: dir.IsDir(),
ModTime: dir.ModTime().Format("2006-01-02 15:04:05"),
Path: s.conf.Prefix + strings.TrimSuffix(path, "/") + "/" + dir.Name(),
}
if item.IsDir {
item.Size = ""
item.Icon = "folder"
} else {
if strings.ToLower(item.Name) == "readme.md" {
readme = item.Path
}
item.Size = FormatSize(dir.Size())
item.Icon = FindFileIcon(item.Name)
}
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool {
if items[i].IsDir == items[j].IsDir {
return strings.Compare(items[i].Name, items[j].Name) < 0
}
return items[i].IsDir
})
s.page.Execute(rsp, &map[string]interface{}{
"Title": s.conf.Title,
"Prefix": s.conf.Prefix,
"Path": path,
"Contents": items,
"ReadMe": readme,
})
return
}
rsp.Header().Set("Content-Disposition", "attachment;filename="+info.Name())
http.ServeContent(rsp, req, info.Name(), info.ModTime(), file)
}
func (s *Service) serveAPI(rsp http.ResponseWriter, req *http.Request) {
type Response struct {
IsOK bool `json:"ok"`
ErrMsg string `json:"errMsg"`
}
status := http.StatusOK
ret := &Response{IsOK: false, ErrMsg: "UNKNOWN"}
switch req.URL.Path {
case "/-/api/folder":
path := req.FormValue("path")
name := req.FormValue("name")
check := path + name
if check == s.conf.Prefix || check == "/-" {
ret.ErrMsg = "保留字段,请使用其他名字!"
break
}
if name == "" || strings.ContainsAny(name, "./\\?!~+") {
ret.ErrMsg = "非法目录名!"
break
}
err := os.MkdirAll(s.conf.Root+path+"/"+name, 777)
if err != nil {
ret.ErrMsg = "创建目录失败:" + err.Error()
break
}
ret.IsOK = true
case "/-/api/upload":
if req.MultipartForm == nil {
if err := req.ParseMultipartForm(64 << 20); err != nil {
ret.ErrMsg = "解析请求失败:" + err.Error()
break
}
}
path := req.FormValue("path")
parts := req.MultipartForm.File
failed := false
for _, part := range parts {
for _, fh := range part {
check := path + fh.Filename
if check == s.conf.Prefix || check == "/-" {
ret.ErrMsg = "文件名与保留字冲突!"
failed = true
break
}
if err := s.save(fh, check); err != nil {
ret.ErrMsg = "保存文件失败:" + err.Error()
failed = true
break
}
}
if failed {
break
}
}
ret.IsOK = !failed
default:
status = http.StatusBadRequest
ret.ErrMsg = "非法的请求!"
}
data, _ := json.Marshal(ret)
rsp.WriteHeader(status)
rsp.Header().Set("Content-Type", "application/json")
rsp.Write(data)
}
func (s *Service) save(fh *multipart.FileHeader, dir string) error {
reader, err := fh.Open()
if err != nil {
return err
}
defer reader.Close()
writer, err := os.OpenFile(s.conf.Root+dir, os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
return err
}
defer writer.Close()
_, err = io.Copy(writer, reader)
return err
}
func main() {
(&Service{}).Start()
}
Go
1
https://gitee.com/love_linger/Share.git
git@gitee.com:love_linger/Share.git
love_linger
Share
Share
master

搜索帮助