forked from xyproto/algernon
/
static.go
110 lines (94 loc) · 3.4 KB
/
static.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package main
// This source file is for the special case of serving a single file.
import (
"errors"
log "github.com/sirupsen/logrus"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
const (
defaultStaticCacheSize = 128 * MiB
maxAttemptsAtIncreasingPortNumber = 128
waitBeforeOpen = time.Millisecond * 200
)
// nextPort increases the port number by 1
func nextPort(colonPort string) (string, error) {
if !strings.HasPrefix(colonPort, ":") {
return colonPort, errors.New("colonPort does not start with a colon! \"" + colonPort + "\"")
}
num, err := strconv.Atoi(colonPort[1:])
if err != nil {
return colonPort, errors.New("Could not convert port number to string: \"" + colonPort[1:] + "\"")
}
// Increase the port number by 1, add a colon, convert to string and return
return ":" + strconv.Itoa(num+1), nil
}
// This is a bit hacky, but it's only used when serving a single static file
func openAfter(wait time.Duration, hostname, colonPort string, https bool, cancelChannel chan bool) {
// Wait a bit
time.Sleep(wait)
select {
case _ = <-cancelChannel:
// Got a message on the cancelChannel:
// don't open the URL with an external application.
return
case <-time.After(waitBeforeOpen):
// Got timeout, assume the port was not busy
openURL(hostname, colonPort, https)
}
}
// shortInfo outputs a short string about which file is served where
func shortInfoAndOpen(filename, colonPort string, cancelChannel chan bool) {
hostname := "localhost"
if serverHost != "" {
hostname = serverHost
}
log.Info("Serving " + filename + " on http://" + hostname + colonPort)
if openURLAfterServing {
go openAfter(waitBeforeOpen, hostname, colonPort, false, cancelChannel)
}
}
// Convenience function for serving only a single file
// (quick and easy way to view a README.md file)
func serveStaticFile(filename, colonPort string, pongomutex *sync.RWMutex) {
log.Info("Single file mode. Not using the regular parameters.")
cancelChannel := make(chan bool, 1)
shortInfoAndOpen(filename, colonPort, cancelChannel)
mux := http.NewServeMux()
// 64 MiB cache, use cache compression, no per-file size limit, use best gzip compression
preferSpeed = false
cache := newFileCache(defaultStaticCacheSize, true, 0)
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Server", versionString)
filePage(w, req, filename, nil, nil, cache, pongomutex)
})
HTTPserver := newGracefulServer(mux, false, serverHost+colonPort, 5*time.Second)
// Attempt to serve just the single file
if errServe := HTTPserver.ListenAndServe(); errServe != nil {
// If it fails, try several times, increasing the port by 1 each time
for i := 0; i < maxAttemptsAtIncreasingPortNumber; i++ {
if errServe = HTTPserver.ListenAndServe(); errServe != nil {
cancelChannel <- true
if !strings.HasSuffix(errServe.Error(), "already in use") {
// Not a problem with address already being in use
fatalExit(errServe)
}
log.Warn("Address already in use. Using next port number.")
if newPort, errNext := nextPort(colonPort); errNext != nil {
fatalExit(errNext)
} else {
colonPort = newPort
}
// Make a new cancel channel, and use the new URL
cancelChannel = make(chan bool, 1)
shortInfoAndOpen(filename, colonPort, cancelChannel)
HTTPserver = newGracefulServer(mux, false, serverHost+colonPort, 5*time.Second)
}
}
// Several attempts failed
fatalExit(errServe)
}
}