gin-contrib/cors、Kafka服务 等

发布时间:2025-06-24 19:34:40  作者:北方职教升学中心  阅读量:081


加载模板,启动 HTTP 服务器:./main.go

packagemainimport("github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.LoadHTMLGlob("./templates/*.html")// 把 "./templates/" 目录下的所有 html 文件加载为模板,对应的模板名称为文件名// router.LoadHTMLFiles("./templates/hello.html") // 加载单个 HTML 文件模板// GET /hellorouter.GET("/hello",func(ctx *gin.Context){// 渲染名称为 "hello.html" 的已加载模板,并向模板中传入一个对象ctx.HTML(http.StatusOK,"hello.html",gin.H{"name":"tom","age":18,})})err :=router.Run(":8080")iferr !=nil{panic(err)}}

测试访问:

$ curlhttp://localhost:8080/hello<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Hello</title></head><body>My name is tom, age is 18.</body></html>

14.2 自定义模板名称

LoadHTMLGlob()LoadHTMLFiles()加载的 HTML 模板,默认使用文件名作为模板名称。

packagemainimport("context""errors""github.com/gin-gonic/gin""log""net/http""os""os/signal""time")funcmain(){router :=gin.Default()router.GET("/ping",func(ctx *gin.Context){ctx.String(http.StatusOK,"pong")})server :=&http.Server{Addr:":8080",Handler:router,}// 异步启动服务gofunc(){err :=server.ListenAndServe()iferr !=nil&&!errors.Is(err,http.ErrServerClosed){log.Fatalf("listen error: %+vn",err)}}()// 监听中断信号quit :=make(chanos.Signal,1)signal.Notify(quit,os.Interrupt)<-quit	log.Println("Interrupt signal received.")// 收到中断信号后,主动关闭服务(设置5秒超时时间,超过5秒没有正常关闭服务,则取消上下文)ctx,cancel :=context.WithTimeout(context.Background(),5*time.Second)defercancel()// 如果顺利关闭服务,则提前取消掉上下文err :=server.Shutdown(ctx)// 主动关闭服务,关闭服务前有5秒钟时间做收尾工作(例如关闭其他关联的服务,把缓存的日志刷到磁盘)iferr !=nil{log.Fatalf("Server Shutdown Error: %+vn",err)}log.Println("Server Shutdown.")}

启动服务后,按 Ctrl + C 向进程发出中断信号。

15.3 运行多个服务

一个进程可以运行多个服务,包括 HTTP服务、

管理多个服务,可以使用 golang.org/x/sync/errgroup 或 sync.WaitGroup。

  • ctx.GetPostFormArray(key string) (values []string, ok bool):以数组的方式获取表单值。
  • 设置/获取 Cookie 方法:

    • ctx.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool):设置 Cookie 到响应头。

      8.1 自定义中间件

      packagemainimport("fmt""github.com/gin-gonic/gin""net/http")// Middleware1 返回一个中间件函数funcMiddleware1()gin.HandlerFunc {returnfunc(ctx *gin.Context){fmt.Println("Middleware1 Start")ctx.Set("name","tom")// 在 Context 中保存变量ctx.Next()// 调用下一个中间件,如果没有更多中间件,则调用 handlerfmt.Println("Middleware1 End")}}// Middleware2 一个中间件函数funcMiddleware2(ctx *gin.Context){name,_:=ctx.Get("name")// 获取 Context 中保存的变量fmt.Println("Middleware2 Start, name:",name)ctx.Next()// 调用下一个中间件,如果没有更多中间件,则调用 handlerfmt.Println("Middleware2 End")}funcmain(){router :=gin.Default()// 注册中间件,多个中间件将依次调用router.Use(Middleware1())router.Use(Middleware2)router.GET("/ping",func(ctx *gin.Context){name,_:=ctx.Get("name")fmt.Println("GET Handler: name:",name)ctx.String(http.StatusOK,"pong")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

      访问 http://localhost:8080/ping,输出日志:

      Middleware1 StartMiddleware2 Start, name: tomGET Handler: name: tomMiddleware2 EndMiddleware1 End

      8.2 日志中间件

      packagemainimport("fmt""github.com/gin-gonic/gin""net/http""time")funcLogFormatter(param gin.LogFormatterParams)string{// 自定义日志格式returnfmt.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,)}funcmain(){router :=gin.Default()// gin.LoggerWithFormatter 中间件会写入日志到 gin.DefaultWriter// 默认 gin.DefaultWriter = os.Stdoutrouter.Use(gin.LoggerWithFormatter(LogFormatter))router.GET("/ping",func(ctx *gin.Context){ctx.String(http.StatusOK,"pong")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

      8.3 BasicAuth 中间件

      packagemainimport("github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()// 全局注册 BasicAuth 中间件 (也可以在某个 path 注册, 或某个路由组注册)router.Use(gin.BasicAuth(gin.Accounts{"user1":"pass1",// 账号 {用户名: 密码}"user2":"pass2",}))router.GET("/user",func(ctx *gin.Context){// 获取通过 BasicAuth 中间件认证的用户名,在 gin.BasicAuth 中通过 ctx.Set(gin.AuthUserKey, user) 设置user :=ctx.MustGet(gin.AuthUserKey).(string)ctx.String(http.StatusOK,"hello %s",user)})err :=router.Run(":8080")iferr !=nil{panic(err)}}

      测试访问:

      curl-v"http://user1:pass1@localhost:8080/user"

      8.4 Gzip 中间件

      Gin 官方的 GitHub 中提供了许多中间件,例如:gin-contrib/gzip、默认文件名为 HTML 文件模板的名称。router:=gin.Default()type Person struct {Name string `json:"name"`Age int `json:"age"`}// GET /get_json, 相当于: r.Handle("GET", path, handlers)router.GET("/get_json",func(ctx *gin.Context){p:=&Person{Name:"tom",Age:18,}// 将给定的结构体序列化为 JSON 写入响应 Body 中,并设置响应头 "Content-Type: application/json; charset=utf-8"ctx.JSON(http.StatusOK,p)// ctx.AsciiJSON(code int, obj any) // 非 ASCII 字符以 \u 的形式表示// 其他自动序列化对象响应的方法// ctx.YAML(code int, obj any) // Content-Type: application/yaml; charset=utf-8// ctx.TOML(code int, obj any) // Content-Type: application/toml; charset=utf-8// ctx.XML(code int, obj any) // Content-Type: application/xml; charset=utf-8// ctx.ProtoBuf(http.StatusOK, resBody) // Content-Type: application/x-protobuf})err:=router.Run(":8080")iferr !=nil {panic(err)}}

      2.2 通用响应数据

      ctx.String()方法用于将字符串写入到 Body,并设置 Content-Type: text/plain; charset=utf-8响应头。

      errgroup.Group内部使用 sync.WaitGroup实现,但增加了对返回错误的任务的处理。gin-contrib/i18n 等,具体参考 gin-contrib — GitHub。// 当传递给 group.Go() 的函数第一次返回非 nil error 或 第一次 group.Wait() 返回时(以先发生者为准),返回的 ctx 会被取消group,ctx :=errgroup.WithContext(ctx)// HTTP 服务router1 :=gin.Default()router1.GET("/ping",func(ctx *gin.Context){ctx.String(http.StatusOK,"pong")})server1 :=&http.Server{Addr:":8080",Handler:router1,}// 在新的 goroutine 中启动服务group.Go(func()error{log.Println("Server1 Start.")returnserver1.ListenAndServe()})// HTTP 服务router2 :=gin.Default()router2.GET("/ping",func(ctx *gin.Context){ctx.String(http.StatusOK,"pong")})server2 :=&http.Server{Addr:":8081",Handler:router1,}group.Go(func()error{log.Println("Server2 Start.")returnserver2.ListenAndServe()})// 异步接收中断信号的服务group.Go(func()error{quit :=make(chanos.Signal,1)signal.Notify(quit,os.Interrupt)<-quit log.Println("Interrupt Signal Received.")// 收到中断信号后,主动关闭服务(超时5秒则强制取消上下文)ctx,cancel :=context.WithTimeout(context.Background(),5*time.Second)defercancel()// 主动关闭 HTTP 服务iferr :=server1.Shutdown(ctx);err !=nil{log.Printf("Shutdown Server1 Error: %+vn",err)}else{log.Println("Server1 Shutdown.")}// 主动关闭 HTTP 服务iferr :=server2.Shutdown(ctx);err !=nil{log.Printf("Shutdown Server2 Error: %+vn",err)}else{log.Println("Server2 Shutdown.")}log.Println("All Server Safely Exited.")returnnil})// 等待组内所有 goroutine 结束(如果其中一个 goroutine 返回了 err,则会取消 ctx),// 返回的 err 是这一组 goroutine 中第一个返回的非 nil erroriferr :=group.Wait();err !=nil&&!errors.Is(err,http.ErrServerClosed){log.Fatalln(err)}// group.Wait() 内部也是通过调用 sync.WaitGroup 的 Wait() 方法实现的等待log.Println("APP EXISTS.")}

    因此可以直接使用 Go 内置的 net/http模块把 *gin.Engine实例作为 http.Handler启动 HTTP/HTTPS 服务器。gin-contrib/cors、Kafka服务 等。

    HTML 渲染器方法:

    • ctx.HTML(code int, name string, obj any):渲染 HTML 模板,name 表示加载的模板名称,obj 是模板中可以使用对象。

      HandlerFunc是一个函数类型:type HandlerFunc func(*gin.Context)。不使用默认中间件,可以通过 gin.New() 创建实例。请求处理器函数的参数就是一个 gin.Context实例,形参通常记作 ctx。自动设置 Content-Type: text/html; charset=utf-8响应头。管理流程、

    • ctx.GetQuery(key string) (string, bool):获取指定名称的 Query 参数值,返回 (参数值, 是否存在)

      原文链接:https://xiets.blog.csdn.net/article/details/144357170

      版权声明:原创文章禁止转载

      专栏目录:Golang 专栏(总目录)

      文章目录

      • 1. 快速入门
      • 2 响应渲染器
        • 2.1 JSON/JSON/YAML/ProtoBuf 渲染器
        • 2.2 通用响应数据
      • 3. 路由参数与自动重定向
      • 4. Query 参数
      • 5. 处理表单提交
      • 6. 绑定 Request Body
      • 7. 设置响应头、YAML、TOML 格式的内容,Gin 支持直接将其绑定到对象,相关方法:

        • ctx.ShouldBindBodyWithJSON(obj any) error
        • ctx.ShouldBindBodyWithXML(obj any) error
        • ctx.ShouldBindBodyWithYAML(obj any) error
        • ctx.ShouldBindBodyWithTOML(obj any) error
        • ctx.ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error):上面的方法内部调用的是此方法。
        • engine.LoadHTMLFiles(files ...string):加载一段 HTML 文件,并将结果与 HTML 渲染器相关联。error传播 和 上下文取消功能。

          在 HTML 模板文件中通过 {{ define "name"}}{{ end}}包裹 HTML 自定义 HTML 对应的模板名称。gRPC服务、

          14.3 自定义模板功能

          模板中通过 {{ .attr}}可以直接引用对象属性,还可以在其中调用一个函数:

          packagemainimport("github.com/gin-gonic/gin""html/template""net/http""time")funcFormatAsDate(t time.Time)string{returnt.Format("2006-01-02 15:04:05")}funcmain(){router :=gin.Default()// 设置函数映射router.SetFuncMap(template.FuncMap{"formatAsDate":FormatAsDate,})// 加载 HTML 模板router.LoadHTMLGlob("./*.tmpl")router.GET("/get-date",func(ctx *gin.Context){ctx.HTML(http.StatusOK,"get-date.tmpl",gin.H{"now":time.Now(),})})err :=router.Run(":8080")iferr !=nil{panic(err)}}

          在模板中调用函数:./get-date.tmpl

          Date: {{ .now | formatAsDate}}

          结果:

          Date: 2025-10-01 15:00:00

          15. 其他

          15.1 gin.Context 副本

          如果要在中间件中使用 Goroutine 执行异步任务,则使用的上下文必须是 gin.Context 的副本:

          packagemainimport("github.com/gin-gonic/gin""log""net/http""time")funcmain(){router :=gin.Default()router.GET("/run-async-task",func(ctx *gin.Context){cpCtx :=ctx.Copy()gofunc(){time.Sleep(5*time.Second)log.Println("Done! in path ",cpCtx.Request.URL.Path)}()ctx.String(http.StatusOK,"task start")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

          15.2 优雅地重启或停止

          优雅地重启或停止服务,主要是通过接收操作系统发出的 中断信号(os.Interrupt) 或 强制停止信号(os.Kill),然后做收尾工作,再主动关闭服务。

        • ctx.ShouldBind(obj any) error:根据请求头中的 Content-Type字段值自动选择绑定类型,例如:
          • "application/json"--> JSON binding
          • "application/xml"--> XML binding

        如果要获取 Request Body 原始数据,可以调用 ctx.GetRawData() ([]byte, error)

        绑定 Request Body 示例:

        packagemainimport("fmt""github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.POST("/post",func(ctx *gin.Context){typePerson struct{Name string`json:"name" yaml:"name" toml:"name" binding:"required"`Age  int`json:"age" yaml:"age" toml:"age" binding:"required"`}p :=&Person{}err :=ctx.ShouldBind(p)// 自动识别 Request Body 内容类型, 并绑定到对象iferr !=nil{// 如果不满足 类型 和 required 约束, 则返回 errorpanic(err)}fmt.Printf("p: %+v\n",p)ctx.String(http.StatusOK,"succeed")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

        测试:

        curl-H"Content-Type: application/json"-d'{"name": "tom", "age": 18}'http://localhost:8080/post

        7. 设置响应头、

      • ctx.MultipartForm() (*multipart.Form, error):获取解析后的复合表单,包括文件字段。

        packagemainimport("bytes""fmt""github.com/gin-gonic/gin""net/http")func main(){router:=gin.Default()// GET /get_inforouter.GET("/get_info",func(ctx *gin.Context){resBody:=bytes.NewBuffer(nil)resBody.WriteString("RemoteIP: "+ctx.RemoteIP()+"n")resBody.WriteString("FullPath: "+ctx.FullPath()+"n")resBody.WriteString(fmt.Sprintf("http.Request: %+v",ctx.Request))// ctx.Request 是 *http.Request 类型// 文本响应:"Content-Type: text/plain; charset=utf-8"ctx.String(http.StatusOK,resBody.String())// 其他任意数据响应// ctx.Data(code int, contentType string, data []byte)// ctx.DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)})err:=router.Run(":8080")iferr !=nil {panic(err)}}

        3. 路由参数与自动重定向

        packagemainimport("fmt""github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()// 路径参数:路径中带有 name 和 id 两个参数router.GET("/user/:name/:id",func(ctx *gin.Context){// 遍历路径中的所有参数for_,param :=rangectx.Params {fmt.Printf("Params: %s=%sn",param.Key,param.Value)}// 路径中的指定参数 (参数值均以字符串返回, 如果不存在则返回空字符串)fmt.Printf("id=%s\n",ctx.Param("id"))fmt.Printf("name=%s\n",ctx.Param("name"))// 路径中的参数绑定到结构体中typePerson struct{ID   int64`uri:"id" binding:"required"`Name string`uri:"name" binding:"required"`}p :=&Person{}err :=ctx.ShouldBindUri(p)// 如果参数值无法解析为字段需要的类型 或者 不符合required约束, 则返回 erroriferr !=nil{panic(err)}fmt.Printf("p=%+v\n",p)ctx.String(http.StatusOK,"succeed")})// 路径泛匹配:以 "/home/*" 开头的路径都会匹配到此路由,action 用于接收 "/home" 后面的路径(以"/"开头)router.GET("/home/*action",func(ctx *gin.Context){action :=ctx.Param("action")ctx.String(http.StatusOK,action)})// 自动重定向:如果注册了 "/hello",但没注册 "/hello/",则后者会自动重定向到前者router.GET("/hello",func(ctx *gin.Context){ctx.String(http.StatusOK,"/hello")})// 自动重定向:如果注册了 "/world/",但没注册 "/world",则后者会自动重定向到前者router.GET("/world/",func(ctx *gin.Context){ctx.String(http.StatusOK,"/world")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

        测试访问:

        • http://localhost:8080/user/abc/123
        • http://localhost:8080/home/abc/def/123
        • http://localhost:8080/hello/
        • http://localhost:8080/world

        4. Query 参数

        *gin.Context中获取 Query 参数值的相关方法:

        • ctx.Query(key string) (value string):获取指定名称的 Query 参数值,没有则返回空字符串。
        • ctx.Cookie(name string) (string, error):从请求头中获取 Cookie。它采用了类似 Martini 的 API,但性能比 Martini 快 40 倍。
        • 同时支持 GETHEAD请求。模板文件本身也会以文件名为名称注册一个模板,其模板内容为 {{ define "name"}} ... {{ end}}之外的内容。

          2 响应渲染器

          gin.Context 是 Gin 最重要的部分。

        • ctx.GetPostForm(key string) (string, bool):获取表单中指定字段的值,返回 (字段值, 是否存在)

          获取 multipart/form-data复合表单中的文件字段:

          • ctx.FormFile(name string) (*multipart.FileHeader, error):获取复合表单中的文件字段。默认文件名为 HTML 文件模板的名称。
          • ctx.DefaultQuery(key, defaultValue string) string:获取指定名称的 Query 参数值,如果不存在则返回 defaultValue

            渲染器方法示例:

            packagemainimport("github.com/gin-gonic/gin""net/http")func main(){// 创建一个 gin.Engine 实例,其中已附加 gin.Logger() 和 gin.Recovery() 中间件。

          处理表单提交示例:

          packagemainimport("fmt""github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()// 为 multipart forms 设置较低的内存限制 (默认是 32 MB)router.MaxMultipartMemory =10<<20// 10 MBrouter.POST("/post",func(ctx *gin.Context){// 获取表单字段name :=ctx.PostForm("name")age :=ctx.DefaultPostForm("age","18")fmt.Printf("name=%s, age=%s\n",name,age)// 获取上传的文件file,err :=ctx.FormFile("file")iferr !=nil{panic(err)}iffile !=nil{fmt.Printf("file: name=%s, size=%d, MIME=%s\n",file.Filename,file.Size,file.Header["Content-Type"])// file.Open() // 打开文件流// 保存文件到指定路径err =ctx.SaveUploadedFile(file,"./"+file.Filename)iferr !=nil{panic(err)}}ctx.String(http.StatusOK,"succeed")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

          测试:

          curl-F"name=tom"-F"age=18"-F"file=@/path/demo.png""http://localhost:8080/post"

          6. 绑定 Request Body

          如果 Request Body 是 JSON、

          errgroup包为执行共同任务的子任务 goroutine 组提供了同步、

          2.1 JSON/JSON/YAML/ProtoBuf 渲染器

          gin.Context提供了许多方便使用的渲染器方法,它可以把一个 结构体实例 或 map/slice 自动序列化为你所需要的数据类型写入响应 Body,并自动添加对应的 Content-Type到响应头。

          ctx.Data()ctx.DataFromReader()方法则用于将任意二进制数据写入响应 Body,并自定义 Content-Type

          渲染器方法包括:

          • ctx.JSON(code int, resBody)
          • ctx.AsciiJSON(code int, obj any)
          • ctx.YAML(code int, resBody)
          • ctx.TOML(code int, resBody)
          • ctx.XML(code int, resBody)
          • ctx.ProtoBuf(code int, resBody)

          以上方法实际内部调用的是一个渲染方法 ctx.Render(code int, r render.Render),通过内置的各种渲染器实现自动序列化,也可以自定义渲染器。

          安装 GZIP 中间件:

          go get github.com/gin-contrib/gzip

          给支持 gzip压缩的客户端进行 gzip 压缩:

          packagemainimport("github.com/gin-contrib/gzip""github.com/gin-gonic/gin""net/http""time")funcmain(){router :=gin.Default()// 注册 gzip 插件,使用默认的压缩等级,排除 ".pdf" 和 ".mp4" 后缀的资源router.Use(gzip.Gzip(gzip.DefaultCompression,gzip.WithExcludedExtensions([]string{".pdf",".mp4"})))// gzip.WithExcludedPathsRegexs() // 排除指定的路径规则 (正则匹配)// gzip.WithExcludedPaths()       // 排除指定的路径router.GET("/ping",func(ctx *gin.Context){text :="pong\n"fori :=0;i <100;i++{text +=time.Now().String()+"n"}ctx.String(http.StatusOK,text)})err :=router.Run(":8080")iferr !=nil{panic(err)}}

          9. 路由组

          packagemainimport("github.com/gin-gonic/gin")funcmain(){router :=gin.Default()v1 :=router.Group("/v1"){// v1.Use(...)                     // 路由组内中间件v1.GET("/home",v1HomeHander)// 访问: GET "/v1/home"v1.POST("/submit",v1SubmitHander)// 访问: POST "/v1/submit"}v2 :=router.Group("/v1"){v2.GET("/home",v2HomeHander)// 访问: GET "/v2/home"v2.POST("/submit",v2SubmitHander)// 访问: POST "/v2/submit"}err :=router.Run(":8080")iferr !=nil{panic(err)}}

          10. 重定向与转发

          packagemainimport("github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.GET("/hi",func(ctx *gin.Context){ctx.String(http.StatusOK,"hi")})router.GET("/hello1",func(ctx *gin.Context){// 外部重定向(由客户端重定向)ctx.Redirect(http.StatusMovedPermanently,"/hi")})router.GET("/hello2",func(ctx *gin.Context){// 内部重定向(服务端转发)ctx.Request.URL.Path ="/hi"router.HandleContext(ctx)})err :=router.Run(":8080")iferr !=nil{panic(err)}}

          11. 静态文件服务

          packagemainimport("github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.Static("/assets","./assets")router.StaticFS("/more_static",http.Dir("my_file_system"))router.StaticFile("/favicon.ico","./resources/favicon.ico")err :=router.Run(":8080")iferr !=nil{panic(err)}}

          静态文件服务,支持以下功能:

          • 响应头中会返回 Last-Modified,再次请求时如果携带 If-Modified-Since,如果内容没改变则会返回 304 Not Modified
          • 支持 Range: bytes=start-end请求头。

          以数组/Map的方式获取 Query 参数值:

          • ctx.QueryArray(key string) (values []string)
          • ctx.GetQueryArray(key string) (values []string, ok bool)

          Query 参数绑定到对象:

          • ctx.ShouldBindQuery(obj any) error

          Query 参数获取示例:

          packagemainimport("fmt""github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.GET("/get",func(ctx *gin.Context){name :=ctx.Query("name")// 参数值自动 UrlDecodeage :=ctx.Query("age")fmt.Printf("name=%s, age=%s\n",name,age)typePerson struct{Name string`query:"name"`// 参数值自动 UrlDecodeAge  int`query:"age"`}p :=&Person{}err :=ctx.ShouldBindQuery(p)iferr !=nil{panic(err)}fmt.Printf("p=%+v\n",p)ctx.String(http.StatusOK,"succeed")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

          访问:http://localhost:8080/get?name=tom&age=18

          5. 处理表单提交

          获取 Form 表单字段的相关方法:

          • ctx.PostForm(key string) (value string):获取表单中指定字段的值。

            import"net/http"funcmain(){router :=gin.Default()http.ListenAndServe(":8080",router)}

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

            实际上 gin.Default().Run()内部也是通过调用 http.ListenAndServe()启动服务。

            13. HTTP2 Server Push

            packagemainimport("github.com/gin-gonic/gin""html/template""log""net/http")funcmain(){// 内存中创建一个 HTML 模板,名称为 "index"indexHtml,err :=template.New("index").Parse(`		<!DOCTYPE html>		<html lang="en">		<head>			<meta charset="UTF-8">			<title>Hello</title>			<script src="/assets/app.js"></script>		</head>		<body>		Hello World		</body>		</html>	`)iferr !=nil{panic(err)}router :=gin.Default()router.Static("/assets","./assets")router.SetHTMLTemplate(indexHtml)router.GET("/",func(ctx *gin.Context){// 主动推送资源ifpusher :=ctx.Writer.Pusher();pusher !=nil{iferr =pusher.Push("/assets/app.js",nil);err !=nil{log.Printf("Push Failed: %+vn",err)}}ctx.HTML(http.StatusOK,"index",gin.H{})})// 必须是 TLS 服务才支持 HTTP2 Server Pusherr =router.RunTLS(":8080","cert.pem","key.pem")iferr !=nil{panic(err)}}

            14. HTML 渲染器

            使用 HTML 渲染器,要先加载 HTML 模板文件,相关方法:

            • engine.LoadHTMLGlob(pattern string):加载由 glob pattern 标识的 HTML 文件,并将结果与 HTML 渲染器相关联。HTML 模板文件,可以命名为 *.tmpl,例如下面一个模板文件:./templates/hello.tmpl

              {{ define "hello-tmpl"}}<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Hello</title></head><body>hello-tmpl: My name is {{ .name}}, age is {{ .age}}.</body></html>{{ end}}

              上面模板文件中,模板名称为 hello-tmpl,当加载这个模板文件后,自动注册该模板,由 {{ define "name"}}{{ end}}中间包裹的内容就是该模板名称对应的 HTML 渲染内容:

              packagemainimport("github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.LoadHTMLGlob("./templates/*.tmpl")// router.LoadHTMLFiles("./templates/hello.tmpl")router.GET("/hello",func(ctx *gin.Context){// 渲染名称为 "hello-tmpl" 的已加载模板,并向模板中传入一个对象ctx.HTML(http.StatusOK,"hello-tmpl",gin.H{"name":"tom","age":18,})})err :=router.Run(":8080")iferr !=nil{panic(err)}}

              测试访问:

              $ curlhttp://localhost:8080/hello<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Hello</title></head><body>hello-tmpl: My name is tom, age is 18.</body></html>

              一个 HTML 模板文件中,可以有多组 {{ define "name"}} ... {{ end}},分别对应多个模板。XML、设置/获取Cookie

              设置响应头方法:

              • ctx.Header(key, value string):设置(覆盖)响应头,如果 value=""则移除响应头。

                安装 errgroup

                go get -ugolang.org/x/sync/errgroup

                下面代码中启动两个 HTTP 服务 和 一个等待接收中断信号的服务,并在接收到中断信号时优雅地关闭服务:

                packagemainimport("context""errors""github.com/gin-gonic/gin""golang.org/x/sync/errgroup""log""net/http""os""os/signal""time")funcmain(){ctx :=context.Background()// 返回一个新的 errgroup.Group 和一个从 ctx 派生的关联 Context。
              • ctx.SaveUploadedFile(file *multipart.FileHeader, dst string) error:保存表单文件字段的文件内容到指定路径。
              • ctx.DefaultPostForm(key, defaultValue string) string:获取表单中指定字段的值,如果不存在则返回 defaultValue

                相关网站:

                • Gin GitHub:https://github.com/gin-gonic/gin
                • Gin 文档:https://gin-gonic.com/zh-cn/docs/
                • API 文档:https://pkg.go.dev/github.com/gin-gonic/gin

                1. 快速入门

                安装 Gin:

                $ go get -ugithub.com/gin-gonic/gin

                基础示例:

                packagemainimport("github.com/gin-gonic/gin""net/http")funcmain(){r :=gin.Default()r.GET("/ping",func(ctx *gin.Context){// gin.H 的底层类型是 map[string]anyctx.JSON(http.StatusOK,gin.H{"message":"pong",})})err :=r.Run()// listen and serve on "0.0.0.0:8080" (default)iferr !=nil{panic(err)}}// 浏览器访问: http://localhost:8080/ping

                gin.Context是一个包含了 RequestResponseWriter等信息的结构体,*gin.Context实现了 context.Context接口。验证请求的 JSON 并呈现 JSON 响应。设置/获取Cookie

              • 8. 中间件
                • 8.1 自定义中间件
                • 8.2 日志中间件
                • 8.3 BasicAuth 中间件
                • 8.4 Gzip 中间件
              • 9. 路由组
              • 10. 重定向与转发
              • 11. 静态文件服务
              • 12. 自定义 HTTP 配置
              • 13. HTTP2 Server Push
              • 14. HTML 渲染器
                • 14.1 HTML 渲染器简单示例
                • 14.2 自定义模板名称
                • 14.3 自定义模板功能
              • 15. 其他
                • 15.1 gin.Context 副本
                • 15.2 优雅地重启或停止
                • 15.3 运行多个服务

      Gin 是一个用 Go(Golang)编写的 HTTP Web 框架。

    12. 自定义 HTTP 配置

    gin.Default()gin.New()返回的是一个 *gin.Engine实例,它实现了 gin.IRoutes路由接口,也是实现了 http.Handler接口。模板中引用对象属性,默认使用 {{}}分隔符,可以通过 router.Delims(left, right string)自定义设置分隔符,例如:router.Delims("{[{", "}]}")

    14.1 HTML 渲染器简单示例

    下面是一个 HTML 模板文件:./templates/hello.html

    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Hello</title></head><body>My name is {{ .name}}, age is {{ .age}}.</body></html>

    {{ .name}}{{ .age}}表示引用对象的属性插值。

  • ctx.PostFormArray(key string) (values []string):以数组的方式获取表单值。gin-contrib/sessions、
  • packagemainimport("errors""fmt""github.com/gin-gonic/gin""net/http")funcmain(){router :=gin.Default()router.POST("/login",func(ctx *gin.Context){ctx.Header("X-Server","My Server")username :=ctx.PostForm("username")password :=ctx.PostForm("password")ifusername =="tom"&&password =="123"{ctx.SetCookie("token","uuid*****",3600,"/","localhost",false,true)ctx.String(http.StatusOK,"succeed")}else{ctx.String(http.StatusOK,"failed")}})router.GET("/home",func(ctx *gin.Context){token,err :=ctx.Cookie("token")iferr !=nil{iferrors.Is(err,http.ErrNoCookie){fmt.Println("no token cookie")}else{panic(err)}}else{fmt.Println("Token:",token)}ctx.Header("X-Server","My Server")ctx.String(http.StatusOK,"home")})err :=router.Run(":8080")iferr !=nil{panic(err)}}

    测试:

    curl-v-d"username=tom&password=123"http://localhost:8080/logincurl-v-H"Cookie: token=uuid%2A%2A%2A%2A%2A"http://localhost:8080/home

    8. 中间件

    中间件注册方法:ctx.Use(middleware ...HandlerFunc) IRoutes。它允许我们在中间件之间传递变量、

    上面获取表单字段值的方法,同时支持 application/x-www-form-urlencodedmultipart/form-data格式的表单。