diff --git a/context.go b/context.go index 2e124f6..e45e977 100644 --- a/context.go +++ b/context.go @@ -1,23 +1,26 @@ package snap import ( - "net/http" "encoding/json" + "io/ioutil" + "net/http" + "strconv" + "git.thirdmartini.com/pub/snap/auth" ) type Context struct { - srv *Server + srv *Server auth *auth.AuthData - w http.ResponseWriter - r *http.Request + w http.ResponseWriter + r *http.Request vars map[string]string } type SnapContent struct { - User string - Theme string + User string + Theme string Content interface{} } @@ -29,7 +32,6 @@ func (c *Context) Writer() http.ResponseWriter { return c.w } - func (c *Context) GetUser() string { if c.auth == nil { return "" @@ -41,12 +43,10 @@ func (c *Context) Error(code int, msg string) { c.srv.renderError(c.w, code, msg) } - func (c *Context) Reply(msg string) { c.srv.reply(c.w, msg) } - func (c *Context) ReplyObject(obj interface{}) { msg, err := json.Marshal(obj) if err != nil { @@ -55,29 +55,35 @@ func (c *Context) ReplyObject(obj interface{}) { c.Reply(string(msg)) } - +func (c *Context) GetObject(obj interface{}) error { + data, err := ioutil.ReadAll(c.r.Body) + defer c.r.Body.Close() + if err != nil { + return err + } + return json.Unmarshal(data, obj) +} func (c *Context) Render(tmpl string, content interface{}) { c.srv.render(c.w, tmpl, content) } - func (c *Context) RenderEx(tmpl string, content interface{}) { - cnt := SnapContent { - User: c.GetUser(), - Theme: c.srv.theme, - Content:content, + cnt := SnapContent{ + User: c.GetUser(), + Theme: c.srv.theme, + Content: content, } c.srv.render(c.w, tmpl, &cnt) } -func (c *Context) GetVar(k string) ( string, bool) { - v,ok := c.vars[k] +func (c *Context) GetVar(k string) (string, bool) { + v, ok := c.vars[k] return v, ok } -func (c *Context) GetVarDefault(k string,def string) (string) { - v,ok := c.vars[k] +func (c *Context) GetVarDefault(k string, def string) string { + v, ok := c.vars[k] if !ok { return def } @@ -90,4 +96,23 @@ func (c *Context) ParseForm() error { func (c *Context) FormValue(k string) string { return c.r.FormValue(k) -} \ No newline at end of file +} + +func (c *Context) FormValueUint64(k string) uint64 { + str := c.r.FormValue(k) + + val, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return 0 + } + + return val +} + +func (c *Context) Redirect(url string) { + http.Redirect(c.w, c.r, url, http.StatusSeeOther) +} + +func (c *Context) QueryValue(key string) string { + return c.r.URL.Query().Get(key) +} diff --git a/debug.go b/debug.go new file mode 100644 index 0000000..9603f67 --- /dev/null +++ b/debug.go @@ -0,0 +1,57 @@ +package snap + +import ( + "bytes" + "encoding/json" + "expvar" + "fmt" + "log" + "net/http" + "net/http/pprof" + "runtime" + + "github.com/gorilla/mux" +) + +func stackHandler(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/sovereign/debug/pprof/goroutine?debug=2", http.StatusSeeOther) +} + +// copy pasted from http://golang.org/src/expvar/expvar.go#L308 +func varsHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + buf := make([]byte, 0, 16384) + body := bytes.NewBuffer(buf) + + fmt.Fprintf(body, "{") + expvar.Do(func(kv expvar.KeyValue) { + fmt.Fprintf(body, "%q: %s,", kv.Key, kv.Value) + }) + + fmt.Fprintf(body, "%q: %d", "NumGoRoutines", runtime.NumGoroutine()) + fmt.Fprintf(body, "}") + + var prettyJSON bytes.Buffer + err := json.Indent(&prettyJSON, body.Bytes(), "", " ") + if err != nil { + log.Println(err, string(body.Bytes())) + } + + w.Write(prettyJSON.Bytes()) +} + +func setupDebugHandler(r *mux.Router) { + r.HandleFunc("/stack", stackHandler) + r.HandleFunc("/vars", varsHandler) + r.HandleFunc("/pprof/", http.HandlerFunc(pprof.Index)) + r.HandleFunc("/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) + r.HandleFunc("/pprof/profile", http.HandlerFunc(pprof.Profile)) + r.HandleFunc("/pprof/symbol", http.HandlerFunc(pprof.Symbol)) + r.HandleFunc("/pprof/trace", http.HandlerFunc(pprof.Trace)) + r.HandleFunc("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) + r.HandleFunc("/pprof/allocs", pprof.Handler("allocs").ServeHTTP) + r.HandleFunc("/pprof/block", pprof.Handler("block").ServeHTTP) + r.HandleFunc("/pprof/heap", pprof.Handler("heap").ServeHTTP) + r.HandleFunc("/pprof/mutex", pprof.Handler("mutex").ServeHTTP) +} diff --git a/server.go b/server.go index 2438d2d..a5598d8 100644 --- a/server.go +++ b/server.go @@ -1,12 +1,13 @@ package snap import ( + "fmt" "html/template" + "io" + "io/ioutil" "net/http" "os" "path" - "fmt" - "io/ioutil" "path/filepath" "strings" "time" @@ -64,11 +65,13 @@ func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc { log.Debug("request: ", r.RequestURI) c := s.makeContext(nil, w, r) handle(c) + + // discard the rest of the body content + io.Copy(ioutil.Discard, r.Body) + defer r.Body.Close() } } - - // This is a bit different then the standard template.parseFiles code in that it gives us hiarchial templates // header.html // mydirectory/service.html ... @@ -88,7 +91,7 @@ func (s *Server) parseTemplates(t *template.Template, filenames ...string) (*tem } data := string(b) name := strings.TrimPrefix(filename, s.templates+"/") - log.Println("Template:", name) + // log.Println("Template:", name) // First template becomes return value if not already defined, // and we use that one for subsequent New calls to associate @@ -118,16 +121,16 @@ func (s *Server) loadTemplates() *template.Template { err := filepath.Walk(path.Join(s.path, s.templates), func(path string, info os.FileInfo, err error) error { if strings.Contains(path, ".html") { - _,err := s.parseTemplates(tmpl, path) + _, err := s.parseTemplates(tmpl, path) if err != nil { log.Println(err) } -/* - _, err = tmpl.ParseFiles(path) - if err != nil { - log.Println(err) - } -*/ + /* + _, err = tmpl.ParseFiles(path) + if err != nil { + log.Println(err) + } + */ } return err }) @@ -182,9 +185,8 @@ func (s *Server) HandleFuncCustomAuth(auth auth.AuthManager, path string, f func return s.router.HandleFunc(path, s.authenticated(auth, f)) } -func (s *Server) HandleFunc(path string, f func(c *Context)) error { - s.router.HandleFunc(path, s.wrapper(f)) - return nil +func (s *Server) HandleFunc(path string, f func(c *Context)) *mux.Route { + return s.router.HandleFunc(path, s.wrapper(f)) } func (s *Server) SetDebug(enable bool) { @@ -202,6 +204,9 @@ func (s *Server) SetTemplatePath(path string) { s.templates = path } +func (s *Server) Router() *mux.Router { + return s.router +} func (s *Server) ServeTLS(keyPath string, certPath string) error { srv := &http.Server{ @@ -228,6 +233,22 @@ func (s *Server) Serve() error { return srv.ListenAndServe() } +func (s *Server) WithStaticFiles(prefix string, path string) *Server { + s.router.PathPrefix(prefix).Handler(http.StripPrefix(prefix, http.FileServer(http.Dir(path)))) + return s +} + +func (s *Server) WithTheme(themeURL string) *Server { + s.theme = themeURL + return s +} + +func (s *Server) WithDebug(debugURL string) *Server { + sub := s.router.PathPrefix("/sovereign/debug").Subrouter() + setupDebugHandler(sub) + return s +} + func New(address string, path string, auth auth.AuthManager) *Server { s := Server{ router: mux.NewRouter(), @@ -238,6 +259,6 @@ func New(address string, path string, auth auth.AuthManager) *Server { theme: "/static/css/default.css", } - s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/")))) + //s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/")))) return &s }