Gin framework for Golang(Go language)

Frameworks have always been a powerful tool in agile development, enabling developers to quickly get started and build applications, and sometimes even fail to write programs without frameworks. Growth doesn’t happen overnight. From writing programs to getting a sense of accomplishment, to mastering frameworks and building applications quickly, when you’re comfortable with these aspects, try adapting some frameworks or creating your own.

I used to think there were enough frameworks in the Python world, but it turned out to be nothing compared to Golang. The NET/HTTP library provided by Golang is already very good, and the implementation of the HTTP protocol is very good. Based on this, it is not difficult to recreate frameworks, so there are many frameworks in the ecosystem. Since the barrier to building a framework is lower, it also leads to frameworks of varying quality.

Several frameworks were examined by their Github activity, maintained teams, and usage in production environments. Gin was also found to be a lightweight framework to learn from.

Gin Gin is a Golang microframework with elegant packaging, API friendly, and clear source code annotations. Version 1.0 has been released. It is fast and flexible, fault-tolerant and convenient. For Golang, web frameworks are far less dependent than Python, Java, etc. Its own NET/HTTP is simple enough and performs very well. Frameworks are more like collections of common functions or tools. Not only can framework development save time for many of the usual encapsulation, but it also helps with the team’s coding style and specification.

Here is a brief introduction to the use of Gin.

Go get -u(update)

The version of Go Get Gopkg. In/gIN-gonic /gin. V1 Gin is hosted on gopkg’s website. During the installation process, gokpg got stuck and I had to download the source code of the response from Github and copy it to the corresponding directory based on the Godep file in GIN.

Using Gin to implement Hello World is very simple. Create a router and use its Run method:

import ( 
    "gopkg.in/gin-gonic/gin.v1" 
    "net/http" 
) 
func main(){ 
    router := gin.Default() 
    router.GET("/".func(c *gin.Context) { 
    c.String(http.StatusOK, "Hello World") 
    }) 
router.Run(": 8000")}Copy the code

You can implement a Web service in a few lines of code. Create a routing handler using gin’s Default method. Routing rules and routing functions are then bound via HTTP methods. Unlike the net/ HTTP library routing functions, GIN encapsulates both request and response into the Context of gin.Context. Finally, the Run method that starts the route listens on the port. Small as a sparrow is, it has all the organs. Gin also supports common restful methods such as POST,PUT,DELETE, and OPTION, in addition to GET.

Restful Route Gin routes come from the Httprouter library. Therefore, httprouter has the same functionality as GIN, but gin does not support routing regular expressions:

func main(){ 
    router := gin.Default() 
    router.GET("/user/:name".func(c *gin.Context) { 
        name := c.Param("name") 
        c.String(http.StatusOK, "Hello %s", name) 
    }) 
}
Copy the code

Colon: adds a parameter name to form the route parameter. Its value can be read using the c. params method. Of course the value is a string. Such as /user/rsj217, and /user/hello will match, but /user/ and /user/rsj217/ will not.

Gin also provides * processing parameters in addition to:, so * matches more rules.

func main(){
    router := gin.Default() 
 
    router.GET("/user/:name/*action".func(c *gin.Context) { 
        name := c.Param("name") 
        action := c.Param("action") 
        message := name + " is " + action 
        c.String(http.StatusOK, message) 
    }) 
}
Copy the code

The Service provided by the Web is typically a client and server interaction. The client sends a request to the server. In addition to routing parameters, other parameters are no more than two types: query String and body parameter. Query String is used for routing. Subsequent concatenation of key1=value2&key2=value2. Of course the key-value is encoded by urlencode.

query string

For arguments, there are often cases where arguments do not exist. Gin also considers whether to provide default values and gives an elegant solution:

func main(){ 
    router := gin.Default() 
 
    router.GET("/welcome".func(c *gin.Context) { 
        firstname := c.DefaultQuery("firstname"."Guest") 
        lastname := c.Query("lastname") 
        c.String(http.StatusOK, "Hello %s %s", firstname, lastname) 
        }) 
 
    router.Run() 
}
Copy the code

The reason for using Chinese is to illustrate urlencode. Note that the default Guest value is not used when firstName is an empty string. The null value is also a value. DefaultQuery provides the default value only when the key does not exist.

body

HTTP messages transfer data in slightly more complex formats than Query String, and there are four common formats. Examples are Application/JSON, Application/X-www-form-urlencoded, Application/XML and multipart/form-data. The latter one is mainly used for uploading pictures. The json format is pretty easy to understand, and the urlencode is actually pretty easy to understand, which is just putting the contents of the Query String inside the body, and you also need the urlencode. By default, C. ostFROM parses x-www-form-urlencoded or FROm-data parameters.

