github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/znet/render.go (about)

     1  package znet
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"html/template"
     8  	"io/ioutil"
     9  	"mime"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/sohaha/zlsgo/zfile"
    16  	"github.com/sohaha/zlsgo/zstring"
    17  	"github.com/sohaha/zlsgo/zutil"
    18  )
    19  
    20  type (
    21  	render interface {
    22  		Content(c *Context) (content []byte)
    23  	}
    24  	renderByte struct {
    25  		Data        []byte
    26  		Type        string
    27  		ContentDate []byte
    28  	}
    29  	renderString struct {
    30  		Format      string
    31  		Data        []interface{}
    32  		ContentDate []byte
    33  	}
    34  	renderJSON struct {
    35  		Data        interface{}
    36  		ContentDate []byte
    37  	}
    38  	renderFile struct {
    39  		Data        string
    40  		ContentDate []byte
    41  		FileExist   bool
    42  	}
    43  	renderHTML struct {
    44  		Template    *template.Template
    45  		Data        interface{}
    46  		ContentDate []byte
    47  		FuncMap     template.FuncMap
    48  		Templates   []string
    49  	}
    50  	// ApiData unified return api format
    51  	ApiData struct {
    52  		Data interface{} `json:"data"`
    53  		Msg  string      `json:"msg,omitempty"`
    54  		Code int32       `json:"code" example:"200"`
    55  	}
    56  	// Data map string
    57  	Data     map[string]interface{}
    58  	PrevData struct {
    59  		Code    *zutil.Int32
    60  		Type    string
    61  		Content []byte
    62  	}
    63  )
    64  
    65  var (
    66  	// ContentTypePlain text
    67  	ContentTypePlain = "text/plain; charset=utf-8"
    68  	// ContentTypeHTML html
    69  	ContentTypeHTML = "text/html; charset=utf-8"
    70  	// ContentTypeJSON json
    71  	ContentTypeJSON = "application/json; charset=utf-8"
    72  )
    73  
    74  func (c *Context) renderProcessing(code int32, r render) {
    75  	// if c.stopHandle.Load() && c.prevData.Code.Load() != 0 {
    76  	// 	return
    77  	// }
    78  	if code != 0 {
    79  		c.prevData.Code.Store(code)
    80  	}
    81  	c.mu.Lock()
    82  	c.render = r
    83  	c.mu.Unlock()
    84  }
    85  
    86  func (r *renderByte) Content(c *Context) []byte {
    87  	if !c.hasContentType() {
    88  		c.SetContentType(ContentTypePlain)
    89  	}
    90  	return r.Data
    91  }
    92  
    93  func (r *renderString) Content(c *Context) []byte {
    94  	if r.ContentDate != nil {
    95  		return r.ContentDate
    96  	}
    97  	if !c.hasContentType() {
    98  		c.SetContentType(ContentTypePlain)
    99  	}
   100  	if len(r.Data) > 0 {
   101  		r.ContentDate = zstring.String2Bytes(fmt.Sprintf(r.Format, r.Data...))
   102  	} else {
   103  		r.ContentDate = zstring.String2Bytes(r.Format)
   104  	}
   105  	return r.ContentDate
   106  }
   107  
   108  func (r *renderJSON) Content(c *Context) []byte {
   109  	if r.ContentDate != nil {
   110  		return r.ContentDate
   111  	}
   112  	c.SetContentType(ContentTypeJSON)
   113  	r.ContentDate, _ = json.Marshal(r.Data)
   114  	return r.ContentDate
   115  }
   116  
   117  func (r *renderFile) Content(c *Context) []byte {
   118  	if !r.FileExist {
   119  		return []byte{}
   120  	}
   121  
   122  	if r.ContentDate != nil {
   123  		return r.ContentDate
   124  	}
   125  	fType := mime.TypeByExtension(filepath.Ext(r.Data))
   126  	c.SetContentType(fType)
   127  	r.ContentDate, _ = ioutil.ReadFile(r.Data)
   128  	return r.ContentDate
   129  }
   130  
   131  func (r *renderHTML) Content(c *Context) []byte {
   132  	if r.ContentDate != nil {
   133  		return r.ContentDate
   134  	}
   135  	c.SetContentType(ContentTypeHTML)
   136  	if len(r.Templates) > 0 {
   137  		var (
   138  			buf bytes.Buffer
   139  			err error
   140  			t   *template.Template
   141  		)
   142  		if c.Engine.views != nil {
   143  			err = c.Engine.views.Render(&buf, r.Templates[0], r.Data)
   144  		} else {
   145  			tpl := c.Engine.template
   146  			if tpl != nil {
   147  				t = tpl.Get(c.Engine.IsDebug())
   148  				if t != nil && len(r.FuncMap) == 0 {
   149  					name := r.Templates[0]
   150  					err = t.ExecuteTemplate(&buf, name, r.Data)
   151  					if err == nil {
   152  						r.ContentDate = buf.Bytes()
   153  						return r.ContentDate
   154  					}
   155  					if !strings.Contains(err.Error(), " is undefined") {
   156  						Log.Error(err)
   157  						return r.ContentDate
   158  					}
   159  				}
   160  			}
   161  			if t, err = templateParse(r.Templates, r.FuncMap); err == nil {
   162  				err = t.Execute(&buf, r.Data)
   163  			}
   164  		}
   165  
   166  		if err != nil {
   167  			Log.Error(err)
   168  		}
   169  		r.ContentDate = buf.Bytes()
   170  	} else {
   171  		r.ContentDate = zstring.String2Bytes(fmt.Sprint(r.Data))
   172  	}
   173  	return r.ContentDate
   174  }
   175  
   176  func (c *Context) Byte(code int32, value []byte) {
   177  	c.renderProcessing(code, &renderByte{Data: value})
   178  }
   179  
   180  func (c *Context) String(code int32, format string, values ...interface{}) {
   181  	c.renderProcessing(code, &renderString{Format: format, Data: values})
   182  }
   183  
   184  // Deprecated: You can directly modify the return value of PrevContent()
   185  func (c *Context) SetContent(data *PrevData) {
   186  	c.mu.Lock()
   187  	c.prevData = data
   188  	c.mu.Unlock()
   189  }
   190  
   191  func (c *Context) File(path string) {
   192  	path = zfile.RealPath(path)
   193  	f, err := os.Stat(path)
   194  	fileExist := err == nil
   195  	var code int32
   196  	if fileExist {
   197  		code = http.StatusOK
   198  	} else {
   199  		code = http.StatusNotFound
   200  	}
   201  	if fileExist {
   202  		c.SetHeader("Last-Modified", f.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
   203  	}
   204  	c.renderProcessing(code, &renderFile{Data: path, FileExist: fileExist})
   205  }
   206  
   207  func (c *Context) JSON(code int32, values interface{}) {
   208  	c.renderProcessing(code, &renderJSON{Data: values})
   209  }
   210  
   211  // ApiJSON ApiJSON
   212  func (c *Context) ApiJSON(code int32, msg string, data interface{}) {
   213  	c.renderProcessing(http.StatusOK, &renderJSON{Data: ApiData{Code: code, Data: data,
   214  		Msg: msg}})
   215  }
   216  
   217  // HTML export html
   218  func (c *Context) HTML(code int32, html string) {
   219  	c.renderProcessing(code, &renderHTML{
   220  		Data: html,
   221  	})
   222  }
   223  
   224  // Template export tpl
   225  func (c *Context) Template(code int32, name string, data interface{}, funcMap ...map[string]interface{}) {
   226  	var fn template.FuncMap
   227  	if len(funcMap) > 0 {
   228  		fn = funcMap[0]
   229  	}
   230  	c.renderProcessing(code, &renderHTML{
   231  		Templates: []string{name},
   232  		Data:      data,
   233  		FuncMap:   fn,
   234  	})
   235  }
   236  func (c *Context) Templates(code int32, templates []string, data interface{}, funcMap ...map[string]interface{}) {
   237  	var fn template.FuncMap
   238  	if len(funcMap) > 0 {
   239  		fn = funcMap[0]
   240  	}
   241  	c.renderProcessing(code, &renderHTML{
   242  		Templates: templates,
   243  		Data:      data,
   244  		FuncMap:   fn,
   245  	})
   246  }
   247  
   248  // Abort stop executing subsequent handlers
   249  func (c *Context) Abort(code ...int32) {
   250  	if c.stopHandle.Load() {
   251  		return
   252  	}
   253  	c.stopHandle.Store(true)
   254  	if len(code) > 0 {
   255  		c.prevData.Code.Store(code[0])
   256  	}
   257  }
   258  
   259  func (c *Context) IsAbort() bool {
   260  	return c.stopHandle.Load()
   261  }
   262  
   263  // Redirect Redirect
   264  func (c *Context) Redirect(link string, statusCode ...int32) {
   265  	c.Writer.Header().Set("Location", c.CompletionLink(link))
   266  	var code int32
   267  	if len(statusCode) > 0 {
   268  		code = statusCode[0]
   269  	} else {
   270  		code = http.StatusFound
   271  	}
   272  	c.SetStatus(code)
   273  }
   274  
   275  func (c *Context) SetStatus(code int32) *Context {
   276  	c.prevData.Code.Store(code)
   277  	return c
   278  }
   279  
   280  func (c *Context) SetContentType(contentType string) *Context {
   281  	c.SetHeader("Content-Type", contentType)
   282  	return c
   283  }
   284  
   285  func (c *Context) hasContentType() bool {
   286  	c.mu.RLock()
   287  	defer c.mu.RUnlock()
   288  	if _, ok := c.header["Content-Type"]; ok {
   289  		return true
   290  	}
   291  	return false
   292  }
   293  
   294  // PrevContent current output content
   295  func (c *Context) PrevContent() *PrevData {
   296  	if c.render == nil {
   297  		return c.prevData
   298  	}
   299  	c.prevData.Content = c.render.Content(c)
   300  	ctype, hasType := c.header["Content-Type"]
   301  	if hasType {
   302  		c.prevData.Type = ctype[0]
   303  	}
   304  	c.mu.Lock()
   305  	c.render = nil
   306  	c.mu.Unlock()
   307  	return c.prevData
   308  }
   309  
   310  func (t *tpl) Get(debug bool) *template.Template {
   311  	if !debug || t.pattern == "" {
   312  		return t.tpl
   313  	}
   314  	tpl, _ := template.New("").Funcs(t.templateFuncMap).ParseGlob(t.pattern)
   315  	return tpl
   316  }