当前位置:网站首页>[gin] gin framework for golang web development

[gin] gin framework for golang web development

2022-06-12 05:32:00 cbdgz

summary


gin The frame is Go web Among all the popular frameworks in development, the better ones
Github Address :

https://github.com/gin-gonic/gin

install Gin frame


$ go get -u github.com/gin-gonic/gin

https://raw.githubusercontent.com/gin-gonic/logo/master/color.png

Custom HTTP configuration Customize HTTP To configure


Use http.ListenAndServe() directly, like this:

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

or

func main() {
    
	router := gin.Default()

	s := &http.Server{
    
		Addr:           ":8080",
		Handler:        router,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	s.ListenAndServe()
}

Gin URL


For me gin Of url Absolutely all used web The most coquettish and powerful development framework

//  With parameters url parameters in path
	r.GET("/user/:name", func(context *gin.Context) {
    
		name := context.Param("name")
		context.String(http.StatusOK, "Hello %s", name)
	})
// Gin The framework supports the setting of default parameters 
r.GET("/index", func(c *gin.Context) {
    
		f := c.DefaultQuery("f", " The default value is ") //  Set the default value of the target parameter 
		l := c.Query("l")
		c.String(http.StatusOK, "Hello %s %s", f, l)
	})

r.POST("/post", func(c *gin.Context) {
    
		m := c.PostForm("m")
		n := c.DefaultPostForm("n", " The default value is ")

		c.JSON(http.StatusOK, gin.H{
    
			"status": "success",
			"msg":    m,
			"nick":   n,
		})
	})
  • Gin One of the coquettish : Support query parameters and post Parameters used together
// query+post form  The query parameter is ?a=1&b=2 Type of ,post form Is in body Inside 
	r.POST("/post2", func(c *gin.Context) {
    
		id := c.Query("id")
		page := c.DefaultQuery("page", "0")
		name := c.PostForm("name")
		msg := c.PostForm("msg")
		fmt.Printf("ID:%s page:%s name:%s msg:%s\n", id, page, name, msg)
	})
  • Gin Coquettish two : Support array type request parameters ( The first time I saw you, I was really shocked )
/*  Array type post POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 Content-Type: application/x-www-form-urlencoded names[first]=thinkerou&names[second]=tianou */
	r.POST("/post3", func(c *gin.Context) {
    

		ids := c.QueryMap("ids")
		names := c.PostFormMap("names")

		fmt.Printf("ids: %v; names: %v\n", ids, names)
	})

Gin File upload service


//  Upload files   The default file size limit is 32MiB
	r.MaxMultipartMemory = 8 << 20 //8MiB
	r.POST("/upload", func(c *gin.Context) {
    
		file, _ := c.FormFile("Filename")
		log.Println(file.Filename)
		//  Save the file to the destination path 
		err := c.SaveUploadedFile(file, "your save path")
		if err != nil {
    
			return
		}
		c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
	})

	//  Upload multiple files 
	r.POST("/uploads", func(c *gin.Context) {
    
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["Filename[]"]

		for _, file := range files {
    
			log.Println(file.Filename)

			// Upload the file to specific dst.
			err := c.SaveUploadedFile(file, "your save path")
			if err != nil {
    
				return
			}
		}
		c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
	})

Gin middleware


  • Do not use any middleware by default (Blank Gin without middleware by default)
r := gin.New() // Initialize routing without middleware 
r := gin.Default() // Use default logging and Recovery middleware The routing 

r.Run() // Default to 8080 Port boot web service 
r.Run(":9020") // With 9020 Port boot web service 
 
  • How to use middleware
r := gin.New()
r.Use(gin.Logger()) //  Use gin Framework's logging middleware ,By default gin.DefaultWriter = os.Stdout

//  Use error handling middleware 
// Recovery middleware recovers from any panics and writes a 500 if there was one
r.Use(gin.Recovery())

// For specific routes , Any number of middleware can be added 
r.GET("/exp",MyBenchLogger(),benchEndPoint)//first para is path,latest is handle func

// You can add middleware to the routing group 
v1 := r.Group("/v1")
v1.Use(AuthRequired()){
    
   v1.POST("/login",loginHandle)
   v1.POST("/logout",loginOutHandle)

   //  Groups can also be nested within groups  nested group
   testing := v1.Group("testing")
	 testing.GET("/analytics", analyticsEndpoint)
}
  • Define middleware
func Logger() gin.HandlerFunc {
    
	return func(c *gin.Context) {
    
		t := time.Now()

		// Set example variable
		c.Set("example", "12345")

		// before request

		c.Next()

		// after request
		latency := time.Since(t)
		log.Print(latency)

		// access the status we are sending
		status := c.Writer.Status()
		log.Println(status)
	}
}

func main() {
    
	r := gin.New()
	r.Use(Logger())

	r.GET("/test", func(c *gin.Context) {
    
		example := c.MustGet("example").(string)

		// it would print: "12345"
		log.Println(example)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}
  • Validate simple authentication middleware
// simulate some private data
//  Record the information of the corresponding user 
var secrets = gin.H{
    
	"foo":    gin.H{
    "email": "[email protected]", "phone": "123433"},
	"austin": gin.H{
    "email": "[email protected]", "phone": "666"},
	"lena":   gin.H{
    "email": "[email protected]", "phone": "523443"},
}

func main() {
    
	r := gin.Default()

	// Group using gin.BasicAuth() middleware
	// gin.Accounts is a shortcut for map[string]string
//  Set the user name and password allowed to log in 
	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
    
		"foo":    "bar",
		"austin": "1234",
		"lena":   "hello2",
		"manu":   "4321",
	}))

	// /admin/secrets endpoint
	// hit "localhost:8080/admin/secrets
	authorized.GET("/secrets", func(c *gin.Context) {
    
		// get user, it was set by the BasicAuth middleware
		user := c.MustGet(gin.AuthUserKey).(string)
		if secret, ok := secrets[user]; ok {
    
			c.JSON(http.StatusOK, gin.H{
    "user": user, "secret": secret})
		} else {
    
			c.JSON(http.StatusOK, gin.H{
    "user": user, "secret": "NO SECRET :("})
		}
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

Goroutines inside a middleware
When starting new Goroutines inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.— Start a new... In middleware or handler goroutine when , you Should not Use the original context , You must use a read-only copy .

func main() {
    
	r := gin.Default()

	r.GET("/long_async", func(c *gin.Context) {
    
		// create copy to be used inside the goroutine
		cCp := c.Copy()
		go func() {
    
			// simulate a long task with time.Sleep(). 5 seconds
			time.Sleep(5 * time.Second)

			// note that you are using the copied context "cCp", IMPORTANT
			log.Println("Done! in path " + cCp.Request.URL.Path)
		}()
	})

	r.GET("/long_sync", func(c *gin.Context) {
    
		// simulate a long task with time.Sleep(). 5 seconds
		time.Sleep(5 * time.Second)

		// since we are NOT using a goroutine, we do not have to copy the context
		log.Println("Done! in path " + c.Request.URL.Path)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

Custom log output format Custom Log Format


func main() {
    
	router := gin.New()

	// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
	// By default gin.DefaultWriter = os.Stdout
	router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
    

		// your custom format
		return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
				param.ClientIP,
				param.TimeStamp.Format(time.RFC1123),
				param.Method,
				param.Path,
				param.Request.Proto,
				param.StatusCode,
				param.Latency,
				param.Request.UserAgent(),
				param.ErrorMessage,
		)
	}))
	router.Use(gin.Recovery())

	router.GET("/ping", func(c *gin.Context) {
    
		c.String(200, "pong")
	})

	router.Run(":8080")
}

Model binding for request parameters Model binding and validation


// Binding from JSON,binding:"required"// Indicates that the parameter must be submitted 
type Login struct {
    
	User     string `form:"user" json:"user" xml:"user" binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

func main() {
    
	router := gin.Default()

	// Example for binding JSON ({"user": "manu", "password": "123"})
	router.POST("/loginJSON", func(c *gin.Context) {
    
		var json Login
		if err := c.ShouldBindJSON(&json); err != nil {
    
			c.JSON(http.StatusBadRequest, gin.H{
    "error": err.Error()})
			return
		}

		if json.User != "manu" || json.Password != "123" {
    
			c.JSON(http.StatusUnauthorized, gin.H{
    "status": "unauthorized"})
			return
		}

		c.JSON(http.StatusOK, gin.H{
    "status": "you are logged in"})
	})

	// Example for binding XML (
	// <?xml version="1.0" encoding="UTF-8"?>
	// <root>
	// <user>manu</user>
	// <password>123</password>
	// </root>)
	router.POST("/loginXML", func(c *gin.Context) {
    
		var xml Login
		if err := c.ShouldBindXML(&xml); err != nil {
    
			c.JSON(http.StatusBadRequest, gin.H{
    "error": err.Error()})
			return
		}

		if xml.User != "manu" || xml.Password != "123" {
    
			c.JSON(http.StatusUnauthorized, gin.H{
    "status": "unauthorized"})
			return
		}

		c.JSON(http.StatusOK, gin.H{
    "status": "you are logged in"})
	})

	// Example for binding a HTML form (user=manu&password=123)
	router.POST("/loginForm", func(c *gin.Context) {
    
		var form Login
		// This will infer what binder to use depending on the content-type header.
		if err := c.ShouldBind(&form); err != nil {
    
			c.JSON(http.StatusBadRequest, gin.H{
    "error": err.Error()})
			return
		}

		if form.User != "manu" || form.Password != "123" {
    
			c.JSON(http.StatusUnauthorized, gin.H{
    "status": "unauthorized"})
			return
		}

		c.JSON(http.StatusOK, gin.H{
    "status": "you are logged in"})
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}
  • Parameter validation fails
$ curl -v -X POST \
  http://localhost:8080/loginJSON \
  -H 'content-type: application/json' \
  -d '{ "user": "manu" }'
> POST /loginJSON HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Fri, 04 Aug 2017 03:51:31 GMT
< Content-Length: 100
<
{
    "error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

Custom request parameter validation


package main

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

// Booking contains binded and validated data.
type Booking struct {
    
//binding Set the parameter to must and must follow bookabledate The rules of ,time_format Set the date format 
	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
//gifield=CheckIn Express check_out The date of must be earlier than check_in On the evening of 
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

// Must return if the rules of the custom validator are met true
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
    
	date, ok := fl.Field().Interface().(time.Time)
	if ok {
    
		today := time.Now()
		if today.After(date) {
    
			return false
		}
	}
	return true
}

func main() {
    
	route := gin.Default()

	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    
		v.RegisterValidation("bookabledate", bookableDate)
	}

	route.GET("/bookable", getBookable)
	route.Run(":8085")
}

func getBookable(c *gin.Context) {
    
	var b Booking
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
    
		c.JSON(http.StatusOK, gin.H{
    "message": "Booking dates are valid!"})
	} else {
    
		c.JSON(http.StatusBadRequest, gin.H{
    "error": err.Error()})
	}
}
  • The test sample
$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
{
    "message":"Booking dates are valid!"}

$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
{
    "error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}

$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
{
    "error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%

Parameter binding Uri (Bind Uri)


package main

import "github.com/gin-gonic/gin"

type Person struct {
    
	ID string `uri:"id" binding:"required,uuid"`
	Name string `uri:"name" binding:"required"`
}

func main() {
    
	route := gin.Default()
	route.GET("/:name/:id", func(c *gin.Context) {
    
		var person Person
		if err := c.ShouldBindUri(&person); err != nil {
    
			c.JSON(400, gin.H{
    "msg": err.Error()})
			return
		}
		c.JSON(200, gin.H{
    "name": person.Name, "uuid": person.ID})
	})
	route.Run(":8088")
}

Parameter request header (Bind Header)


Little cheese : Request header verification can be used to block most crawlers lacking request headers

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type testHeader struct {
    
	Rate   int    `header:"Rate"`
	Domain string `header:"Domain"`
}

func main() {
    
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
    
		h := testHeader{
    }

		if err := c.ShouldBindHeader(&h); err != nil {
    
			c.JSON(200, err)
		}

		fmt.Printf("%#v\n", h)
		c.JSON(200, gin.H{
    "Rate": h.Rate, "Domain": h.Domain})
	})

	r.Run()
}

Use curl Access the target interface

 *client
 curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
 output
 {
     "Domain":"music","Rate":300}*

Bind HTML checkboxes

In fact, it's just , Receive parameters of array type

type myForm struct {
     
    Colors []string `form:"colors[]"`
}

...

func formHandler(c *gin.Context) {
     
    var fakeForm myForm
    c.ShouldBind(&fakeForm)
    c.JSON(200, gin.H{
     "color": fakeForm.Colors})
}

Gin Framework of the JSON Introduce


relevant json Introduction to hijacking :
JSON hijacked

Using SecureJSON to prevent json hijacking. Default prepends "while(1)," to response body if the given struct is array values.

func main() {
    
	r := gin.Default()

	// You can also use your own secure json prefix
	// r.SecureJsonPrefix(")]}',\n")

	r.GET("/someJSON", func(c *gin.Context) {
    
		names := []string{
    "lena", "austin", "foo"}

		// Will output : while(1);["lena","austin","foo"]
		c.SecureJSON(http.StatusOK, names)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

func main() {
    
	r := gin.Default()

	r.GET("/JSONP", func(c *gin.Context) {
    
		data := gin.H{
    
			"foo": "bar",
		}

		//callback is x
		// Will output : x({\"foo\":\"bar\"})
		c.JSONP(http.StatusOK, data)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")

        // client
        // curl http://127.0.0.1:8080/JSONP?callback=x
}


unc main() {
    
	r := gin.Default()

	r.GET("/someJSON", func(c *gin.Context) {
    
		data := gin.H{
    
			"lang": "GO Language ",
			"tag":  "<br>",
		}

		// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
		c.AsciiJSON(http.StatusOK, data)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

Normally, JSON replaces special HTML characters with their unicode entities, e.g.< becomes \u003c. If you want to encode such characters literally, you can use PureJSON instead.
This feature is unavailable in Go 1.6 and lower.

Usually ,JSON Use it unicode Entities replace special HTML character , for example “<` become “\u003c”. If you want to encode these characters literally , have access to PureJSON.

This function is available in Go 1.6 Not available in and earlier versions .

func main() {
    
	r := gin.Default()

	// Serves unicode entities
	r.GET("/json", func(c *gin.Context) {
    
		c.JSON(200, gin.H{
    
			"html": "<b>Hello, world!</b>",
		})
	})

	// Serves literal characters
	r.GET("/purejson", func(c *gin.Context) {
    
		c.PureJSON(200, gin.H{
    
			"html": "<b>Hello, world!</b>",
		})
	})

	// listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

Gin file management


func main() {
    
	router := gin.Default()
	router.Static("/assets", "./assets")
	router.StaticFS("/more_static", http.Dir("my_file_system"))
	router.StaticFile("/favicon.ico", "./resources/favicon.ico")

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}
func main() {
    
	router := gin.Default()

	router.GET("/local/file", func(c *gin.Context) {
    
		c.File("local/file.go")
	})

	var fs http.FileSystem = // ...
	router.GET("/fs/file", func(c *gin.Context) {
    
		c.FileFromFS("fs/file.go", fs)
	})
}
func main() {
    
	router := gin.Default()
	router.GET("/someDataFromReader", func(c *gin.Context) {
    
		response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
		if err != nil || response.StatusCode != http.StatusOK {
    
			c.Status(http.StatusServiceUnavailable)
			return
		}

		reader := response.Body
 		defer reader.Close()
		contentLength := response.ContentLength
		contentType := response.Header.Get("Content-Type")

		extraHeaders := map[string]string{
    
			"Content-Disposition": `attachment; filename="gopher.png"`,
		}

		c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
	})
	router.Run(":8080")
}

HTML Template


Using LoadHTMLGlob() or LoadHTMLFiles()

func main() {
    
  router := gin.Default()
  router.LoadHTMLGlob("templates/*")
  //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
  router.GET("/index", func(c *gin.Context) {
    
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
    
      "title": "Main website",
    })
  })
  router.Run(":8080")
}

templates/index.tmpl

<html>
  <h1>
    {
   { .title }}
  </h1>
</html>

Using templates with same name in different directories

func main() {
    
  router := gin.Default()
  router.LoadHTMLGlob("templates/**/*")
  router.GET("/posts/index", func(c *gin.Context) {
    
    c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
    
      "title": "Posts",
    })
  })
  router.GET("/users/index", func(c *gin.Context) {
    
    c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
    
      "title": "Users",
    })
  })
  router.Run(":8080")
}