func main(){ 
    router := gin.Default() 
    router.POST("/form_post".func(c *gin.Context) { 
        message := c.PostForm("message") 
        nick := c.DefaultPostForm("nick"."anonymous") 
        c.JSON(http.StatusOK, gin.H{ 
            "status": gin.H{ 
                "status_code": http.StatusOK, 
                "status": "ok",},"message": message, 
            "nick": nick,         
        }) 
    }) 
}
Copy the code

Just as GET handles query arguments, the POST method provides cases for handling default arguments. Similarly, if the argument does not exist, you will get an empty string.

Youdaoplaceholder0 ~ curlose-x POST HTTP:/ / 127.0.0.1:8000 / form_post -h "content-type: application/x - WWW - form - urlencoded" 3-d "message = = hello&nick rsj217" | python -m json.tool % Total % Received % Xferd
 
Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
100 104 100 79 100 25 48555 15365- : -- : -- - : -- : -- -- - : -79000 
{ 
    "message": "hello"."nick": "rsj217"."status": { 
        "status": "ok"."status_code": 200}}Copy the code

We used c. string to return the response, which, as the name implies, returns a string. Content-type is plain or text. Calling c.json returns JSON data. Gin.H is a powerful tool that encapsulates how to generate JSON. Using Golang, you can write faceted JSON like a dynamic language, or for nested JSON implementations, nested gin.H.

Send data to the server, not post, but put as well. Querystring and body are also not separate; both can be sent at the same time:

func main(){ 
    router := gin.Default() 
    router.PUT("/post".func(c *gin.Context) { 
        id := c.Query("id") 
        page := c.DefaultQuery("page"."0") 
        name := c.PostForm("name") 
        message := c.PostForm("message") 
        fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message)                   
        c.JSON(http.StatusOK, gin.H{ "status_code": http.StatusOK, }) 
    }) 
}
Copy the code

The above example shows sending data to the server using both the query string and the body argument.

File upload The basic send data was introduced earlier, where multipart/form-data forwarding is used for file upload. Gin file uploads are also very convenient, similar to the native NET/HTTP method, except that gin encapsulates the native request into C. Request.

func main(){ 
    router := gin.Default() 
    router.POST("/upload".func(c *gin.Context) { 
        name := c.PostForm("name") 
        fmt.Println(name) 
        file, header, err := c.Request.FormFile("upload") iferr ! = nil { c.String(http.StatusBadRequest,"Bad request") 
            return 
        } 
        filename := header.Filename 
        fmt.Println(file, err, filename) 
        out, err := os.Create(filename) 
        iferr ! = nil { log.Fatal(err) } defer out.Close() _, err = io.Copy(out, file)iferr ! = nil { log.Fatal(err) } c.String(http.StatusCreated,"upload successful") 
    }) 
    router.Run(": 8000")}Copy the code

Parse the client file name property using c.equest. FormFile. If you do not pass the file, an error will be thrown, so you need to handle this error. One way is to return directly. The file data is then copied to the hard disk using OS operations.

To test the upload, use the following command. Note that the upload parameter specified for c.equest. FormFile must be an absolute path:

curl -X POST http:/ / 127.0.0.1:8000 / upload - F "upload = @ / Users/ghost/Desktop/PIC. JPG" -h "content-type: multipart/form - the data"
Copy the code

