// WrapHandler wraps an httpctx.Handler in the paraphernalia needed by devd for // logging, latency, and so forth. func (dd *Devd) WrapHandler(log termlog.Logger, next httpctx.Handler) http.Handler { h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { revertOriginalHost(r) timr := timer.Timer{} sublog := log.Group() defer func() { timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ") sublog.SayAs("timer", timing+timr.String()) sublog.Done() }() if matchStringAny(dd.IgnoreLogs, fmt.Sprintf("%s%s", r.URL.Host, r.RequestURI)) { sublog.Quiet() } timr.RequestHeaders() time.Sleep(time.Millisecond * time.Duration(dd.Latency)) sublog.Say("%s %s", r.Method, r.URL) LogHeader(sublog, r.Header) ctx := timr.NewContext(context.Background()) ctx = termlog.NewContext(ctx, sublog) next.ServeHTTPContext( ctx, &ResponseLogWriter{Log: sublog, Resp: w, Timer: &timr}, r, ) }) return h }
// Determine if a file should be included, based on the given exclude paths. func shouldInclude(file string, excludePatterns []string, log termlog.Logger) bool { for _, pattern := range excludePatterns { match, err := doublestar.Match(pattern, file) if err != nil { log.Warn("Error matching pattern '%s': %s", pattern, err) } else if match { return false } } return true }
func dirList(ci inject.CopyInject, logger termlog.Logger, w http.ResponseWriter, name string, f http.File, templates *template.Template) { w.Header().Set("Cache-Control", "no-store, must-revalidate") files, err := f.Readdir(0) if err != nil { logger.Shout("Error reading directory for listing: %s", err) return } data := dirData{Name: name, Files: files} err = ci.ServeTemplate(http.StatusOK, w, templates.Lookup("dirlist.html"), data) if err != nil { logger.Shout("Failed to generate dir listing: %s", err) } }
// Determine if a file should be included, based on the given exclude paths. func shouldInclude(file string, excludePatterns []string, log termlog.Logger) bool { for _, pattern := range excludePatterns { match, err := filepath.Match(pattern, file) if err != nil { log.Warn("Invalid pattern: `%s'", pattern) } else if match { return false } } return true }
func dirList(ci inject.CopyInject, logger termlog.Logger, w http.ResponseWriter, name string, f http.File, templates *template.Template) { w.Header().Set("Cache-Control", "no-store, must-revalidate") files, err := f.Readdir(0) if err != nil { logger.Shout("Error reading directory for listing: %s", err) return } data := dirData{Name: name, Files: files} buff := bytes.NewBuffer(make([]byte, 0, 0)) err = templates.Lookup("dirlist.html").Execute(buff, data) length := buff.Len() if err != nil { logger.Shout("Error producing directory listing: %s", err) } inj, err := ci.Sniff(buff) if err != nil { logger.Shout("Failed to inject in dir listing: %s", err) return } w.Header().Set( "Content-Length", fmt.Sprintf("%d", length+inj.Extra()), ) _, err = inj.Copy(w) if err != nil { logger.Shout("Failed to inject in dir listing: %s", err) return } }
// LogHeader logs a header func LogHeader(log termlog.Logger, h http.Header) { max := 0 for k := range h { if len(k) > max { max = len(k) } } for k, vals := range h { for _, v := range vals { pad := fmt.Sprintf(fmt.Sprintf("%%%ds", max-len(k)+1), " ") log.Say( "\t%s%s%s", color.BlueString(k)+":", pad, v, ) } } }
// Serve starts the devd server. The callback is called with the serving URL // just before service starts. func (dd *Devd) Serve(address string, port int, certFile string, logger termlog.Logger, callback func(string)) error { templates, err := ricetemp.MakeTemplates(rice.MustFindBox("templates")) if err != nil { return fmt.Errorf("Error loading templates: %s", err) } mux, err := dd.Router(logger, templates) if err != nil { return err } var tlsConfig *tls.Config var tlsEnabled bool if certFile != "" { tlsConfig, err = getTLSConfig(certFile) if err != nil { return fmt.Errorf("Could not load certs: %s", err) } tlsEnabled = true } var hl net.Listener if port > 0 { hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", address, port)) } else { hl, err = pickPort(address, portLow, portHigh, tlsEnabled) } if err != nil { return fmt.Errorf("Could not bind to port: %s", err) } if tlsConfig != nil { hl = tls.NewListener(hl, tlsConfig) } hl = slowdown.NewSlowListener(hl, dd.UpKbps*1024, dd.DownKbps*1024) url := formatURL(tlsEnabled, address, hl.Addr().(*net.TCPAddr).Port) logger.Say("Listening on %s (%s)", url, hl.Addr().String()) server := &http.Server{Addr: hl.Addr().String(), Handler: mux} callback(url) err = server.Serve(hl) logger.Shout("Server stopped: %v", err) return nil }
// RouteHandler handles a single route func (dd *Devd) RouteHandler(log termlog.Logger, route Route, templates *template.Template) http.Handler { ci := inject.CopyInject{} if dd.HasLivereload() { ci = livereload.Injector } next := route.Endpoint.Handler(templates, ci) h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { timr := timer.Timer{} sublog := log.Group() defer func() { timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ") sublog.SayAs( "timer", timing+timr.String(), ) sublog.Done() }() if matchStringAny(dd.IgnoreLogs, fmt.Sprintf("%s%s", route.Host, r.RequestURI)) { sublog.Quiet() } timr.RequestHeaders() time.Sleep(time.Millisecond * time.Duration(dd.Latency)) sublog.Say("%s %s", r.Method, r.URL) LogHeader(sublog, r.Header) ctx := timr.NewContext(context.Background()) ctx = termlog.NewContext(ctx, sublog) next.ServeHTTPContext( ctx, &ResponseLogWriter{ Log: sublog, Resp: w, Timer: &timr, }, r, ) }) return http.StripPrefix(route.Path, h) }
func devdHandler(log termlog.Logger, route Route, templates *template.Template, logHeaders bool, ignoreHeaders []*regexp.Regexp, livereload bool, latency int) http.Handler { ci := inject.CopyInject{} if livereload { ci = injectLivereload } next := route.Endpoint.Handler(templates, ci) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var sublog termlog.Logger if matchStringAny(ignoreHeaders, fmt.Sprintf("%s%s", route.Host, r.RequestURI)) { sublog = termlog.DummyLogger{} } else { sublog = log.Group() } timr := timer.Timer{} defer func() { timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ") sublog.SayAs( "timer", timing+timr.String(), ) sublog.Done() }() timr.RequestHeaders() time.Sleep(time.Millisecond * time.Duration(latency)) sublog.Say("%s %s", r.Method, r.URL) if logHeaders { LogHeader(sublog, r.Header) } ctx := timr.NewContext(context.Background()) ctx = termlog.NewContext(ctx, sublog) next.ServeHTTPContext( ctx, &ResponseLogWriter{ log: sublog, rw: w, timr: &timr, logHeaders: logHeaders, }, r, ) }) }
// name is '/'-separated, not filepath.Separator. func (fserver *FileServer) serveFile( logger termlog.Logger, w http.ResponseWriter, r *http.Request, name string, redirect bool, ) { const indexPage = "/index.html" // redirect .../index.html to .../ // can't use Redirect() because that would make the path absolute, // which would be a problem running under StripPrefix if strings.HasSuffix(r.URL.Path, indexPage) { logger.SayAs( "debug", "debug fileserver: redirecting %s -> ./", indexPage, ) localRedirect(w, r, "./") return } f, err := fserver.Root.Open(name) if err != nil { logger.WarnAs("debug", "debug fileserver: %s", err) if err := notFound(fserver.Inject, fserver.Templates, w); err != nil { logger.Shout("Internal error: %s", err) } return } defer f.Close() d, err1 := f.Stat() if err1 != nil { logger.WarnAs("debug", "debug fileserver: %s", err) if err := notFound(fserver.Inject, fserver.Templates, w); err != nil { logger.Shout("Internal error: %s", err) } return } if redirect { // redirect to canonical path: / at end of directory url // r.URL.Path always begins with / url := r.URL.Path if d.IsDir() { if url[len(url)-1] != '/' { localRedirect(w, r, path.Base(url)+"/") return } } else { if url[len(url)-1] == '/' { localRedirect(w, r, "../"+path.Base(url)) return } } } // use contents of index.html for directory, if present if d.IsDir() { index := name + indexPage ff, err := fserver.Root.Open(index) if err == nil { defer ff.Close() dd, err := ff.Stat() if err == nil { name = index d = dd f = ff } } } // Still a directory? (we didn't find an index.html file) if d.IsDir() { if checkLastModified(w, r, d.ModTime()) { return } dirList(fserver.Inject, logger, w, name, f, fserver.Templates) return } // serverContent will check modification time sizeFunc := func() (int64, error) { return d.Size(), nil } err = serveContent(fserver.Inject, w, r, d.Name(), d.ModTime(), sizeFunc, f) if err != nil { logger.Warn("Error serving file: %s", err) } }
func testpatt(l termlog.Logger) { l.Say("Log") l.Notice("Notice!") l.Warn("Warn!") l.Shout("Error!") }
// Serve starts the devd server func (dd *Devd) Serve(logger termlog.Logger) error { templates, err := ricetemp.MakeTemplates(rice.MustFindBox("templates")) if err != nil { return fmt.Errorf("Error loading templates: %s", err) } ignores := make([]*regexp.Regexp, 0, 0) for _, expr := range dd.IgnoreLogs { v, err := regexp.Compile(expr) if err != nil { return fmt.Errorf("%s", err) } ignores = append(ignores, v) } routeColl := make(RouteCollection) for _, s := range dd.Routes { err := routeColl.Set(s) if err != nil { return fmt.Errorf("Invalid route specification: %s", err) } } mux := http.NewServeMux() for match, route := range routeColl { handler := dd.RouteHandler(logger, route, templates, ignores) mux.Handle(match, http.StripPrefix(route.Path, handler)) } lr := livereload.NewServer("livereload", logger) if dd.LivereloadEnabled() { mux.Handle("/livereload", lr) mux.Handle("/livereload.js", http.HandlerFunc(lr.ServeScript)) } if dd.LivereloadRoutes { err = WatchRoutes(routeColl, lr, dd.Excludes, logger) if err != nil { return fmt.Errorf("Could not watch routes for livereload: %s", err) } } if len(dd.Watch) > 0 { err = WatchPaths(dd.Watch, dd.Excludes, lr, logger) if err != nil { return fmt.Errorf("Could not watch path for livereload: %s", err) } } var hl net.Listener tlsEnabled := false if dd.CertFile != "" { tlsEnabled = true } if dd.Port > 0 { hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", dd.Address, dd.Port)) } else { hl, err = pickPort(dd.Address, portLow, portHigh, tlsEnabled) } if err != nil { return fmt.Errorf("Could not bind to port: %s", err) } var tlsConfig *tls.Config if dd.CertFile != "" { tlsConfig, err = getTLSConfig(dd.CertFile) if err != nil { return fmt.Errorf("Could not load certs: %s", err) } hl = tls.NewListener(hl, tlsConfig) } hl = slowdown.NewSlowListener(hl, dd.UpKbps*1024, dd.DownKbps*1024) url := formatURL(tlsEnabled, dd.Address, hl.Addr().(*net.TCPAddr).Port) logger.Say("Listening on %s (%s)", url, hl.Addr().String()) if dd.OpenBrowser { go func() { webbrowser.Open(url) }() } server := &http.Server{ Addr: hl.Addr().String(), Handler: hostPortStrip(mux), } err = server.Serve(hl) logger.Shout("Server stopped: %v", err) return nil }