render.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. // Copyright 2013 Martini Authors
  2. // Copyright 2013 oxtoacart
  3. // Copyright 2014 Unknwon
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  6. // not use this file except in compliance with the License. You may obtain
  7. // a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. // License for the specific language governing permissions and limitations
  15. // under the License.
  16. package macaron
  17. import (
  18. "bytes"
  19. "encoding/json"
  20. "encoding/xml"
  21. "fmt"
  22. "html/template"
  23. "io"
  24. "io/ioutil"
  25. "net/http"
  26. "os"
  27. "path"
  28. "path/filepath"
  29. "strings"
  30. "sync"
  31. "time"
  32. "github.com/Unknwon/com"
  33. )
  34. // BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
  35. type BufferPool struct {
  36. c chan *bytes.Buffer
  37. }
  38. // NewBufferPool creates a new BufferPool bounded to the given size.
  39. func NewBufferPool(size int) (bp *BufferPool) {
  40. return &BufferPool{
  41. c: make(chan *bytes.Buffer, size),
  42. }
  43. }
  44. // Get gets a Buffer from the BufferPool, or creates a new one if none are available
  45. // in the pool.
  46. func (bp *BufferPool) Get() (b *bytes.Buffer) {
  47. select {
  48. case b = <-bp.c:
  49. // reuse existing buffer
  50. default:
  51. // create new buffer
  52. b = bytes.NewBuffer([]byte{})
  53. }
  54. return
  55. }
  56. // Put returns the given Buffer to the BufferPool.
  57. func (bp *BufferPool) Put(b *bytes.Buffer) {
  58. b.Reset()
  59. bp.c <- b
  60. }
  61. const (
  62. ContentType = "Content-Type"
  63. ContentLength = "Content-Length"
  64. ContentBinary = "application/octet-stream"
  65. ContentJSON = "application/json"
  66. ContentHTML = "text/html"
  67. CONTENT_PLAIN = "text/plain"
  68. ContentXHTML = "application/xhtml+xml"
  69. ContentXML = "text/xml"
  70. defaultCharset = "UTF-8"
  71. )
  72. var (
  73. // Provides a temporary buffer to execute templates into and catch errors.
  74. bufpool = NewBufferPool(64)
  75. // Included helper functions for use when rendering html
  76. helperFuncs = template.FuncMap{
  77. "yield": func() (string, error) {
  78. return "", fmt.Errorf("yield called with no layout defined")
  79. },
  80. "current": func() (string, error) {
  81. return "", nil
  82. },
  83. }
  84. )
  85. type (
  86. // TemplateFile represents a interface of template file that has name and can be read.
  87. TemplateFile interface {
  88. Name() string
  89. Data() []byte
  90. Ext() string
  91. }
  92. // TemplateFileSystem represents a interface of template file system that able to list all files.
  93. TemplateFileSystem interface {
  94. ListFiles() []TemplateFile
  95. }
  96. // Delims represents a set of Left and Right delimiters for HTML template rendering
  97. Delims struct {
  98. // Left delimiter, defaults to {{
  99. Left string
  100. // Right delimiter, defaults to }}
  101. Right string
  102. }
  103. // RenderOptions represents a struct for specifying configuration options for the Render middleware.
  104. RenderOptions struct {
  105. // Directory to load templates. Default is "templates".
  106. Directory string
  107. // Layout template name. Will not render a layout if "". Default is to "".
  108. Layout string
  109. // Extensions to parse template files from. Defaults are [".tmpl", ".html"].
  110. Extensions []string
  111. // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
  112. Funcs []template.FuncMap
  113. // Delims sets the action delimiters to the specified strings in the Delims struct.
  114. Delims Delims
  115. // Appends the given charset to the Content-Type header. Default is "UTF-8".
  116. Charset string
  117. // Outputs human readable JSON.
  118. IndentJSON bool
  119. // Outputs human readable XML.
  120. IndentXML bool
  121. // Prefixes the JSON output with the given bytes.
  122. PrefixJSON []byte
  123. // Prefixes the XML output with the given bytes.
  124. PrefixXML []byte
  125. // Allows changing of output to XHTML instead of HTML. Default is "text/html"
  126. HTMLContentType string
  127. // TemplateFileSystem is the interface for supporting any implmentation of template file system.
  128. TemplateFileSystem
  129. }
  130. // HTMLOptions is a struct for overriding some rendering Options for specific HTML call
  131. HTMLOptions struct {
  132. // Layout template name. Overrides Options.Layout.
  133. Layout string
  134. }
  135. Render interface {
  136. http.ResponseWriter
  137. RW() http.ResponseWriter
  138. JSON(int, interface{})
  139. JSONString(interface{}) (string, error)
  140. RawData(int, []byte)
  141. RenderData(int, []byte)
  142. HTML(int, string, interface{}, ...HTMLOptions)
  143. HTMLSet(int, string, string, interface{}, ...HTMLOptions)
  144. HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
  145. HTMLString(string, interface{}, ...HTMLOptions) (string, error)
  146. HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
  147. HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
  148. XML(int, interface{})
  149. Error(int, ...string)
  150. Status(int)
  151. SetTemplatePath(string, string)
  152. HasTemplateSet(string) bool
  153. }
  154. )
  155. // TplFile implements TemplateFile interface.
  156. type TplFile struct {
  157. name string
  158. data []byte
  159. ext string
  160. }
  161. // NewTplFile cerates new template file with given name and data.
  162. func NewTplFile(name string, data []byte, ext string) *TplFile {
  163. return &TplFile{name, data, ext}
  164. }
  165. func (f *TplFile) Name() string {
  166. return f.name
  167. }
  168. func (f *TplFile) Data() []byte {
  169. return f.data
  170. }
  171. func (f *TplFile) Ext() string {
  172. return f.ext
  173. }
  174. // TplFileSystem implements TemplateFileSystem interface.
  175. type TplFileSystem struct {
  176. files []TemplateFile
  177. }
  178. // NewTemplateFileSystem creates new template file system with given options.
  179. func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
  180. fs := TplFileSystem{}
  181. fs.files = make([]TemplateFile, 0, 10)
  182. if err := filepath.Walk(opt.Directory, func(path string, info os.FileInfo, err error) error {
  183. r, err := filepath.Rel(opt.Directory, path)
  184. if err != nil {
  185. return err
  186. }
  187. ext := GetExt(r)
  188. for _, extension := range opt.Extensions {
  189. if ext == extension {
  190. var data []byte
  191. if !omitData {
  192. data, err = ioutil.ReadFile(path)
  193. if err != nil {
  194. return err
  195. }
  196. }
  197. name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
  198. fs.files = append(fs.files, NewTplFile(name, data, ext))
  199. break
  200. }
  201. }
  202. return nil
  203. }); err != nil {
  204. panic("NewTemplateFileSystem: " + err.Error())
  205. }
  206. return fs
  207. }
  208. func (fs TplFileSystem) ListFiles() []TemplateFile {
  209. return fs.files
  210. }
  211. func PrepareCharset(charset string) string {
  212. if len(charset) != 0 {
  213. return "; charset=" + charset
  214. }
  215. return "; charset=" + defaultCharset
  216. }
  217. func GetExt(s string) string {
  218. index := strings.Index(s, ".")
  219. if index == -1 {
  220. return ""
  221. }
  222. return s[index:]
  223. }
  224. func compile(opt RenderOptions) *template.Template {
  225. dir := opt.Directory
  226. t := template.New(dir)
  227. t.Delims(opt.Delims.Left, opt.Delims.Right)
  228. // Parse an initial template in case we don't have any.
  229. template.Must(t.Parse("Macaron"))
  230. if opt.TemplateFileSystem == nil {
  231. opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
  232. }
  233. for _, f := range opt.TemplateFileSystem.ListFiles() {
  234. tmpl := t.New(f.Name())
  235. for _, funcs := range opt.Funcs {
  236. tmpl.Funcs(funcs)
  237. }
  238. // Bomb out if parse fails. We don't want any silent server starts.
  239. template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
  240. }
  241. return t
  242. }
  243. const (
  244. _DEFAULT_TPL_SET_NAME = "DEFAULT"
  245. )
  246. // templateSet represents a template set of type *template.Template.
  247. type templateSet struct {
  248. lock sync.RWMutex
  249. sets map[string]*template.Template
  250. dirs map[string]string
  251. }
  252. func newTemplateSet() *templateSet {
  253. return &templateSet{
  254. sets: make(map[string]*template.Template),
  255. dirs: make(map[string]string),
  256. }
  257. }
  258. func (ts *templateSet) Set(name string, opt *RenderOptions) *template.Template {
  259. t := compile(*opt)
  260. ts.lock.Lock()
  261. defer ts.lock.Unlock()
  262. ts.sets[name] = t
  263. ts.dirs[name] = opt.Directory
  264. return t
  265. }
  266. func (ts *templateSet) Get(name string) *template.Template {
  267. ts.lock.RLock()
  268. defer ts.lock.RUnlock()
  269. return ts.sets[name]
  270. }
  271. func (ts *templateSet) GetDir(name string) string {
  272. ts.lock.RLock()
  273. defer ts.lock.RUnlock()
  274. return ts.dirs[name]
  275. }
  276. func prepareOptions(options []RenderOptions) RenderOptions {
  277. var opt RenderOptions
  278. if len(options) > 0 {
  279. opt = options[0]
  280. }
  281. // Defaults.
  282. if len(opt.Directory) == 0 {
  283. opt.Directory = "templates"
  284. }
  285. if len(opt.Extensions) == 0 {
  286. opt.Extensions = []string{".tmpl", ".html"}
  287. }
  288. if len(opt.HTMLContentType) == 0 {
  289. opt.HTMLContentType = ContentHTML
  290. }
  291. return opt
  292. }
  293. func ParseTplSet(tplSet string) (tplName string, tplDir string) {
  294. tplSet = strings.TrimSpace(tplSet)
  295. if len(tplSet) == 0 {
  296. panic("empty template set argument")
  297. }
  298. infos := strings.Split(tplSet, ":")
  299. if len(infos) == 1 {
  300. tplDir = infos[0]
  301. tplName = path.Base(tplDir)
  302. } else {
  303. tplName = infos[0]
  304. tplDir = infos[1]
  305. }
  306. if !com.IsDir(tplDir) {
  307. panic("template set path does not exist or is not a directory")
  308. }
  309. return tplName, tplDir
  310. }
  311. func renderHandler(opt RenderOptions, tplSets []string) Handler {
  312. cs := PrepareCharset(opt.Charset)
  313. ts := newTemplateSet()
  314. ts.Set(_DEFAULT_TPL_SET_NAME, &opt)
  315. var tmpOpt RenderOptions
  316. for _, tplSet := range tplSets {
  317. tplName, tplDir := ParseTplSet(tplSet)
  318. tmpOpt = opt
  319. tmpOpt.Directory = tplDir
  320. ts.Set(tplName, &tmpOpt)
  321. }
  322. return func(ctx *Context) {
  323. r := &TplRender{
  324. ResponseWriter: ctx.Resp,
  325. templateSet: ts,
  326. Opt: &opt,
  327. CompiledCharset: cs,
  328. }
  329. ctx.Data["TmplLoadTimes"] = func() string {
  330. if r.startTime.IsZero() {
  331. return ""
  332. }
  333. return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
  334. }
  335. ctx.Render = r
  336. ctx.MapTo(r, (*Render)(nil))
  337. }
  338. }
  339. // Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
  340. // An single variadic macaron.RenderOptions struct can be optionally provided to configure
  341. // HTML rendering. The default directory for templates is "templates" and the default
  342. // file extension is ".tmpl" and ".html".
  343. //
  344. // If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
  345. // MACARON_ENV environment variable to "production".
  346. func Renderer(options ...RenderOptions) Handler {
  347. return renderHandler(prepareOptions(options), []string{})
  348. }
  349. func Renderers(options RenderOptions, tplSets ...string) Handler {
  350. return renderHandler(prepareOptions([]RenderOptions{options}), tplSets)
  351. }
  352. type TplRender struct {
  353. http.ResponseWriter
  354. *templateSet
  355. Opt *RenderOptions
  356. CompiledCharset string
  357. startTime time.Time
  358. }
  359. func (r *TplRender) RW() http.ResponseWriter {
  360. return r.ResponseWriter
  361. }
  362. func (r *TplRender) JSON(status int, v interface{}) {
  363. var (
  364. result []byte
  365. err error
  366. )
  367. if r.Opt.IndentJSON {
  368. result, err = json.MarshalIndent(v, "", " ")
  369. } else {
  370. result, err = json.Marshal(v)
  371. }
  372. if err != nil {
  373. http.Error(r, err.Error(), 500)
  374. return
  375. }
  376. // json rendered fine, write out the result
  377. r.Header().Set(ContentType, ContentJSON+r.CompiledCharset)
  378. r.WriteHeader(status)
  379. if len(r.Opt.PrefixJSON) > 0 {
  380. r.Write(r.Opt.PrefixJSON)
  381. }
  382. r.Write(result)
  383. }
  384. func (r *TplRender) JSONString(v interface{}) (string, error) {
  385. var result []byte
  386. var err error
  387. if r.Opt.IndentJSON {
  388. result, err = json.MarshalIndent(v, "", " ")
  389. } else {
  390. result, err = json.Marshal(v)
  391. }
  392. if err != nil {
  393. return "", err
  394. }
  395. return string(result), nil
  396. }
  397. func (r *TplRender) XML(status int, v interface{}) {
  398. var result []byte
  399. var err error
  400. if r.Opt.IndentXML {
  401. result, err = xml.MarshalIndent(v, "", " ")
  402. } else {
  403. result, err = xml.Marshal(v)
  404. }
  405. if err != nil {
  406. http.Error(r, err.Error(), 500)
  407. return
  408. }
  409. // XML rendered fine, write out the result
  410. r.Header().Set(ContentType, ContentXML+r.CompiledCharset)
  411. r.WriteHeader(status)
  412. if len(r.Opt.PrefixXML) > 0 {
  413. r.Write(r.Opt.PrefixXML)
  414. }
  415. r.Write(result)
  416. }
  417. func (r *TplRender) data(status int, contentType string, v []byte) {
  418. if r.Header().Get(ContentType) == "" {
  419. r.Header().Set(ContentType, contentType)
  420. }
  421. r.WriteHeader(status)
  422. r.Write(v)
  423. }
  424. func (r *TplRender) RawData(status int, v []byte) {
  425. r.data(status, ContentBinary, v)
  426. }
  427. func (r *TplRender) RenderData(status int, v []byte) {
  428. r.data(status, CONTENT_PLAIN, v)
  429. }
  430. func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
  431. buf := bufpool.Get()
  432. return buf, t.ExecuteTemplate(buf, name, data)
  433. }
  434. func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
  435. funcs := template.FuncMap{
  436. "yield": func() (template.HTML, error) {
  437. buf, err := r.execute(t, tplName, data)
  438. // return safe html here since we are rendering our own template
  439. return template.HTML(buf.String()), err
  440. },
  441. "current": func() (string, error) {
  442. return tplName, nil
  443. },
  444. }
  445. t.Funcs(funcs)
  446. }
  447. func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
  448. t := r.templateSet.Get(setName)
  449. if Env == DEV {
  450. opt := *r.Opt
  451. opt.Directory = r.templateSet.GetDir(setName)
  452. t = r.templateSet.Set(setName, &opt)
  453. }
  454. if t == nil {
  455. return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
  456. }
  457. opt := r.prepareHTMLOptions(htmlOpt)
  458. if len(opt.Layout) > 0 {
  459. r.addYield(t, tplName, data)
  460. tplName = opt.Layout
  461. }
  462. out, err := r.execute(t, tplName, data)
  463. if err != nil {
  464. return nil, err
  465. }
  466. return out, nil
  467. }
  468. func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
  469. r.startTime = time.Now()
  470. out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
  471. if err != nil {
  472. http.Error(r, err.Error(), http.StatusInternalServerError)
  473. return
  474. }
  475. r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
  476. r.WriteHeader(status)
  477. io.Copy(r, out)
  478. bufpool.Put(out)
  479. }
  480. func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
  481. r.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
  482. }
  483. func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
  484. r.renderHTML(status, setName, tplName, data, htmlOpt...)
  485. }
  486. func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
  487. out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
  488. if err != nil {
  489. return []byte(""), err
  490. }
  491. return out.Bytes(), nil
  492. }
  493. func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
  494. return r.HTMLSetBytes(_DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
  495. }
  496. func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
  497. p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
  498. return string(p), err
  499. }
  500. func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
  501. p, err := r.HTMLBytes(name, data, htmlOpt...)
  502. return string(p), err
  503. }
  504. // Error writes the given HTTP status to the current ResponseWriter
  505. func (r *TplRender) Error(status int, message ...string) {
  506. r.WriteHeader(status)
  507. if len(message) > 0 {
  508. r.Write([]byte(message[0]))
  509. }
  510. }
  511. func (r *TplRender) Status(status int) {
  512. r.WriteHeader(status)
  513. }
  514. func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
  515. if len(htmlOpt) > 0 {
  516. return htmlOpt[0]
  517. }
  518. return HTMLOptions{
  519. Layout: r.Opt.Layout,
  520. }
  521. }
  522. func (r *TplRender) SetTemplatePath(setName, dir string) {
  523. if len(setName) == 0 {
  524. setName = _DEFAULT_TPL_SET_NAME
  525. }
  526. opt := *r.Opt
  527. opt.Directory = dir
  528. r.templateSet.Set(setName, &opt)
  529. }
  530. func (r *TplRender) HasTemplateSet(name string) bool {
  531. return r.templateSet.Get(name) != nil
  532. }