Uploading multiple files Is easy. Don’t assume that uploading multiple files is a hassle. By analogy, the so-called multiple files, nothing more than one more time through the file, and then copy the data store. Handler () {route () {route ();}

router.POST("/multi/upload".func(c *gin.Context) { 
    err := c.Request.ParseMultipartForm(200000) 
    iferr ! = nil { log.Fatal(err) }formdata := c.Request.MultipartForm 
    files := formdata.File["upload"] 
 
    for i, _ := range files { / 
        file, err := files[i].Open() 
        defer file.Close() 
        iferr ! = nil { log.Fatal(err) } out,err := os.Create(files[i].Filename) 
        defer out.Close() 
        iferr ! = nil { log.Fatal(err) } _, err = io.Copy(out, file)iferr ! = nil { log.Fatal(err) } c.String(http.StatusCreated,"upload successful")}})Copy the code

This is similar to a single file upload, except that c.equest.multipartform is used to get the file handle, get the file data, and then iterate over the read and write.

Using curl to upload data

curl -X POST http:/ / 127.0.0.1:8000 / multi/upload - F "upload = @ / Users/ghost/Desktop/PIC. JPG" -f "upload = @ / Users/ghost/Desktop/journey. PNG" - H  "Content-Type: multipart/form-data"
Copy the code

Curl uploads are often done with forms, Ajax, and requests. Here’s how to upload a Web form.

We need to write a form page first, so we need to introduce the Gin How Render template. Earlier we saw C. Sterling and C. Johnson. Let’s take a look at the C.HTML method.

First you need to define a template folder. Then call c.HTML to render the template, and you can pass values to the template via gin.H. At this point, whether it’s String, JSON, HTML, and later XML and YAML, you can see that the Gin packaged interface is simple and easy to use.

Create a “templates” folder and create an HTML file upload.html inside it:

<! DOCTYPE html><html lang="en"> 
<head> 
    <meta charset="UTF-8"> 
    <title>upload</title> 
</head> 
 
<body> 
<h3>Single Upload</h3> 
<form action="/upload".method="post" enctype="multipart/form-data"> 
    <input type="text" value="hello gin" /> 
    <input type="file" name="upload" /> 
    <input type="submit" value="upload" /> 
</form> 
 
<h3>Multi Upload</h3> 
<form action="/multi/upload".method="post" enctype="multipart/form-data"> 
    <input type="text" value="hello gin" /> 
    <input type="file" name="upload" /> 
    <input type="file" name="upload" /> 
    <input type="submit" value="upload" /> 
</form> 
 
</body> 
</html>Upload is simple and takes no parameters. One for single file uploads and one for multiple file uploads. router.LoadHTMLGlob("templates/*") router.GET("/upload", func(c *gin.Context) { c.HTML(http.StatusOK, "upload.html", gin.H{}) })Copy the code

Define the template file path using LoadHTMLGlob.

We have already seen x-www-form-urlencoded parameter processing, and now more and more applications are used to JSON communication, that is, whether a response is returned or a request is submitted, The content-Type is in application/ JSON format. Some old Web form pages were x-www-form-urlencoded, which required our server to be able to handle multiple Content-Type parameters.

It’s easy to solve in the Python world, because dynamic languages don’t need to implement defined data models. So you can write a decorator to encapsulate two formats of data into a data model. Golang isn’t easy to handle, but gin has a very powerful model Bind feature.

type User struct { 
    Username string `form:"username" json:"username" binding:"required"` 
    Passwd string `form:"passwd" json:"passwd" bdinding:"required"` 
    Age int `form:"age" json:"age"` 
} 
 
func main(){ 
    router := gin.Default() 
 
    router.POST("/login".func(c *gin.Context) { 
        var user User 
        var err error 
        contentType := c.Request.Header.Get("Content-Type") 
 
        switch contentType { 
            case "application/json": 
                err = c.BindJSON(&user) 
            case "application/x-www-form-urlencoded": 
                err = c.BindWith(&user, binding.Form) 
        } 
        iferr ! = nil { fmt.Println(err) log.Fatal(err) } c.JSON(http.StatusOK, gin.H{"user": user.Username, 
            "passwd": user.Passwd, 
            "age": user.Age, 
        }) 
    }) 
}
Copy the code

Define a User model structure, and then apply the BindJSON and BindWith methods once for the client-side Content-Type.

Youdaoplaceholder0 ~ curlose-x POST HTTP:/ / 127.0.0.1:8000 / login - H "content-type: application/x - WWW - form - urlencoded" 3-d "username = rsj217 & passwd = 123 & age = 21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed
100 79 100 46 100 33 41181 29543- : -- : -- - : -- : -- -- - : -46000
{ 
    "age": 21."passwd": "123"."username": "rsj217"} youdaoplaceholder0 ~ curl-x POST HTTP:/ / 127.0.0.1:8000 / login - H "content-type: application/x - WWW - form - urlencoded" 3-d "username = rsj217 & passwd = 123 & new = 21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed
100 78 100 45 100 33 37751 27684- : -- : -- - : -- : -- -- - : -45000 
{ 
    "age": 0."passwd": "123"."username": "rsj217"} youdaoplaceholder0 ~ curl-x POST HTTP:/ / 127.0.0.1:8000 / login - H "content-type: application/x - WWW - form - urlencoded" 3-d "username = rsj217 & new = 21" | python - m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
No JSON object could be decoded
Copy the code

As you can see, the binding tag fields (username and passwd) are set in the structure. If not passed, an error will be thrown. For non-banding fields (age) that are not passed by the client, the User structure will be filled with zero values. Parameters that do not exist in the User structure are automatically ignored.

If you change to JSON, the effect is similar:

Youdaoplaceholder0 ~ curlose-x POST HTTP://127.0.0.1:8000/login -h "content-Type :application/json" -d '{"username":" rsJ217 ", "passwd": "123", "age": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left  Speed
100 96 100 46 100 50 32670 35511- : -- : -- - : -- : -- -- - : -50000 
{ 
    "age": 21."passwd": "123"."username": "rsj217"} youdaoplaceholder0 ~ curl-x POST HTTP://127.0.0.1:8000/login -h "content-Type :application/json" -d '{"username":" rsJ217 ", "passwd": "123", "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left  Speed
100 95 100 45 100 50 49559 55066- : -- : -- - : -- : -- -- - : -50000 
{ 
    "age": 0."passwd": "123"."username": "rsj217"} youdaoplaceholder0 ~ curl-x POST HTTP://127.0.0.1:8000/login -h "content-type :application/json" -d '{"username":" rsJ217 ", "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left  Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
No JSONObject could be decoded ~ curl -X POST HTTP://127.0.0.1:8000/login -h "content-Type :application/json" -d '{"username":" rsJ217 ", "passwd": 123, "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left  Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
No JSON object could be decoded
Copy the code

Json has data types. Therefore, {“passwd”: “123”} and {“passwd”: 123} are different data types. Otherwise, an error may occur.

Of course, gin also provides a more advanced method, C. Bind, which automatically deduces whether a bind form or json parameter is a content-type.

router.POST("/login".func(c *gin.Context) 
{ 
    var user User err := c.Bind(&user) 
    iferr ! = nil { fmt.Println(err) log.Fatal(err) } c.JSON(http.StatusOK, gin.H{"username": user.Username, 
        "passwd": user.Passwd, 
        "age": user.Age, 
    }) 
})
Copy the code

Multi-format Rendering Since requests can use different Content-Types, so can responses. Typically the response will be HTML, Text, plain, JSON, XML, and so on. Gin provides a very elegant rendering method. So far, we have seen C.struing, C.json, c.HTML, and here is c.xml.

router.GET("/render".func(c *gin.Context) { 
    contentType := c.DefaultQuery("content_type"."json") 
    if contentType == "json" { 
        c.JSON(http.StatusOK, gin.H{ 
        "user": "rsj217"."passwd": "123"})},else if contentType == "xml" { 
        c.XML(http.StatusOK, gin.H{ 
            "user": "rsj217"."passwd": "123",})}})Copy the code

The result is as follows: Redirection GIN for redirected requests, it’s pretty simple. Call the context Redirect method:

router.GET("/redict/google".func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "https://google.com")})Copy the code

