Skip to content

Commit 94f2ada

Browse files
committed
Initial commit
0 parents  commit 94f2ada

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# httpserver
2+
3+
Http file server written with Go using directory listing similar to apache
4+
5+
```
6+
$ go get github.com/pacur/httpserver
7+
$ httpserver
8+
Listening and serving /home/user on :8000
9+
```

main.go

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"github.com/gin-gonic/gin"
7+
"io/ioutil"
8+
"net/http"
9+
"os"
10+
"path/filepath"
11+
"sort"
12+
)
13+
14+
const body = `<html>
15+
<head><title>Index of %s</title></head>
16+
<body bgcolor="white">
17+
<h1>Index of %s</h1><hr><pre><a href="../">../</a>
18+
%s</pre><hr></body>
19+
</html>
20+
`
21+
22+
func IsDirectory(path string) (dir bool, err error) {
23+
stat, err := os.Stat(path)
24+
if err != nil {
25+
if os.IsNotExist(err) {
26+
err = nil
27+
}
28+
return
29+
}
30+
31+
dir = stat.IsDir()
32+
return
33+
}
34+
35+
type Item struct {
36+
Name string
37+
IsDir bool
38+
Formatted string
39+
}
40+
41+
type Items struct {
42+
items []Item
43+
}
44+
45+
func (s *Items) Len() (n int) {
46+
n = len(s.items)
47+
return
48+
}
49+
50+
func (s *Items) Less(i int, j int) bool {
51+
if s.items[i].IsDir && !s.items[j].IsDir {
52+
return true
53+
}
54+
if s.items[i].Name[:1] == "." && s.items[i].Name[:1] != "." {
55+
return true
56+
}
57+
return s.items[i].Name < s.items[j].Name
58+
}
59+
60+
func (s *Items) Swap(i int, j int) {
61+
s.items[i], s.items[j] = s.items[j], s.items[i]
62+
}
63+
64+
func (s *Items) Add(item Item) {
65+
s.items = append(s.items, item)
66+
}
67+
68+
func (s *Items) Sort() {
69+
sort.Sort(s)
70+
}
71+
72+
func (s *Items) Join(sep string) (data string) {
73+
for i, item := range s.items {
74+
if i != 0 {
75+
data += sep
76+
}
77+
data += item.Formatted
78+
}
79+
return
80+
}
81+
82+
type StaticHandler struct {
83+
Root string
84+
fileServer http.Handler
85+
}
86+
87+
func (h *StaticHandler) Handle(c *gin.Context) {
88+
path := filepath.Join(h.Root, filepath.FromSlash(
89+
filepath.Clean("/"+c.Param("filepath"))))
90+
91+
isDir, err := IsDirectory(path)
92+
if err != nil {
93+
c.AbortWithError(500, err)
94+
return
95+
}
96+
97+
ok := false
98+
if isDir {
99+
ok, err = h.HandleDirList(path, c)
100+
if err != nil {
101+
c.AbortWithError(500, err)
102+
return
103+
}
104+
}
105+
106+
if !ok {
107+
h.fileServer.ServeHTTP(c.Writer, c.Request)
108+
}
109+
}
110+
111+
func (h *StaticHandler) HandleDirList(path string, c *gin.Context) (
112+
ok bool, err error) {
113+
114+
pathFrm := filepath.Clean("/" + c.Param("filepath"))
115+
if pathFrm[len(pathFrm)-1:] != "/" {
116+
pathFrm += "/"
117+
}
118+
119+
items := &Items{}
120+
121+
itemsAll, err := ioutil.ReadDir(path)
122+
if err != nil {
123+
return
124+
}
125+
126+
for _, item := range itemsAll {
127+
name := item.Name()
128+
if name == "index.html" {
129+
return
130+
}
131+
132+
modTime := item.ModTime().Format("02-Jan-2006 15:04")
133+
134+
if item.Mode()&os.ModeSymlink != 0 {
135+
linkPath, e := os.Readlink(filepath.Join(path, item.Name()))
136+
if e != nil {
137+
err = e
138+
return
139+
}
140+
141+
itm, e := os.Lstat(linkPath)
142+
if e != nil {
143+
if os.IsNotExist(e) {
144+
continue
145+
}
146+
err = e
147+
return
148+
}
149+
item = itm
150+
}
151+
152+
size := ""
153+
if item.IsDir() {
154+
name += "/"
155+
size = "-"
156+
} else {
157+
size = fmt.Sprintf("%d", item.Size())
158+
}
159+
160+
formattedName := name
161+
if len(formattedName) > 50 {
162+
formattedName = formattedName[:47] + "..>"
163+
}
164+
165+
items.Add(Item{
166+
Name: name,
167+
IsDir: item.IsDir(),
168+
Formatted: fmt.Sprintf(
169+
`<a href="%s">`, name) + fmt.Sprintf(
170+
"%-54s %s % 19s", formattedName+"</a>", modTime, size),
171+
})
172+
}
173+
174+
items.Sort()
175+
176+
ok = true
177+
data := []byte(fmt.Sprintf(body, pathFrm, pathFrm, items.Join("\n")))
178+
c.Data(200, "text/html", data)
179+
180+
return
181+
}
182+
183+
func (h *StaticHandler) Setup(engine *gin.Engine) {
184+
fs := gin.Dir(h.Root, false)
185+
h.fileServer = http.StripPrefix("/", http.FileServer(fs))
186+
187+
engine.GET("/*filepath", h.Handle)
188+
engine.HEAD("/*filepath", h.Handle)
189+
190+
return
191+
}
192+
193+
func main() {
194+
path, err := os.Getwd()
195+
if err != nil {
196+
panic(err)
197+
}
198+
199+
path = *flag.String("path", path, "Path to serve")
200+
host := *flag.String("host", "", "Server host")
201+
port := *flag.Int("port", 8000, "Server port number")
202+
flag.Parse()
203+
204+
path, err = filepath.Abs(path)
205+
if err != nil {
206+
panic(err)
207+
}
208+
209+
static := &StaticHandler{
210+
Root: path,
211+
}
212+
213+
gin.SetMode(gin.ReleaseMode)
214+
router := gin.New()
215+
router.Use(gin.Logger())
216+
router.Use(gin.Recovery())
217+
218+
static.Setup(router)
219+
220+
fmt.Printf("Listening and serving %s on %s:%d\n", path, host, port)
221+
222+
err = router.Run(fmt.Sprintf("%s:%d", host, port))
223+
if err != nil {
224+
panic(err)
225+
}
226+
}

0 commit comments

Comments
 (0)