SoFunction
Updated on 2025-03-05

A brief discussion on the code analysis of go language renderer package

renderer is a simple, lightweight, fast-responsive rendering package of Go language. It can support JSON, JSONP, XML, HYAML, HTML, File and other types of responses. When developing web applications or RESTFul APIs, this package is a very convenient toolkit.

This article bypasses how to use it, studies it in code implementation, and also tastes the development routines of Go language packages.

Introduction to the basic Go package

Code structure

package pkgname

import (
  "fmt"
  ...
)
const (
  CONST1 typeX = xx
  ...
)

var (
  VAR1 typeX = xxx
  ...
)

func Fn1() {
}

In Go language, the package name and directory name are consistent, and the namespace can be shared within the same package.

  1. In addition to comments at the beginning of the package file, the first line must be package pkgname, which declares the name of the package.
  2. After the package declaration, you can import packages and other external packages in the standard library.
  3. Package constants, package variables (exposed variables and non-exposed variables, which distinguish implementations by case).
  4. Then define a custom type, function, or method.

Import statement

Import can introduce packages from the standard library or external packages. Once a package is introduced in Go, the namespace of the package must be used in the program. Otherwise, a compilation error will tell you that a package has been introduced, but it has not been used in the code.

Of course you will also have questions, what should I do if I need to introduce the package but don’t want to use it. This Go language has a special symbol "_", which is placed before the package name to prevent compile errors. Why did this kind of consideration be there? Because sometimes, we just want to introduce a package and then execute some initialization settings for this package. Then no methods and variables of the package are used in the code for the time being.

import (
  _ "/xxxxx/pkgname"
)

The above statement will introduce the pkgname namespace, but this namespace is not used in the code for the time being. After this introduction, the init() function will be looked for init() in the pkgname package, and then they will be executed before the main() function is executed. This is very useful for initialization before using the package.

Exposure and non-exposed realization

We have been exposed to modifiers such as private, protected, and public in other programming languages. However, there is no such thing in Go language, but Go language can still expose certain things from the package, while some things are not exposed. The principle it uses is very simple, that is, if the identifier starts with lowercase letters, it will not be visible outside the package; if the identifier starts with uppercase characters, it will be visible outside the package and accessible.

It is very intuitive and simple to expose variables and functions (methods), but if it is an exposed structure, the situation is a little more complicated. However, it is essentially the same. If the external part of the structure starts with lowercase letters, the internal attribute starts with uppercase letters. The external package will not be accessed directly, but if this external type is returned through a function or method, then this external type can be obtained by:=, so that its internal properties can be accessed. As an example:

// package pkgname
package pkgname

type admin struct {
  Name string
  Email String
}

func Admin() *admin {
  return &admin{
    Name: "admin",
    Email: "admin@",
  }
}

Then in the external package, we can directly access the properties inside the admin structure through the following code:

admin := ()

(, )

Of course, in this case, you need to know the structure of admin and the attribute names contained in advance.

Built-in and custom types

Go language contains several simple built-in types: integers, booleans, arrays, strings, shards, maps, etc. In addition to built-in types, Go also supports convenient custom types.

There are two types of custom types:

  1. Custom structure type: type MyType struct {} This form definition is similar to the structure definition in C language.
  2. Named type: type MyInt int. This method is achieved by naming the built-in or custom type as a new type. It should be noted that MyInt and int are different types, and they cannot directly assign values ​​to each other.

Functions and methods

The functions and methods of Go language are declared using the func keyword. The only difference between methods and functions is that methods need to bind the target type; while functions do not need to bind.

type MyType struct {
}

// This is a functionfunc DoSomething() {

}

// This is the methodfunc (mt MyType) MyMethod() {
}

// Or another type of methodfunc (mt *MyType) MyMethod2() {
}

For methods, you need to bind a receiver, which I call the receiver. There are two types of recipients:

  1. Recipient of value type
  2. Receiver of pointer type

Regarding the receiver and interface part, there is a lot to be extended, and there is time to sort it out and supplement it later.

interface

Code Analysis

Constant part

In the code analysis part, we first skip the import part and go directly to the declaration part of the constant.

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 = `<?xml version="1.0" encoding="ISO-8859-1" ?>\n`
  defaultTemplateExt    string = "tpl"
  defaultLayoutExt     string = "lout"
  defaultTemplateLeftDelim string = "{{"
  defaultTemplateRightDelim string = "}}"
)

The above constants declare content type constants and various content type MIME values ​​supported by this package. As well as constants related to various specific content types, such as character encoding method, JSONP prefix, XML prefix, template left and right splitters, etc.

Type declaration section

This section declares the following types:

  1. M: Map type, description represents the convenient type of response data used to send.
  2. Options: Describe the option type.
  3. Render: Used to describe renderer type.
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 []
    // ParseGlobPattern contain parse glob pattern
    ParseGlobPattern string
  }

  // Render describes a renderer type
  Render struct {
    opts     Options
    templates   map[string]*
    globTemplates *
    headers    map[string]string
  }
)

New function

// 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]*),
  }

  // build options for the Render instance
  ()

  // if TemplateDir is not empty then call the parseTemplates
  if  != "" {
    ()
  }

  // ParseGlobPattern is not empty then parse template with pattern
  if  != "" {
    ()
  }

  return r
}

Used to create a function of type Render. It accepts parameters of Options type and returns a Render type.

We usually do not pass in the Options type variable to call() to create a Render type.

  var opt Options
  if opts != nil {
    opt = opts[0]
  }

  r := &Render{
    opts:   opt,
    templates: make(map[string]*),
  }