Packet routing Those of you who are familiar with Flask should be familiar with blueprint grouping. Flask provides blueprints for managing organizational grouping apis. Gin also provides the ability to make your code logic more modular, while grouping is also easy to define the scope of middleware use.

v1 := router.Group("/v1") 
v1.GET("/login".func(c *gin.Context) { 
    c.String(http.StatusOK, "v1 login")})v2 := router.Group("/v2") 
v2.GET("/login".func(c *gin.Context) { 
    c.String(http.StatusOK, "v2 login")})Copy the code

One of the key features of golang’s NET/HTTP design is the ease with which middleware can be built. Gin provides similar middleware. Note that the middleware only works with registered routing functions. For packet routing, middleware is nested and the scope of the middleware can be limited. Middleware is divided into global middleware, single routing middleware and group middleware.

Global middleware defines a middleware function:

func MiddleWare() gin.HandlerFunc { 
    return func(c *gin.Context) { 
        fmt.Println("before middleware") 
        c.Set("request"."clinet_request") 
        c.Next() 
        fmt.Println("before middleware")}}Copy the code

This function is simple and simply adds an attribute to the C context and assigns a value. The following route handler can extract its value based on the middleware decoration. Note that despite the name global middleware, routes set prior to the process of registering middleware will not be affected by the registered middleware. Middleware decorates routing function rules only if they are registered with middleware code.

router.Use(MiddleWare()) { 
    router.GET("/middleware".func(c *gin.Context) { 
        request := c.MustGet("request").(string) 
        req, _ := c.Get("request") 
        c.JSON(http.StatusOK, gin.H{ 
            "middile_request": request, "request": req, 
        }) 
    }) 
}
Copy the code

Use router to decorate the MiddleWare and then read the request value in /middlerware. Note that routing functions above router.use (MiddleWare()) code will not be decorated by MiddleWare.

