/
handyhttpd.go
243 lines (202 loc) · 7.01 KB
/
handyhttpd.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package main
import (
"flag"
"log"
"io"
"os"
"os/signal"
"path/filepath"
"net"
"net/url"
"net/http"
"syscall"
"fmt"
"strconv"
"strings"
)
const (
HANDY_SOCK_FILENAME = "handyhttpd.sock"
HANDY_LOG_FILENAME = "handyhttpd.log"
)
// remember all handler instances
var (
gLogger *log.Logger
)
func parseParams(root, pattern string, port int, remove bool) bool {
handy, ok := Find(port)
if !ok {
if handy = New(port, gLogger); handy == nil {
gLogger.Println("cannot create new handy server")
return false
}
}
if remove {
handy.Del(root)
} else {
handy.Add(root, pattern)
}
handy.Start()
return true
}
func main() {
port := flag.Int("port", 0, "Port to serve http request. By default, the port is the last port you've used.")
dir := flag.String("dir", "", "Dir served as www root. By default, current dir will be served.")
alias := flag.String("alias", "", "URL alias to serve. By default, dir name will be used as alias.")
remove := flag.Bool("remove", false, "Remove current dir so that no one can visit it thru http anymore.")
list := flag.Bool("list", false, "List all running servers and hosted dirs.")
worker := flag.Bool("worker", false, "Indicate it's a worker process. It's used to daemonize handyhttpd. Never use it in command line.")
quit := flag.Bool("quit", false, "Quit server completely.")
flag.Parse()
tempdir := os.TempDir()
file, err := os.OpenFile(tempdir + "/" + HANDY_LOG_FILENAME, os.O_APPEND | os.O_CREATE | os.O_RDWR, 0666)
if err != nil {
log.Println("cannot open log file to write. filename:", tempdir + "/" + HANDY_LOG_FILENAME, "err:", err)
log.Println("print log to stdout now")
file = os.Stdout
}
// worker need to close all std in/out/err
if *worker {
fd, err := syscall.Open("/dev/null", syscall.O_RDWR, 0)
if err != nil {
fmt.Println("cannot open /dev/null", "err:", err)
panic(err)
}
syscall.Dup2(fd, syscall.Stdin)
syscall.Dup2(fd, syscall.Stdout)
syscall.Dup2(fd, syscall.Stderr)
if fd > syscall.Stderr {
syscall.Close(fd)
}
}
gLogger = log.New(file, "", log.LstdFlags)
gLogger.Printf("parsed params. [port: %d] [dir: %s] [alias: %s] [remove: %t] [list: %t] [quit: %t]\n",
*port, *dir, *alias, *remove, *list, *quit)
root := *dir
if root == "" {
root, _ = os.Getwd()
}
pattern := *alias
if pattern == "" {
pattern = filepath.Base(root)
}
socket := tempdir + "/" + HANDY_SOCK_FILENAME
l, err := net.Listen("unix", socket)
// there is a handyhttpd running. notify it with current options.
if err != nil {
// hack http client's transport to force it to use unix socket rather than tcp
client := http.Client {
Transport: &http.Transport {
Dial: func (n, addr string) (conn net.Conn, err error) {
return net.Dial("unix", socket)
},
},
}
var r *http.Response
if *list {
r, err = client.Get("http://localhost/list")
} else if *quit {
r, err = client.Get("http://localhost/quit")
} else {
var verb string
if *remove {
verb = "remove"
} else {
verb = "add"
}
// format is:
// GET /?verb=add&alias=abc&dir=/path/to/www/root&port=9696
r, err = client.Get(
fmt.Sprintf("http://localhost/?verb=%s&alias=%s&dir=%s&port=%d",
verb, url.QueryEscape(pattern), url.QueryEscape(root), *port))
}
if err != nil {
gLogger.Println("cannot connect handy server. err:", err)
return
}
if r.StatusCode != 200 {
gLogger.Println("handy server denies the request. code:", r.StatusCode)
return
}
io.Copy(os.Stdout, r.Body)
return
}
defer l.Close()
// daemonize handyhttpd
if !*worker {
args := append([]string{os.Args[0], "-worker"}, os.Args[1:]...)
exec := os.Args[0]
// if exec is called without any path separator, it must be in a PATH dir.
if !strings.ContainsRune(exec, os.PathSeparator) {
path := os.Getenv("PATH")
paths := strings.Split(path, fmt.Sprintf("%c", os.PathListSeparator))
for _, s := range(paths) {
if file, err := os.Stat(s + "/" + exec); err == nil && !file.IsDir() {
exec = s + "/" + exec
}
}
}
_, _, err := syscall.StartProcess(exec, args, nil)
if err != nil {
fmt.Println("cannot daemonize handyhttpd", "err:", err)
gLogger.Println("cannot daemonize handyhttpd", "err:", err)
return
}
return
}
// there is no running handy, just return
if *quit {
gLogger.Println("gracefully exit with command line")
return
}
// as this server is the only running server, nothing to list
if *list {
fmt.Println("No server is running")
return
}
parseParams(root, pattern, *port, *remove)
go func() {
// handle other server's request
http.HandleFunc("/list", func(w http.ResponseWriter, r *http.Request) {
gLogger.Println("listing all ports per request")
List(w)
})
http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Handy server is quiting now")
// gracefully exit
process, _ := os.FindProcess(os.Getpid())
process.Signal(syscall.SIGINT)
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
gLogger.Println("handle a verb request. url:", r.URL)
r.ParseForm()
verb, verbOk := r.Form["verb"]
alias, aliasOk := r.Form["alias"]
dir, dirOk := r.Form["dir"]
port, portOk := r.Form["port"]
if !verbOk || !aliasOk || !dirOk || !portOk {
gLogger.Println("missing required query string params")
w.WriteHeader(http.StatusBadRequest)
return
}
remove := false
if verb[0] == "remove" {
remove = true
}
portNumber, _ := strconv.Atoi(port[0])
if parseParams(dir[0], alias[0], portNumber, remove) {
fmt.Fprintf(w, "%s dir %s as /%s on port %d\n", verb[0], dir[0], alias[0], LastPort())
}
})
http.Serve(l, nil)
}()
defer Stop()
sig := make(chan os.Signal, 1)
signal.Notify(sig)
for {
s := <-sig
if s == syscall.SIGKILL || s == syscall.SIGINT || s == syscall.SIGTERM {
gLogger.Println("gracefully exit with signal", s)
return
}
}
}