Commits

Liam Staskawicz committed 2863e5d

* capture compilation output and return to the browser on failure
* allow stdout/stderr to pass through the console as well via io.MultiWriter
* return error when target app fails to start up - still not reporting errors on failure during runtime
* add tmpl to file suffixes to monitor

Comments (0)

Files changed (3)

src/compilation.go

     	if thistime.After(modtime) {
     	    ext := filepath.Ext(info.Name())
     	    // TODO - make extensions configurable
-    	    if ext == ".go" || ext == ".html" {
+    	    if ext == ".go" || ext == ".html" || ext == ".tmpl" {
     	        mostRecentModTime = thistime
     	    }
     	}
 
 	// config file defaults, overwritten by anything in the real file
 	conf := &Config{
-		"127.0.0.1:8080", // DevServerAddress
-		"127.0.0.1:8081", // AppAddress
-		"",               // App
-		100000000,        // CrashTimeout
-		1 * time.Second,  // StartupTime
-		"",               // LogFile
+		"127.0.0.1:8080",   // DevServerAddress
+		"127.0.0.1:8081",   // AppAddress
+		"",                 // App
+		100000000,          // CrashTimeout
+		time.Second / 2,    // StartupTime
+		"",                 // LogFile
 	}
 
 	if err = json.Unmarshal(b, &conf); err != nil {
 )
 
 type Reloader struct {
-	reverseProxy *httputil.ReverseProxy
-	config       *Config
-	appRoot      string
-	appCmd       *exec.Cmd
-	cmdOutputBuf bytes.Buffer
+	reverseProxy    *httputil.ReverseProxy
+	config          *Config
+	appRoot         string
+	appCmd          *exec.Cmd
+	appOutBuf       bytes.Buffer
 }
 
 func main() {
 func (r *Reloader) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	appPath := filepath.Join(r.appRoot, r.config.App)
 	if r.targetMustBeBuilt(appPath) {
-		if !r.buildWithGoMake(r.appRoot) {
+		if output, err := r.buildWithGoMake(r.appRoot); err != nil {
 			w.Write([]byte("so sorry, there appear to be some errors:\n\n"))
-			r.cmdOutputBuf.WriteTo(w)
+			w.Write(output)
 			// TODO: a nicer template here
 			return
 		}
-		r.cmdOutputBuf.Reset()
-	}
-	if r.ensureTargetIsRunning(appPath, w) {
-	    r.reverseProxy.ServeHTTP(w, req)
 	}
+	r.ensureTargetIsRunning(appPath)
+    if !r.checkForTargetErrors(w) {
+        r.reverseProxy.ServeHTTP(w, req)
+    }
+    
+    // TODO - figure out how to write errors back to browser
+    // after app fails while serving request. currently, fails because the content-length
+    // header gets set somewhere, and we cann't write any more bytes to the connection.
+    // r.checkForTargetErrors(w)
+    // r.appOutBuf.Reset()
 }
 
 // build a project
 // right now, we only support calling gomake
-// but we could potentially call out to the compiler
-// ourselves in the future if that seems useful
-func (r *Reloader) buildWithGoMake(projectDir string) bool {
+// but we could potentially call out to the compiler or some other build tool
+// in the future if that seems useful
+func (r *Reloader) buildWithGoMake(projectDir string) ([]byte, error) {
 	gomake := exec.Command("gomake")
 	gomake.Dir = projectDir
-	gomake.Stdout = &r.cmdOutputBuf
-	gomake.Stderr = &r.cmdOutputBuf
-	err := gomake.Run()
-	return err == nil
+	return gomake.CombinedOutput()
 }
 
 // determine whether the target app needs to be built.
 }
 
 // start up the target app, and keep a pointer to it on the Reloader
-func (r *Reloader) ensureTargetIsRunning(appPath string, errorDest io.Writer) bool {
+func (r *Reloader) ensureTargetIsRunning(appPath string) {
 	if r.appCmd != nil {
-		return true
+		return
 	}
-	var appOutputBuf bytes.Buffer   // capture output in case it fails to start
 	r.appCmd = exec.Command(appPath)
 	r.appCmd.Dir = r.appRoot
-	r.appCmd.Stdout = &appOutputBuf
-	r.appCmd.Stderr = &appOutputBuf
+	r.appCmd.Stdout = io.MultiWriter(os.Stdout, &r.appOutBuf)
+	r.appCmd.Stderr = io.MultiWriter(os.Stderr, &r.appOutBuf)
 	if err := r.appCmd.Start(); err != nil {
 		log.Fatal("couldn't start app:", err.Error())
-		return false
 	}
     time.Sleep(r.config.StartupTime) // give it a moment to start up...surely there's a better way?
-    return true
+}
+
+func (r *Reloader) checkForTargetErrors(errDest io.Writer) bool {
+    // check for error
+    waitmsg, err := r.appCmd.Process.Wait(os.WNOHANG)
+    // TODO - on darwin, even if WaitStatus is 256, Stopped() returns false...
+    // just check directly for now, but it's not nice
+    if err != nil || waitmsg.WaitStatus != 0 { //}.Stopped()
+        errDest.Write([]byte("so sorry, not running anymore:\n\n"))
+        // NOTE: write outbuf bytes, but leave them in the buffer so we still
+        // keep the same error message in case we see subsequent reloads before
+        // the error is resolved
+        errDest.Write(r.appOutBuf.Bytes())
+        return true;
+    }
+    r.appOutBuf.Reset()
+    return false
 }