// TestLinuxSendfileChild isn't a real test. It's used as a helper process // for TestLinuxSendfile. func TestLinuxSendfileChild(*testing.T) { if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { return } defer os.Exit(0) fd3 := os.NewFile(3, "ephemeral-port-listener") ln, err := net.FileListener(fd3) if err != nil { panic(err) } mux := http.NewServeMux() fs := &FileServer{ http.Dir("testdata"), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), } mux.Handle("/", fs) mux.HandleFunc("/quit", func(http.ResponseWriter, *http.Request) { os.Exit(0) }) s := &http.Server{Handler: mux} err = s.Serve(ln) if err != nil { panic(err) } }
func TestServeIndexHtml(t *testing.T) { defer afterTest(t) const want = "index.html says hello\n" fs := &FileServer{ http.Dir("."), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), } ts := httptest.NewServer(fs) defer ts.Close() for _, path := range []string{"/testdata/", "/testdata/index.html"} { res, err := http.Get(ts.URL + path) if err != nil { t.Fatal(err) } b, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal("reading Body:", err) } if s := string(b); s != want { t.Errorf("for path %q got %q, want %q", path, s, want) } res.Body.Close() } }
func TestFSRedirect(t *testing.T) { defer afterTest(t) ts := httptest.NewServer( http.StripPrefix( "/test", &FileServer{ http.Dir("."), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), }, ), ) defer ts.Close() for _, data := range fsRedirectTestData { res, err := http.Get(ts.URL + data.original) if err != nil { t.Fatal(err) } res.Body.Close() if g, e := res.Request.URL.Path, data.redirect; g != e { t.Errorf("redirect from %s: got %s, want %s", data.original, g, e) } } }
func TestFileServerCleans(t *testing.T) { defer afterTest(t) ch := make(chan string, 1) fs := &FileServer{ &testFileSystem{ func(name string) (http.File, error) { ch <- name return nil, errors.New("file does not exist") }, }, inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), } tests := []struct { reqPath, openArg string }{ {"/foo.txt", "/foo.txt"}, {"//foo.txt", "/foo.txt"}, {"/../foo.txt", "/foo.txt"}, } req, _ := http.NewRequest("GET", "http://example.com", nil) for n, test := range tests { rec := httptest.NewRecorder() req.URL.Path = test.reqPath fs.ServeHTTP(rec, req) if got := <-ch; got != test.openArg { t.Errorf("test %d: got %q, want %q", n, got, test.openArg) } } }
func TestFileServerZeroByte(t *testing.T) { defer afterTest(t) fs := &FileServer{ "version", http.Dir("."), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), []routespec.RouteSpec{}, "", } ts := httptest.NewServer(fs) defer ts.Close() res, err := http.Get(ts.URL + "/..\x00") if err != nil { t.Fatal(err) } b, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal("reading Body:", err) } if res.StatusCode == 200 { t.Errorf("got status 200; want an error. Body is:\n%s", string(b)) } }
func TestDevdRouteHandler(t *testing.T) { logger := termlog.NewLog() logger.Quiet() r := Route{"", "/", fsEndpoint("./testdata")} templates := ricetemp.MustMakeTemplates(rice.MustFindBox("templates")) devd := Devd{ Routes: make([]string, 0, 0), OpenBrowser: false, CertFile: "", Address: "", Port: 0, // Shaping Latency: 0, DownKbps: 0, UpKbps: 0, // Livereload LivereloadRoutes: false, Watch: make([]string, 0, 0), Excludes: make([]string, 0, 0), // Logging IgnoreLogs: make([]string, 0, 0), } h := devd.RouteHandler(logger, r, templates, nil) ht := handlerTester{t, h} AssertCode(t, ht.Request("GET", "/", nil), 200) }
func TestDevdHandler(t *testing.T) { logger := termlog.DummyLogger{} r := Route{"", "/", fsEndpoint("./testdata")} templates := ricetemp.MustMakeTemplates(rice.MustFindBox("templates")) h := devdHandler(&logger, r, templates, true, nil, true, 0) ht := handlerTester{t, h} AssertCode(t, ht.Request("GET", "/", nil), 200) }
// ServeFile replies to the request with the contents of the named file or directory. func ServeFile(w http.ResponseWriter, r *http.Request, name string) { dir, file := filepath.Split(name) logger := termlog.DummyLogger{} fs := FileServer{ http.Dir(dir), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), } fs.serveFile(logger, w, r, file, false) }
func TestDevdRouteHandler(t *testing.T) { logger := termlog.NewLog() logger.Quiet() r := Route{"", "/", fsEndpoint("./testdata")} templates := ricetemp.MustMakeTemplates(rice.MustFindBox("templates")) devd := Devd{LivereloadRoutes: true} h := devd.RouteHandler(logger, r, templates) ht := handlerTester{t, h} AssertCode(t, ht.Request("GET", "/", nil), 200) }
// ServeFile replies to the request with the contents of the named file or directory. func ServeFile(w http.ResponseWriter, r *http.Request, name string) { dir, file := filepath.Split(name) logger := termlog.NewLog() logger.Quiet() fs := FileServer{ "version", http.Dir(dir), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), []routespec.RouteSpec{}, "", } fs.serveFile(logger, w, r, file, false) }
func TestDevdHandler(t *testing.T) { logger := termlog.NewLog() logger.Quiet() templates := ricetemp.MustMakeTemplates(rice.MustFindBox("templates")) devd := Devd{LivereloadRoutes: true, WatchPaths: []string{"./"}} devd.AddRoutes([]string{"./"}) h, err := devd.Handler(logger, templates) if err != nil { t.Error(err) } ht := handlerTester{t, h} AssertCode(t, ht.Request("GET", "/", nil), 200) AssertCode(t, ht.Request("GET", "/nonexistent", nil), 404) }
func TestFileServerImplicitLeadingSlash(t *testing.T) { defer afterTest(t) tempDir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("TempDir: %v", err) } defer mustRemoveAll(tempDir) if err := ioutil.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } fs := &FileServer{ "version", http.Dir(tempDir), inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), []routespec.RouteSpec{}, "", } ts := httptest.NewServer(http.StripPrefix("/bar/", fs)) defer ts.Close() get := func(suffix string) string { res, err := http.Get(ts.URL + suffix) if err != nil { t.Fatalf("Get %s: %v", suffix, err) } b, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("ReadAll %s: %v", suffix, err) } _ = res.Body.Close() return string(b) } if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") { t.Logf("expected a directory listing with foo.txt, got %q", s) } if s := get("/bar/foo.txt"); s != "Hello world" { t.Logf("expected %q, got %q", "Hello world", s) } }
func TestDirectoryIfNotModified(t *testing.T) { defer afterTest(t) const indexContents = "I am a fake index.html file" fileMod := time.Unix(1000000000, 0).UTC() fileModStr := fileMod.Format(http.TimeFormat) dirMod := time.Unix(123, 0).UTC() indexFile := &fakeFileInfo{ basename: "index.html", modtime: fileMod, contents: indexContents, } fsys := fakeFS{ "/": &fakeFileInfo{ dir: true, modtime: dirMod, ents: []*fakeFileInfo{indexFile}, }, "/index.html": indexFile, } fs := &FileServer{ fsys, inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), } ts := httptest.NewServer(fs) defer ts.Close() res, err := http.Get(ts.URL) if err != nil { t.Fatal(err) } b, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } if string(b) != indexContents { t.Fatalf("Got body %q; want %q", b, indexContents) } res.Body.Close() lastMod := res.Header.Get("Last-Modified") if lastMod != fileModStr { t.Fatalf("initial Last-Modified = %q; want %q", lastMod, fileModStr) } req, _ := http.NewRequest("GET", ts.URL, nil) req.Header.Set("If-Modified-Since", lastMod) res, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } if res.StatusCode != 304 { t.Fatalf("Code after If-Modified-Since request = %v; want 304", res.StatusCode) } res.Body.Close() // Advance the index.html file's modtime, but not the directory's. indexFile.modtime = indexFile.modtime.Add(1 * time.Hour) res, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } if res.StatusCode != 200 { t.Fatalf("Code after second If-Modified-Since request = %v; want 200; res is %#v", res.StatusCode, res) } res.Body.Close() }
func main() { httpIP := kingpin.Flag("address", "Address to listen on"). Short('A'). Default("127.0.0.1"). String() allInterfaces := kingpin.Flag("a", "Listen on all addresses"). Short('a'). Bool() certFile := kingpin.Flag("cert", "Certificate bundle file - enables TLS"). Short('c'). PlaceHolder("PATH"). Default(""). ExistingFile() throttleDownKbps := kingpin.Flag( "down", "Throttle downstream from the client to N kilobytes per second", ). PlaceHolder("N"). Short('d'). Default("0"). Int() logHeaders := kingpin.Flag("logheaders", "Log headers"). Short('H'). Default("false"). Bool() ignoreHeaders := kingpin.Flag( "ignore", "Disable logging matching requests. Regexes are matched over 'host/path'", ). Short('I'). PlaceHolder("REGEX"). Strings() livereloadRoutes := kingpin.Flag("livereload", "Enable livereload for static files"). Short('l'). Default("false"). Bool() latency := kingpin.Flag("latency", "Add N milliseconds of round-trip latency"). PlaceHolder("N"). Short('n'). Default("0"). Int() openBrowser := kingpin.Flag("open", "Open browser window on startup"). Short('o'). Default("false"). Bool() httpPort := kingpin.Flag( "port", "Port to listen on - if not specified, devd will auto-pick a sensible port", ). Short('p'). Int() enableTimer := kingpin.Flag("logtime", "Log timing"). Short('T'). Default("false"). Bool() throttleUpKbps := kingpin.Flag( "up", "Throttle upstream from the client to N kilobytes per second", ). PlaceHolder("N"). Short('u'). Default("0"). Int() watch := kingpin.Flag("watch", "Watch path to trigger livereload"). PlaceHolder("PATH"). Short('w'). Strings() debug := kingpin.Flag("debug", "Debugging for devd development"). Default("false"). Bool() excludes := kingpin.Flag("exclude", "Glob pattern for files to exclude from livereload."). PlaceHolder("PATTERN"). Short('x'). Strings() routes := kingpin.Arg( "route", `Routes have the following forms: [SUBDOMAIN]/<PATH>=<DIR> [SUBDOMAIN]/<PATH>=<URL> <DIR> <URL> `, ).Required().Strings() kingpin.Version(version) kingpin.Parse() logger := termlog.NewLog() if *debug { logger.Enable("debug") } if *enableTimer { logger.Enable("timer") } if *throttleDownKbps == 0 { *throttleDownKbps = slowdown.MaxRate } if *throttleUpKbps == 0 { *throttleUpKbps = slowdown.MaxRate } if *allInterfaces { *httpIP = "0.0.0.0" } tlsEnabled := false if *certFile != "" { tlsEnabled = true } var hl net.Listener var err error if *httpPort > 0 { hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", *httpIP, *httpPort)) } else { hl, err = pickPort(*httpIP, portLow, portHigh, tlsEnabled) } if err != nil { kingpin.Fatalf("Could not bind to port: %s", err) return } templates := ricetemp.MustMakeTemplates(rice.MustFindBox("templates")) if err != nil { kingpin.Fatalf("Error loading templates: %s", err) return } ignores := make([]*regexp.Regexp, 0, 0) for _, expr := range *ignoreHeaders { v, err := regexp.Compile(expr) if err != nil { kingpin.Fatalf("%s", err) } ignores = append(ignores, v) } routeColl := make(routeCollection) for _, s := range *routes { err := routeColl.Set(s) if err != nil { kingpin.FatalUsage("Invalid route specification: %s", err) } } mux := http.NewServeMux() var livereloadEnabled = false if *livereloadRoutes || len(*watch) > 0 { livereloadEnabled = true } for match, route := range routeColl { handler := devdHandler( logger, route, templates, *logHeaders, ignores, livereloadEnabled, *latency, ) mux.Handle(match, http.StripPrefix(route.Path, handler)) } lr := livereload.NewServer("livereload", logger) if livereloadEnabled { mux.Handle("/livereload", lr) mux.Handle("/livereload.js", http.HandlerFunc(lr.ServeScript)) } if *livereloadRoutes { err = WatchRoutes(routeColl, lr) if err != nil { kingpin.Fatalf("Could not watch routes for livereload: %s", err) } } if len(*watch) > 0 { err = WatchPaths(*watch, *excludes, lr, logger) if err != nil { kingpin.Fatalf("Could not watch path for livereload: %s", err) } } var tlsConfig *tls.Config if *certFile != "" { tlsConfig, err = getTLSConfig(*certFile) if err != nil { kingpin.Fatalf("Could not load certs: %s", err) return } hl = tls.NewListener(hl, tlsConfig) } hl = slowdown.NewSlowListener( hl, float64(*throttleUpKbps)*1024, float64(*throttleDownKbps)*1024, ) url := formatURL(tlsEnabled, *httpIP, hl.Addr().(*net.TCPAddr).Port) logger.Say("Listening on %s (%s)", url, hl.Addr().String()) if *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) }
func TestNotFoundOverride(t *testing.T) { defer afterTest(t) ffile := &fakeFileInfo{ basename: "foo.html", modtime: time.Unix(1000000000, 0).UTC(), contents: "I am a fake file", } fsys := fakeFS{ "/": &fakeFileInfo{ dir: true, modtime: time.Unix(123, 0).UTC(), ents: []*fakeFileInfo{}, }, "/one": &fakeFileInfo{ dir: true, modtime: time.Unix(123, 0).UTC(), ents: []*fakeFileInfo{ffile}, }, "/one/foo.html": ffile, } fs := &FileServer{ "version", fsys, inject.CopyInject{}, ricetemp.MustMakeTemplates(rice.MustFindBox("../templates")), []routespec.RouteSpec{ {Host: "", Path: "/", Value: "foo.html"}, }, "", } ts := httptest.NewServer(fs) defer ts.Close() res, err := http.Get(ts.URL + "/one/nonexistent.html") if err != nil { t.Fatal(err) } _ = res.Body.Close() if res.StatusCode != 200 { t.Error("Expected to find over-ride file.") } res, err = http.Get(ts.URL + "/one/two/nonexistent.html") if err != nil { t.Fatal(err) } _ = res.Body.Close() if res.StatusCode != 200 { t.Error("Expected to find over-ride file.") } res, err = http.Get(ts.URL + "/nonexistent.html") if err != nil { t.Fatal(err) } _ = res.Body.Close() if res.StatusCode != 404 { t.Error("Expected to find over-ride file.") } res, err = http.Get(ts.URL + "/two/nonexistent.html") if err != nil { t.Fatal(err) } _ = res.Body.Close() if res.StatusCode != 404 { t.Error("Expected to find over-ride file.") } }
// Serve starts the devd server func (dd *Devd) Serve() error { logger := termlog.NewLog() if dd.Debug { logger.Enable("debug") } if dd.EnableTimer { logger.Enable("timer") } if dd.DownKbps == 0 { dd.DownKbps = slowdown.MaxRate } if dd.UpKbps == 0 { dd.UpKbps = slowdown.MaxRate } if dd.AllInterfaces { dd.Address = "0.0.0.0" } tlsEnabled := false if dd.CertFile != "" { tlsEnabled = true } var hl net.Listener var err error 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) } templates := ricetemp.MustMakeTemplates(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() var livereloadEnabled = false if dd.LivereloadRoutes || len(dd.Watch) > 0 { livereloadEnabled = true } for match, route := range routeColl { handler := devdHandler( logger, route, templates, dd.LogHeaders, ignores, livereloadEnabled, dd.Latency, ) mux.Handle(match, http.StripPrefix(route.Path, handler)) } lr := livereload.NewServer("livereload", logger) if livereloadEnabled { mux.Handle("/livereload", lr) mux.Handle("/livereload.js", http.HandlerFunc(lr.ServeScript)) } if dd.LivereloadRoutes { err = WatchRoutes(routeColl, lr) 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 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, float64(dd.UpKbps)*1024, float64(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 }