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.
- 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.
- After the package declaration, you can import packages and other external packages in the standard library.
- Package constants, package variables (exposed variables and non-exposed variables, which distinguish implementations by case).
- 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:
- Custom structure type: type MyType struct {} This form definition is similar to the structure definition in C language.
- 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:
- Recipient of value type
- 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:
- M: Map type, description represents the convenient type of response data used to send.
- Options: Describe the option type.
- 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 }
- 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?
- Then set the response status code the same way, there is nothing special about this.
- Convert values to json byte sequence. This json byte sequence has not been written to the response.
- 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.
- 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
- renderer
- 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.