The above code actually initializes a Render type r variable. opts is nil, templates is map type, which is initialized here.

Next, call the() method.

buildOptions method

func (r *Render) buildOptions() {
  if  == "" { // There is no encoding method specified, the default encoding method UTF-8 is used     = defaultCharSet
  }

  if  == "" { // No JSON prefix is ​​specified, the default one is used     = defaultJSONPrefix
  }

  if  == "" { // No XML prefix is ​​specified, use the default XML prefix     = defaultXMLPrefix
  }

  if  == "" { // Template extension settings     = "." + defaultTemplateExt
  } else {
     = "." + 
  }

  if  == "" { // Layout extension settings     = "." + defaultLayoutExt
  } else {
     = "." + 
  }

  if  == "" { // Set the left separator of template variable     = defaultTemplateLeftDelim
  }

  if  == "" { // Template variable right splitter setting     = defaultTemplateRightDelim
  }

  // Set content type attribute constant   = ContentJSON 
   = ContentJSONP
   = ContentXML
   = ContentYAML
   = ContentHTML
   = ContentText
   = ContentBinary

  // If the encoding set is not disabled, then the character set attribute is added after the content type.  if ! {
    ()
  }
}

This method builds the Opts property of Render and binds the default value.

We looked at the New function and got a Render type, and the next step is to present the content of the specific type. Let's take JSON as an example to see how it is implemented.

JSON method

If there is no renderer package, we want to send JSON data response in Go language. Our implementation code is roughly as follows:

func DoSomething(w , ...) {
  // json from a variable v
  jData, err := (v)
  if err != nil {
    panic(err)
    return
  }
  ().Set("Content-Type", "application/json")
  (200)
  (jData)
}

The principle is very simple. First, parse out JSON from the variable, then send the Content-Type as application/json, then send the status code, and finally send the json sequence.

Then let's take a look at the implementation of the JSON method in renderer in detail:

func (r *Render) JSON(w , status int, v interface{}) error {
  ().Set(ContentType, )
  (status)

  bs, err := (v)
  if err != nil {
    return err
  }
  if  != "" {
    ([]byte())
  }
  _, err = (bs)
  return err
}

It looks basically the same as our implementation without using the renderer package. Specify Content-Type, send the HTTP status code, and then see if the JSON prefix is ​​set. If set, the prefix is ​​also sent to the byte stream. Finally, send the json byte stream.

The only difference is that we use the Marshal method of the encoding/json package to convert the given value into a binary sequence, and renderer wraps this method:

func (r *Render) json(v interface{}) ([]byte, error) {
  var bs []byte
  var err error
  if  {
    bs, err = (v, "", " ")
  } else {
    bs, err = (v)
  }
  if err != nil {
    return bs, err
  }
  if  {
    bs = (bs, []byte("\\u003c"), []byte("<"), -1)
    bs = (bs, []byte("\\u003e"), []byte(">"), -1)
    bs = (bs, []byte("\\u0026"), []byte("&"), -1)
  }
  return bs, nil
}

If JSONIndent is set, i.e. JSON indent, then use it to convert the variable into a json byte stream. This method is actually to format JSON so that the results look better.

In addition, this method will also escape the html entity according to the configuration.

Therefore, overall the principle is basically the same as the code at the beginning. It's just that there are some extra modifications and patches.

JSONP method

We understand the JSON method, and it is even easier to understand JSONP.

JSONP is full name JSON with Padding, a solution to Ajax cross-domain problem.

Its principle is very simple:

// Client codevar dosomething = function(data) {
  // do something with data
}

var url = "?callback=dosomething";
 // Create a <script> tag and set its src attribute var script = ('script');
 ('src', url);

 // Add the <script> tag to the end of the <body>, and the call begins. ('body')[0].appendChild(script);
A background script above,Return its output immediately after access, This isrendererofJSONP要响应of内容。

func (r *Render) JSONP(w , status int, callback string, v interface{}) error {
  ().Set(ContentType, )
  (status)

  bs, err := (v)
  if err != nil {
    return err
  }

  if callback == "" {
    return ("renderer: callback can not bet empty")
  }

  ([]byte(callback + "("))
  _, err = (bs)
  ([]byte(");"))

  return err
}

  1. Setting Content-Type to application/javascript is a very critical point. Think about whether the mime type of the js file embedded in html is also this value?
  2. Then set the response status code the same way, there is nothing special about this.
  3. Convert values ​​to json byte sequence. This json byte sequence has not been written to the response.
  4. At this time, we check whether the callback exists and report an error. Because it is JSONP, there must be a callback, which is passed in the request parameter.
  5. Then use "callbak(" and ")" to enclose the json byte sequence and output it to the response stream together. In this way, the jsonp response is generated.

Then, if we look back and combine a previous jsonp code we wrote at the beginning, we know that after requesting ?callback=xxxx, the content of an application/javascript is embedded in the body. It is a js file. And its content replaces callback with the incoming dosomething, we get similar js content:

dosomething({
  // ....
});

In this way, the server generates data, calls the front-end js method, passes in this data, and jsonp is completed. Once such js is loaded successfully, it is the same as the currently accessed domain name and there is no cross-domain problem. This solves the cross-domain problem of ajax.

The rest of the other methods are basically the same routine. I won’t go into details here. If you have time, I will reorganize the content at the beginning.

This article is only studied and compiled by myself. If there is any wrong point, I hope you will point it out.

In the link section, I have my own translation of Go in Action's English books. The English is relatively poor, and I am also a beginner in Go and the translation is not in place. Friends who are interested can translate this book together, or there are other good technical books in the future to translate and learn together.

Quote link

  1. renderer
  2. Go In Action

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.