render.go 15 KB

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