You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

render.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. package render
  2. import (
  3. "bytes"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "log"
  8. "net/http"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "sync"
  13. "github.com/fsnotify/fsnotify"
  14. )
  15. const (
  16. // ContentBinary header value for binary data.
  17. ContentBinary = "application/octet-stream"
  18. // ContentHTML header value for HTML data.
  19. ContentHTML = "text/html"
  20. // ContentJSON header value for JSON data.
  21. ContentJSON = "application/json"
  22. // ContentJSONP header value for JSONP data.
  23. ContentJSONP = "application/javascript"
  24. // ContentLength header constant.
  25. ContentLength = "Content-Length"
  26. // ContentText header value for Text data.
  27. ContentText = "text/plain"
  28. // ContentType header constant.
  29. ContentType = "Content-Type"
  30. // ContentXHTML header value for XHTML data.
  31. ContentXHTML = "application/xhtml+xml"
  32. // ContentXML header value for XML data.
  33. ContentXML = "text/xml"
  34. // Default character encoding.
  35. defaultCharset = "UTF-8"
  36. )
  37. // helperFuncs had to be moved out. See helpers.go|helpers_pre16.go files.
  38. // Delims represents a set of Left and Right delimiters for HTML template rendering.
  39. type Delims struct {
  40. // Left delimiter, defaults to {{.
  41. Left string
  42. // Right delimiter, defaults to }}.
  43. Right string
  44. }
  45. // Options is a struct for specifying configuration options for the render.Render object.
  46. type Options struct {
  47. // Directory to load templates. Default is "templates".
  48. Directory string
  49. // FileSystem to access files
  50. FileSystem FileSystem
  51. // Asset function to use in place of directory. Defaults to nil.
  52. Asset func(name string) ([]byte, error)
  53. // AssetNames function to use in place of directory. Defaults to nil.
  54. AssetNames func() []string
  55. // Layout template name. Will not render a layout if blank (""). Defaults to blank ("").
  56. Layout string
  57. // Extensions to parse template files from. Defaults to [".tmpl"].
  58. Extensions []string
  59. // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Defaults to empty map.
  60. Funcs []template.FuncMap
  61. // Delims sets the action delimiters to the specified strings in the Delims struct.
  62. Delims Delims
  63. // Appends the given character set to the Content-Type header. Default is "UTF-8".
  64. Charset string
  65. // If DisableCharset is set to true, it will not append the above Charset value to the Content-Type header. Default is false.
  66. DisableCharset bool
  67. // Outputs human readable JSON.
  68. IndentJSON bool
  69. // Outputs human readable XML. Default is false.
  70. IndentXML bool
  71. // Prefixes the JSON output with the given bytes. Default is false.
  72. PrefixJSON []byte
  73. // Prefixes the XML output with the given bytes.
  74. PrefixXML []byte
  75. // Allows changing the binary content type.
  76. BinaryContentType string
  77. // Allows changing the HTML content type.
  78. HTMLContentType string
  79. // Allows changing the JSON content type.
  80. JSONContentType string
  81. // Allows changing the JSONP content type.
  82. JSONPContentType string
  83. // Allows changing the Text content type.
  84. TextContentType string
  85. // Allows changing the XML content type.
  86. XMLContentType string
  87. // If IsDevelopment is set to true, this will recompile the templates on every request. Default is false.
  88. IsDevelopment bool
  89. // If UseMutexLock is set to true, the standard `sync.RWMutex` lock will be used instead of the lock free implementation. Default is false.
  90. // Note that when `IsDevelopment` is true, the standard `sync.RWMutex` lock is always used. Lock free is only a production feature.
  91. UseMutexLock bool
  92. // Unescape HTML characters "&<>" to their original values. Default is false.
  93. UnEscapeHTML bool
  94. // Streams JSON responses instead of marshalling prior to sending. Default is false.
  95. StreamingJSON bool
  96. // Require that all partials executed in the layout are implemented in all templates using the layout. Default is false.
  97. RequirePartials bool
  98. // Deprecated: Use the above `RequirePartials` instead of this. As of Go 1.6, blocks are built in. Default is false.
  99. RequireBlocks bool
  100. // Disables automatic rendering of http.StatusInternalServerError when an error occurs. Default is false.
  101. DisableHTTPErrorRendering bool
  102. // Enables using partials without the current filename suffix which allows use of the same template in multiple files. e.g {{ partial "carosuel" }} inside the home template will match carosel-home or carosel.
  103. // ***NOTE*** - This option should be named RenderPartialsWithoutSuffix as that is what it does. "Prefix" is a typo. Maintaining the existing name for backwards compatibility.
  104. RenderPartialsWithoutPrefix bool
  105. // BufferPool to use when rendering HTML templates. If none is supplied
  106. // defaults to SizedBufferPool of size 32 with 512KiB buffers.
  107. BufferPool GenericBufferPool
  108. }
  109. // HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
  110. type HTMLOptions struct {
  111. // Layout template name. Overrides Options.Layout.
  112. Layout string
  113. // Funcs added to Options.Funcs.
  114. Funcs template.FuncMap
  115. }
  116. // Render is a service that provides functions for easily writing JSON, XML,
  117. // binary data, and HTML templates out to a HTTP Response.
  118. type Render struct {
  119. lock rwLock
  120. // Customize Secure with an Options struct.
  121. opt Options
  122. templates *template.Template
  123. compiledCharset string
  124. hasWatcher bool
  125. }
  126. // New constructs a new Render instance with the supplied options.
  127. func New(options ...Options) *Render {
  128. var o Options
  129. if len(options) > 0 {
  130. o = options[0]
  131. }
  132. r := Render{opt: o}
  133. r.prepareOptions()
  134. r.CompileTemplates()
  135. return &r
  136. }
  137. func (r *Render) prepareOptions() {
  138. // Fill in the defaults if need be.
  139. if len(r.opt.Charset) == 0 {
  140. r.opt.Charset = defaultCharset
  141. }
  142. if !r.opt.DisableCharset {
  143. r.compiledCharset = "; charset=" + r.opt.Charset
  144. }
  145. if len(r.opt.Directory) == 0 {
  146. r.opt.Directory = "templates"
  147. }
  148. if r.opt.FileSystem == nil {
  149. r.opt.FileSystem = &LocalFileSystem{}
  150. }
  151. if len(r.opt.Extensions) == 0 {
  152. r.opt.Extensions = []string{".tmpl"}
  153. }
  154. if len(r.opt.BinaryContentType) == 0 {
  155. r.opt.BinaryContentType = ContentBinary
  156. }
  157. if len(r.opt.HTMLContentType) == 0 {
  158. r.opt.HTMLContentType = ContentHTML
  159. }
  160. if len(r.opt.JSONContentType) == 0 {
  161. r.opt.JSONContentType = ContentJSON
  162. }
  163. if len(r.opt.JSONPContentType) == 0 {
  164. r.opt.JSONPContentType = ContentJSONP
  165. }
  166. if len(r.opt.TextContentType) == 0 {
  167. r.opt.TextContentType = ContentText
  168. }
  169. if len(r.opt.XMLContentType) == 0 {
  170. r.opt.XMLContentType = ContentXML
  171. }
  172. if r.opt.BufferPool == nil {
  173. r.opt.BufferPool = NewSizedBufferPool(32, 1<<19) // 32 buffers of size 512KiB each
  174. }
  175. if r.opt.IsDevelopment || r.opt.UseMutexLock {
  176. r.lock = &sync.RWMutex{}
  177. } else {
  178. r.lock = &emptyLock{}
  179. }
  180. }
  181. func (r *Render) CompileTemplates() {
  182. if r.opt.Asset == nil || r.opt.AssetNames == nil {
  183. r.compileTemplatesFromDir()
  184. return
  185. }
  186. r.compileTemplatesFromAsset()
  187. }
  188. func (r *Render) compileTemplatesFromDir() {
  189. dir := r.opt.Directory
  190. tmpTemplates := template.New(dir)
  191. tmpTemplates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
  192. var watcher *fsnotify.Watcher
  193. if r.opt.IsDevelopment {
  194. var err error
  195. watcher, err = fsnotify.NewWatcher()
  196. if err != nil {
  197. log.Printf("Unable to create new watcher for template files. Templates will be recompiled on every render. Error: %v\n", err)
  198. }
  199. }
  200. // Walk the supplied directory and compile any files that match our extension list.
  201. _ = r.opt.FileSystem.Walk(dir, func(path string, info os.FileInfo, _ error) error {
  202. // Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
  203. // These dirs should be excluded as they are not valid golang templates, but files under
  204. // them should be treat as normal.
  205. // If is a dir, return immediately (dir is not a valid golang template).
  206. if info != nil && watcher != nil {
  207. _ = watcher.Add(path)
  208. }
  209. if info == nil || info.IsDir() {
  210. return nil
  211. }
  212. rel, err := filepath.Rel(dir, path)
  213. if err != nil {
  214. return err
  215. }
  216. ext := ""
  217. if strings.Contains(rel, ".") {
  218. ext = filepath.Ext(rel)
  219. }
  220. for _, extension := range r.opt.Extensions {
  221. if ext == extension {
  222. buf, err := r.opt.FileSystem.ReadFile(path)
  223. if err != nil {
  224. panic(err)
  225. }
  226. name := (rel[0 : len(rel)-len(ext)])
  227. tmpl := tmpTemplates.New(filepath.ToSlash(name))
  228. // Add our funcmaps.
  229. for _, funcs := range r.opt.Funcs {
  230. tmpl.Funcs(funcs)
  231. }
  232. // Break out if this parsing fails. We don't want any silent server starts.
  233. template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
  234. break
  235. }
  236. }
  237. return nil
  238. })
  239. r.lock.Lock()
  240. defer r.lock.Unlock()
  241. r.templates = tmpTemplates
  242. if r.hasWatcher = watcher != nil; r.hasWatcher {
  243. go func() {
  244. select {
  245. case _, ok := <-watcher.Events:
  246. if !ok {
  247. return
  248. }
  249. case _, ok := <-watcher.Errors:
  250. if !ok {
  251. return
  252. }
  253. }
  254. watcher.Close()
  255. r.CompileTemplates()
  256. }()
  257. }
  258. }
  259. func (r *Render) compileTemplatesFromAsset() {
  260. dir := r.opt.Directory
  261. tmpTemplates := template.New(dir)
  262. tmpTemplates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
  263. for _, path := range r.opt.AssetNames() {
  264. if !strings.HasPrefix(path, dir) {
  265. continue
  266. }
  267. rel, err := filepath.Rel(dir, path)
  268. if err != nil {
  269. panic(err)
  270. }
  271. ext := ""
  272. if strings.Contains(rel, ".") {
  273. ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
  274. }
  275. for _, extension := range r.opt.Extensions {
  276. if ext == extension {
  277. buf, err := r.opt.Asset(path)
  278. if err != nil {
  279. panic(err)
  280. }
  281. name := (rel[0 : len(rel)-len(ext)])
  282. tmpl := tmpTemplates.New(filepath.ToSlash(name))
  283. // Add our funcmaps.
  284. for _, funcs := range r.opt.Funcs {
  285. tmpl.Funcs(funcs)
  286. }
  287. // Break out if this parsing fails. We don't want any silent server starts.
  288. template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
  289. break
  290. }
  291. }
  292. }
  293. r.lock.Lock()
  294. defer r.lock.Unlock()
  295. r.templates = tmpTemplates
  296. }
  297. // TemplateLookup is a wrapper around template.Lookup and returns
  298. // the template with the given name that is associated with t, or nil
  299. // if there is no such template.
  300. func (r *Render) TemplateLookup(t string) *template.Template {
  301. r.lock.RLock()
  302. defer r.lock.RUnlock()
  303. return r.templates.Lookup(t)
  304. }
  305. func (r *Render) execute(templates *template.Template, name string, binding interface{}) (*bytes.Buffer, error) {
  306. buf := new(bytes.Buffer)
  307. return buf, templates.ExecuteTemplate(buf, name, binding)
  308. }
  309. func (r *Render) layoutFuncs(templates *template.Template, name string, binding interface{}) template.FuncMap {
  310. return template.FuncMap{
  311. "yield": func() (template.HTML, error) {
  312. buf, err := r.execute(templates, name, binding)
  313. // Return safe HTML here since we are rendering our own template.
  314. return template.HTML(buf.String()), err
  315. },
  316. "current": func() (string, error) {
  317. return name, nil
  318. },
  319. "block": func(partialName string) (template.HTML, error) {
  320. log.Println("Render's `block` implementation is now depericated. Use `partial` as a drop in replacement.")
  321. fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
  322. if templates.Lookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
  323. fullPartialName = partialName
  324. }
  325. if r.opt.RequireBlocks || templates.Lookup(fullPartialName) != nil {
  326. buf, err := r.execute(templates, fullPartialName, binding)
  327. // Return safe HTML here since we are rendering our own template.
  328. return template.HTML(buf.String()), err
  329. }
  330. return "", nil
  331. },
  332. "partial": func(partialName string) (template.HTML, error) {
  333. fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
  334. if templates.Lookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
  335. fullPartialName = partialName
  336. }
  337. if r.opt.RequirePartials || templates.Lookup(fullPartialName) != nil {
  338. buf, err := r.execute(templates, fullPartialName, binding)
  339. // Return safe HTML here since we are rendering our own template.
  340. return template.HTML(buf.String()), err
  341. }
  342. return "", nil
  343. },
  344. }
  345. }
  346. func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
  347. layout := r.opt.Layout
  348. funcs := template.FuncMap{}
  349. for _, tmp := range r.opt.Funcs {
  350. for k, v := range tmp {
  351. funcs[k] = v
  352. }
  353. }
  354. if len(htmlOpt) > 0 {
  355. opt := htmlOpt[0]
  356. if len(opt.Layout) > 0 {
  357. layout = opt.Layout
  358. }
  359. for k, v := range opt.Funcs {
  360. funcs[k] = v
  361. }
  362. }
  363. return HTMLOptions{
  364. Layout: layout,
  365. Funcs: funcs,
  366. }
  367. }
  368. // Render is the generic function called by XML, JSON, Data, HTML, and can be called by custom implementations.
  369. func (r *Render) Render(w io.Writer, e Engine, data interface{}) error {
  370. err := e.Render(w, data)
  371. if hw, ok := w.(http.ResponseWriter); err != nil && !r.opt.DisableHTTPErrorRendering && ok {
  372. http.Error(hw, err.Error(), http.StatusInternalServerError)
  373. }
  374. return err
  375. }
  376. // Data writes out the raw bytes as binary data.
  377. func (r *Render) Data(w io.Writer, status int, v []byte) error {
  378. head := Head{
  379. ContentType: r.opt.BinaryContentType,
  380. Status: status,
  381. }
  382. d := Data{
  383. Head: head,
  384. }
  385. return r.Render(w, d, v)
  386. }
  387. // HTML builds up the response from the specified template and bindings.
  388. func (r *Render) HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...HTMLOptions) error {
  389. // If we are in development mode, recompile the templates on every HTML request.
  390. r.lock.RLock() // rlock here because we're reading the hasWatcher
  391. if r.opt.IsDevelopment && !r.hasWatcher {
  392. r.lock.RUnlock() // runlock here because CompileTemplates will lock
  393. r.CompileTemplates()
  394. r.lock.RLock()
  395. }
  396. templates := r.templates
  397. r.lock.RUnlock()
  398. opt := r.prepareHTMLOptions(htmlOpt)
  399. if tpl := templates.Lookup(name); tpl != nil {
  400. if len(opt.Layout) > 0 {
  401. tpl.Funcs(r.layoutFuncs(templates, name, binding))
  402. name = opt.Layout
  403. }
  404. if len(opt.Funcs) > 0 {
  405. tpl.Funcs(opt.Funcs)
  406. }
  407. }
  408. head := Head{
  409. ContentType: r.opt.HTMLContentType + r.compiledCharset,
  410. Status: status,
  411. }
  412. h := HTML{
  413. Head: head,
  414. Name: name,
  415. Templates: templates,
  416. bp: r.opt.BufferPool,
  417. }
  418. return r.Render(w, h, binding)
  419. }
  420. // JSON marshals the given interface object and writes the JSON response.
  421. func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
  422. head := Head{
  423. ContentType: r.opt.JSONContentType + r.compiledCharset,
  424. Status: status,
  425. }
  426. j := JSON{
  427. Head: head,
  428. Indent: r.opt.IndentJSON,
  429. Prefix: r.opt.PrefixJSON,
  430. UnEscapeHTML: r.opt.UnEscapeHTML,
  431. StreamingJSON: r.opt.StreamingJSON,
  432. }
  433. return r.Render(w, j, v)
  434. }
  435. // JSONP marshals the given interface object and writes the JSON response.
  436. func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{}) error {
  437. head := Head{
  438. ContentType: r.opt.JSONPContentType + r.compiledCharset,
  439. Status: status,
  440. }
  441. j := JSONP{
  442. Head: head,
  443. Indent: r.opt.IndentJSON,
  444. Callback: callback,
  445. }
  446. return r.Render(w, j, v)
  447. }
  448. // Text writes out a string as plain text.
  449. func (r *Render) Text(w io.Writer, status int, v string) error {
  450. head := Head{
  451. ContentType: r.opt.TextContentType + r.compiledCharset,
  452. Status: status,
  453. }
  454. t := Text{
  455. Head: head,
  456. }
  457. return r.Render(w, t, v)
  458. }
  459. // XML marshals the given interface object and writes the XML response.
  460. func (r *Render) XML(w io.Writer, status int, v interface{}) error {
  461. head := Head{
  462. ContentType: r.opt.XMLContentType + r.compiledCharset,
  463. Status: status,
  464. }
  465. x := XML{
  466. Head: head,
  467. Indent: r.opt.IndentXML,
  468. Prefix: r.opt.PrefixXML,
  469. }
  470. return r.Render(w, x, v)
  471. }