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 }