forked from revel/revel
/
run.go
333 lines (297 loc) · 8.41 KB
/
run.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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package harness
import (
"bytes"
"fmt"
"github.com/robfig/revel"
"go/build"
"io"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"text/template"
)
var (
cmd *exec.Cmd // The app server cmd
)
// Run the Revel program in a given RunMode.
// If RunMode is PROD, it builds and runs the app directly.
// Otherwise, it instantiates a proxy (harness) that watches for changes and
// rebuilds as necessary.
func Run(mode string) {
// If we are in prod mode, just build and run the application.
if mode == rev.PROD {
rev.INFO.Println("Building...")
if err := rebuild(getAppAddress(), getAppPort()); err != nil {
rev.ERROR.Fatalln(err)
}
cmd.Wait()
return
}
// If the harness exits, be sure to kill the app server.
defer func() {
if cmd != nil {
cmd.Process.Kill()
cmd = nil
}
}()
// Run a reverse proxy to it.
harness := NewHarness()
harness.Run()
}
// Rebuild the Revel application and run it on the given port.
func rebuild(addr string, port int) (compileError *rev.Error) {
rev.TRACE.Println("Rebuild")
controllerSpecs, compileError := ScanControllers(path.Join(rev.AppPath, "controllers"))
if compileError != nil {
return compileError
}
tmpl := template.New("RegisterControllers")
tmpl = template.Must(tmpl.Parse(REGISTER_CONTROLLERS))
var registerControllerSource string = rev.ExecuteTemplate(tmpl, map[string]interface{}{
"AppName": rev.AppName,
"Controllers": controllerSpecs,
"ImportPaths": uniqueImportPaths(controllerSpecs),
"RunMode": rev.RunMode,
})
// Terminate the server if it's already running.
if cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
rev.TRACE.Println("Killing revel server pid", cmd.Process.Pid)
err := cmd.Process.Kill()
if err != nil {
rev.ERROR.Fatalln("Failed to kill revel server:", err)
}
}
// Create a fresh temp dir.
tmpPath := path.Join(rev.AppPath, "tmp")
err := os.RemoveAll(tmpPath)
if err != nil {
rev.ERROR.Println("Failed to remove tmp dir:", err)
}
err = os.Mkdir(tmpPath, 0777)
if err != nil {
rev.ERROR.Fatalf("Failed to make tmp directory: %v", err)
}
// Create the new file
controllersFile, err := os.Create(path.Join(tmpPath, "main.go"))
if err != nil {
rev.ERROR.Fatalf("Failed to create main.go: %v", err)
}
_, err = controllersFile.WriteString(registerControllerSource)
if err != nil {
rev.ERROR.Fatalf("Failed to write to main.go: %v", err)
}
// Build the user program (all code under app).
// It relies on the user having "go" installed.
goPath, err := exec.LookPath("go")
if err != nil {
rev.ERROR.Fatalf("Go executable not found in PATH.")
}
ctx := build.Default
pkg, err := ctx.Import(rev.ImportPath, "", build.FindOnly)
if err != nil {
rev.ERROR.Fatalf("Failure importing", rev.ImportPath)
}
binName := path.Join(pkg.BinDir, rev.AppName)
buildCmd := exec.Command(goPath, "build", "-o", binName, path.Join(rev.ImportPath, "app", "tmp"))
rev.TRACE.Println("Exec build:", buildCmd.Path, buildCmd.Args)
output, err := buildCmd.CombinedOutput()
// If we failed to build, parse the error message.
if err != nil {
return newCompileError(output)
}
// Run the server, via tmp/main.go.
cmd = exec.Command(binName,
fmt.Sprintf("-addr=%s", addr),
fmt.Sprintf("-port=%d", port),
fmt.Sprintf("-importPath=%s", rev.ImportPath))
rev.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
listeningWriter := startupListeningWriter{os.Stdout, make(chan bool)}
cmd.Stdout = listeningWriter
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
rev.ERROR.Fatalln("Error running:", err)
}
<-listeningWriter.notifyReady
return nil
}
// A io.Writer that copies to the destination, and listens for "Listening on.."
// in the stream. (Which tells us when the revel server has finished starting up)
// This is super ghetto, but by far the simplest thing that should work.
type startupListeningWriter struct {
dest io.Writer
notifyReady chan bool
}
func (w startupListeningWriter) Write(p []byte) (n int, err error) {
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening")) {
w.notifyReady <- true
w.notifyReady = nil
}
return w.dest.Write(p)
}
// Return port that the app should listen on.
// 9000 by default.
func getAppPort() int {
port, err := rev.Config.Int("http.port")
if err != nil {
return 9000
}
return port
}
// Return address that the app should listen on.
// Wildcard by default.
func getAppAddress() string {
addr, err := rev.Config.String("http.addr")
if err != nil {
return ""
}
return addr
}
// Find an unused port
func getFreePort() (port int) {
conn, err := net.Listen("tcp", ":0")
if err != nil {
rev.ERROR.Fatal(err)
}
port = conn.Addr().(*net.TCPAddr).Port
err = conn.Close()
if err != nil {
rev.ERROR.Fatal(err)
}
return port
}
// Looks through all the method args and returns a set of unique import paths
// that cover all the method arg types.
func uniqueImportPaths(specs []*ControllerSpec) (paths []string) {
importPathMap := make(map[string]bool)
for _, spec := range specs {
importPathMap[spec.ImportPath] = true
for _, methSpec := range spec.MethodSpecs {
for _, methArg := range methSpec.Args {
if methArg.ImportPath != "" {
importPathMap[methArg.ImportPath] = true
}
}
}
}
for importPath := range importPathMap {
paths = append(paths, importPath)
}
return
}
// Parse the output of the "go build" command.
// Return a detailed Error.
func newCompileError(output []byte) *rev.Error {
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
FindSubmatch(output)
if errorMatch == nil {
rev.ERROR.Println("Failed to parse build errors:\n", string(output))
return &rev.Error{
SourceType: "Go code",
Title: "Go Compilation Error",
Description: "See console for build error.",
}
}
// Read the source for the offending file.
var (
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
absFilename, _ = filepath.Abs(relFilename)
line, _ = strconv.Atoi(string(errorMatch[2]))
description = string(errorMatch[4])
compileError = &rev.Error{
SourceType: "Go code",
Title: "Go Compilation Error",
Path: relFilename,
Description: description,
Line: line,
}
)
fileStr, err := rev.ReadLines(absFilename)
if err != nil {
compileError.MetaError = absFilename + ": " + err.Error()
rev.ERROR.Println(compileError.MetaError)
return compileError
}
compileError.SourceLines = fileStr
return compileError
}
// proxyWebsocket copies data between websocket client and server until one side
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
d, err := net.Dial("tcp", host)
if err != nil {
http.Error(w, "Error contacting backend server.", 500)
rev.ERROR.Printf("Error dialing websocket backend %s: %v", host, err)
return
}
hj, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Not a hijacker?", 500)
return
}
nc, _, err := hj.Hijack()
if err != nil {
rev.ERROR.Printf("Hijack error: %v", err)
return
}
defer nc.Close()
defer d.Close()
err = r.Write(d)
if err != nil {
rev.ERROR.Printf("Error copying request to target: %v", err)
return
}
errc := make(chan error, 2)
cp := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
errc <- err
}
go cp(d, nc)
go cp(nc, d)
<-errc
}
const REGISTER_CONTROLLERS = `package main
import (
"flag"
"reflect"
"github.com/robfig/revel"
{{range .ImportPaths}}
"{{.}}"
{{end}}
)
var (
addr *string = flag.String("addr", "", "Address to listen on")
port *int = flag.Int("port", 0, "Port")
importPath *string = flag.String("importPath", "", "Path to the app.")
// So compiler won't complain if the generated code doesn't reference reflect package...
_ = reflect.Invalid
)
func main() {
rev.INFO.Println("Running revel server")
flag.Parse()
rev.Init(*importPath, "{{.RunMode}}")
{{range $i, $c := .Controllers}}
rev.RegisterController((*{{.PackageName}}.{{.StructName}})(nil),
[]*rev.MethodType{
{{range .MethodSpecs}}&rev.MethodType{
Name: "{{.Name}}",
Args: []*rev.MethodArg{ {{range .Args}}
&rev.MethodArg{Name: "{{.Name}}", Type: reflect.TypeOf((*{{.TypeName}})(nil)) },{{end}}
},
RenderArgNames: map[int][]string{ {{range .RenderCalls}}
{{.Line}}: []string{ {{range .Names}}
"{{.}}",{{end}}
},{{end}}
},
},
{{end}}
})
{{end}}
rev.Run(*addr, *port)
}
`