templates/posts/index.tmpl

{
   { define "posts/index.tmpl" }}
<html><h1>
  {
   { .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{
   { end }}

templates/users/index.tmpl

{
   { define "users/index.tmpl" }}
<html><h1>
  {
   { .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{
   { end }}

Custom Template renderer

You can also use your own html template render

import "html/template"

func main() {
    
  router := gin.Default()
  html := template.Must(template.ParseFiles("file1", "file2"))
  router.SetHTMLTemplate(html)
  router.Run(":8080")
}

Custom Delimiters

You may use custom delims

  r := gin.Default()
  r.Delims("{[{", "}]}")
  r.LoadHTMLGlob("/path/to/templates")

Custom Template Funcs

See the detail example code.

main.go

import (
    "fmt"
    "html/template"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
)

func formatAsDate(t time.Time) string {
    
    year, month, day := t.Date()
    return fmt.Sprintf("%d%02d/%02d", year, month, day)
}

func main() {
    
    router := gin.Default()
    router.Delims("{[{", "}]}")
    router.SetFuncMap(template.FuncMap{
    
        "formatAsDate": formatAsDate,
    })
    router.LoadHTMLFiles("./testdata/template/raw.tmpl")

    router.GET("/raw", func(c *gin.Context) {
    
        c.HTML(http.StatusOK, "raw.tmpl", gin.H{
    
            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
        })
    })

    router.Run(":8080")
}

raw.tmpl

Date: {[{.now | formatAsDate}]}

Result:

Date: 2017/07/01

Multitemplate

Gin allow by default use only one html.Template. Check a multitemplate render for using features like go 1.6 block template.

Gin Redirect


r.GET("/test", func(c *gin.Context) {
    
  c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})

Issuing a HTTP redirect from POST. Refer to issue: #444

r.POST("/test", func(c *gin.Context) {
    
  c.Redirect(http.StatusFound, "/foo")
})

Issuing a Router redirect, use HandleContext like below.

r.GET("/test", func(c *gin.Context) {
    
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    
    c.JSON(200, gin.H{
    "hello": "world"})
})

Support Let’s Encrypt


Let’s Encrypt Introduction to :

Let’s Encrypt Introduce

  • example for 1-line LetsEncrypt HTTPS servers
package main

import (
	"log"

	"github.com/gin-gonic/autotls"
	"github.com/gin-gonic/gin"
)

func main() {
    
	r := gin.Default()

	// Ping handler
	r.GET("/ping", func(c *gin.Context) {
    
		c.String(200, "pong")
	})

	log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
}
  • example for custom autocert manager
package main

import (
	"log"

	"github.com/gin-gonic/autotls"
	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/acme/autocert"
)

func main() {
    
	r := gin.Default()

	// Ping handler
	r.GET("/ping", func(c *gin.Context) {
    
		c.String(200, "pong")
	})

	m := autocert.Manager{
    
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
		Cache:      autocert.DirCache("/var/www/.cache"),
	}

	log.Fatal(autotls.RunWithManager(r, &m))
}

utilize Gin Open multiple services


Take a chestnut

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

var (
	g errgroup.Group
)

func router01() http.Handler {
    
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
    
		c.JSON(
			http.StatusOK,
			gin.H{
    
				"code":  http.StatusOK,
				"error": "Welcome server 01",
			},
		)
	})

	return e
}

func router02() http.Handler {
    
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
    
		c.JSON(
			http.StatusOK,
			gin.H{
    
				"code":  http.StatusOK,
				"error": "Welcome server 02",
			},
		)
	})

	return e
}

