来源:自学PHP网 时间:2019-08-07 16:47 作者:小飞侠 阅读:次
[导读] 浅谈go语言renderer包代码分析...
renderer是Go语言的一个简单的、轻量的、快速响应的呈现包,它可以支持JSON、JSONP、XML、HYAML、HTML、File等类型的响应。在开发web应用或RESTFul API的时候,这个包是非常方便的toolkit。 本文绕开如何使用它,深入到代码实现中研究它,同时也尝尝Go语言包的开发套路。 Go包基础介绍 代码结构 package pkgname import ( "fmt" ... ) const ( CONST1 typeX = xx ... ) var ( VAR1 typeX = xxx ... ) func Fn1() { } 在Go语言中包名和目录名保持一致,同一包内可共享命名空间。
import语句 import可以引入标准库的包,也可以引入外部包。Go语言中一旦引入某个包,必须在程序中使用到这个包的命名空间,否则编译报错会告诉你引入了某个包,但代码中未曾使用。 当然你也会有疑问,我如果需要引入包,但又不想使用怎么办。这个Go语言有一个特殊的符号"_", 放在引入包名前面,就可以防止编译报错。为什么会有这种考虑呢? 因为有时候,我们只是希望引入一个包,然后执行这个包的一些初始化设置。然后在代码中暂时不使用该包的任何方法和变量。 import ( _ "gitHub.com/xxxxx/pkgname" ) 上面语句会引入pkgname命名空间,但是暂时不在代码中使用这个命名空间。这样引入之后,会在pkgname包中寻找init()函数,然后在main()函数执行之前先执行它们,这点对于需要使用包之前做初始化非常有用。 暴露与非暴露的实现 我们在其他编程语言中,都接触过private, protected, public之类的修饰符。 但是在Go语言中完全没有这些,但是Go语言还是可以某些东西从包中暴露出去,而某些东西不暴露出去,它用的原则很简单的,就是标识符如果以小写字母开头的,包外不可见; 而如果是标识符以大写字符开头的,包外可见,可访问。 对于暴露变量和函数(方法)非常直观简单,但是如果是暴露的结构体,情况稍微复杂一点。 不过本质上也是差不多, 结构体外部如果小写字母开头,内部属性大写字母开头。 则外部包直接不访问,但如果通过函数或方法返回这个外部类型,那么可以通过:=得到这个外部类型,从而可以访问其内部属性。举例如下: // package pkgname package pkgname type admin struct { Name string Email String } func Admin() *admin { return &admin{ Name: "admin", Email: "admin@email.com", } } 那么我们在外部包中,可以直接通过下面代码访问admin结构体内部的属性: admin := pkgname.Admin() fmt.Println(admin.Name, admin.Email) 当然这种情况下,需要你事先知道admin的结构以及包含的属性名。 内置类型和自定义类型 Go语言包含了几种简单的内置类型:整数、布尔值、数组、字符串、分片、映射等。除了内置类型,Go语言还支持方便的自定义类型。 自定义类型有两种:
函数和方法 Go语言的函数和方法都是使用func关键词声明的,方法和函数的唯一区别在于,方法需要绑定目标类型; 而函数则无需绑定。 type MyType struct { } // 这是函数 func DoSomething() { } // 这是方法 func (mt MyType) MyMethod() { } // 或者另外一种类型的方法 func (mt *MyType) MyMethod2() { } 对于方法来说,需要绑定一个receiver, 我称之为接收者。 接收者有两种类型:
关于接收者和接口部分,有很多需要延伸的,后续有时间整理补充出来。 接口 代码分析 常量部分 代码分析部分,我们先跳过import部分, 直接进入到常量的声明部分。 const ( // ContentType represents content type ContentType string = "Content-Type" // ContentJSON represents content type application/json ContentJSON string = "application/json" // ContentJSONP represents content type application/javascript ContentJSONP string = "application/javascript" // ContentXML represents content type application/xml ContentXML string = "application/xml" // ContentYAML represents content type application/x-yaml ContentYAML string = "application/x-yaml" // ContentHTML represents content type text/html ContentHTML string = "text/html" // ContentText represents content type text/plain ContentText string = "text/plain" // ContentBinary represents content type application/octet-stream ContentBinary string = "application/octet-stream" // ContentDisposition describes contentDisposition ContentDisposition string = "Content-Disposition" // contentDispositionInline describes content disposition type contentDispositionInline string = "inline" // contentDispositionAttachment describes content disposition type contentDispositionAttachment string = "attachment" defaultCharSet string = "utf-8" defaultJSONPrefix string = "" defaultXMLPrefix string = `\n` defaultTemplateExt string = "tpl" defaultLayoutExt string = "lout" defaultTemplateLeftDelim string = "{{" defaultTemplateRightDelim string = "}}" ) 以上常量声明了内容类型常量以及本包支持的各种内容类型MIME值。以及各种具体内容类型相关的常量,比如字符编码方式、JSONP前缀、XML前缀,模版左右分割符等等一些常量。 类型声明部分 这部分声明了如下类型:
type ( // M describes handy type that represents data to send as response M map[string]interface{} // Options describes an option type Options struct { // Charset represents the Response charset; default: utf-8 Charset string // ContentJSON represents the Content-Type for JSON ContentJSON string // ContentJSONP represents the Content-Type for JSONP ContentJSONP string // ContentXML represents the Content-Type for XML ContentXML string // ContentYAML represents the Content-Type for YAML ContentYAML string // ContentHTML represents the Content-Type for HTML ContentHTML string // ContentText represents the Content-Type for Text ContentText string // ContentBinary represents the Content-Type for octet-stream ContentBinary string // UnEscapeHTML set UnEscapeHTML for JSON; default false UnEscapeHTML bool // DisableCharset set DisableCharset in Response Content-Type DisableCharset bool // Debug set the debug mode. if debug is true then every time "VIEW" call parse the templates Debug bool // JSONIndent set JSON Indent in response; default false JSONIndent bool // XMLIndent set XML Indent in response; default false XMLIndent bool // JSONPrefix set Prefix in JSON response JSONPrefix string // XMLPrefix set Prefix in XML response XMLPrefix string // TemplateDir set the Template directory TemplateDir string // TemplateExtension set the Template extension TemplateExtension string // LeftDelim set template left delimiter default is {{ LeftDelim string // RightDelim set template right delimiter default is }} RightDelim string // LayoutExtension set the Layout extension LayoutExtension string // FuncMap contain function map for template FuncMap []template.FuncMap // ParseGlobPattern contain parse glob pattern ParseGlobPattern string } // Render describes a renderer type Render struct { opts Options templates map[string]*template.Template globTemplates *template.Template headers map[string]string } ) New函数 // New return a new instance of a pointer to Render func New(opts ...Options) *Render { var opt Options if opts != nil { opt = opts[0] } r := &Render{ opts: opt, templates: make(map[string]*template.Template), } // build options for the Render instance r.buildOptions() // if TemplateDir is not empty then call the parseTemplates if r.opts.TemplateDir != "" { r.parseTemplates() } // ParseGlobPattern is not empty then parse template with pattern if r.opts.ParseGlobPattern != "" { r.parseGlob() } return r } 用于创建Render类型的函数。它接受Options类型的参数,返回一个Render类型。 我们一般通常不传入Options类型变量调用renderer.New()来创建一个Render类型。 var opt Options if opts != nil { opt = opts[0] } r := &Render{ opts: opt, templates: make(map[string]*template.Template), } 上面这段代码实际上就是初始化了一个Render类型的r变量。opts为nil, templates为map类型,这里被初始化。 接下来调用r.buildOptions()方法。 buildOptions方法 func (r *Render) buildOptions() { if r.opts.Charset == "" { // 没有指定编码方式,使用默认的编码方式UTF-8 r.opts.Charset = defaultCharSet } if r.opts.JSONPrefix == "" { // 没有指定JSON前缀,使用默认的 r.opts.JSONPrefix = defaultJSONPrefix } if r.opts.XMLPrefix == "" { // 没有指定XML前缀,使用默认XML前缀 r.opts.XMLPrefix = defaultXMLPrefix } if r.opts.TemplateExtension == "" { // 模版扩展名设置 r.opts.TemplateExtension = "." + defaultTemplateExt } else { r.opts.TemplateExtension = "." + r.opts.TemplateExtension } if r.opts.LayoutExtension == "" { // 布局扩展名设置 r.opts.LayoutExtension = "." + defaultLayoutExt } else { r.opts.LayoutExtension = "." + r.opts.LayoutExtension } if r.opts.LeftDelim == "" { // 模版变量左分割符设置 r.opts.LeftDelim = defaultTemplateLeftDelim } if r.opts.RightDelim == "" { // 模版变量右分割符设置 r.opts.RightDelim = defaultTemplateRightDelim } // 设置内容类型属性常量 r.opts.ContentJSON = ContentJSON r.opts.ContentJSONP = ContentJSONP r.opts.ContentXML = ContentXML r.opts.ContentYAML = ContentYAML r.opts.ContentHTML = ContentHTML r.opts.ContentText = ContentText r.opts.ContentBinary = ContentBinary // 如果没有禁用编码集,那么就将内容类型后面添加字符集属性。 if !r.opts.DisableCharset { r.enableCharset() } } 该方法构建Render的opts属性,并绑定默认的值。 我们看了New函数,得到了一个Render类型,接下来就是呈现具体类型的内容。我们以JSON为例,看看它怎么实现的。 JSON方法 如果没有renderer包,我们想要用Go语言发送JSON数据响应,我们的实现代码大致如下: func DoSomething(w http.ResponseWriter, ...) { // json from a variable v jData, err := json.Marshal(v) if err != nil { panic(err) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) w.Write(jData) } 原理很简单,首先从变量中解析出JSON, 然后发送Content-Type为application/json, 然后发送状态码, 最后将json序列发送出去。 那么我们再详细看看renderer中的JSON方法的实现: func (r *Render) JSON(w http.ResponseWriter, status int, v interface{}) error { w.Header().Set(ContentType, r.opts.ContentJSON) w.WriteHeader(status) bs, err := r.json(v) if err != nil { return err } if r.opts.JSONPrefix != "" { w.Write([]byte(r.opts.JSONPrefix)) } _, err = w.Write(bs) return err } 大致看上去,和我们不使用renderer包的实现基本一样。指定Content-Type, 发送HTTP状态码,然后看JSON前缀是否设置,如果设置,前缀也发送到字节流中。 最后就是发送json字节流。 唯一区别在于,我们使用encoding/json包的Marshal方法来将给定的值转换成二进制序列,而renderer对这个方法进行了包装: func (r *Render) json(v interface{}) ([]byte, error) { var bs []byte var err error if r.opts.JSONIndent { bs, err = json.MarshalIndent(v, "", " ") } else { bs, err = json.Marshal(v) } if err != nil { return bs, err } if r.opts.UnEscapeHTML { bs = bytes.Replace(bs, []byte("\\u003c"), []byte("<"), -1) bs = bytes.Replace(bs, []byte("\\u003e"), []byte(">"), -1) bs = bytes.Replace(bs, []byte("\\u0026"), []byte("&"), -1) } return bs, nil } 如果有设置JSONIndent, 即JSON缩进,那么使用json.MarshalIndent来将变量转换为json字节流。 这个方法其实就是将JSON格式化,使得结果看起来更好看。 另外这个方法还会根据配置,进行html实体的转义。 因此总体来说原理和开头的代码基本一样。只不过多了一些额外的修饰修补。 JSONP方法 我们理解了JSON方法,理解起JSONP就更加简单了。 JSONP全称为JSON with Padding, 用于解决Ajax跨域问题的一种方案。 它的原理非常简单: // 客户端代码 var dosomething = function(data) { // do something with data } var url = "server.jsonp?callback=dosomething"; // 创建 更多文章推荐
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习 京ICP备14009008号-1@版权所有www.zixuephp.com 网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com
添加评论 |