github.com/cloudwego/hertz@v0.9.3/pkg/app/server/render/html.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * The MIT License (MIT) 17 * 18 * Copyright (c) 2014 Manuel MartÃnez-Almeida 19 * 20 * Permission is hereby granted, free of charge, to any person obtaining a copy 21 * of this software and associated documentation files (the "Software"), to deal 22 * in the Software without restriction, including without limitation the rights 23 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 * copies of the Software, and to permit persons to whom the Software is 25 * furnished to do so, subject to the following conditions: 26 * 27 * The above copyright notice and this permission notice shall be included in 28 * all copies or substantial portions of the Software. 29 * 30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 * THE SOFTWARE. 37 38 * This file may have been modified by CloudWeGo authors. All CloudWeGo 39 * Modifications are Copyright 2022 CloudWeGo Authors. 40 */ 41 42 package render 43 44 import ( 45 "html/template" 46 "log" 47 "sync" 48 "time" 49 50 "github.com/cloudwego/hertz/pkg/common/hlog" 51 "github.com/cloudwego/hertz/pkg/protocol" 52 "github.com/fsnotify/fsnotify" 53 ) 54 55 // Delims represents a set of Left and Right delimiters for HTML template rendering. 56 type Delims struct { 57 // Left delimiter, defaults to {{. 58 Left string 59 // Right delimiter, defaults to }}. 60 Right string 61 } 62 63 // HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. 64 type HTMLRender interface { 65 // Instance returns an HTML instance. 66 Instance(string, interface{}) Render 67 Close() error 68 } 69 70 // HTMLProduction contains template reference and its delims. 71 type HTMLProduction struct { 72 Template *template.Template 73 } 74 75 // HTML contains template reference and its name with given interface object. 76 type HTML struct { 77 Template *template.Template 78 Name string 79 Data interface{} 80 } 81 82 var htmlContentType = "text/html; charset=utf-8" 83 84 // Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. 85 func (r HTMLProduction) Instance(name string, data interface{}) Render { 86 return HTML{ 87 Template: r.Template, 88 Name: name, 89 Data: data, 90 } 91 } 92 93 func (r HTMLProduction) Close() error { 94 return nil 95 } 96 97 // Render (HTML) executes template and writes its result with custom ContentType for response. 98 func (r HTML) Render(resp *protocol.Response) error { 99 r.WriteContentType(resp) 100 101 if r.Name == "" { 102 return r.Template.Execute(resp.BodyWriter(), r.Data) 103 } 104 return r.Template.ExecuteTemplate(resp.BodyWriter(), r.Name, r.Data) 105 } 106 107 // WriteContentType (HTML) writes HTML ContentType. 108 func (r HTML) WriteContentType(resp *protocol.Response) { 109 writeContentType(resp, htmlContentType) 110 } 111 112 type HTMLDebug struct { 113 sync.Once 114 Template *template.Template 115 RefreshInterval time.Duration 116 117 Files []string 118 FuncMap template.FuncMap 119 Delims Delims 120 121 reloadCh chan struct{} 122 watcher *fsnotify.Watcher 123 } 124 125 func (h *HTMLDebug) Instance(name string, data interface{}) Render { 126 h.Do(func() { 127 h.startChecker() 128 }) 129 130 select { 131 case <-h.reloadCh: 132 h.reload() 133 default: 134 } 135 136 return HTML{ 137 Template: h.Template, 138 Name: name, 139 Data: data, 140 } 141 } 142 143 func (h *HTMLDebug) Close() error { 144 if h.watcher == nil { 145 return nil 146 } 147 return h.watcher.Close() 148 } 149 150 func (h *HTMLDebug) reload() { 151 h.Template = template.Must(template.New(""). 152 Delims(h.Delims.Left, h.Delims.Right). 153 Funcs(h.FuncMap). 154 ParseFiles(h.Files...)) 155 } 156 157 func (h *HTMLDebug) startChecker() { 158 h.reloadCh = make(chan struct{}) 159 160 if h.RefreshInterval > 0 { 161 go func() { 162 hlog.SystemLogger().Debugf("[HTMLDebug] HTML template reloader started with interval %v", h.RefreshInterval) 163 for range time.Tick(h.RefreshInterval) { 164 hlog.SystemLogger().Debugf("[HTMLDebug] triggering HTML template reloader") 165 h.reloadCh <- struct{}{} 166 hlog.SystemLogger().Debugf("[HTMLDebug] HTML template has been reloaded, next reload in %v", h.RefreshInterval) 167 } 168 }() 169 return 170 } 171 172 watcher, err := fsnotify.NewWatcher() 173 if err != nil { 174 log.Fatal(err) 175 } 176 h.watcher = watcher 177 for _, f := range h.Files { 178 err := watcher.Add(f) 179 hlog.SystemLogger().Debugf("[HTMLDebug] watching file: %s", f) 180 if err != nil { 181 hlog.SystemLogger().Errorf("[HTMLDebug] add watching file: %s, error happened: %v", f, err) 182 } 183 184 } 185 186 go func() { 187 hlog.SystemLogger().Debugf("[HTMLDebug] HTML template reloader started with file watcher") 188 for { 189 select { 190 case event, ok := <-watcher.Events: 191 if !ok { 192 return 193 } 194 if event.Op&fsnotify.Write == fsnotify.Write { 195 hlog.SystemLogger().Debugf("[HTMLDebug] modified file: %s, html render template will be reloaded at the next rendering", event.Name) 196 h.reloadCh <- struct{}{} 197 hlog.SystemLogger().Debugf("[HTMLDebug] HTML template has been reloaded") 198 } 199 case err, ok := <-watcher.Errors: 200 if !ok { 201 return 202 } 203 hlog.SystemLogger().Errorf("error happened when watching the rendering files: %v", err) 204 } 205 } 206 }() 207 }