GO自研微服务框架-页面渲染

页面渲染

在实际开发中,接口返回需要支持返回HTML,JSON,XML等,在HTML返回中,要支持模板

1. HTML

渲染HTML,需要明确几个元素

  1. content-type = text/html; charset=utf-8
  2. 模板Template
  3. 渲染数据

渲染页面的操作是用户来完成,所以需要在Context中提供对应的方法

package msgo

import (
	"log"
	"net/http"
)

type Context struct {
   
	W http.ResponseWriter
	R *http.Request
}
func (c *Context) HTML(status int, html string) error {
   
	//状态是200
	c.W.Header().Set("Content-Type", "text/html; charset=utf-8")
	c.W.WriteHeader(http.StatusOK)
	_, err := c.W.Write([]byte(html))
	return err
}
g.Get("/html", func(ctx *msgo.Context) {
   
    ctx.HTML(http.StatusOK, "<h1>GO自研微服务框架</h1>")
})

1.1 加入模板支持

func (c *Context) HTMLTemplate(name string, funcMap template.FuncMap, data any, fileName ...string) {
   
	t := template.New(name)
	t.Funcs(funcMap)
	t, err := t.ParseFiles(fileName...)
	if err != nil {
   
		log.Println(err)
		return
	}
	c.W.Header().Set("Content-Type", "text/html; charset=utf-8")
	err = t.Execute(c.W, data)
	if err != nil {
   
		log.Println(err)
	}
}

func (c *Context) HTMLTemplateGlob(name string, funcMap template.FuncMap, pattern string, data any) {
   
	t := template.New(name)
	t.Funcs(funcMap)
	t, err := t.ParseGlob(pattern)
	if err != nil {
   
		log.Println(err)
		return
	}
	c.W.Header().Set("Content-Type", "text/html; charset=utf-8")
	err = t.Execute(c.W, data)
	if err != nil {
   
		log.Println(err)
	}
}
g.Get("/htmltemplate", func(ctx *msgo.Context) {
   
		user := &User{
   
			Name: "lisus",
		}
		err := ctx.HTMLTemplate("login.html", user, "tpl/login.html", "tpl/header.html")
		if err != nil {
   
			log.Println(err)
		}
	})
	g.Get("/htmltemplateGlob", func(ctx *msgo.Context) {
   
		user := &User{
   
			Name: "lisus",
		}
		err := ctx.HTMLTemplateGlob("login.html", user, "tpl/*.html")
		if err != nil {
   
			log.Println(err)
		}
	})
{
   {
    define "header" }}
<h1>这是头部页</h1>
{
   {
    end }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>这是首页</h1>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {
   {
    template "header" .}}
    <h1>这是登录页</h1>
    <h2>用户名:{
   {
    .Name }}</h2>
</body>
</html>

1.2 改造-提前将模板加载到内存

如果使用到模板,并不需要在访问的时候再加载,可以在启动的时候,就将所有的模板加载到内存中,这样加快访问速度

type Engine struct {
   
	*router
	funcMap    template.FuncMap
	HTMLRender render.HTMLRender
}
func (e *Engine) SetFuncMap(funcMap template.FuncMap) {
   
	e.funcMap = funcMap
}

// LoadTemplateGlob 加载所有模板
func (e *Engine) LoadTemplateGlob(pattern string) {
   
	t := template.Must(template.New("").Funcs(e.funcMap).ParseGlob(pattern))
	e.SetHtmlTemplate(t)
}

func (e *Engine) SetHtmlTemplate(t *template.Template) {
   
	e.HTMLRender = render.HTMLRender{
   Template: t}
}
type HTMLRender struct {
   
	Template *template.Template
}
func (c *Context) Template(name string, data any) error {
   
	c.W.Header().Set("Content-Type", "text/html; charset=utf-8")
	err := c.engine.HTMLRender.Template.ExecuteTemplate(c.W, name, data)
	return err
}

engine.LoadTemplate("tpl/*.html")
g.Get("/template", func(ctx *msgo.Context) {
   
    user := &User{
   
        Name: "lisus",
    }
    err := ctx.Template("login.html", user)
    if err != nil {
   
        log.Println(err)
    }
})

2. JSON

除了返回模板页面,在多数情况下,返回JSON的应用场景也非常普遍。

有了上面的经验,在处理返回json的时候,会变得比较容易。

json的content-type=application/json; charset=utf-8

func (c *Context) JSON(status int, data any) error {
   
	c.W.Header().Set("Content-Type", "application/json; charset=utf-8")
	c.W.WriteHeader(status)
	rsp, err := json.Marshal(data)
	if err != nil {
   
		return err
	}
	_, err = c.W.Write(rsp)
	if err != nil {
   
		return err
	}
	return nil
}
g.Get("/json", func(ctx *msgo.Context) {
   
    user := &User{
   
        Name: "lisus",
    }
    err := ctx.JSON(200, user)
    if err != nil {
   
        log.Println(err)
    }
})

3. XML

content-type=application/xml;charset=utf-8

func (c *Context) XML(status int, data any) error {
   
	c.W.Header().Set("Content-Type", "application/xml; charset=utf-8")
	c.W.WriteHeader(status)
	err := xml.NewEncoder(c.W).Encode(data)
	return err
}
g.Get("/xml", func(ctx *msgo.Context) {
   
    user := &User{
   
        Name: "lisus",
        Age:  20,
    }
    err := ctx.XML(200, user)
    if err != nil {
   
        log.Println(err)
    }
})

4. 文件

下载文件的需求,需要返回excel文件,word文件等等的

g.Get("/excel", func(ctx *msgo.Context) {
   
		ctx.File("tpl/test.xlsx")
	})
func (c *Context) File(filePath string) {
   
	http.ServeFile(c.W, c.R, filePath)
}

指定文件名字:

func isASCII(s string) bool {
   
	for i := 0; i < len(s); i++ {
   
		if s[i] > unicode.MaxASCII {
   
			return false
		}
	}
	return true
}
func (c *Context) FileAttachment(filepath, filename string) {
   
	if isASCII(filename) {
   
		c.W.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
	} else {
   
		c.W.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
	}
	http.ServeFile(c.W, c.R, filepath)
}

从文件系统获取:

g.Get("/fs", func(ctx *msgo.Context) {
   
    ctx.FileFromFS("test.xlsx", http.Dir("tpl"))

})
func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
   
	defer func(old string) {
   
		c.R.URL.Path = old
	}(c.R.URL.Path)

	c.R.URL.Path = filepath

	http.FileServer(fs).ServeHTTP(c.W, c.R)
}

