server.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package snap
  2. import (
  3. "crypto/tls"
  4. "encoding/json"
  5. "fmt"
  6. "html/template"
  7. "io"
  8. "io/ioutil"
  9. "net/http"
  10. "os"
  11. "path"
  12. "strings"
  13. "time"
  14. "git.thirdmartini.com/pub/snap/pkg/autocert"
  15. "github.com/gorilla/mux"
  16. "git.thirdmartini.com/pub/fancylog"
  17. "git.thirdmartini.com/pub/snap/auth"
  18. )
  19. type Server struct {
  20. address string
  21. theme string
  22. debug bool
  23. fs http.FileSystem
  24. auth auth.Authenticator
  25. router *mux.Router
  26. templates string
  27. cachedTmpl *template.Template
  28. templateFuncs template.FuncMap
  29. meta map[string]string
  30. // testModeEnabled if test mode is enabled we will not render the template but jsut return the
  31. // json object
  32. testModeEnabled bool
  33. }
  34. type SnapBaseContent struct {
  35. Theme string
  36. Content interface{}
  37. }
  38. func noescape(str string) template.HTML {
  39. return template.HTML(str)
  40. }
  41. var builtinFuncMap = template.FuncMap{
  42. "noescape": noescape,
  43. }
  44. func (s *Server) makeContext(auth *auth.AuthData, w http.ResponseWriter, r *http.Request) *Context {
  45. c := &Context{
  46. r: r,
  47. w: w,
  48. srv: s,
  49. auth: auth,
  50. vars: mux.Vars(r),
  51. }
  52. return c
  53. }
  54. func (s *Server) withLoginHandler(auth auth.Authenticator, loginHandler func(c *Context) bool, handle func(c *Context)) http.HandlerFunc {
  55. return func(w http.ResponseWriter, r *http.Request) {
  56. if s.debug {
  57. log.Debug("authenticated request with login ui handler: ", r.RequestURI)
  58. }
  59. rec, ok := auth.DoAuth(w, r)
  60. if !ok {
  61. log.Debug("authenticated request with login ui handler to login ", r.RequestURI)
  62. c := s.makeContext(rec, w, r)
  63. if loginHandler(c) {
  64. handle(c)
  65. }
  66. } else {
  67. c := s.makeContext(rec, w, r)
  68. handle(c)
  69. }
  70. }
  71. }
  72. func (s *Server) authenticated(auth auth.Authenticator, redirect string, handle func(c *Context)) http.HandlerFunc {
  73. return func(w http.ResponseWriter, r *http.Request) {
  74. if s.debug {
  75. log.Debug("authenticated request: ", r.RequestURI)
  76. }
  77. rec, ok := auth.DoAuth(w, r)
  78. if !ok {
  79. if redirect != "" {
  80. http.Redirect(w, r, redirect, http.StatusSeeOther)
  81. } else {
  82. http.Error(w, "Not authorized", http.StatusUnauthorized)
  83. }
  84. } else {
  85. c := s.makeContext(rec, w, r)
  86. handle(c)
  87. }
  88. }
  89. }
  90. func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc {
  91. return func(w http.ResponseWriter, r *http.Request) {
  92. if s.debug {
  93. log.Debug("request: ", r.RequestURI)
  94. }
  95. c := s.makeContext(nil, w, r)
  96. if s.auth != nil {
  97. if rec, ok := s.auth.DoAuth(w, r); ok {
  98. c.auth = rec
  99. }
  100. }
  101. handle(c)
  102. // discard the rest of the body content
  103. io.Copy(ioutil.Discard, r.Body)
  104. defer r.Body.Close()
  105. }
  106. }
  107. // This is a bit different then the standard template.parseFiles code in that it gives us hiarchial templates
  108. // header.html
  109. // mydirectory/service.html ...
  110. func (s *Server) parseTemplates(t *template.Template, filenames ...string) (*template.Template, error) {
  111. //if err := t.checkCanParse(); err != nil {
  112. // return nil, err
  113. //}
  114. if len(filenames) == 0 {
  115. // Not really a problem, but be consistent.
  116. return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
  117. }
  118. for _, filename := range filenames {
  119. f, err := s.fs.Open(filename)
  120. if err != nil {
  121. return nil, err
  122. }
  123. b, err := ioutil.ReadAll(f)
  124. f.Close()
  125. if err != nil {
  126. return nil, err
  127. }
  128. data := string(b)
  129. name := strings.TrimPrefix(filename, s.templates+"/")
  130. // First template becomes return value if not already defined,
  131. // and we use that one for subsequent New calls to associate
  132. // all the templates together. Also, if this file has the same name
  133. // as t, this file becomes the contents of t, so
  134. // t, err := New(name).Funcs(xxx).ParseFiles(name)
  135. // works. Otherwise we create a new template associated with t.
  136. var tmpl *template.Template
  137. if t == nil {
  138. t = template.New(name).Funcs(s.templateFuncs)
  139. }
  140. if name == t.Name() {
  141. tmpl = t
  142. } else {
  143. tmpl = t.New(name)
  144. }
  145. _, err = tmpl.Parse(data)
  146. if err != nil {
  147. return nil, err
  148. }
  149. }
  150. return t, nil
  151. }
  152. func Walk(fs http.FileSystem, base string, walkFunc func(path string, info os.FileInfo, err error) error) error {
  153. f, err := fs.Open(base)
  154. if err != nil {
  155. return err
  156. }
  157. defer f.Close()
  158. s, err := f.Stat()
  159. if err != nil {
  160. return err
  161. }
  162. if s.IsDir() {
  163. // its a directory, recurse
  164. files, err := f.Readdir(1024)
  165. if err != nil {
  166. return err
  167. }
  168. for _, cf := range files {
  169. if err = Walk(fs, path.Join(base, cf.Name()), walkFunc); err != nil {
  170. return err
  171. }
  172. }
  173. return nil
  174. }
  175. return walkFunc(base, s, nil)
  176. }
  177. func (s *Server) LoadTemplatesFS(fs http.FileSystem, base string) (*template.Template, error) {
  178. tmpl := template.New("").Funcs(s.templateFuncs)
  179. err := Walk(fs, base, func(path string, info os.FileInfo, err error) error {
  180. if strings.Contains(path, ".html") {
  181. _, err := s.parseTemplates(tmpl, path)
  182. if err != nil {
  183. log.Println(err)
  184. }
  185. }
  186. return err
  187. })
  188. if err != nil {
  189. return nil, err
  190. }
  191. return tmpl, nil
  192. }
  193. func (s *Server) loadTemplates() *template.Template {
  194. tmpl, err := s.LoadTemplatesFS(s.fs, s.templates)
  195. if err != nil {
  196. log.Fatal("loadTemplates", err, s.templates)
  197. }
  198. return tmpl
  199. }
  200. func (s *Server) getTemplates() *template.Template {
  201. if s.debug {
  202. return s.loadTemplates()
  203. }
  204. if s.cachedTmpl == nil {
  205. s.cachedTmpl = s.loadTemplates()
  206. }
  207. return s.cachedTmpl
  208. }
  209. func (s *Server) render(w http.ResponseWriter, tmpl string, content interface{}) {
  210. if s.testModeEnabled {
  211. msg, err := json.Marshal(content)
  212. if err != nil {
  213. s.renderError(w, 400, "Internal Server Error")
  214. }
  215. s.reply(w, string(msg))
  216. return
  217. }
  218. err := s.getTemplates().ExecuteTemplate(w, tmpl, content)
  219. if err != nil {
  220. log.Warn(err)
  221. }
  222. }
  223. func (s *Server) reply(w http.ResponseWriter, msg string) {
  224. w.Write([]byte(msg))
  225. }
  226. func (s *Server) renderError(w http.ResponseWriter, code int, msg string) {
  227. w.WriteHeader(code)
  228. w.Write([]byte(msg))
  229. }
  230. func (s *Server) HandleFuncAuthenticated(path, redirect string, f func(c *Context)) *mux.Route {
  231. if s.auth == nil {
  232. return nil
  233. }
  234. return s.router.HandleFunc(path, s.authenticated(s.auth, redirect, f))
  235. }
  236. func (s *Server) HandleFuncAuthenticatedWithLogin(path string, loginHandler func(c *Context) bool, contentHandler func(c *Context)) *mux.Route {
  237. if s.auth == nil {
  238. return nil
  239. }
  240. return s.router.HandleFunc(path, s.withLoginHandler(s.auth, loginHandler, contentHandler))
  241. }
  242. func (s *Server) HandleFuncCustomAuth(auth auth.Authenticator, path, redirect string, f func(c *Context)) *mux.Route {
  243. if auth == nil {
  244. log.Warn("Nil auth on", path)
  245. return nil
  246. }
  247. return s.router.HandleFunc(path, s.authenticated(auth, redirect, f))
  248. }
  249. func (s *Server) HandleFunc(path string, f func(c *Context)) *mux.Route {
  250. return s.router.HandleFunc(path, s.wrapper(f))
  251. }
  252. func (s *Server) AddRoute(path string, r *RouteBuilder) *mux.Route {
  253. return s.router.HandleFunc(path, r.BuildRoute(s))
  254. }
  255. func (s *Server) SetDebug(enable bool) {
  256. s.debug = enable
  257. }
  258. func (s *Server) EnableStatus(path string) {
  259. }
  260. func (s *Server) SetTheme(themePath string) {
  261. s.theme = path.Clean(themePath)
  262. }
  263. func (s *Server) SetTemplatePath(tmplPath string) {
  264. s.templates = path.Clean(tmplPath)
  265. }
  266. func (s *Server) Router() *mux.Router {
  267. return s.router
  268. }
  269. func (s *Server) ServeTLS(keyPath string, certPath string) error {
  270. kpr, err := autocert.NewManager(certPath, keyPath)
  271. if err != nil {
  272. log.Fatal(err)
  273. }
  274. srv := &http.Server{
  275. Handler: s.router,
  276. Addr: s.address,
  277. // Good practice: enforce timeouts for servers you create!
  278. WriteTimeout: 120 * time.Second,
  279. ReadTimeout: 120 * time.Second,
  280. TLSConfig: &tls.Config{},
  281. }
  282. srv.TLSConfig.GetCertificate = kpr.GetCertificateFunc()
  283. return srv.ListenAndServeTLS("", "")
  284. }
  285. func (s *Server) ServeTLSRedirect(address string) error {
  286. srv := &http.Server{
  287. Addr: address,
  288. // Good practice: enforce timeouts for servers you create!
  289. WriteTimeout: 120 * time.Second,
  290. ReadTimeout: 120 * time.Second,
  291. }
  292. return srv.ListenAndServe()
  293. }
  294. // Serve serve content forever
  295. func (s *Server) Serve() error {
  296. srv := &http.Server{
  297. Handler: s.router,
  298. Addr: s.address,
  299. // Good practice: enforce timeouts for servers you create!
  300. WriteTimeout: 120 * time.Second,
  301. ReadTimeout: 120 * time.Second,
  302. }
  303. return srv.ListenAndServe()
  304. }
  305. func (s *Server) WithStaticFiles(prefix string) *Server {
  306. s.router.PathPrefix(prefix).Handler(http.FileServer(s.fs))
  307. return s
  308. }
  309. func (s *Server) WithTheme(themeURL string) *Server {
  310. s.theme = path.Clean(themeURL)
  311. return s
  312. }
  313. func (s *Server) EnableTestMode(enable bool) *Server {
  314. s.testModeEnabled = enable
  315. return s
  316. }
  317. func (s *Server) WithRootFileSystem(fs http.FileSystem) *Server {
  318. s.fs = fs
  319. return s
  320. }
  321. func (s *Server) WithDebug(debugURL string) *Server {
  322. sub := s.router.PathPrefix(debugURL).Subrouter()
  323. setupDebugHandler(sub)
  324. return s
  325. }
  326. func (s *Server) WithMetadata(meta map[string]string) *Server {
  327. s.meta = meta
  328. return s
  329. }
  330. func (s *Server) WithTemplateFuncs(funcs template.FuncMap) *Server {
  331. for k, f := range funcs {
  332. s.templateFuncs[k] = f
  333. }
  334. return s
  335. }
  336. func (s *Server) WithAuth(auth auth.Authenticator) *Server {
  337. s.auth = auth
  338. return s
  339. }
  340. func (s *Server) WithHealthCheck(version, date string, status func() (bool, string)) {
  341. s.HandleFunc("/_health", func(c *Context) {
  342. ok, msg := status()
  343. hc := struct {
  344. Version string
  345. Date string
  346. Status string
  347. }{
  348. Version: version,
  349. Date: date,
  350. Status: msg,
  351. }
  352. if ok {
  353. c.ReplyObject(&hc)
  354. return
  355. }
  356. c.ErrorObject(http.StatusServiceUnavailable, &hc)
  357. })
  358. }
  359. func (s *Server) Dump() {
  360. fmt.Printf(" Theme: %s\n", s.theme)
  361. fmt.Printf(" Templates: %s\n", s.templates)
  362. }
  363. func New(address string, path string, auth auth.Authenticator) *Server {
  364. s := Server{
  365. router: mux.NewRouter(),
  366. auth: auth,
  367. address: address,
  368. fs: http.FileSystem(http.Dir(path)),
  369. templates: "/templates",
  370. templateFuncs: builtinFuncMap,
  371. theme: "/static/css/default.css",
  372. meta: make(map[string]string),
  373. }
  374. return &s
  375. }