// 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) } }
// 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 }
// 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.SayAs( "headers", "\t%s%s%s", color.BlueString(k)+":", pad, v, ) } } }
// WatchPaths watches a set of paths, and broadcasts changes through reloader. func WatchPaths(paths, excludePatterns []string, reloader livereload.Reloader, log termlog.Logger) error { ch := make(chan []string, 1) for _, path := range paths { modchan := make(chan *moddwatch.Mod, 1) _, err := moddwatch.Watch([]string{path}, batchTime, modchan) if err != nil { return err } go func() { for mod := range modchan { filteredMod, err := mod.Filter([]string{"**/*"}, excludePatterns) if err != nil { log.Shout("Error filtering watches: %s", err) } if !filteredMod.Empty() { ch <- filteredMod.All() } } }() } go reloader.Watch(ch) return nil }
// Watch watches an endpoint for changes, if it supports them. func (r Route) Watch(ch chan []string, excludePatterns []string, log termlog.Logger) error { switch r.Endpoint.(type) { case *filesystemEndpoint: ep := *r.Endpoint.(*filesystemEndpoint) modchan := make(chan *moddwatch.Mod, 1) _, err := moddwatch.Watch([]string{ep.Root + "/..."}, batchTime, modchan) if err != nil { return err } go func() { for mod := range modchan { filteredMod, err := mod.Filter([]string{"**/*"}, excludePatterns) if err != nil { log.Shout("Error filtering watches: %s", err) } if !filteredMod.Empty() { ch <- filteredMod.All() } } }() } return nil }
func (fserver *FileServer) dirList(logger termlog.Logger, w http.ResponseWriter, name string, f http.File) { 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 } sortedFiles := fileSlice(files) sort.Sort(sortedFiles) data := dirData{ Version: fserver.Version, Name: name, Files: sortedFiles, } err = fserver.Inject.ServeTemplate( http.StatusOK, w, fserver.Templates.Lookup("dirlist.html"), data, ) if err != nil { logger.Shout("Failed to generate dir listing: %s", err) } }
// 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 := fserver.notFound(logger, w, r, name, nil); err != nil { logger.Shout("Internal error: %s", err) } return } defer func() { _ = f.Close() }() d, err1 := f.Stat() if err1 != nil { logger.WarnAs("debug", "debug fileserver: %s", err) if err := fserver.notFound(logger, w, r, name, nil); err != nil { logger.Shout("Internal error: %s", err) } return } if redirect { // redirect to canonical path: / at end of directory url url := r.URL.Path if !strings.HasPrefix(url, "/") { url = "/" + url } 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 func() { _ = 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 err := fserver.notFound(logger, w, r, name, &f); err != nil { logger.Shout("Internal error: %s", err) } 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 (fserver *FileServer) notFound( logger termlog.Logger, w http.ResponseWriter, r *http.Request, name string, dir *http.File, ) (err error) { sm := http.NewServeMux() seen := make(map[string]bool) for _, nfr := range fserver.NotFoundRoutes { seen[nfr.MuxMatch()] = true sm.HandleFunc( nfr.MuxMatch(), func(nfr routespec.RouteSpec) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if matchTypes(nfr.Value, r.URL.Path) { for _, pth := range notFoundSearchPaths(name, nfr.Value) { next, err := fserver.serveNotFoundFile(w, r, pth) if err != nil { logger.Shout("Unable to serve not-found override: %s", err) } if !next { return } } } err = fserver.serve404(w) if err != nil { logger.Shout("Internal error: %s", err) } } }(nfr), ) } if _, exists := seen["/"]; !exists { sm.HandleFunc( "/", func(response http.ResponseWriter, request *http.Request) { if dir != nil { d, err := (*dir).Stat() if err != nil { logger.Shout("Internal error: %s", err) return } if checkLastModified(response, request, d.ModTime()) { return } fserver.dirList(logger, response, name, *dir) return } err = fserver.serve404(w) if err != nil { logger.Shout("Internal error: %s", err) } }, ) } handle, _ := sm.Handler(r) handle.ServeHTTP(w, r) return err }
func testpatt(l termlog.Logger) { l.Say("Log") l.Notice("Notice!") l.Warn("Warn!") l.Shout("Error!") }