forked from revel/revel
/
app.go
118 lines (102 loc) · 3 KB
/
app.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
package harness
import (
"bytes"
"errors"
"fmt"
"github.com/revel/revel"
"io"
"os"
"os/exec"
"time"
)
// App contains the configuration for running a Revel app. (Not for the app itself)
// Its only purpose is constructing the command to execute.
type App struct {
BinaryPath string // Path to the app executable
Port int // Port to pass as a command line argument.
cmd AppCmd // The last cmd returned.
}
func NewApp(binPath string) *App {
return &App{BinaryPath: binPath}
}
// Return a command to run the app server using the current configuration.
func (a *App) Cmd() AppCmd {
a.cmd = NewAppCmd(a.BinaryPath, a.Port)
return a.cmd
}
// Kill the last app command returned.
func (a *App) Kill() {
a.cmd.Kill()
}
// AppCmd manages the running of a Revel app server.
// It requires revel.Init to have been called previously.
type AppCmd struct {
*exec.Cmd
}
func NewAppCmd(binPath string, port int) AppCmd {
cmd := exec.Command(binPath,
fmt.Sprintf("-port=%d", port),
fmt.Sprintf("-importPath=%s", revel.ImportPath),
fmt.Sprintf("-runMode=%s", revel.RunMode))
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
return AppCmd{cmd}
}
// Start the app server, and wait until it is ready to serve requests.
func (cmd AppCmd) Start() error {
listeningWriter := startupListeningWriter{os.Stdout, make(chan bool)}
cmd.Stdout = listeningWriter
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
if err := cmd.Cmd.Start(); err != nil {
revel.ERROR.Fatalln("Error running:", err)
}
select {
case <-cmd.waitChan():
return errors.New("revel/harness: app died")
case <-time.After(30 * time.Second):
cmd.Kill()
return errors.New("revel/harness: app timed out")
case <-listeningWriter.notifyReady:
return nil
}
panic("Impossible")
}
// Run the app server inline. Never returns.
func (cmd AppCmd) Run() {
revel.TRACE.Println("Exec app:", cmd.Path, cmd.Args)
if err := cmd.Cmd.Run(); err != nil {
revel.ERROR.Fatalln("Error running:", err)
}
}
// Terminate the app server if it's running.
func (cmd AppCmd) Kill() {
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
revel.TRACE.Println("Killing revel server pid", cmd.Process.Pid)
err := cmd.Process.Kill()
if err != nil {
revel.ERROR.Fatalln("Failed to kill revel server:", err)
}
}
}
// Return a channel that is notified when Wait() returns.
func (cmd AppCmd) waitChan() <-chan struct{} {
ch := make(chan struct{}, 1)
go func() {
cmd.Wait()
ch <- struct{}{}
}()
return ch
}
// 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)
}