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.

726 lines
18KB

  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"
  23. "io/ioutil"
  24. "net/http"
  25. "os"
  26. "path"
  27. "path/filepath"
  28. "strings"
  29. "sync"
  30. "time"
  31. "github.com/unknwon/com"
  32. )
  33. const (
  34. _CONTENT_TYPE = "Content-Type"
  35. _CONTENT_LENGTH = "Content-Length"
  36. _CONTENT_BINARY = "application/octet-stream"
  37. _CONTENT_JSON = "application/json"
  38. _CONTENT_HTML = "text/html"
  39. _CONTENT_PLAIN = "text/plain"
  40. _CONTENT_XHTML = "application/xhtml+xml"
  41. _CONTENT_XML = "text/xml"
  42. _DEFAULT_CHARSET = "UTF-8"
  43. )
  44. var (
  45. // Provides a temporary buffer to execute templates into and catch errors.
  46. bufpool = sync.Pool{
  47. New: func() interface{} { return new(bytes.Buffer) },
  48. }
  49. // Included helper functions for use when rendering html
  50. helperFuncs = template.FuncMap{
  51. "yield": func() (string, error) {
  52. return "", fmt.Errorf("yield called with no layout defined")
  53. },
  54. "current": func() (string, error) {
  55. return "", nil
  56. },
  57. }
  58. )
  59. type (
  60. // TemplateFile represents a interface of template file that has name and can be read.
  61. TemplateFile interface {
  62. Name() string
  63. Data() []byte
  64. Ext() string
  65. }
  66. // TemplateFileSystem represents a interface of template file system that able to list all files.
  67. TemplateFileSystem interface {
  68. ListFiles() []TemplateFile
  69. Get(string) (io.Reader, error)
  70. }
  71. // Delims represents a set of Left and Right delimiters for HTML template rendering
  72. Delims struct {
  73. // Left delimiter, defaults to {{
  74. Left string
  75. // Right delimiter, defaults to }}
  76. Right string
  77. }
  78. // RenderOptions represents a struct for specifying configuration options for the Render middleware.
  79. RenderOptions struct {
  80. // Directory to load templates. Default is "templates".
  81. Directory string
  82. // Addtional directories to overwite templates.
  83. AppendDirectories []string
  84. // Layout template name. Will not render a layout if "". Default is to "".
  85. Layout string
  86. // Extensions to parse template files from. Defaults are [".tmpl", ".html"].
  87. Extensions []string
  88. // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
  89. Funcs []template.FuncMap
  90. // Delims sets the action delimiters to the specified strings in the Delims struct.
  91. Delims Delims
  92. // Appends the given charset to the Content-Type header. Default is "UTF-8".
  93. Charset string
  94. // Outputs human readable JSON.
  95. IndentJSON bool
  96. // Outputs human readable XML.
  97. IndentXML bool
  98. // Prefixes the JSON output with the given bytes.
  99. PrefixJSON []byte
  100. // Prefixes the XML output with the given bytes.
  101. PrefixXML []byte
  102. // Allows changing of output to XHTML instead of HTML. Default is "text/html"
  103. HTMLContentType string
  104. // TemplateFileSystem is the interface for supporting any implmentation of template file system.
  105. TemplateFileSystem
  106. }
  107. // HTMLOptions is a struct for overriding some rendering Options for specific HTML call
  108. HTMLOptions struct {
  109. // Layout template name. Overrides Options.Layout.
  110. Layout string
  111. }
  112. Render interface {
  113. http.ResponseWriter
  114. SetResponseWriter(http.ResponseWriter)
  115. JSON(int, interface{})
  116. JSONString(interface{}) (string, error)
  117. RawData(int, []byte) // Serve content as binary
  118. PlainText(int, []byte) // Serve content as plain text
  119. HTML(int, string, interface{}, ...HTMLOptions)
  120. HTMLSet(int, string, string, interface{}, ...HTMLOptions)
  121. HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
  122. HTMLString(string, interface{}, ...HTMLOptions) (string, error)
  123. HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
  124. HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
  125. XML(int, interface{})
  126. Error(int, ...string)
  127. Status(int)
  128. SetTemplatePath(string, string)
  129. HasTemplateSet(string) bool
  130. }
  131. )
  132. // TplFile implements TemplateFile interface.
  133. type TplFile struct {
  134. name string
  135. data []byte
  136. ext string
  137. }
  138. // NewTplFile cerates new template file with given name and data.
  139. func NewTplFile(name string, data []byte, ext string) *TplFile {
  140. return &TplFile{name, data, ext}
  141. }
  142. func (f *TplFile) Name() string {
  143. return f.name
  144. }
  145. func (f *TplFile) Data() []byte {
  146. return f.data
  147. }
  148. func (f *TplFile) Ext() string {
  149. return f.ext
  150. }
  151. // TplFileSystem implements TemplateFileSystem interface.
  152. type TplFileSystem struct {
  153. files []TemplateFile
  154. }
  155. // NewTemplateFileSystem creates new template file system with given options.
  156. func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
  157. fs := TplFileSystem{}
  158. fs.files = make([]TemplateFile, 0, 10)
  159. // Directories are composed in reverse order because later one overwrites previous ones,
  160. // so once found, we can directly jump out of the loop.
  161. dirs := make([]string, 0, len(opt.AppendDirectories)+1)
  162. for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {
  163. dirs = append(dirs, opt.AppendDirectories[i])
  164. }
  165. dirs = append(dirs, opt.Directory)
  166. var err error
  167. for i := range dirs {
  168. // Skip ones that does not exists for symlink test,
  169. // but allow non-symlink ones added after start.
  170. if !com.IsExist(dirs[i]) {
  171. continue
  172. }
  173. dirs[i], err = filepath.EvalSymlinks(dirs[i])
  174. if err != nil {
  175. panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())
  176. }
  177. }
  178. lastDir := dirs[len(dirs)-1]
  179. // We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
  180. if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, err error) error {
  181. r, err := filepath.Rel(lastDir, path)
  182. if err != nil {
  183. return err
  184. }
  185. ext := GetExt(r)
  186. for _, extension := range opt.Extensions {
  187. if ext != extension {
  188. continue
  189. }
  190. var data []byte
  191. if !omitData {
  192. // Loop over candidates of directory, break out once found.
  193. // The file always exists because it's inside the walk function,
  194. // and read original file is the worst case.
  195. for i := range dirs {
  196. path = filepath.Join(dirs[i], r)
  197. if !com.IsFile(path) {
  198. continue
  199. }
  200. data, err = ioutil.ReadFile(path)
  201. if err != nil {
  202. return err
  203. }
  204. break
  205. }
  206. }
  207. name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
  208. fs.files = append(fs.files, NewTplFile(name, data, ext))
  209. }
  210. return nil
  211. }); err != nil {
  212. panic("NewTemplateFileSystem: " + err.Error())
  213. }
  214. return fs
  215. }
  216. func (fs TplFileSystem) ListFiles() []TemplateFile {
  217. return fs.files
  218. }
  219. func (fs TplFileSystem) Get(name string) (io.Reader, error) {
  220. for i := range fs.files {
  221. if fs.files[i].Name()+fs.files[i].Ext() == name {
  222. return bytes.NewReader(fs.files[i].Data()), nil
  223. }
  224. }
  225. return nil, fmt.Errorf("file '%s' not found", name)
  226. }
  227. func PrepareCharset(charset string) string {
  228. if len(charset) != 0 {
  229. return "; charset=" + charset
  230. }
  231. return "; charset=" + _DEFAULT_CHARSET
  232. }
  233. func GetExt(s string) string {
  234. index := strings.Index(s, ".")
  235. if index == -1 {
  236. return ""
  237. }
  238. return s[index:]
  239. }
  240. func compile(opt RenderOptions) *template.Template {
  241. t := template.New(opt.Directory)
  242. t.Delims(opt.Delims.Left, opt.Delims.Right)
  243. // Parse an initial template in case we don't have any.
  244. template.Must(t.Parse("Macaron"))
  245. if opt.TemplateFileSystem == nil {
  246. opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
  247. }
  248. for _, f := range opt.TemplateFileSystem.ListFiles() {
  249. tmpl := t.New(f.Name())
  250. for _, funcs := range opt.Funcs {
  251. tmpl.Funcs(funcs)
  252. }
  253. // Bomb out if parse fails. We don't want any silent server starts.
  254. template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
  255. }
  256. return t
  257. }
  258. const (
  259. DEFAULT_TPL_SET_NAME = "DEFAULT"
  260. )
  261. // TemplateSet represents a template set of type *template.Template.
  262. type TemplateSet struct {
  263. lock sync.RWMutex
  264. sets map[string]*template.Template
  265. dirs map[string]string
  266. }
  267. // NewTemplateSet initializes a new empty template set.
  268. func NewTemplateSet() *TemplateSet {
  269. return &TemplateSet{
  270. sets: make(map[string]*template.Template),
  271. dirs: make(map[string]string),
  272. }
  273. }
  274. func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {
  275. t := compile(*opt)
  276. ts.lock.Lock()
  277. defer ts.lock.Unlock()
  278. ts.sets[name] = t
  279. ts.dirs[name] = opt.Directory
  280. return t
  281. }
  282. func (ts *TemplateSet) Get(name string) *template.Template {
  283. ts.lock.RLock()
  284. defer ts.lock.RUnlock()
  285. return ts.sets[name]
  286. }
  287. func (ts *TemplateSet) GetDir(name string) string {
  288. ts.lock.RLock()
  289. defer ts.lock.RUnlock()
  290. return ts.dirs[name]
  291. }
  292. func prepareRenderOptions(options []RenderOptions) RenderOptions {
  293. var opt RenderOptions
  294. if len(options) > 0 {
  295. opt = options[0]
  296. }
  297. // Defaults.
  298. if len(opt.Directory) == 0 {
  299. opt.Directory = "templates"
  300. }
  301. if len(opt.Extensions) == 0 {
  302. opt.Extensions = []string{".tmpl", ".html"}
  303. }
  304. if len(opt.HTMLContentType) == 0 {
  305. opt.HTMLContentType = _CONTENT_HTML
  306. }
  307. return opt
  308. }
  309. func ParseTplSet(tplSet string) (tplName string, tplDir string) {
  310. tplSet = strings.TrimSpace(tplSet)
  311. if len(tplSet) == 0 {
  312. panic("empty template set argument")
  313. }
  314. infos := strings.Split(tplSet, ":")
  315. if len(infos) == 1 {
  316. tplDir = infos[0]
  317. tplName = path.Base(tplDir)
  318. } else {
  319. tplName = infos[0]
  320. tplDir = infos[1]
  321. }
  322. if !com.IsDir(tplDir) {
  323. panic("template set path does not exist or is not a directory")
  324. }
  325. return tplName, tplDir
  326. }
  327. func renderHandler(opt RenderOptions, tplSets []string) Handler {
  328. cs := PrepareCharset(opt.Charset)
  329. ts := NewTemplateSet()
  330. ts.Set(DEFAULT_TPL_SET_NAME, &opt)
  331. var tmpOpt RenderOptions
  332. for _, tplSet := range tplSets {
  333. tplName, tplDir := ParseTplSet(tplSet)
  334. tmpOpt = opt
  335. tmpOpt.Directory = tplDir
  336. ts.Set(tplName, &tmpOpt)
  337. }
  338. return func(ctx *Context) {
  339. r := &TplRender{
  340. ResponseWriter: ctx.Resp,
  341. TemplateSet: ts,
  342. Opt: &opt,
  343. CompiledCharset: cs,
  344. }
  345. ctx.Data["TmplLoadTimes"] = func() string {
  346. if r.startTime.IsZero() {
  347. return ""
  348. }
  349. return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
  350. }
  351. ctx.Render = r
  352. ctx.MapTo(r, (*Render)(nil))
  353. }
  354. }
  355. // Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
  356. // An single variadic macaron.RenderOptions struct can be optionally provided to configure
  357. // HTML rendering. The default directory for templates is "templates" and the default
  358. // file extension is ".tmpl" and ".html".
  359. //
  360. // If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
  361. // MACARON_ENV environment variable to "production".
  362. func Renderer(options ...RenderOptions) Handler {
  363. return renderHandler(prepareRenderOptions(options), []string{})
  364. }
  365. func Renderers(options RenderOptions, tplSets ...string) Handler {
  366. return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
  367. }
  368. type TplRender struct {
  369. http.ResponseWriter
  370. *TemplateSet
  371. Opt *RenderOptions
  372. CompiledCharset string
  373. startTime time.Time
  374. }
  375. func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
  376. r.ResponseWriter = rw
  377. }
  378. func (r *TplRender) JSON(status int, v interface{}) {
  379. var (
  380. result []byte
  381. err error
  382. )
  383. if r.Opt.IndentJSON {
  384. result, err = json.MarshalIndent(v, "", " ")
  385. } else {
  386. result, err = json.Marshal(v)
  387. }
  388. if err != nil {
  389. http.Error(r, err.Error(), 500)
  390. return
  391. }
  392. // json rendered fine, write out the result
  393. r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)
  394. r.WriteHeader(status)
  395. if len(r.Opt.PrefixJSON) > 0 {
  396. r.Write(r.Opt.PrefixJSON)
  397. }
  398. r.Write(result)
  399. }
  400. func (r *TplRender) JSONString(v interface{}) (string, error) {
  401. var result []byte
  402. var err error
  403. if r.Opt.IndentJSON {
  404. result, err = json.MarshalIndent(v, "", " ")
  405. } else {
  406. result, err = json.Marshal(v)
  407. }
  408. if err != nil {
  409. return "", err
  410. }
  411. return string(result), nil
  412. }
  413. func (r *TplRender) XML(status int, v interface{}) {
  414. var result []byte
  415. var err error
  416. if r.Opt.IndentXML {
  417. result, err = xml.MarshalIndent(v, "", " ")
  418. } else {
  419. result, err = xml.Marshal(v)
  420. }
  421. if err != nil {
  422. http.Error(r, err.Error(), 500)
  423. return
  424. }
  425. // XML rendered fine, write out the result
  426. r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)
  427. r.WriteHeader(status)
  428. if len(r.Opt.PrefixXML) > 0 {
  429. r.Write(r.Opt.PrefixXML)
  430. }
  431. r.Write(result)
  432. }
  433. func (r *TplRender) data(status int, contentType string, v []byte) {
  434. if r.Header().Get(_CONTENT_TYPE) == "" {
  435. r.Header().Set(_CONTENT_TYPE, contentType)
  436. }
  437. r.WriteHeader(status)
  438. r.Write(v)
  439. }
  440. func (r *TplRender) RawData(status int, v []byte) {
  441. r.data(status, _CONTENT_BINARY, v)
  442. }
  443. func (r *TplRender) PlainText(status int, v []byte) {
  444. r.data(status, _CONTENT_PLAIN, v)
  445. }
  446. func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
  447. buf := bufpool.Get().(*bytes.Buffer)
  448. return buf, t.ExecuteTemplate(buf, name, data)
  449. }
  450. func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
  451. funcs := template.FuncMap{
  452. "yield": func() (template.HTML, error) {
  453. buf, err := r.execute(t, tplName, data)
  454. // return safe html here since we are rendering our own template
  455. return template.HTML(buf.String()), err
  456. },
  457. "current": func() (string, error) {
  458. return tplName, nil
  459. },
  460. }
  461. t.Funcs(funcs)
  462. }
  463. func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
  464. t := r.TemplateSet.Get(setName)
  465. if Env == DEV {
  466. opt := *r.Opt
  467. opt.Directory = r.TemplateSet.GetDir(setName)
  468. t = r.TemplateSet.Set(setName, &opt)
  469. }
  470. if t == nil {
  471. return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
  472. }
  473. opt := r.prepareHTMLOptions(htmlOpt)
  474. if len(opt.Layout) > 0 {
  475. r.addYield(t, tplName, data)
  476. tplName = opt.Layout
  477. }
  478. out, err := r.execute(t, tplName, data)
  479. if err != nil {
  480. return nil, err
  481. }
  482. return out, nil
  483. }
  484. func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
  485. r.startTime = time.Now()
  486. out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
  487. if err != nil {
  488. http.Error(r, err.Error(), http.StatusInternalServerError)
  489. return
  490. }
  491. r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)
  492. r.WriteHeader(status)
  493. if _, err := out.WriteTo(r); err != nil {
  494. out.Reset()
  495. }
  496. bufpool.Put(out)
  497. }
  498. func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
  499. r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
  500. }
  501. func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
  502. r.renderHTML(status, setName, tplName, data, htmlOpt...)
  503. }
  504. func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
  505. out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
  506. if err != nil {
  507. return []byte(""), err
  508. }
  509. return out.Bytes(), nil
  510. }
  511. func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
  512. return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
  513. }
  514. func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
  515. p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
  516. return string(p), err
  517. }
  518. func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
  519. p, err := r.HTMLBytes(name, data, htmlOpt...)
  520. return string(p), err
  521. }
  522. // Error writes the given HTTP status to the current ResponseWriter
  523. func (r *TplRender) Error(status int, message ...string) {
  524. r.WriteHeader(status)
  525. if len(message) > 0 {
  526. r.Write([]byte(message[0]))
  527. }
  528. }
  529. func (r *TplRender) Status(status int) {
  530. r.WriteHeader(status)
  531. }
  532. func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
  533. if len(htmlOpt) > 0 {
  534. return htmlOpt[0]
  535. }
  536. return HTMLOptions{
  537. Layout: r.Opt.Layout,
  538. }
  539. }
  540. func (r *TplRender) SetTemplatePath(setName, dir string) {
  541. if len(setName) == 0 {
  542. setName = DEFAULT_TPL_SET_NAME
  543. }
  544. opt := *r.Opt
  545. opt.Directory = dir
  546. r.TemplateSet.Set(setName, &opt)
  547. }
  548. func (r *TplRender) HasTemplateSet(name string) bool {
  549. return r.TemplateSet.Get(name) != nil
  550. }
  551. // DummyRender is used when user does not choose any real render to use.
  552. // This way, we can print out friendly message which asks them to register one,
  553. // instead of ugly and confusing 'nil pointer' panic.
  554. type DummyRender struct {
  555. http.ResponseWriter
  556. }
  557. func renderNotRegistered() {
  558. panic("middleware render hasn't been registered")
  559. }
  560. func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {
  561. renderNotRegistered()
  562. }
  563. func (r *DummyRender) JSON(int, interface{}) {
  564. renderNotRegistered()
  565. }
  566. func (r *DummyRender) JSONString(interface{}) (string, error) {
  567. renderNotRegistered()
  568. return "", nil
  569. }
  570. func (r *DummyRender) RawData(int, []byte) {
  571. renderNotRegistered()
  572. }
  573. func (r *DummyRender) PlainText(int, []byte) {
  574. renderNotRegistered()
  575. }
  576. func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {
  577. renderNotRegistered()
  578. }
  579. func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {
  580. renderNotRegistered()
  581. }
  582. func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {
  583. renderNotRegistered()
  584. return "", nil
  585. }
  586. func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {
  587. renderNotRegistered()
  588. return "", nil
  589. }
  590. func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {
  591. renderNotRegistered()
  592. return nil, nil
  593. }
  594. func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {
  595. renderNotRegistered()
  596. return nil, nil
  597. }
  598. func (r *DummyRender) XML(int, interface{}) {
  599. renderNotRegistered()
  600. }
  601. func (r *DummyRender) Error(int, ...string) {
  602. renderNotRegistered()
  603. }
  604. func (r *DummyRender) Status(int) {
  605. renderNotRegistered()
  606. }
  607. func (r *DummyRender) SetTemplatePath(string, string) {
  608. renderNotRegistered()
  609. }
  610. func (r *DummyRender) HasTemplateSet(string) bool {
  611. renderNotRegistered()
  612. return false
  613. }