5. 重定向页面

在一些前后端分离开发中,我们需要进行页面的跳转,并不是去加载模板

func (c *Context) Redirect(status int, location string) {
   
	if (status < http.StatusMultipleChoices || status > http.StatusPermanentRedirect) && status != http.StatusCreated {
   
		panic(fmt.Sprintf("Cannot redirect with status code %d", status))
	}
	http.Redirect(c.W, c.R, location, status)
}
g.Get("/redirect", func(ctx *msgo.Context) {
   
    ctx.Redirect(http.StatusFound, "/user/template")
})

6. String

func StringToBytes(s string) []byte {
   
	return *(*[]byte)(unsafe.Pointer(
		&struct {
   
			string
			Cap int
		}{
   s, len(s)},
	))
}
func (c *Context) String(status int, format string, values ...any) (err error) {
   
	plainContentType := "text/plain; charset=utf-8"
    c.W.Header().Set("Content-Type", plainContentType)
	c.W.WriteHeader(status)
	if len(values) > 0 {
   
		_, err = fmt.Fprintf(c.W, format, values...)
		return
	}
	_, err = c.W.Write(StringToBytes(format))
	return
}
g.Get("/string", func(ctx *msgo.Context) {
   
		ctx.String(http.StatusOK, "%s 是由 %s 制作 \n", "goweb框架", "go微服务框架")

	})

7. 接口提取

实际上,我们需要支持的格式是很多的,将其抽象提取成接口,便于后续拓展

package render

import "net/http"

type Render interface {
   
	Render(w http.ResponseWriter) error
	WriteContentType(w http.ResponseWriter)
}

internal 目录下的包,不允许被其他项目中进行导入,这是在 Go 1.4 当中引入的 feature,会在编译时执行

package render

import (
	"fmt"
	"github.com/mszlu521/msgo/internal/bytesconv"
	"net/http"
)

type String struct {
   
	Format string
	Data   []any
}

var plainContentType = []string{
   "text/plain; charset=utf-8"}

func (r String) WriteContentType(w http.ResponseWriter) {
   
	writeContentType(w, plainContentType)
}

func (r String) Render(w http.ResponseWriter) error {
   
	return WriteString(w, r.Format, r.Data)
}

func WriteString(w http.ResponseWriter, format string, data []any) (err error) {
   
	writeContentType(w, plainContentType)
	if len(data) > 0 {
   
		_, err = fmt.Fprintf(w, format, data...)
		return
	}
	_, err = w.Write(bytesconv.StringToBytes(format))
	return
}

func (c *Context) String(status int, format string, values ...any) (err error) {
   
	err = c.Render(status, render.String{
   
		Format: format,
		Data:   values,
	})
	return
}

func (c *Context) Render(code int, r render.Render) error {
   
	err := r.Render(c.W)
	c.W.WriteHeader(code)
	return err
}

