renderer.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package renderer
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "html/template"
  6. "mime"
  7. "net/http"
  8. "net/url"
  9. "path"
  10. "path/filepath"
  11. "reflect"
  12. "strings"
  13. "github.com/gocarina/gocsv"
  14. "github.com/gorilla/schema"
  15. )
  16. type Renderer interface {
  17. Render(http.ResponseWriter, *http.Request, interface{}, ...url.Values) error
  18. }
  19. type JSONRenderer struct{}
  20. type CSVRenderer struct{}
  21. type htmlTemplateData struct {
  22. Data interface{}
  23. Options url.Values
  24. }
  25. type JsonResponse struct {
  26. Result []byte
  27. Error []byte
  28. }
  29. var (
  30. currRenderer Renderer
  31. Render map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values)
  32. funcMap = template.FuncMap{
  33. "query": query,
  34. }
  35. contentTypeToFormat = map[string]string{
  36. "application/x-www-form-urlencoded": "html",
  37. "application/json": "json",
  38. "text/csv; charset=utf-8": "csv",
  39. }
  40. )
  41. func init() {
  42. htmlRenderer, err := NewHTMLRenderer("templates/")
  43. if err != nil {
  44. panic(err)
  45. }
  46. jsonRenderer, err := NewJSONRenderer()
  47. if err != nil {
  48. panic(err)
  49. }
  50. csvRenderer, err := NewCSVRenderer()
  51. if err != nil {
  52. panic(err)
  53. }
  54. Render = make(map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values))
  55. Render["html"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
  56. htmlRenderer.Render(w, r, data, options...)
  57. }
  58. Render["json"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
  59. jsonRenderer.Render(w, r, data, options...)
  60. }
  61. Render["csv"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
  62. csvRenderer.Render(w, r, data, options...)
  63. }
  64. }
  65. func Query(values ...string) template.URL {
  66. return query(values...)
  67. }
  68. func query(values ...string) template.URL {
  69. var urlValues url.Values
  70. urlValues = make(url.Values)
  71. urlValues.Set("format", "html")
  72. for i := 0; i < len(values); i += 2 {
  73. urlValues.Add(values[i], values[i+1])
  74. }
  75. return template.URL(urlValues.Encode())
  76. }
  77. // FIXME: Not safe. We should check if it responds to the error interface instead.
  78. func isErrorType(data interface{}) bool {
  79. return strings.Contains(strings.ToLower(reflect.TypeOf(data).String()), "error")
  80. }
  81. func NewJSONRenderer() (*JSONRenderer, error) {
  82. return &JSONRenderer{}, nil
  83. }
  84. func (rend *JSONRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
  85. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  86. if isErrorType(data) {
  87. j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
  88. if err != nil {
  89. return err
  90. }
  91. w.WriteHeader(http.StatusInternalServerError)
  92. w.Write(j)
  93. } else {
  94. result, err := json.Marshal(data)
  95. if err != nil {
  96. return err
  97. }
  98. response, err := json.Marshal(JsonResponse{result, nil})
  99. if err != nil {
  100. return err
  101. }
  102. w.Write(response)
  103. }
  104. return nil
  105. }
  106. func NewCSVRenderer() (*CSVRenderer, error) {
  107. return &CSVRenderer{}, nil
  108. }
  109. func (rend *CSVRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
  110. w.Header().Set("Content-Type", "text/csv; charset=utf-8")
  111. if isErrorType(data) {
  112. j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
  113. if err != nil {
  114. return err
  115. }
  116. w.WriteHeader(http.StatusInternalServerError)
  117. w.Write(j)
  118. } else {
  119. result, err := gocsv.MarshalString(data)
  120. if err != nil {
  121. return err
  122. }
  123. response, err := json.Marshal(JsonResponse{[]byte(result), nil})
  124. if err != nil {
  125. return err
  126. }
  127. w.Write(response)
  128. }
  129. return nil
  130. }
  131. type HTMLRenderer struct {
  132. TemplatePath string
  133. templates map[string]*template.Template
  134. }
  135. func NewHTMLRenderer(templatePath string) (*HTMLRenderer, error) {
  136. r := &HTMLRenderer{
  137. TemplatePath: templatePath,
  138. templates: make(map[string]*template.Template),
  139. }
  140. fns, err := filepath.Glob(filepath.Join(templatePath, "*.tpl"))
  141. if err != nil {
  142. panic(err)
  143. }
  144. lfns, err := filepath.Glob(filepath.Join(templatePath, "layout", "*.tpl"))
  145. if err != nil {
  146. panic(err)
  147. }
  148. for _, fn := range fns {
  149. tplName := filepath.Base(fn)
  150. tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
  151. tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
  152. files := append(lfns, fn)
  153. if err != nil {
  154. return nil, err
  155. }
  156. r.templates[tplName] = template.New(tplName)
  157. r.templates[tplName] = template.Must(r.templates[tplName].Funcs(funcMap).ParseFiles(files...))
  158. }
  159. return r, nil
  160. }
  161. func Use(r Renderer) {
  162. currRenderer = r
  163. }
  164. func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, err error) {
  165. t, ok := rend.templates["error"]
  166. if !ok {
  167. panic(fmt.Errorf("Error template not found! Can't proceed, sorry."))
  168. }
  169. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  170. e := t.ExecuteTemplate(w, "base", err)
  171. if e != nil {
  172. panic(e)
  173. }
  174. }
  175. func (rend *HTMLRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
  176. if data != nil && isErrorType(data) {
  177. rend.writeError(w, r, data.(error))
  178. } else {
  179. t, ok := rend.templates[options[0]["tpl_content"][0]]
  180. if !ok {
  181. rend.writeError(w, r, fmt.Errorf("Template %s not found", options[0]["tpl_content"][0]))
  182. }
  183. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  184. err := t.ExecuteTemplate(w, options[0]["tpl_layout"][0], &htmlTemplateData{data, options[0]})
  185. if err != nil {
  186. rend.writeError(w, r, err)
  187. }
  188. }
  189. }
  190. func GetContentFormat(r *http.Request) string {
  191. var (
  192. t string
  193. err error
  194. )
  195. contentType := r.Header.Get("Content-type")
  196. if contentType == "" {
  197. return "application/octet-stream"
  198. }
  199. t, _, err = mime.ParseMediaType(contentType)
  200. if err != nil {
  201. return ""
  202. }
  203. return contentTypeToFormat[t]
  204. }
  205. func Decode(dst interface{}, r *http.Request) error {
  206. switch GetContentFormat(r) {
  207. case "html":
  208. if err := r.ParseForm(); err != nil {
  209. return err
  210. }
  211. decoder := schema.NewDecoder()
  212. if err := decoder.Decode(dst, r.PostForm); err != nil {
  213. return err
  214. }
  215. case "json":
  216. decoder := json.NewDecoder(r.Body)
  217. err := decoder.Decode(&dst)
  218. if err != nil {
  219. return err
  220. }
  221. }
  222. return nil
  223. }