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  }