go与web框架(gin)

    xiaoxiao2025-09-30  48

    参考

    官方中文文档:https://gin-gonic.com/zh-cn/docs/

    https://github.com/skyhee/gin-doc-cn

    https://www.jianshu.com/p/98965b3ff638/


    简单例子

    import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 } //打开浏览器得到json格式的文字:{"message":"pong"}

    详述

    HTTP 服务器

    router := gin.Default() router.Run() //默认8080端口 //router.Run(":8080") //或指定端口

    或者自定义服务器配置

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

    路由

    基本路由 gin 框架中采用的路由库是 httprouter。

    router := gin.Default() //创建带有默认中间件的路由:日志与恢复中间件 //router := gin.New() //创建不带中间件的路由 router.GET("/someGet", getting) router.POST("/somePost", posting) router.PUT("/somePut", putting) router.DELETE("/someDelete", deleting) router.PATCH("/somePatch", patching) router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options)

     路由组

    v1 := router.Group("/v1") //后面路径加在此后面 { v1.POST("/login", loginEndpoint) // /v1/login v1.POST("/submit", submitEndpoint) // /v1/submit v1.POST("/read", readEndpoint) // /v1/read }

    添加中间件

    r := gin.New() // 新建一个没有任何默认中间件的路由 // 全局中间件 // Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。 // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) // Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。 r.Use(gin.Recovery()) // 你可以为每个路由添加任意数量的中间件。 r.GET("/benchmark", MyBenchLogger(), benchEndpoint) //路由组中间件!在此例中,我们在"authorized"路由组中使用自定义创建的 AuthRequired()中间件 // authorized := r.Group("/", AuthRequired()) // 和使用以下两行代码的效果完全一样: authorized := r.Group("/") authorized.Use(AuthRequired()) { authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) // 嵌套路由组 testing := authorized.Group("testing") testing.GET("/analytics", analyticsEndpoint) }

     自定义中间件

    func Logger() gin.HandlerFunc { return func(c *gin.Context) { // 在gin上下文中定义变量 c.Set("example", "12345") fmt.Println("开始处理:") //处理请求前 c.Next() //执行r.GET里的func // 处理请求后 status := c.Writer.Status() log.Println(status) //200 } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) //获取gin上下文中的变量 log.Println(example) // 会打印: "12345" }) r.Run() } //依次打印:开始处理 // 12345 // 200

     在中间件及handler中启动新的 Goroutine

    不能使用原始的上下文,必须使用只读副本。

    r.GET("/long_async", func(c *gin.Context) { // 创建在 goroutine 中使用的副本 cCp := c.Copy() go func() { time.Sleep(5 * time.Second) // 用 time.Sleep() 模拟一个长任务。 log.Println("Done! in path " + cCp.Request.URL.Path) }() })

     获取参数

    // 此规则能够匹配/user/john这种格式,但不能匹配/user/ 或 /user这种格式 router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") }) // 但是,这个规则既能匹配/user/john/格式也能匹配/user/john/send这种格式 // 如果没有其他路由器匹配/user/john,它将重定向到/user/john/ router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") })

     获取GET方法的参数

    // url 为 http://localhost:8080/welcome?name=ningskyer时 router.GET("/welcome", func(c *gin.Context) { name := c.DefaultQuery("name", "Guest") //url中未传入name时,name取后面的默认值 lastname := c.Query("lastname") //是c.Request.URL.Query().Get("lastname") 的简写 })

    获取Post方法的参数

    router.POST("/form", func(c *gin.Context) { type := c.DefaultPostForm("type", "alert")//可设置默认值 msg := c.PostForm("msg") })

     POST请求同时url中携带参数

    //curl -d "name=lilei" "http://localhost:8080/?id=5" router.POST("/post", func(c *gin.Context) { id := c.Query("id") name := c.PostForm("name") })

    使用自定义结构绑定表单数据

    GET和POST中均可使用

    type StructA struct { FieldA string `form:"field_a"` } type StructB struct { StructA FieldB string `form:"field_b"` } func main() { r := gin.Default() r.GET("/", func (c *gin.Context) { var b StructB c.Bind(&b) //get或post请求中未携带的参数,结构体中保持初始值 fmt.Println(b) }) r.Run() } //浏览器访问:http://localhost:8080/?field_a=hello&field_b=world //打印:{{hello} world}

     JSON、XML、YAML的绑定和验证

    //binding是验证器,可添加多个参数: //如果有required且在绑定时该字段的值为空,将返回错误。 //如果有uuid或email且该字段绑定的值不是uuid或email标准格式的,将返回错误。 //如果有dive,它告诉required校验深入到slice、array这样的子结构体里。 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() // 绑定 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"}) }) // 绑定 XML ( // <?xml version="1.0" encoding="UTF-8"?> // <root> // <user>user</user> // <password>123</user> // </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"}) }) // 绑定 HTML 表单 (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // 根据 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"}) }) router.Run() }

    上传文件

    单文件

    // 为 multipart forms 设置内存限制 (默认是 32 MiB) // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { file, _ := c.FormFile("file") c.SaveUploadedFile(file, dst) //将上传的文件存至指定目录 })

    多文件

    router.POST("/upload", func(c *gin.Context) { form, _ := c.MultipartForm() files := form.File["upload[]"] for _, file := range files { c.SaveUploadedFile(file, dst) //将上传的文件存至指定目录 } })

    日志

    写日志文件

    //gin.DisableConsoleColor() // 禁用控制台颜色 f, _ := os.Create("gin.log") // 创建记录日志的文件 gin.DefaultWriter = io.MultiWriter(f) // 如果需要将日志同时写入文件和控制台,请使用以下代码 //gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })

    自定义日志格式

    router := gin.New() // LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter // By default gin.DefaultWriter = os.Stdout router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { // 你的自定义格式 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.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })

    静态资源服务

    router.Static("/assets", "./assets") router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico")

    模板

    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", }) })

    templates/index.tmpl

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

     使用不同目录下名称相同的模板

    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",}) })

    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 }}

    使用原生的模板渲染器

    import "html/template" html := template.Must(template.ParseFiles("file1", "file2")) router.SetHTMLTemplate(html)

    自定义分隔符

    router.Delims("{[{", "}]}") router.LoadHTMLGlob("/path/to/templates")

    自定义模板功能

    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("%dd/d", 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", map[string]interface{}{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) router.Run() }

    raw.tmpl

    使用了管道

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

    结果:

    Date: 2019/06/01

    将模板写入二进制文件

    你可以使用go-assets将服务器构建成一个包含模板的二进制文件

    func main() { r := gin.New() t, err := loadTemplate() if err != nil { panic(err) } r.SetHTMLTemplate(t) r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "/html/index.tmpl",nil) }) r.Run(":8080") } func loadTemplate() (*template.Template, error) { t := template.New("") for name, file := range Assets.Files { if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { continue } h, err := ioutil.ReadAll(file) if err != nil { return nil, err } t, err = t.New(name).Parse(string(h)) if err != nil { return nil, err } } return t, nil }

    参见examples/assets-in-binary目录中的例子

    重定向

    HTTP 重定向很容易。 内部、外部重定向均支持。

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

    路由重定向,使用 HandleContext:

    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"}) })

    Gin运行多个服务(多个端口)

     

    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{"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{"error": "Welcome server 01",},) }) 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 { return server01.ListenAndServe() }) g.Go(func() error { return server02.ListenAndServe() }) if err := g.Wait(); err != nil { log.Fatal(err) } }

     cookie的获取和设置

    router.GET("/cookie", func(c *gin.Context) { cookie, err := c.Cookie("gin_cookie") //获取cookie gin_cookie的值,没有则返回错误 if err != nil { cookie = "NotSet" //参数分别是name、value、maxAge、path、domain、secure、httpOnly c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) } fmt.Printf("Cookie value: %s \n", cookie) })

     

    最新回复(0)