The inclusion of decorated routing functions in curly braces is a code specification, even if there are no included routing functions, any routing using the Router is decorated. To differentiate the scope of permission, you can register the middleware with the objects returned by the group. Using MustGet to read c without registration will throw an error, and you can use Get instead.

The above registry decoration makes all the code written below default to using the router’s registered middleware.

Gin, of course, also provides registration for specified routing functions.

router.GET("/before", MiddleWare(), func(c *gin.Context) { 
    request := c.MustGet("request").(string) 
    c.JSON(http.StatusOK, gin.H{ 
        "middile_request": request, 
    }) 
})
Copy the code

Group middleware is similar to group middleware, just register the middleware function on the appropriate group route:

authorized := router.Group("/", MyMiddelware()) 
// Or use it like this:
authorized := router.Group("/") 
authorized.Use(MyMiddelware()) { 
    authorized.POST("/login", loginEndpoint) 
}
Copy the code

Groups can be nested because middleware can also be nested according to the group’s nesting rules.

Middleware practices The greatest use of middleware is for logging, error handlers, and authentication of some interfaces. The following implementation of a simple authentication middleware.

router.GET("/auth/signin".func(c *gin.Context) { 
    cookie := &http.Cookie{ 
        Name: "session_id".Value: "123".Path: "/".HttpOnly: true, 
    } 
    http.SetCookie(c.Writer, cookie) 
    c.String(http.StatusOK, "Login successful") 
}) 
 
router.GET("/home", AuthMiddleWare(), func(c *gin.Context) { 
    c.JSON(http.StatusOK, gin.H{"data": "home"})})Copy the code

The login function will set a cookie with session_id. Note that you need to specify path as /, otherwise gin will automatically set the cookie path as /auth. The logic of /home is simple. After registering with AuthMiddleWare, the logic of AuthMiddleWare will be executed before the logic of /home.

The code for AuthMiddleWare is as follows:

func AuthMiddleWare() gin.HandlerFunc { 
    return func(c *gin.Context) { 
        if cookie, err := c.Request.Cookie("session_id"); err == nil { 
            value := cookie.Value fmt.Println(value) 
            if value == "123" { c.Next() return } 
        } 
        c.JSON(http.StatusUnauthorized, gin.H{ 
            "error": "Unauthorized", 
        })
        c.Abort() 
        return}}Copy the code

The cookie is read from the context’s request, and then proofread the cookie, terminating the request and returning directly if there is a problem, using the c.abort () method.

In [7]: resp = requests.get('http://127.0.0.1:8000/home') 
 
In [8]: resp.json() 
Out[8]: {u'error': u'Unauthorized'} 
 
In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin') 
 
In [10]: login.cookies 
Out[10] :
      
        In [11]: Resp = requests. Get (' http://127.0.0.1:8000/home 'cookies = login. Cookies) In [12], resp. The json () Out [12] : {u' data ': u'home'}
      [cookie(version=0,>Copy the code

One of golang’s most powerful tools for high concurrency is the coroutine. Gin can use coroutines to implement asynchronous tasks. Because asynchronous processes are involved, the requested context needs to be copied to the asynchronous context, which is read-only.

router.GET("/sync".func(c *gin.Context) { 
    time.Sleep(5 * time.Second) 
    log.Println("Done! in path" + c.Request.URL.Path) 
}) 
 
router.GET("/async".func(c *gin.Context) { 
    cCp := c.Copy() 
    go func() { 
        time.Sleep(5 * time.Second) 
        log.Println("Done! in path" + cCp.Request.URL.Path) 
    }() 
})
Copy the code

At the time of the request, sleep5 seconds, synchronization logic can see that the service process is asleep. The asynchronous logic sees the response return, and the program is still in the background with the coroutine.

A custom router gin can not only Run with the router of the framework itself, but also with the functionality of NET/HTTP itself:

func main() {
    router := gin.Default()
    http.ListenAndServe(": 8080", router)
}
Copy the code

or

func main() { 
    router := gin.Default() 
    s := &http.Server{ 
        Addr: ": 8000".Handler: router, 
        ReadTimeout: 10 * time.Second, 
        WriteTimeout: 10 * time.Second, 
        MaxHeaderBytes: 1 << 20, 
    } 
    s.ListenAndServe() 
}
Copy the code

Of course, there is an elegant way to restart and end the process. The process of managing Golang with supervisor will be explored later.

Gin is a lightweight and powerful Golang Web framework. We have provided a brief overview of common development features. About service startup, processing of request parameters and rendering of response format, as well as for upload and middleware authentication examples are given. A better grasp comes from practice, and gin’s source notes are very detailed. Read the source code for more detailed functions and magic features.