Skip to content

Instantly share code, notes, and snippets.

@hucsmn
Last active February 28, 2020 05:17
Show Gist options
  • Save hucsmn/06e6d3a81b01b4b100b5 to your computer and use it in GitHub Desktop.
Save hucsmn/06e6d3a81b01b4b100b5 to your computer and use it in GitHub Desktop.
简易的HTTP文件夹分享
/* Simple HTTP file sharing service */
package main
import (
"flag"
"fmt"
"html/template"
"net/http"
"os"
"path"
"strings"
)
// Template sources.
const (
src_notfound = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>{{.Name}}</title>
<style type="text/css">
body {
color: #777777;
background-color: #FCFCFC;
height: 100%;
}
h1 {
color: #336699;
}
</style>
</head>
<body>
<h1>文件不存在</h1>
<p>无法访问 {{.Path}}</p>
</body>
</html>
`
src_browse = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>{{.Name}}</title>
<style type="text/css">
body {
color: #777777;
background-color: #FCFCFC;
height: 100%;
}
a {
color: #336699;
text-decoration: none;
}
a:visited {
color: #CC6666;
text-decoration: none;
}
.paths, .paths a {
color: #336699;
text-decoration: none;
}
.paths a:hover {
background-color: #EEF0F0;
border-radius: 3px;
}
.files table {
background-color: #EDEDED;
border-radius: 1px;
padding: 3px 3px;
width: 100%;
}
.files td:first-child {
width: 75%;
}
.files tr.theader {
color: #F2F7FC;
background-color: #4F94CD;
text-align: left;
}
.files tr.tbody:hover {
background-color: #EEF0F0;
}
.files a {
display: block;
}
</style>
</head>
<body>
<div class="paths">
<h1>文件夹 {{range .Paths}}<a href="{{.Path}}">{{.Name}}</a>{{end}} </h1>
</div>
<div class="files">
<table border="0">
<tr class="theader"> <th>文件名</th> <th>大小</th> <th>修改时间</th> </tr>
{{range .Files}}
<tr class="tbody">
<td><a href="{{.Path}}">{{.Name}}</a></td>
<td align="right">{{.Size}}</td>
<td>{{.Time}}</td>
</tr>
{{else}}
<tr>
<td>空目录</td>
</tr>
{{end}}
</table>
</div>
</body>
</html>
`
)
// Templates.
var (
tmpl_notfound = template.Must(template.New("notfound").Parse(src_notfound))
tmpl_browse = template.Must(template.New("browse").Parse(src_browse))
)
// Command args.
var (
prog string
addr string
quiet bool
dir http.Dir
)
// Record file info.
type FileInfo struct {
Name, Path, Size, Time string
}
// Parse args.
func init() {
var help bool
prog = os.Args[0]
flag.StringVar(&addr, "a", "0.0.0.0:80", "which address to serve")
flag.BoolVar(&help, "h", false, "show this help")
flag.BoolVar(&quiet, "q", false, "be quiet")
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "share files through http")
fmt.Fprintln(os.Stderr, "usage:", prog, "[-h] [-q] [-a [ip]:port] [directory]")
fmt.Fprintln(os.Stderr, "options:")
flag.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(os.Stderr, " -%s %s\n", f.Name, f.Usage)
})
}
flag.Parse()
if help {
flag.Usage()
os.Exit(2)
}
if flag.NArg() == 0 {
dir = http.Dir(".")
} else {
dpath := flag.Arg(0)
if fi, err := os.Stat(dpath); err != nil {
fmt.Fprintf(os.Stderr, "%s: %s: %s\n", prog, dpath, err)
os.Exit(1)
} else if !fi.IsDir() {
fmt.Fprintf(os.Stderr, "%s: not a directory: %s\n", prog, dpath)
os.Exit(1)
}
dir = http.Dir(dpath)
}
}
// Start sharing.
func main() {
Msgf("share directory `%s` via address `%s`\n", dir, addr)
http.HandleFunc("/", Visit)
err := http.ListenAndServe(addr, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s\n", prog, err)
os.Exit(1)
}
}
// Display message.
func Msgf(format string, v ...interface{}) {
if !quiet {
fmt.Printf(format, v...)
}
}
// Handle 404 not found.
func NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
v := struct {
Name, Path string
}{path.Base(r.URL.Path), r.URL.Path}
tmpl_notfound.Execute(w, v)
}
// Display file list or serve file.
func Visit(w http.ResponseWriter, r *http.Request) {
Msgf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL.Path)
name := path.Clean(r.URL.Path)
f, err := dir.Open(name)
if err != nil {
NotFound(w, r)
return
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
NotFound(w, r)
return
}
if fi.IsDir() {
// split path.
var paths []FileInfo
var dpath string
for _, dname := range strings.SplitAfter(name, "/") {
dpath += dname
paths = append(paths, FileInfo{Name: dname, Path: path.Clean(dpath)})
}
// file list.
var dirs, files []FileInfo
for {
fis, _ := f.Readdir(100)
if len(fis) == 0 {
break
}
for _, fi := range fis {
info := FileInfo{
Name: fi.Name(),
Path: path.Join(name, fi.Name()),
Size: FormatSize(fi.Size()),
Time: fi.ModTime().Format("2006-01-02 15:04"),
}
if fi.IsDir() {
info.Name += "/"
info.Size = "-"
dirs = append(dirs, info)
} else {
files = append(files, info)
}
}
}
// display file list.
data := struct {
Name string
Paths []FileInfo
Files []FileInfo
}{
path.Base(name),
paths,
append(dirs, files...),
}
tmpl_browse.Execute(w, data)
} else {
// serve file.
http.ServeContent(w, r, name, fi.ModTime(), f)
}
}
// Human-readable size.
func FormatSize(size int64) string {
var unit rune
for _, unit = range "BKMGT" {
if size < 1024 {
break
}
size /= 1024
}
return fmt.Sprintf("%d%c", size, unit)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment