123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- package renderer
- import (
- "bufio"
- "encoding/json"
- "fmt"
- "html/template"
- "io"
- "mime"
- "net/http"
- "net/url"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "strings"
- "time"
- "github.com/gocarina/gocsv"
- "github.com/gorilla/schema"
- )
- type Renderer interface {
- Render(http.ResponseWriter, *http.Request, interface{}, ...url.Values) error
- }
- type JSONRenderer struct{}
- type CSVRenderer struct{}
- type PDFRenderer struct{}
- type htmlTemplateData struct {
- Data interface{}
- Options url.Values
- }
- type JsonResponse struct {
- Result []byte
- Error []byte
- }
- var (
- currRenderer Renderer
- Render map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values)
- contentTypeToFormat = map[string]string{
- "application/x-www-form-urlencoded": "html",
- "application/json": "json",
- "text/csv; charset=utf-8": "csv",
- "application/pdf": "pdf",
- }
- )
- func init() {
- htmlRenderer, err := NewHTMLRenderer("templates/")
- if err != nil {
- panic(err)
- }
- jsonRenderer, err := NewJSONRenderer()
- if err != nil {
- panic(err)
- }
- csvRenderer, err := NewCSVRenderer()
- if err != nil {
- panic(err)
- }
- pdfRenderer, err := NewPDFRenderer()
- if err != nil {
- panic(err)
- }
- Render = make(map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values))
- Render["html"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- htmlRenderer.Render(w, r, data, options...)
- }
- Render["json"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- jsonRenderer.Render(w, r, data, options...)
- }
- Render["csv"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- csvRenderer.Render(w, r, data, options...)
- }
- Render["pdf"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- pdfRenderer.Render(w, r, data, options...)
- }
- }
- func Query(values ...string) template.URL {
- return query(values...)
- }
- // FIXME: Not safe. We should check if it responds to the error interface instead.
- func isErrorType(data interface{}) bool {
- return strings.Contains(strings.ToLower(reflect.TypeOf(data).String()), "error")
- }
- func NewJSONRenderer() (*JSONRenderer, error) {
- return &JSONRenderer{}, nil
- }
- func (rend *JSONRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- if isErrorType(data) {
- j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
- if err != nil {
- return err
- }
- w.WriteHeader(http.StatusInternalServerError)
- w.Write(j)
- } else {
- result, err := json.Marshal(data)
- if err != nil {
- return err
- }
- response, err := json.Marshal(JsonResponse{result, nil})
- if err != nil {
- return err
- }
- w.Write(response)
- }
- return nil
- }
- func NewCSVRenderer() (*CSVRenderer, error) {
- return &CSVRenderer{}, nil
- }
- func (rend *CSVRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
- w.Header().Set("Content-Type", "text/csv; charset=utf-8")
- if isErrorType(data) {
- j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
- if err != nil {
- return err
- }
- w.WriteHeader(http.StatusInternalServerError)
- w.Write(j)
- } else {
- result, err := gocsv.MarshalString(data)
- if err != nil {
- return err
- }
- response, err := json.Marshal(JsonResponse{[]byte(result), nil})
- if err != nil {
- return err
- }
- w.Write(response)
- }
- return nil
- }
- func NewPDFRenderer() (*PDFRenderer, error) {
- return &PDFRenderer{}, nil
- }
- func (rend *PDFRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
- fileInfo := data.(map[string]string)
- filename := fileInfo["filename"]
- w.Header().Set("Content-Type", "application/pdf")
- w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(filename)))
- f, err := os.Open(filename)
- if err != nil {
- panic(err)
- }
- reader := bufio.NewReader(f)
- io.Copy(w, reader)
- return nil
- }
- type HTMLRenderer struct {
- TemplatePath string
- templates map[string]*template.Template
- }
- func NewHTMLRenderer(templatePath string) (*HTMLRenderer, error) {
- r := &HTMLRenderer{
- TemplatePath: templatePath,
- templates: make(map[string]*template.Template),
- }
- fns, err := filepath.Glob(filepath.Join(templatePath, "*.tpl"))
- if err != nil {
- panic(err)
- }
- lfns, err := filepath.Glob(filepath.Join(templatePath, "layout", "*.tpl"))
- if err != nil {
- panic(err)
- }
- for _, fn := range fns {
- tplName := filepath.Base(fn)
- tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
- tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
- files := append(lfns, fn)
- if err != nil {
- return nil, err
- }
- r.templates[tplName] = template.New(tplName)
- r.templates[tplName] = template.Must(r.templates[tplName].Funcs(funcMap).ParseFiles(files...))
- }
- return r, nil
- }
- func Use(r Renderer) {
- currRenderer = r
- }
- func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, err error) {
- t, ok := rend.templates["error"]
- if !ok {
- panic(fmt.Errorf("Error template not found! Can't proceed, sorry."))
- }
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- e := t.ExecuteTemplate(w, "base", err)
- if e != nil {
- panic(e)
- }
- }
- func (rend *HTMLRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- if data != nil && isErrorType(data) {
- rend.writeError(w, r, data.(error))
- } else {
- t, ok := rend.templates[options[0]["tpl_content"][0]]
- if !ok {
- rend.writeError(w, r, fmt.Errorf("Template %s not found", options[0]["tpl_content"][0]))
- }
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- err := t.ExecuteTemplate(w, options[0]["tpl_layout"][0], &htmlTemplateData{data, options[0]})
- if err != nil {
- rend.writeError(w, r, err)
- }
- }
- }
- func GetContentFormat(r *http.Request) string {
- var (
- t string
- err error
- )
- contentType := r.Header.Get("Content-type")
- if contentType == "" {
- return "application/octet-stream"
- }
- t, _, err = mime.ParseMediaType(contentType)
- if err != nil {
- return ""
- }
- return contentTypeToFormat[t]
- }
- func Decode(dst interface{}, r *http.Request) error {
- switch GetContentFormat(r) {
- case "html":
- var timeConverter = func(value string) reflect.Value {
- if value == "" {
- return reflect.ValueOf(time.Time{})
- }
- if v, err := time.Parse("2006-01-02", value); err == nil {
- return reflect.ValueOf(v)
- }
- return reflect.Value{}
- }
- if err := r.ParseForm(); err != nil {
- return err
- }
- decoder := schema.NewDecoder()
- decoder.RegisterConverter(time.Time{}, timeConverter)
- if err := decoder.Decode(dst, r.PostForm); err != nil {
- return err
- }
- case "json":
- decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&dst)
- if err != nil {
- return err
- }
- }
- return nil
- }
|