7.1 其他渲染方式重构

7.1.1 XML
package render

import (
	"encoding/xml"
	"net/http"
)

type XML struct {
   
	Data any
}

var xmlContentType = []string{
   "application/xml; charset=utf-8"}

func (r XML) Render(w http.ResponseWriter) error {
   
	r.WriteContentType(w)
	return xml.NewEncoder(w).Encode(r.Data)
}

func (r XML) WriteContentType(w http.ResponseWriter) {
   
	writeContentType(w, xmlContentType)
}

func (c *Context) XML(status int, data any) error {
   
	return c.Render(status, render.XML{
   Data: data})
}

7.1.2 JSON
package render

import (
	"encoding/json"
	"net/http"
)

type JSON struct {
   
	Data any
}

var jsonContentType = []string{
   "application/json; charset=utf-8"}

func (r JSON) Render(w http.ResponseWriter) error {
   
	return WriteJSON(w, r.Data)
}
func (r JSON) WriteContentType(w http.ResponseWriter) {
   
	writeContentType(w, jsonContentType)
}

func WriteJSON(w http.ResponseWriter, obj any) error {
   
	writeContentType(w, jsonContentType)
	jsonBytes, err := json.Marshal(obj)
	if err != nil {
   
		return err
	}
	_, err = w.Write(jsonBytes)
	return err
}

7.1.3 HTML
package render

import (
	"html/template"
	"net/http"
)

type HTMLData any

type HTML struct {
   
	Template   *template.Template
	Name       string
	Data       HTMLData
	IsTemplate bool
}

var htmlContentType = []string{
   "text/html; charset=utf-8"}

type HTMLRender struct {
   
	Template *template.Template
}

func (r HTML) Render(w http.ResponseWriter) error {
   
	r.WriteContentType(w)
	if !r.IsTemplate {
   
		_, err := w.Write([]byte(r.Data.(string)))
		return err
	}
	err := r.Template.ExecuteTemplate(w, r.Name, r.Data)
	return err
}

func (r HTML) WriteContentType(w http.ResponseWriter) {
   
	writeContentType(w, htmlContentType)
}

func (c *Context) HTML(status int, html string) {
   
	c.Render(status, render.HTML{
   IsTemplate: false, Data: html})
}

func (c *Context) HTMLTemplate(name string, data any) {
   
	c.Render(http.StatusOK, render.HTML{
   
		IsTemplate: true,
		Name:       name,
		Data:       data,
		Template:   c.engin.HTMLRender.Template,
	})
}
7.1.4 Redirect
package render

import (
	"fmt"
	"net/http"
)

type Redirect struct {
   
	Code     int
	Request  *http.Request
	Location string
}

func (r Redirect) Render(w http.ResponseWriter) error {
   
	if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
   
		panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
	}
	http.Redirect(w, r.Request, r.Location, r.Code)
	return nil
}

// WriteContentType (Redirect) don't write any ContentType.
func (r Redirect) WriteContentType(http.ResponseWriter) {
   }

func (c *Context) Redirect(status int, location string) {
   
	c.Render(status, render.Redirect{
   
		Code:     status,
		Request:  c.R,
		Location: location,
	})
}

相关推荐

  1. GO服务框架-页面渲染

    2024-01-16 13:06:05       48 阅读
  2. GO服务框架-中间件

    2024-01-16 13:06:05       44 阅读
  3. Go服务框架-日志处理

    2024-01-16 13:06:05       43 阅读
  4. Go服务框架-参数处理

    2024-01-16 13:06:05       43 阅读
  5. Go服务: 基于Go Micro框架实现服务调用

    2024-01-16 13:06:05       41 阅读
  6. 信小程序:7.页面渲染

    2024-01-16 13:06:05       32 阅读
  7. 【Tars-go】腾讯服务框架学习使用02-- http 服务

    2024-01-16 13:06:05       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-16 13:06:05       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-16 13:06:05       100 阅读
  3. 在Django里面运行非项目文件

    2024-01-16 13:06:05       82 阅读
  4. Python语言-面向对象

    2024-01-16 13:06:05       91 阅读

热门阅读

  1. 设计模式——单例模式

    2024-01-16 13:06:05       62 阅读
  2. 探索DirectoryEntry对象

    2024-01-16 13:06:05       60 阅读
  3. bcd编码精讲

    2024-01-16 13:06:05       59 阅读
  4. 单片机学习记录(三)

    2024-01-16 13:06:05       62 阅读
  5. K3S+Rancher

    2024-01-16 13:06:05       57 阅读
  6. Abaqus许可分析最佳实践:为企业创造最大化价值

    2024-01-16 13:06:05       52 阅读
  7. LRC歌词格式文件

    2024-01-16 13:06:05       75 阅读