github.com/jd-ly/cmd@v1.0.10/harness/harness.go (about)

     1  // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
     2  // Revel Framework source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package harness for a Revel Framework.
     6  //
     7  // It has a following responsibilities:
     8  // 1. Parse the user program, generating a main.go file that registers
     9  //    controller classes and starts the user's server.
    10  // 2. Build and run the user program.  Show compile errors.
    11  // 3. Monitor the user source and re-build / restart the program when necessary.
    12  //
    13  // Source files are generated in the app/tmp directory.
    14  package harness
    15  
    16  import (
    17  	"crypto/tls"
    18  	"fmt"
    19  	"time"
    20  	"go/build"
    21  	"io"
    22  	"net"
    23  	"net/http"
    24  	"net/http/httputil"
    25  	"net/url"
    26  	"os"
    27  	"os/signal"
    28  	"path/filepath"
    29  	"strings"
    30  	"sync/atomic"
    31  
    32  	"github.com/jd-ly/cmd/model"
    33  	"github.com/jd-ly/cmd/utils"
    34  	"github.com/jd-ly/cmd/watcher"
    35  	"html/template"
    36  	"io/ioutil"
    37  	"sync"
    38  	"encoding/json"
    39  )
    40  
    41  var (
    42  	doNotWatch = []string{"tmp", "views", "routes"}
    43  
    44  	lastRequestHadError int32
    45  )
    46  
    47  // Harness reverse proxies requests to the application server.
    48  // It builds / runs / rebuilds / restarts the server when code is changed.
    49  type Harness struct {
    50  	app        *App                   // The application
    51  	useProxy   bool                   // True if proxy is in use
    52  	serverHost string                 // The proxy server host
    53  	port       int                    // The proxy serber port
    54  	proxy      *httputil.ReverseProxy // The proxy
    55  	watcher    *watcher.Watcher       // The file watched
    56  	mutex      *sync.Mutex            // A mutex to prevent concurrent updates
    57  	paths      *model.RevelContainer  // The Revel container
    58  	config     *model.CommandConfig   // The configuration
    59  	runMode    string                 // The runmode the harness is running in
    60  	isError    bool                   // True if harness is in error state
    61  	ranOnce    bool                   // True app compiled once
    62  }
    63  
    64  func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
    65  	// Render error here
    66  	// Grab the template from three places
    67  	// 1) Application/views/errors
    68  	// 2) revel_home/views/errors
    69  	// 3) views/errors
    70  	if err == nil {
    71  		utils.Logger.Panic("Caller passed in a nil error")
    72  	}
    73  	templateSet := template.New("__root__")
    74  	seekViewOnPath := func(view string) (path string) {
    75  		path = filepath.Join(h.paths.ViewsPath, "errors", view)
    76  		if !utils.Exists(path) {
    77  			path = filepath.Join(h.paths.RevelPath, "templates", "errors", view)
    78  		}
    79  
    80  		data, err := ioutil.ReadFile(path)
    81  		if err != nil {
    82  			utils.Logger.Error("Unable to read template file", path)
    83  		}
    84  		_, err = templateSet.New("errors/" + view).Parse(string(data))
    85  		if err != nil {
    86  			utils.Logger.Error("Unable to parse template file", path)
    87  		}
    88  		return
    89  	}
    90  	target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")}
    91  	if !utils.Exists(target[0]) {
    92  		fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
    93  		fmt.Fprintf(iw, "An error ocurred %s", err.Error())
    94  		return
    95  	}
    96  	var revelError *utils.SourceError
    97  	switch e := err.(type) {
    98  	case *utils.SourceError:
    99  		revelError = e
   100  	case error:
   101  		revelError = &utils.SourceError{
   102  			Title:       "Server Error",
   103  			Description: e.Error(),
   104  		}
   105  	}
   106  
   107  	if revelError == nil {
   108  		panic("no error provided")
   109  	}
   110  	viewArgs := map[string]interface{}{}
   111  	viewArgs["RunMode"] = h.paths.RunMode
   112  	viewArgs["DevMode"] = h.paths.DevMode
   113  	viewArgs["Error"] = revelError
   114  
   115  	// Render the template from the file
   116  	err = templateSet.ExecuteTemplate(iw, "errors/500.html", viewArgs)
   117  	if err != nil {
   118  		utils.Logger.Error("Failed to execute", "error", err)
   119  	}
   120  }
   121  
   122  // ServeHTTP handles all requests.
   123  // It checks for changes to app, rebuilds if necessary, and forwards the request.
   124  func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   125  	// Don't rebuild the app for favicon requests.
   126  	if lastRequestHadError > 0 && r.URL.Path == "/favicon.ico" {
   127  		return
   128  	}
   129  
   130  	// Flush any change events and rebuild app if necessary.
   131  	// Render an error page if the rebuild / restart failed.
   132  	err := h.watcher.Notify()
   133  	if err != nil {
   134  		// In a thread safe manner update the flag so that a request for
   135  		// /favicon.ico does not trigger a rebuild
   136  		atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
   137  		h.renderError(w, r, err)
   138  		return
   139  	}
   140  
   141  	// In a thread safe manner update the flag so that a request for
   142  	// /favicon.ico is allowed
   143  	atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
   144  
   145  	// Reverse proxy the request.
   146  	// (Need special code for websockets, courtesy of bradfitz)
   147  	if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
   148  		h.proxyWebsocket(w, r, h.serverHost)
   149  	} else {
   150  		h.proxy.ServeHTTP(w, r)
   151  	}
   152  }
   153  
   154  // NewHarness method returns a reverse proxy that forwards requests
   155  // to the given port.
   156  func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *Harness {
   157  	// Get a template loader to render errors.
   158  	// Prefer the app's views/errors directory, and fall back to the stock error pages.
   159  	//revel.MainTemplateLoader = revel.NewTemplateLoader(
   160  	//	[]string{filepath.Join(revel.RevelPath, "templates")})
   161  	//if err := revel.MainTemplateLoader.Refresh(); err != nil {
   162  	//	revel.RevelLog.Error("Template loader error", "error", err)
   163  	//}
   164  
   165  	addr := paths.HTTPAddr
   166  	port := paths.Config.IntDefault("harness.port", 0)
   167  	scheme := "http"
   168  
   169  	if paths.HTTPSsl {
   170  		scheme = "https"
   171  	}
   172  
   173  	// If the server is running on the wildcard address, use "localhost"
   174  	if addr == "" {
   175  		utils.Logger.Warn("No http.addr specified in the app.conf listening on localhost interface only. " +
   176  			"This will not allow external access to your application")
   177  		addr = "localhost"
   178  	}
   179  
   180  	if port == 0 {
   181  		port = getFreePort()
   182  	}
   183  
   184  	serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
   185  
   186  	serverHarness := &Harness{
   187  		port:       port,
   188  		serverHost: serverURL.String()[len(scheme+"://"):],
   189  		proxy:      httputil.NewSingleHostReverseProxy(serverURL),
   190  		mutex:      &sync.Mutex{},
   191  		paths:      paths,
   192  		useProxy:   !noProxy,
   193  		config:     c,
   194  		runMode:    runMode,
   195  	}
   196  
   197  	if paths.HTTPSsl {
   198  		serverHarness.proxy.Transport = &http.Transport{
   199  			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   200  		}
   201  	}
   202  	return serverHarness
   203  }
   204  
   205  // Refresh method rebuilds the Revel application and run it on the given port.
   206  // called by the watcher
   207  func (h *Harness) Refresh() (err *utils.SourceError) {
   208  	t  := time.Now();
   209  	fmt.Println("Changed detected, recompiling")
   210  	err = h.refresh()
   211  	if err!=nil && !h.ranOnce && h.useProxy {
   212  		addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
   213  
   214  		fmt.Printf("\nError compiling code, to view error details see proxy running on http://%s\n\n",addr)
   215  	}
   216  
   217  	h.ranOnce = true
   218  	fmt.Printf("\nTime to recompile %s\n",time.Now().Sub(t).String())
   219  	return
   220  }
   221  
   222  func (h *Harness) refresh() (err *utils.SourceError) {
   223  	// Allow only one thread to rebuild the process
   224  	// If multiple requests to rebuild are queued only the last one is executed on
   225  	// So before a build is started we wait for a second to determine if
   226  	// more requests for a build are triggered.
   227  	// Once no more requests are triggered the build will be processed
   228  	h.mutex.Lock()
   229  	defer h.mutex.Unlock()
   230  
   231  	if h.app != nil {
   232  		h.app.Kill()
   233  	}
   234  
   235  	utils.Logger.Info("Rebuild Called")
   236  	var newErr error
   237  	h.app, newErr = Build(h.config, h.paths)
   238  	if newErr != nil {
   239  		utils.Logger.Error("Build detected an error", "error", newErr)
   240  		if castErr, ok := newErr.(*utils.SourceError); ok {
   241  			return castErr
   242  		}
   243  		err = &utils.SourceError{
   244  			Title:       "App failed to start up",
   245  			Description: err.Error(),
   246  		}
   247  		return
   248  	}
   249  
   250  	if h.useProxy {
   251  		h.app.Port = h.port
   252  		runMode := h.runMode
   253  		if !h.config.HistoricMode {
   254  			// Recalulate run mode based on the config
   255  			var paths []byte
   256  			if len(h.app.PackagePathMap)>0 {
   257  				paths, _ = json.Marshal(h.app.PackagePathMap)
   258  			}
   259  			runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.Verbose, string(paths))
   260  
   261  		}
   262  		if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil {
   263  			utils.Logger.Error("Could not start application", "error", err2)
   264  			if err,k :=err2.(*utils.SourceError);k {
   265  				return err
   266  			}
   267  			return &utils.SourceError{
   268  				Title:       "App failed to start up",
   269  				Description: err2.Error(),
   270  			}
   271  		}
   272  	} else {
   273  		h.app = nil
   274  	}
   275  
   276  	return
   277  }
   278  
   279  // WatchDir method returns false to file matches with doNotWatch
   280  // otheriwse true
   281  func (h *Harness) WatchDir(info os.FileInfo) bool {
   282  	return !utils.ContainsString(doNotWatch, info.Name())
   283  }
   284  
   285  // WatchFile method returns true given filename HasSuffix of ".go"
   286  // otheriwse false - implements revel.DiscerningListener
   287  func (h *Harness) WatchFile(filename string) bool {
   288  	return strings.HasSuffix(filename, ".go")
   289  }
   290  
   291  // Run the harness, which listens for requests and proxies them to the app
   292  // server, which it runs and rebuilds as necessary.
   293  func (h *Harness) Run() {
   294  	var paths []string
   295  	if h.paths.Config.BoolDefault("watch.gopath", false) {
   296  		gopaths := filepath.SplitList(build.Default.GOPATH)
   297  		paths = append(paths, gopaths...)
   298  	}
   299  	paths = append(paths, h.paths.CodePaths...)
   300  	h.watcher = watcher.NewWatcher(h.paths, false)
   301  	h.watcher.Listen(h, paths...)
   302  	go h.Refresh()
   303  	// h.watcher.Notify()
   304  
   305  	if h.useProxy {
   306  		go func() {
   307  			// Check the port to start on a random port
   308  			if h.paths.HTTPPort == 0 {
   309  				h.paths.HTTPPort = getFreePort()
   310  			}
   311  			addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
   312  			utils.Logger.Infof("Proxy server is listening on %s", addr)
   313  
   314  
   315  			var err error
   316  			if h.paths.HTTPSsl {
   317  				err = http.ListenAndServeTLS(
   318  					addr,
   319  					h.paths.HTTPSslCert,
   320  					h.paths.HTTPSslKey,
   321  					h)
   322  			} else {
   323  				err = http.ListenAndServe(addr, h)
   324  			}
   325  			if err != nil {
   326  				utils.Logger.Error("Failed to start reverse proxy:", "error", err)
   327  			}
   328  		}()
   329  
   330  	}
   331  
   332  	// Make a new channel to listen for the interrupt event
   333  	ch := make(chan os.Signal)
   334  	signal.Notify(ch, os.Interrupt, os.Kill)
   335  	<-ch
   336  	// Kill the app and exit
   337  	if h.app != nil {
   338  		h.app.Kill()
   339  	}
   340  	os.Exit(1)
   341  }
   342  
   343  // Find an unused port
   344  func getFreePort() (port int) {
   345  	conn, err := net.Listen("tcp", ":0")
   346  	if err != nil {
   347  		utils.Logger.Fatal("Unable to fetch a freee port address", "error", err)
   348  	}
   349  
   350  	port = conn.Addr().(*net.TCPAddr).Port
   351  	err = conn.Close()
   352  	if err != nil {
   353  		utils.Logger.Fatal("Unable to close port", "error", err)
   354  	}
   355  	return port
   356  }
   357  
   358  // proxyWebsocket copies data between websocket client and server until one side
   359  // closes the connection.  (ReverseProxy doesn't work with websocket requests.)
   360  func (h *Harness) proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
   361  	var (
   362  		d   net.Conn
   363  		err error
   364  	)
   365  	if h.paths.HTTPSsl {
   366  		// since this proxy isn't used in production,
   367  		// it's OK to set InsecureSkipVerify to true
   368  		// no need to add another configuration option.
   369  		d, err = tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
   370  	} else {
   371  		d, err = net.Dial("tcp", host)
   372  	}
   373  	if err != nil {
   374  		http.Error(w, "Error contacting backend server.", 500)
   375  		utils.Logger.Error("Error dialing websocket backend ", "host", host, "error", err)
   376  		return
   377  	}
   378  	hj, ok := w.(http.Hijacker)
   379  	if !ok {
   380  		http.Error(w, "Not a hijacker?", 500)
   381  		return
   382  	}
   383  	nc, _, err := hj.Hijack()
   384  	if err != nil {
   385  		utils.Logger.Error("Hijack error", "error", err)
   386  		return
   387  	}
   388  	defer func() {
   389  		if err = nc.Close(); err != nil {
   390  			utils.Logger.Error("Connection close error", "error", err)
   391  		}
   392  		if err = d.Close(); err != nil {
   393  			utils.Logger.Error("Dial close error", "error", err)
   394  		}
   395  	}()
   396  
   397  	err = r.Write(d)
   398  	if err != nil {
   399  		utils.Logger.Error("Error copying request to target", "error", err)
   400  		return
   401  	}
   402  
   403  	errc := make(chan error, 2)
   404  	cp := func(dst io.Writer, src io.Reader) {
   405  		_, err := io.Copy(dst, src)
   406  		errc <- err
   407  	}
   408  	go cp(d, nc)
   409  	go cp(nc, d)
   410  	<-errc
   411  }