func main() {
    
	server01 := &http.Server{
    
		Addr:         ":8080",
		Handler:      router01(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server02 := &http.Server{
    
		Addr:         ":8081",
		Handler:      router02(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	g.Go(func() error {
    
		err := server01.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
    
			log.Fatal(err)
		}
		return err
	})

	g.Go(func() error {
    
		err := server02.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
    
			log.Fatal(err)
		}
		return err
	})

	if err := g.Wait(); err != nil {
    
		log.Fatal(err)
	}
}

Set and get cookie


import (
    "fmt"

    "github.com/gin-gonic/gin"
)

func main() {
    

    router := gin.Default()

    router.GET("/cookie", func(c *gin.Context) {
    

        cookie, err := c.Cookie("gin_cookie")

        if err != nil {
    
            cookie = "NotSet"
            c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
        }

        fmt.Printf("Cookie value: %s \n", cookie)
    })

    router.Run()
}

Test paper


package main

func setupRouter() *gin.Engine {
    
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
    
		c.String(200, "pong")
	})
	return r
}

func main() {
    
	r := setupRouter()
	r.Run(":8080")
}

Test for code example above

package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestPingRoute(t *testing.T) {
    
	router := setupRouter()

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/ping", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, "pong", w.Body.String())
}
原网站

版权声明
本文为[cbdgz]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/03/202203010616200263.html