...

Text file src/github.com/gin-gonic/gin/docs/doc.md

Documentation: github.com/gin-gonic/gin/docs

     1# Gin Quick Start
     2
     3## Contents
     4
     5- [Build Tags](#build-tags)
     6  - [Build with json replacement](#build-with-json-replacement)
     7  - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature)
     8- [API Examples](#api-examples)
     9  - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
    10  - [Parameters in path](#parameters-in-path)
    11  - [Querystring parameters](#querystring-parameters)
    12  - [Multipart/Urlencoded Form](#multiparturlencoded-form)
    13  - [Another example: query + post form](#another-example-query--post-form)
    14  - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
    15  - [Upload files](#upload-files)
    16    - [Single file](#single-file)
    17    - [Multiple files](#multiple-files)
    18  - [Grouping routes](#grouping-routes)
    19  - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
    20  - [Using middleware](#using-middleware)
    21  - [Custom Recovery behavior](#custom-recovery-behavior)
    22  - [How to write log file](#how-to-write-log-file)
    23  - [Custom Log Format](#custom-log-format)
    24  - [Controlling Log output coloring](#controlling-log-output-coloring)
    25  - [Model binding and validation](#model-binding-and-validation)
    26  - [Custom Validators](#custom-validators)
    27  - [Only Bind Query String](#only-bind-query-string)
    28  - [Bind Query String or Post Data](#bind-query-string-or-post-data)
    29  - [Bind Uri](#bind-uri)
    30  - [Bind Header](#bind-header)
    31  - [Bind HTML checkboxes](#bind-html-checkboxes)
    32  - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
    33  - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering)
    34    - [SecureJSON](#securejson)
    35    - [JSONP](#jsonp)
    36    - [AsciiJSON](#asciijson)
    37    - [PureJSON](#purejson)
    38  - [Serving static files](#serving-static-files)
    39  - [Serving data from file](#serving-data-from-file)
    40  - [Serving data from reader](#serving-data-from-reader)
    41  - [HTML rendering](#html-rendering)
    42    - [Custom Template renderer](#custom-template-renderer)
    43    - [Custom Delimiters](#custom-delimiters)
    44    - [Custom Template Funcs](#custom-template-funcs)
    45  - [Multitemplate](#multitemplate)
    46  - [Redirects](#redirects)
    47  - [Custom Middleware](#custom-middleware)
    48  - [Using BasicAuth() middleware](#using-basicauth-middleware)
    49  - [Goroutines inside a middleware](#goroutines-inside-a-middleware)
    50  - [Custom HTTP configuration](#custom-http-configuration)
    51  - [Support Let's Encrypt](#support-lets-encrypt)
    52  - [Run multiple service using Gin](#run-multiple-service-using-gin)
    53  - [Graceful shutdown or restart](#graceful-shutdown-or-restart)
    54    - [Third-party packages](#third-party-packages)
    55    - [Manually](#manually)
    56  - [Build a single binary with templates](#build-a-single-binary-with-templates)
    57  - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
    58  - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
    59  - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
    60  - [http2 server push](#http2-server-push)
    61  - [Define format for the log of routes](#define-format-for-the-log-of-routes)
    62  - [Set and get a cookie](#set-and-get-a-cookie)
    63- [Don't trust all proxies](#dont-trust-all-proxies)
    64- [Testing](#testing)
    65
    66## Build tags
    67
    68### Build with json replacement
    69
    70Gin uses `encoding/json` as default json package but you can change it by build from other tags.
    71
    72[jsoniter](https://github.com/json-iterator/go)
    73
    74```sh
    75go build -tags=jsoniter .
    76```
    77
    78[go-json](https://github.com/goccy/go-json)
    79
    80```sh
    81go build -tags=go_json .
    82```
    83
    84[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.)
    85
    86```sh
    87$ go build -tags="sonic avx" .
    88```
    89
    90### Build without `MsgPack` rendering feature
    91
    92Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.
    93
    94```sh
    95go build -tags=nomsgpack .
    96```
    97
    98This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).
    99
   100## API Examples
   101
   102You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples).
   103
   104### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
   105
   106```go
   107func main() {
   108  // Creates a gin router with default middleware:
   109  // logger and recovery (crash-free) middleware
   110  router := gin.Default()
   111
   112  router.GET("/someGet", getting)
   113  router.POST("/somePost", posting)
   114  router.PUT("/somePut", putting)
   115  router.DELETE("/someDelete", deleting)
   116  router.PATCH("/somePatch", patching)
   117  router.HEAD("/someHead", head)
   118  router.OPTIONS("/someOptions", options)
   119
   120  // By default it serves on :8080 unless a
   121  // PORT environment variable was defined.
   122  router.Run()
   123  // router.Run(":3000") for a hard coded port
   124}
   125```
   126
   127### Parameters in path
   128
   129```go
   130func main() {
   131  router := gin.Default()
   132
   133  // This handler will match /user/john but will not match /user/ or /user
   134  router.GET("/user/:name", func(c *gin.Context) {
   135    name := c.Param("name")
   136    c.String(http.StatusOK, "Hello %s", name)
   137  })
   138
   139  // However, this one will match /user/john/ and also /user/john/send
   140  // If no other routers match /user/john, it will redirect to /user/john/
   141  router.GET("/user/:name/*action", func(c *gin.Context) {
   142    name := c.Param("name")
   143    action := c.Param("action")
   144    message := name + " is " + action
   145    c.String(http.StatusOK, message)
   146  })
   147
   148  // For each matched request Context will hold the route definition
   149  router.POST("/user/:name/*action", func(c *gin.Context) {
   150    b := c.FullPath() == "/user/:name/*action" // true
   151    c.String(http.StatusOK, "%t", b)
   152  })
   153
   154  // This handler will add a new router for /user/groups.
   155  // Exact routes are resolved before param routes, regardless of the order they were defined.
   156  // Routes starting with /user/groups are never interpreted as /user/:name/... routes
   157  router.GET("/user/groups", func(c *gin.Context) {
   158    c.String(http.StatusOK, "The available groups are [...]")
   159  })
   160
   161  router.Run(":8080")
   162}
   163```
   164
   165### Querystring parameters
   166
   167```go
   168func main() {
   169  router := gin.Default()
   170
   171  // Query string parameters are parsed using the existing underlying request object.
   172  // The request responds to an url matching:  /welcome?firstname=Jane&lastname=Doe
   173  router.GET("/welcome", func(c *gin.Context) {
   174    firstname := c.DefaultQuery("firstname", "Guest")
   175    lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
   176
   177    c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
   178  })
   179  router.Run(":8080")
   180}
   181```
   182
   183### Multipart/Urlencoded Form
   184
   185```go
   186func main() {
   187  router := gin.Default()
   188
   189  router.POST("/form_post", func(c *gin.Context) {
   190    message := c.PostForm("message")
   191    nick := c.DefaultPostForm("nick", "anonymous")
   192
   193    c.JSON(http.StatusOK, gin.H{
   194      "status":  "posted",
   195      "message": message,
   196      "nick":    nick,
   197    })
   198  })
   199  router.Run(":8080")
   200}
   201```
   202
   203### Another example: query + post form
   204
   205```sh
   206POST /post?id=1234&page=1 HTTP/1.1
   207Content-Type: application/x-www-form-urlencoded
   208
   209name=manu&message=this_is_great
   210```
   211
   212```go
   213func main() {
   214  router := gin.Default()
   215
   216  router.POST("/post", func(c *gin.Context) {
   217
   218    id := c.Query("id")
   219    page := c.DefaultQuery("page", "0")
   220    name := c.PostForm("name")
   221    message := c.PostForm("message")
   222
   223    fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
   224  })
   225  router.Run(":8080")
   226}
   227```
   228
   229```sh
   230id: 1234; page: 1; name: manu; message: this_is_great
   231```
   232
   233### Map as querystring or postform parameters
   234
   235```sh
   236POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
   237Content-Type: application/x-www-form-urlencoded
   238
   239names[first]=thinkerou&names[second]=tianou
   240```
   241
   242```go
   243func main() {
   244  router := gin.Default()
   245
   246  router.POST("/post", func(c *gin.Context) {
   247
   248    ids := c.QueryMap("ids")
   249    names := c.PostFormMap("names")
   250
   251    fmt.Printf("ids: %v; names: %v", ids, names)
   252  })
   253  router.Run(":8080")
   254}
   255```
   256
   257```sh
   258ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]
   259```
   260
   261### Upload files
   262
   263#### Single file
   264
   265References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single).
   266
   267`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693)
   268
   269> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.
   270
   271```go
   272func main() {
   273  router := gin.Default()
   274  // Set a lower memory limit for multipart forms (default is 32 MiB)
   275  router.MaxMultipartMemory = 8 << 20  // 8 MiB
   276  router.POST("/upload", func(c *gin.Context) {
   277    // Single file
   278    file, _ := c.FormFile("file")
   279    log.Println(file.Filename)
   280
   281    // Upload the file to specific dst.
   282    c.SaveUploadedFile(file, dst)
   283
   284    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
   285  })
   286  router.Run(":8080")
   287}
   288```
   289
   290How to `curl`:
   291
   292```bash
   293curl -X POST http://localhost:8080/upload \
   294  -F "file=@/Users/appleboy/test.zip" \
   295  -H "Content-Type: multipart/form-data"
   296```
   297
   298#### Multiple files
   299
   300See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
   301
   302```go
   303func main() {
   304  router := gin.Default()
   305  // Set a lower memory limit for multipart forms (default is 32 MiB)
   306  router.MaxMultipartMemory = 8 << 20  // 8 MiB
   307  router.POST("/upload", func(c *gin.Context) {
   308    // Multipart form
   309    form, _ := c.MultipartForm()
   310    files := form.File["upload[]"]
   311
   312    for _, file := range files {
   313      log.Println(file.Filename)
   314
   315      // Upload the file to specific dst.
   316      c.SaveUploadedFile(file, dst)
   317    }
   318    c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
   319  })
   320  router.Run(":8080")
   321}
   322```
   323
   324How to `curl`:
   325
   326```bash
   327curl -X POST http://localhost:8080/upload \
   328  -F "upload[]=@/Users/appleboy/test1.zip" \
   329  -F "upload[]=@/Users/appleboy/test2.zip" \
   330  -H "Content-Type: multipart/form-data"
   331```
   332
   333### Grouping routes
   334
   335```go
   336func main() {
   337  router := gin.Default()
   338
   339  // Simple group: v1
   340  v1 := router.Group("/v1")
   341  {
   342    v1.POST("/login", loginEndpoint)
   343    v1.POST("/submit", submitEndpoint)
   344    v1.POST("/read", readEndpoint)
   345  }
   346
   347  // Simple group: v2
   348  v2 := router.Group("/v2")
   349  {
   350    v2.POST("/login", loginEndpoint)
   351    v2.POST("/submit", submitEndpoint)
   352    v2.POST("/read", readEndpoint)
   353  }
   354
   355  router.Run(":8080")
   356}
   357```
   358
   359### Blank Gin without middleware by default
   360
   361Use
   362
   363```go
   364r := gin.New()
   365```
   366
   367instead of
   368
   369```go
   370// Default With the Logger and Recovery middleware already attached
   371r := gin.Default()
   372```
   373
   374### Using middleware
   375
   376```go
   377func main() {
   378  // Creates a router without any middleware by default
   379  r := gin.New()
   380
   381  // Global middleware
   382  // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
   383  // By default gin.DefaultWriter = os.Stdout
   384  r.Use(gin.Logger())
   385
   386  // Recovery middleware recovers from any panics and writes a 500 if there was one.
   387  r.Use(gin.Recovery())
   388
   389  // Per route middleware, you can add as many as you desire.
   390  r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
   391
   392  // Authorization group
   393  // authorized := r.Group("/", AuthRequired())
   394  // exactly the same as:
   395  authorized := r.Group("/")
   396  // per group middleware! in this case we use the custom created
   397  // AuthRequired() middleware just in the "authorized" group.
   398  authorized.Use(AuthRequired())
   399  {
   400    authorized.POST("/login", loginEndpoint)
   401    authorized.POST("/submit", submitEndpoint)
   402    authorized.POST("/read", readEndpoint)
   403
   404    // nested group
   405    testing := authorized.Group("testing")
   406    // visit 0.0.0.0:8080/testing/analytics
   407    testing.GET("/analytics", analyticsEndpoint)
   408  }
   409
   410  // Listen and serve on 0.0.0.0:8080
   411  r.Run(":8080")
   412}
   413```
   414
   415### Custom Recovery behavior
   416
   417```go
   418func main() {
   419  // Creates a router without any middleware by default
   420  r := gin.New()
   421
   422  // Global middleware
   423  // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
   424  // By default gin.DefaultWriter = os.Stdout
   425  r.Use(gin.Logger())
   426
   427  // Recovery middleware recovers from any panics and writes a 500 if there was one.
   428  r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {
   429    if err, ok := recovered.(string); ok {
   430      c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
   431    }
   432    c.AbortWithStatus(http.StatusInternalServerError)
   433  }))
   434
   435  r.GET("/panic", func(c *gin.Context) {
   436    // panic with a string -- the custom middleware could save this to a database or report it to the user
   437    panic("foo")
   438  })
   439
   440  r.GET("/", func(c *gin.Context) {
   441    c.String(http.StatusOK, "ohai")
   442  })
   443
   444  // Listen and serve on 0.0.0.0:8080
   445  r.Run(":8080")
   446}
   447```
   448
   449### How to write log file
   450
   451```go
   452func main() {
   453  // Disable Console Color, you don't need console color when writing the logs to file.
   454  gin.DisableConsoleColor()
   455
   456  // Logging to a file.
   457  f, _ := os.Create("gin.log")
   458  gin.DefaultWriter = io.MultiWriter(f)
   459
   460  // Use the following code if you need to write the logs to file and console at the same time.
   461  // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
   462
   463  router := gin.Default()
   464  router.GET("/ping", func(c *gin.Context) {
   465      c.String(http.StatusOK, "pong")
   466  })
   467
   468   router.Run(":8080")
   469}
   470```
   471
   472### Custom Log Format
   473
   474```go
   475func main() {
   476  router := gin.New()
   477
   478  // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
   479  // By default gin.DefaultWriter = os.Stdout
   480  router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
   481
   482    // your custom format
   483    return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
   484        param.ClientIP,
   485        param.TimeStamp.Format(time.RFC1123),
   486        param.Method,
   487        param.Path,
   488        param.Request.Proto,
   489        param.StatusCode,
   490        param.Latency,
   491        param.Request.UserAgent(),
   492        param.ErrorMessage,
   493    )
   494  }))
   495  router.Use(gin.Recovery())
   496
   497  router.GET("/ping", func(c *gin.Context) {
   498    c.String(http.StatusOK, "pong")
   499  })
   500
   501  router.Run(":8080")
   502}
   503```
   504
   505Sample Output
   506
   507```sh
   508::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
   509```
   510
   511### Controlling Log output coloring
   512
   513By default, logs output on console should be colorized depending on the detected TTY.
   514
   515Never colorize logs:
   516
   517```go
   518func main() {
   519  // Disable log's color
   520  gin.DisableConsoleColor()
   521
   522  // Creates a gin router with default middleware:
   523  // logger and recovery (crash-free) middleware
   524  router := gin.Default()
   525
   526  router.GET("/ping", func(c *gin.Context) {
   527      c.String(http.StatusOK, "pong")
   528  })
   529
   530  router.Run(":8080")
   531}
   532```
   533
   534Always colorize logs:
   535
   536```go
   537func main() {
   538  // Force log's color
   539  gin.ForceConsoleColor()
   540
   541  // Creates a gin router with default middleware:
   542  // logger and recovery (crash-free) middleware
   543  router := gin.Default()
   544
   545  router.GET("/ping", func(c *gin.Context) {
   546      c.String(http.StatusOK, "pong")
   547  })
   548
   549  router.Run(":8080")
   550}
   551```
   552
   553### Model binding and validation
   554
   555To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
   556
   557Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
   558
   559Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
   560
   561Also, Gin provides two sets of methods for binding:
   562
   563- **Type** - Must bind
   564  - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
   565  - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
   566- **Type** - Should bind
   567  - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
   568  - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
   569
   570When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
   571
   572You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned.
   573
   574```go
   575// Binding from JSON
   576type Login struct {
   577  User     string `form:"user" json:"user" xml:"user"  binding:"required"`
   578  Password string `form:"password" json:"password" xml:"password" binding:"required"`
   579}
   580
   581func main() {
   582  router := gin.Default()
   583
   584  // Example for binding JSON ({"user": "manu", "password": "123"})
   585  router.POST("/loginJSON", func(c *gin.Context) {
   586    var json Login
   587    if err := c.ShouldBindJSON(&json); err != nil {
   588      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
   589      return
   590    }
   591
   592    if json.User != "manu" || json.Password != "123" {
   593      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
   594      return
   595    }
   596
   597    c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
   598  })
   599
   600  // Example for binding XML (
   601  //  <?xml version="1.0" encoding="UTF-8"?>
   602  //  <root>
   603  //    <user>manu</user>
   604  //    <password>123</password>
   605  //  </root>)
   606  router.POST("/loginXML", func(c *gin.Context) {
   607    var xml Login
   608    if err := c.ShouldBindXML(&xml); err != nil {
   609      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
   610      return
   611    }
   612
   613    if xml.User != "manu" || xml.Password != "123" {
   614      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
   615      return
   616    }
   617
   618    c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
   619  })
   620
   621  // Example for binding a HTML form (user=manu&password=123)
   622  router.POST("/loginForm", func(c *gin.Context) {
   623    var form Login
   624    // This will infer what binder to use depending on the content-type header.
   625    if err := c.ShouldBind(&form); err != nil {
   626      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
   627      return
   628    }
   629
   630    if form.User != "manu" || form.Password != "123" {
   631      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
   632      return
   633    }
   634
   635    c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
   636  })
   637
   638  // Listen and serve on 0.0.0.0:8080
   639  router.Run(":8080")
   640}
   641```
   642
   643Sample request
   644
   645```sh
   646$ curl -v -X POST \
   647  http://localhost:8080/loginJSON \
   648  -H 'content-type: application/json' \
   649  -d '{ "user": "manu" }'
   650> POST /loginJSON HTTP/1.1
   651> Host: localhost:8080
   652> User-Agent: curl/7.51.0
   653> Accept: */*
   654> content-type: application/json
   655> Content-Length: 18
   656>
   657* upload completely sent off: 18 out of 18 bytes
   658< HTTP/1.1 400 Bad Request
   659< Content-Type: application/json; charset=utf-8
   660< Date: Fri, 04 Aug 2017 03:51:31 GMT
   661< Content-Length: 100
   662<
   663{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
   664```
   665
   666Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
   667
   668### Custom Validators
   669
   670It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go).
   671
   672```go
   673package main
   674
   675import (
   676  "net/http"
   677  "time"
   678
   679  "github.com/gin-gonic/gin"
   680  "github.com/gin-gonic/gin/binding"
   681  "github.com/go-playground/validator/v10"
   682)
   683
   684// Booking contains binded and validated data.
   685type Booking struct {
   686  CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
   687  CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
   688}
   689
   690var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
   691  date, ok := fl.Field().Interface().(time.Time)
   692  if ok {
   693    today := time.Now()
   694    if today.After(date) {
   695      return false
   696    }
   697  }
   698  return true
   699}
   700
   701func main() {
   702  route := gin.Default()
   703
   704  if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
   705    v.RegisterValidation("bookabledate", bookableDate)
   706  }
   707
   708  route.GET("/bookable", getBookable)
   709  route.Run(":8085")
   710}
   711
   712func getBookable(c *gin.Context) {
   713  var b Booking
   714  if err := c.ShouldBindWith(&b, binding.Query); err == nil {
   715    c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
   716  } else {
   717    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
   718  }
   719}
   720```
   721
   722```console
   723$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
   724{"message":"Booking dates are valid!"}
   725
   726$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
   727{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
   728
   729$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
   730{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
   731```
   732
   733[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
   734See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.
   735
   736### Only Bind Query String
   737
   738`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
   739
   740```go
   741package main
   742
   743import (
   744  "log"
   745  "net/http"
   746
   747  "github.com/gin-gonic/gin"
   748)
   749
   750type Person struct {
   751  Name    string `form:"name"`
   752  Address string `form:"address"`
   753}
   754
   755func main() {
   756  route := gin.Default()
   757  route.Any("/testing", startPage)
   758  route.Run(":8085")
   759}
   760
   761func startPage(c *gin.Context) {
   762  var person Person
   763  if c.ShouldBindQuery(&person) == nil {
   764    log.Println("====== Only Bind By Query String ======")
   765    log.Println(person.Name)
   766    log.Println(person.Address)
   767  }
   768  c.String(http.StatusOK, "Success")
   769}
   770
   771```
   772
   773### Bind Query String or Post Data
   774
   775See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).
   776
   777```go
   778package main
   779
   780import (
   781  "log"
   782  "net/http"
   783  "time"
   784
   785  "github.com/gin-gonic/gin"
   786)
   787
   788type Person struct {
   789  Name       string    `form:"name"`
   790  Address    string    `form:"address"`
   791  Birthday   time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
   792  CreateTime time.Time `form:"createTime" time_format:"unixNano"`
   793  UnixTime   time.Time `form:"unixTime" time_format:"unix"`
   794}
   795
   796func main() {
   797  route := gin.Default()
   798  route.GET("/testing", startPage)
   799  route.Run(":8085")
   800}
   801
   802func startPage(c *gin.Context) {
   803  var person Person
   804  // If `GET`, only `Form` binding engine (`query`) used.
   805  // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
   806  // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
   807  if c.ShouldBind(&person) == nil {
   808    log.Println(person.Name)
   809    log.Println(person.Address)
   810    log.Println(person.Birthday)
   811    log.Println(person.CreateTime)
   812    log.Println(person.UnixTime)
   813  }
   814
   815  c.String(http.StatusOK, "Success")
   816}
   817```
   818
   819Test it with:
   820
   821```sh
   822curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
   823```
   824
   825### Bind Uri
   826
   827See the [detail information](https://github.com/gin-gonic/gin/issues/846).
   828
   829```go
   830package main
   831
   832import (
   833  "net/http"
   834
   835  "github.com/gin-gonic/gin"
   836)
   837
   838type Person struct {
   839  ID string `uri:"id" binding:"required,uuid"`
   840  Name string `uri:"name" binding:"required"`
   841}
   842
   843func main() {
   844  route := gin.Default()
   845  route.GET("/:name/:id", func(c *gin.Context) {
   846    var person Person
   847    if err := c.ShouldBindUri(&person); err != nil {
   848      c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
   849      return
   850    }
   851    c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
   852  })
   853  route.Run(":8088")
   854}
   855```
   856
   857Test it with:
   858
   859```sh
   860curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
   861curl -v localhost:8088/thinkerou/not-uuid
   862```
   863
   864### Bind Header
   865
   866```go
   867package main
   868
   869import (
   870  "fmt"
   871  "net/http"
   872
   873  "github.com/gin-gonic/gin"
   874)
   875
   876type testHeader struct {
   877  Rate   int    `header:"Rate"`
   878  Domain string `header:"Domain"`
   879}
   880
   881func main() {
   882  r := gin.Default()
   883  r.GET("/", func(c *gin.Context) {
   884    h := testHeader{}
   885
   886    if err := c.ShouldBindHeader(&h); err != nil {
   887      c.JSON(http.StatusOK, err)
   888    }
   889
   890    fmt.Printf("%#v\n", h)
   891    c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})
   892  })
   893
   894  r.Run()
   895
   896// client
   897// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
   898// output
   899// {"Domain":"music","Rate":300}
   900}
   901```
   902
   903### Bind HTML checkboxes
   904
   905See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
   906
   907main.go
   908
   909```go
   910...
   911
   912type myForm struct {
   913    Colors []string `form:"colors[]"`
   914}
   915
   916...
   917
   918func formHandler(c *gin.Context) {
   919    var fakeForm myForm
   920    c.ShouldBind(&fakeForm)
   921    c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors})
   922}
   923
   924...
   925
   926```
   927
   928form.html
   929
   930```html
   931<form action="/" method="POST">
   932    <p>Check some colors</p>
   933    <label for="red">Red</label>
   934    <input type="checkbox" name="colors[]" value="red" id="red">
   935    <label for="green">Green</label>
   936    <input type="checkbox" name="colors[]" value="green" id="green">
   937    <label for="blue">Blue</label>
   938    <input type="checkbox" name="colors[]" value="blue" id="blue">
   939    <input type="submit">
   940</form>
   941```
   942
   943result:
   944
   945```json
   946{"color":["red","green","blue"]}
   947```
   948
   949### Multipart/Urlencoded binding
   950
   951```go
   952type ProfileForm struct {
   953  Name   string                `form:"name" binding:"required"`
   954  Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
   955
   956  // or for multiple files
   957  // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
   958}
   959
   960func main() {
   961  router := gin.Default()
   962  router.POST("/profile", func(c *gin.Context) {
   963    // you can bind multipart form with explicit binding declaration:
   964    // c.ShouldBindWith(&form, binding.Form)
   965    // or you can simply use autobinding with ShouldBind method:
   966    var form ProfileForm
   967    // in this case proper binding will be automatically selected
   968    if err := c.ShouldBind(&form); err != nil {
   969      c.String(http.StatusBadRequest, "bad request")
   970      return
   971    }
   972
   973    err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
   974    if err != nil {
   975      c.String(http.StatusInternalServerError, "unknown error")
   976      return
   977    }
   978
   979    // db.Save(&form)
   980
   981    c.String(http.StatusOK, "ok")
   982  })
   983  router.Run(":8080")
   984}
   985```
   986
   987Test it with:
   988
   989```sh
   990curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile
   991```
   992
   993### XML, JSON, YAML, TOML and ProtoBuf rendering
   994
   995```go
   996func main() {
   997  r := gin.Default()
   998
   999  // gin.H is a shortcut for map[string]any
  1000  r.GET("/someJSON", func(c *gin.Context) {
  1001    c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  1002  })
  1003
  1004  r.GET("/moreJSON", func(c *gin.Context) {
  1005    // You also can use a struct
  1006    var msg struct {
  1007      Name    string `json:"user"`
  1008      Message string
  1009      Number  int
  1010    }
  1011    msg.Name = "Lena"
  1012    msg.Message = "hey"
  1013    msg.Number = 123
  1014    // Note that msg.Name becomes "user" in the JSON
  1015    // Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}
  1016    c.JSON(http.StatusOK, msg)
  1017  })
  1018
  1019  r.GET("/someXML", func(c *gin.Context) {
  1020    c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  1021  })
  1022
  1023  r.GET("/someYAML", func(c *gin.Context) {
  1024    c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  1025  })
  1026
  1027  r.GET("/someTOML", func(c *gin.Context) {
  1028    c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  1029  })
  1030
  1031  r.GET("/someProtoBuf", func(c *gin.Context) {
  1032    reps := []int64{int64(1), int64(2)}
  1033    label := "test"
  1034    // The specific definition of protobuf is written in the testdata/protoexample file.
  1035    data := &protoexample.Test{
  1036      Label: &label,
  1037      Reps:  reps,
  1038    }
  1039    // Note that data becomes binary data in the response
  1040    // Will output protoexample.Test protobuf serialized data
  1041    c.ProtoBuf(http.StatusOK, data)
  1042  })
  1043
  1044  // Listen and serve on 0.0.0.0:8080
  1045  r.Run(":8080")
  1046}
  1047```
  1048
  1049#### SecureJSON
  1050
  1051Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values.
  1052
  1053```go
  1054func main() {
  1055  r := gin.Default()
  1056
  1057  // You can also use your own secure json prefix
  1058  // r.SecureJsonPrefix(")]}',\n")
  1059
  1060  r.GET("/someJSON", func(c *gin.Context) {
  1061    names := []string{"lena", "austin", "foo"}
  1062
  1063    // Will output  :   while(1);["lena","austin","foo"]
  1064    c.SecureJSON(http.StatusOK, names)
  1065  })
  1066
  1067  // Listen and serve on 0.0.0.0:8080
  1068  r.Run(":8080")
  1069}
  1070```
  1071
  1072#### JSONP
  1073
  1074Using JSONP to request data from a server  in a different domain. Add callback to response body if the query parameter callback exists.
  1075
  1076```go
  1077func main() {
  1078  r := gin.Default()
  1079
  1080  r.GET("/JSONP", func(c *gin.Context) {
  1081    data := gin.H{
  1082      "foo": "bar",
  1083    }
  1084
  1085    //callback is x
  1086    // Will output  :   x({\"foo\":\"bar\"})
  1087    c.JSONP(http.StatusOK, data)
  1088  })
  1089
  1090  // Listen and serve on 0.0.0.0:8080
  1091  r.Run(":8080")
  1092
  1093        // client
  1094        // curl http://127.0.0.1:8080/JSONP?callback=x
  1095}
  1096```
  1097
  1098#### AsciiJSON
  1099
  1100Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.
  1101
  1102```go
  1103func main() {
  1104  r := gin.Default()
  1105
  1106  r.GET("/someJSON", func(c *gin.Context) {
  1107    data := gin.H{
  1108      "lang": "GO语言",
  1109      "tag":  "<br>",
  1110    }
  1111
  1112    // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
  1113    c.AsciiJSON(http.StatusOK, data)
  1114  })
  1115
  1116  // Listen and serve on 0.0.0.0:8080
  1117  r.Run(":8080")
  1118}
  1119```
  1120
  1121#### PureJSON
  1122
  1123Normally, 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.
  1124This feature is unavailable in Go 1.6 and lower.
  1125
  1126```go
  1127func main() {
  1128  r := gin.Default()
  1129
  1130  // Serves unicode entities
  1131  r.GET("/json", func(c *gin.Context) {
  1132    c.JSON(http.StatusOK, gin.H{
  1133      "html": "<b>Hello, world!</b>",
  1134    })
  1135  })
  1136
  1137  // Serves literal characters
  1138  r.GET("/purejson", func(c *gin.Context) {
  1139    c.PureJSON(http.StatusOK, gin.H{
  1140      "html": "<b>Hello, world!</b>",
  1141    })
  1142  })
  1143
  1144  // listen and serve on 0.0.0.0:8080
  1145  r.Run(":8080")
  1146}
  1147```
  1148
  1149### Serving static files
  1150
  1151```go
  1152func main() {
  1153  router := gin.Default()
  1154  router.Static("/assets", "./assets")
  1155  router.StaticFS("/more_static", http.Dir("my_file_system"))
  1156  router.StaticFile("/favicon.ico", "./resources/favicon.ico")
  1157  router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
  1158  
  1159  // Listen and serve on 0.0.0.0:8080
  1160  router.Run(":8080")
  1161}
  1162```
  1163
  1164### Serving data from file
  1165
  1166```go
  1167func main() {
  1168  router := gin.Default()
  1169
  1170  router.GET("/local/file", func(c *gin.Context) {
  1171    c.File("local/file.go")
  1172  })
  1173
  1174  var fs http.FileSystem = // ...
  1175  router.GET("/fs/file", func(c *gin.Context) {
  1176    c.FileFromFS("fs/file.go", fs)
  1177  })
  1178}
  1179
  1180```
  1181
  1182### Serving data from reader
  1183
  1184```go
  1185func main() {
  1186  router := gin.Default()
  1187  router.GET("/someDataFromReader", func(c *gin.Context) {
  1188    response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
  1189    if err != nil || response.StatusCode != http.StatusOK {
  1190      c.Status(http.StatusServiceUnavailable)
  1191      return
  1192    }
  1193
  1194    reader := response.Body
  1195     defer reader.Close()
  1196    contentLength := response.ContentLength
  1197    contentType := response.Header.Get("Content-Type")
  1198
  1199    extraHeaders := map[string]string{
  1200      "Content-Disposition": `attachment; filename="gopher.png"`,
  1201    }
  1202
  1203    c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
  1204  })
  1205  router.Run(":8080")
  1206}
  1207```
  1208
  1209### HTML rendering
  1210
  1211Using LoadHTMLGlob() or LoadHTMLFiles()
  1212
  1213```go
  1214func main() {
  1215  router := gin.Default()
  1216  router.LoadHTMLGlob("templates/*")
  1217  //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
  1218  router.GET("/index", func(c *gin.Context) {
  1219    c.HTML(http.StatusOK, "index.tmpl", gin.H{
  1220      "title": "Main website",
  1221    })
  1222  })
  1223  router.Run(":8080")
  1224}
  1225```
  1226
  1227templates/index.tmpl
  1228
  1229```html
  1230<html>
  1231  <h1>
  1232    {{ .title }}
  1233  </h1>
  1234</html>
  1235```
  1236
  1237Using templates with same name in different directories
  1238
  1239```go
  1240func main() {
  1241  router := gin.Default()
  1242  router.LoadHTMLGlob("templates/**/*")
  1243  router.GET("/posts/index", func(c *gin.Context) {
  1244    c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
  1245      "title": "Posts",
  1246    })
  1247  })
  1248  router.GET("/users/index", func(c *gin.Context) {
  1249    c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
  1250      "title": "Users",
  1251    })
  1252  })
  1253  router.Run(":8080")
  1254}
  1255```
  1256
  1257templates/posts/index.tmpl
  1258
  1259```html
  1260{{ define "posts/index.tmpl" }}
  1261<html><h1>
  1262  {{ .title }}
  1263</h1>
  1264<p>Using posts/index.tmpl</p>
  1265</html>
  1266{{ end }}
  1267```
  1268
  1269templates/users/index.tmpl
  1270
  1271```html
  1272{{ define "users/index.tmpl" }}
  1273<html><h1>
  1274  {{ .title }}
  1275</h1>
  1276<p>Using users/index.tmpl</p>
  1277</html>
  1278{{ end }}
  1279```
  1280
  1281#### Custom Template renderer
  1282
  1283You can also use your own html template render
  1284
  1285```go
  1286import "html/template"
  1287
  1288func main() {
  1289  router := gin.Default()
  1290  html := template.Must(template.ParseFiles("file1", "file2"))
  1291  router.SetHTMLTemplate(html)
  1292  router.Run(":8080")
  1293}
  1294```
  1295
  1296#### Custom Delimiters
  1297
  1298You may use custom delims
  1299
  1300```go
  1301  r := gin.Default()
  1302  r.Delims("{[{", "}]}")
  1303  r.LoadHTMLGlob("/path/to/templates")
  1304```
  1305
  1306#### Custom Template Funcs
  1307
  1308See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template).
  1309
  1310main.go
  1311
  1312```go
  1313import (
  1314  "fmt"
  1315  "html/template"
  1316  "net/http"
  1317  "time"
  1318
  1319  "github.com/gin-gonic/gin"
  1320)
  1321
  1322func formatAsDate(t time.Time) string {
  1323  year, month, day := t.Date()
  1324  return fmt.Sprintf("%d/%02d/%02d", year, month, day)
  1325}
  1326
  1327func main() {
  1328  router := gin.Default()
  1329  router.Delims("{[{", "}]}")
  1330  router.SetFuncMap(template.FuncMap{
  1331      "formatAsDate": formatAsDate,
  1332  })
  1333  router.LoadHTMLFiles("./testdata/template/raw.tmpl")
  1334
  1335  router.GET("/raw", func(c *gin.Context) {
  1336      c.HTML(http.StatusOK, "raw.tmpl", gin.H{
  1337          "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
  1338      })
  1339  })
  1340
  1341  router.Run(":8080")
  1342}
  1343
  1344```
  1345
  1346raw.tmpl
  1347
  1348```html
  1349Date: {[{.now | formatAsDate}]}
  1350```
  1351
  1352Result:
  1353
  1354```sh
  1355Date: 2017/07/01
  1356```
  1357
  1358### Multitemplate
  1359
  1360Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
  1361
  1362### Redirects
  1363
  1364Issuing a HTTP redirect is easy. Both internal and external locations are supported.
  1365
  1366```go
  1367r.GET("/test", func(c *gin.Context) {
  1368  c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
  1369})
  1370```
  1371
  1372Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444)
  1373
  1374```go
  1375r.POST("/test", func(c *gin.Context) {
  1376  c.Redirect(http.StatusFound, "/foo")
  1377})
  1378```
  1379
  1380Issuing a Router redirect, use `HandleContext` like below.
  1381
  1382``` go
  1383r.GET("/test", func(c *gin.Context) {
  1384    c.Request.URL.Path = "/test2"
  1385    r.HandleContext(c)
  1386})
  1387r.GET("/test2", func(c *gin.Context) {
  1388    c.JSON(http.StatusOK, gin.H{"hello": "world"})
  1389})
  1390```
  1391
  1392### Custom Middleware
  1393
  1394```go
  1395func Logger() gin.HandlerFunc {
  1396  return func(c *gin.Context) {
  1397    t := time.Now()
  1398
  1399    // Set example variable
  1400    c.Set("example", "12345")
  1401
  1402    // before request
  1403
  1404    c.Next()
  1405
  1406    // after request
  1407    latency := time.Since(t)
  1408    log.Print(latency)
  1409
  1410    // access the status we are sending
  1411    status := c.Writer.Status()
  1412    log.Println(status)
  1413  }
  1414}
  1415
  1416func main() {
  1417  r := gin.New()
  1418  r.Use(Logger())
  1419
  1420  r.GET("/test", func(c *gin.Context) {
  1421    example := c.MustGet("example").(string)
  1422
  1423    // it would print: "12345"
  1424    log.Println(example)
  1425  })
  1426
  1427  // Listen and serve on 0.0.0.0:8080
  1428  r.Run(":8080")
  1429}
  1430```
  1431
  1432### Using BasicAuth() middleware
  1433
  1434```go
  1435// simulate some private data
  1436var secrets = gin.H{
  1437  "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
  1438  "austin": gin.H{"email": "austin@example.com", "phone": "666"},
  1439  "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
  1440}
  1441
  1442func main() {
  1443  r := gin.Default()
  1444
  1445  // Group using gin.BasicAuth() middleware
  1446  // gin.Accounts is a shortcut for map[string]string
  1447  authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
  1448    "foo":    "bar",
  1449    "austin": "1234",
  1450    "lena":   "hello2",
  1451    "manu":   "4321",
  1452  }))
  1453
  1454  // /admin/secrets endpoint
  1455  // hit "localhost:8080/admin/secrets
  1456  authorized.GET("/secrets", func(c *gin.Context) {
  1457    // get user, it was set by the BasicAuth middleware
  1458    user := c.MustGet(gin.AuthUserKey).(string)
  1459    if secret, ok := secrets[user]; ok {
  1460      c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
  1461    } else {
  1462      c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
  1463    }
  1464  })
  1465
  1466  // Listen and serve on 0.0.0.0:8080
  1467  r.Run(":8080")
  1468}
  1469```
  1470
  1471### Goroutines inside a middleware
  1472
  1473When 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.
  1474
  1475```go
  1476func main() {
  1477  r := gin.Default()
  1478
  1479  r.GET("/long_async", func(c *gin.Context) {
  1480    // create copy to be used inside the goroutine
  1481    cCp := c.Copy()
  1482    go func() {
  1483      // simulate a long task with time.Sleep(). 5 seconds
  1484      time.Sleep(5 * time.Second)
  1485
  1486      // note that you are using the copied context "cCp", IMPORTANT
  1487      log.Println("Done! in path " + cCp.Request.URL.Path)
  1488    }()
  1489  })
  1490
  1491  r.GET("/long_sync", func(c *gin.Context) {
  1492    // simulate a long task with time.Sleep(). 5 seconds
  1493    time.Sleep(5 * time.Second)
  1494
  1495    // since we are NOT using a goroutine, we do not have to copy the context
  1496    log.Println("Done! in path " + c.Request.URL.Path)
  1497  })
  1498
  1499  // Listen and serve on 0.0.0.0:8080
  1500  r.Run(":8080")
  1501}
  1502```
  1503
  1504### Custom HTTP configuration
  1505
  1506Use `http.ListenAndServe()` directly, like this:
  1507
  1508```go
  1509func main() {
  1510  router := gin.Default()
  1511  http.ListenAndServe(":8080", router)
  1512}
  1513```
  1514
  1515or
  1516
  1517```go
  1518func main() {
  1519  router := gin.Default()
  1520
  1521  s := &http.Server{
  1522    Addr:           ":8080",
  1523    Handler:        router,
  1524    ReadTimeout:    10 * time.Second,
  1525    WriteTimeout:   10 * time.Second,
  1526    MaxHeaderBytes: 1 << 20,
  1527  }
  1528  s.ListenAndServe()
  1529}
  1530```
  1531
  1532### Support Let's Encrypt
  1533
  1534example for 1-line LetsEncrypt HTTPS servers.
  1535
  1536```go
  1537package main
  1538
  1539import (
  1540  "log"
  1541  "net/http"
  1542
  1543  "github.com/gin-gonic/autotls"
  1544  "github.com/gin-gonic/gin"
  1545)
  1546
  1547func main() {
  1548  r := gin.Default()
  1549
  1550  // Ping handler
  1551  r.GET("/ping", func(c *gin.Context) {
  1552    c.String(http.StatusOK, "pong")
  1553  })
  1554
  1555  log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
  1556}
  1557```
  1558
  1559example for custom autocert manager.
  1560
  1561```go
  1562package main
  1563
  1564import (
  1565  "log"
  1566  "net/http"
  1567
  1568  "github.com/gin-gonic/autotls"
  1569  "github.com/gin-gonic/gin"
  1570  "golang.org/x/crypto/acme/autocert"
  1571)
  1572
  1573func main() {
  1574  r := gin.Default()
  1575
  1576  // Ping handler
  1577  r.GET("/ping", func(c *gin.Context) {
  1578    c.String(http.StatusOK, "pong")
  1579  })
  1580
  1581  m := autocert.Manager{
  1582    Prompt:     autocert.AcceptTOS,
  1583    HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
  1584    Cache:      autocert.DirCache("/var/www/.cache"),
  1585  }
  1586
  1587  log.Fatal(autotls.RunWithManager(r, &m))
  1588}
  1589```
  1590
  1591### Run multiple service using Gin
  1592
  1593See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example:
  1594
  1595```go
  1596package main
  1597
  1598import (
  1599  "log"
  1600  "net/http"
  1601  "time"
  1602
  1603  "github.com/gin-gonic/gin"
  1604  "golang.org/x/sync/errgroup"
  1605)
  1606
  1607var (
  1608  g errgroup.Group
  1609)
  1610
  1611func router01() http.Handler {
  1612  e := gin.New()
  1613  e.Use(gin.Recovery())
  1614  e.GET("/", func(c *gin.Context) {
  1615    c.JSON(
  1616      http.StatusOK,
  1617      gin.H{
  1618        "code":  http.StatusOK,
  1619        "error": "Welcome server 01",
  1620      },
  1621    )
  1622  })
  1623
  1624  return e
  1625}
  1626
  1627func router02() http.Handler {
  1628  e := gin.New()
  1629  e.Use(gin.Recovery())
  1630  e.GET("/", func(c *gin.Context) {
  1631    c.JSON(
  1632      http.StatusOK,
  1633      gin.H{
  1634        "code":  http.StatusOK,
  1635        "error": "Welcome server 02",
  1636      },
  1637    )
  1638  })
  1639
  1640  return e
  1641}
  1642
  1643func main() {
  1644  server01 := &http.Server{
  1645    Addr:         ":8080",
  1646    Handler:      router01(),
  1647    ReadTimeout:  5 * time.Second,
  1648    WriteTimeout: 10 * time.Second,
  1649  }
  1650
  1651  server02 := &http.Server{
  1652    Addr:         ":8081",
  1653    Handler:      router02(),
  1654    ReadTimeout:  5 * time.Second,
  1655    WriteTimeout: 10 * time.Second,
  1656  }
  1657
  1658  g.Go(func() error {
  1659    err := server01.ListenAndServe()
  1660    if err != nil && err != http.ErrServerClosed {
  1661      log.Fatal(err)
  1662    }
  1663    return err
  1664  })
  1665
  1666  g.Go(func() error {
  1667    err := server02.ListenAndServe()
  1668    if err != nil && err != http.ErrServerClosed {
  1669      log.Fatal(err)
  1670    }
  1671    return err
  1672  })
  1673
  1674  if err := g.Wait(); err != nil {
  1675    log.Fatal(err)
  1676  }
  1677}
  1678```
  1679
  1680### Graceful shutdown or restart
  1681
  1682There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages.
  1683
  1684#### Third-party packages
  1685
  1686We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
  1687
  1688```go
  1689router := gin.Default()
  1690router.GET("/", handler)
  1691// [...]
  1692endless.ListenAndServe(":4242", router)
  1693```
  1694
  1695Alternatives:
  1696
  1697* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.
  1698* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
  1699* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
  1700
  1701#### Manually
  1702
  1703In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown).
  1704
  1705```go
  1706// +build go1.8
  1707
  1708package main
  1709
  1710import (
  1711  "context"
  1712  "log"
  1713  "net/http"
  1714  "os"
  1715  "os/signal"
  1716  "syscall"
  1717  "time"
  1718
  1719  "github.com/gin-gonic/gin"
  1720)
  1721
  1722func main() {
  1723  router := gin.Default()
  1724  router.GET("/", func(c *gin.Context) {
  1725    time.Sleep(5 * time.Second)
  1726    c.String(http.StatusOK, "Welcome Gin Server")
  1727  })
  1728
  1729  srv := &http.Server{
  1730    Addr:    ":8080",
  1731    Handler: router,
  1732  }
  1733
  1734  // Initializing the server in a goroutine so that
  1735  // it won't block the graceful shutdown handling below
  1736  go func() {
  1737    if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
  1738      log.Printf("listen: %s\n", err)
  1739    }
  1740  }()
  1741
  1742  // Wait for interrupt signal to gracefully shutdown the server with
  1743  // a timeout of 5 seconds.
  1744  quit := make(chan os.Signal)
  1745  // kill (no param) default send syscall.SIGTERM
  1746  // kill -2 is syscall.SIGINT
  1747  // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
  1748  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  1749  <-quit
  1750  log.Println("Shutting down server...")
  1751
  1752  // The context is used to inform the server it has 5 seconds to finish
  1753  // the request it is currently handling
  1754  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1755  defer cancel()
  1756
  1757  if err := srv.Shutdown(ctx); err != nil {
  1758    log.Fatal("Server forced to shutdown:", err)
  1759  }
  1760
  1761  log.Println("Server exiting")
  1762}
  1763```
  1764
  1765### Build a single binary with templates
  1766
  1767You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package.
  1768
  1769```go
  1770package main
  1771
  1772import (
  1773  "embed"
  1774  "html/template"
  1775  "net/http"
  1776
  1777  "github.com/gin-gonic/gin"
  1778)
  1779
  1780//go:embed assets/* templates/*
  1781var f embed.FS
  1782
  1783func main() {
  1784  router := gin.Default()
  1785  templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl"))
  1786  router.SetHTMLTemplate(templ)
  1787
  1788  // example: /public/assets/images/example.png
  1789  router.StaticFS("/public", http.FS(f))
  1790
  1791  router.GET("/", func(c *gin.Context) {
  1792    c.HTML(http.StatusOK, "index.tmpl", gin.H{
  1793      "title": "Main website",
  1794    })
  1795  })
  1796
  1797  router.GET("/foo", func(c *gin.Context) {
  1798    c.HTML(http.StatusOK, "bar.tmpl", gin.H{
  1799      "title": "Foo website",
  1800    })
  1801  })
  1802
  1803  router.GET("favicon.ico", func(c *gin.Context) {
  1804    file, _ := f.ReadFile("assets/favicon.ico")
  1805    c.Data(
  1806      http.StatusOK,
  1807      "image/x-icon",
  1808      file,
  1809    )
  1810  })
  1811
  1812  router.Run(":8080")
  1813}
  1814```
  1815
  1816See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory.
  1817
  1818### Bind form-data request with custom struct
  1819
  1820The follow example using custom struct:
  1821
  1822```go
  1823type StructA struct {
  1824    FieldA string `form:"field_a"`
  1825}
  1826
  1827type StructB struct {
  1828    NestedStruct StructA
  1829    FieldB string `form:"field_b"`
  1830}
  1831
  1832type StructC struct {
  1833    NestedStructPointer *StructA
  1834    FieldC string `form:"field_c"`
  1835}
  1836
  1837type StructD struct {
  1838    NestedAnonyStruct struct {
  1839        FieldX string `form:"field_x"`
  1840    }
  1841    FieldD string `form:"field_d"`
  1842}
  1843
  1844func GetDataB(c *gin.Context) {
  1845    var b StructB
  1846    c.Bind(&b)
  1847    c.JSON(http.StatusOK, gin.H{
  1848        "a": b.NestedStruct,
  1849        "b": b.FieldB,
  1850    })
  1851}
  1852
  1853func GetDataC(c *gin.Context) {
  1854    var b StructC
  1855    c.Bind(&b)
  1856    c.JSON(http.StatusOK, gin.H{
  1857        "a": b.NestedStructPointer,
  1858        "c": b.FieldC,
  1859    })
  1860}
  1861
  1862func GetDataD(c *gin.Context) {
  1863    var b StructD
  1864    c.Bind(&b)
  1865    c.JSON(http.StatusOK, gin.H{
  1866        "x": b.NestedAnonyStruct,
  1867        "d": b.FieldD,
  1868    })
  1869}
  1870
  1871func main() {
  1872    r := gin.Default()
  1873    r.GET("/getb", GetDataB)
  1874    r.GET("/getc", GetDataC)
  1875    r.GET("/getd", GetDataD)
  1876
  1877    r.Run()
  1878}
  1879```
  1880
  1881Using the command `curl` command result:
  1882
  1883```sh
  1884$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
  1885{"a":{"FieldA":"hello"},"b":"world"}
  1886$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
  1887{"a":{"FieldA":"hello"},"c":"world"}
  1888$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
  1889{"d":"world","x":{"FieldX":"hello"}}
  1890```
  1891
  1892### Try to bind body into different structs
  1893
  1894The normal methods for binding request body consumes `c.Request.Body` and they
  1895cannot be called multiple times.
  1896
  1897```go
  1898type formA struct {
  1899  Foo string `json:"foo" xml:"foo" binding:"required"`
  1900}
  1901
  1902type formB struct {
  1903  Bar string `json:"bar" xml:"bar" binding:"required"`
  1904}
  1905
  1906func SomeHandler(c *gin.Context) {
  1907  objA := formA{}
  1908  objB := formB{}
  1909  // This c.ShouldBind consumes c.Request.Body and it cannot be reused.
  1910  if errA := c.ShouldBind(&objA); errA == nil {
  1911    c.String(http.StatusOK, `the body should be formA`)
  1912  // Always an error is occurred by this because c.Request.Body is EOF now.
  1913  } else if errB := c.ShouldBind(&objB); errB == nil {
  1914    c.String(http.StatusOK, `the body should be formB`)
  1915  } else {
  1916    ...
  1917  }
  1918}
  1919```
  1920
  1921For this, you can use `c.ShouldBindBodyWith`.
  1922
  1923```go
  1924func SomeHandler(c *gin.Context) {
  1925  objA := formA{}
  1926  objB := formB{}
  1927  // This reads c.Request.Body and stores the result into the context.
  1928  if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
  1929    c.String(http.StatusOK, `the body should be formA`)
  1930  // At this time, it reuses body stored in the context.
  1931  } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
  1932    c.String(http.StatusOK, `the body should be formB JSON`)
  1933  // And it can accepts other formats
  1934  } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
  1935    c.String(http.StatusOK, `the body should be formB XML`)
  1936  } else {
  1937    ...
  1938  }
  1939}
  1940```
  1941
  19421. `c.ShouldBindBodyWith` stores body into the context before binding. This has
  1943a slight impact to performance, so you should not use this method if you are
  1944enough to call binding at once.
  19452. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
  1946`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
  1947can be called by `c.ShouldBind()` multiple times without any damage to
  1948performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
  1949
  1950### Bind form-data request with custom struct and custom tag
  1951
  1952```go
  1953const (
  1954  customerTag = "url"
  1955  defaultMemory = 32 << 20
  1956)
  1957
  1958type customerBinding struct {}
  1959
  1960func (customerBinding) Name() string {
  1961  return "form"
  1962}
  1963
  1964func (customerBinding) Bind(req *http.Request, obj any) error {
  1965  if err := req.ParseForm(); err != nil {
  1966    return err
  1967  }
  1968  if err := req.ParseMultipartForm(defaultMemory); err != nil {
  1969    if err != http.ErrNotMultipart {
  1970      return err
  1971    }
  1972  }
  1973  if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
  1974    return err
  1975  }
  1976  return validate(obj)
  1977}
  1978
  1979func validate(obj any) error {
  1980  if binding.Validator == nil {
  1981    return nil
  1982  }
  1983  return binding.Validator.ValidateStruct(obj)
  1984}
  1985
  1986// Now we can do this!!!
  1987// FormA is an external type that we can't modify it's tag
  1988type FormA struct {
  1989  FieldA string `url:"field_a"`
  1990}
  1991
  1992func ListHandler(s *Service) func(ctx *gin.Context) {
  1993  return func(ctx *gin.Context) {
  1994    var urlBinding = customerBinding{}
  1995    var opt FormA
  1996    err := ctx.MustBindWith(&opt, urlBinding)
  1997    if err != nil {
  1998      ...
  1999    }
  2000    ...
  2001  }
  2002}
  2003```
  2004
  2005### http2 server push
  2006
  2007http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.
  2008
  2009```go
  2010package main
  2011
  2012import (
  2013  "html/template"
  2014  "log"
  2015  "net/http"
  2016
  2017  "github.com/gin-gonic/gin"
  2018)
  2019
  2020var html = template.Must(template.New("https").Parse(`
  2021<html>
  2022<head>
  2023  <title>Https Test</title>
  2024  <script src="/assets/app.js"></script>
  2025</head>
  2026<body>
  2027  <h1 style="color:red;">Welcome, Ginner!</h1>
  2028</body>
  2029</html>
  2030`))
  2031
  2032func main() {
  2033  r := gin.Default()
  2034  r.Static("/assets", "./assets")
  2035  r.SetHTMLTemplate(html)
  2036
  2037  r.GET("/", func(c *gin.Context) {
  2038    if pusher := c.Writer.Pusher(); pusher != nil {
  2039      // use pusher.Push() to do server push
  2040      if err := pusher.Push("/assets/app.js", nil); err != nil {
  2041        log.Printf("Failed to push: %v", err)
  2042      }
  2043    }
  2044    c.HTML(http.StatusOK, "https", gin.H{
  2045      "status": "success",
  2046    })
  2047  })
  2048
  2049  // Listen and Server in https://127.0.0.1:8080
  2050  r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
  2051}
  2052```
  2053
  2054### Define format for the log of routes
  2055
  2056The default log of routes is:
  2057
  2058```sh
  2059[GIN-debug] POST   /foo                      --> main.main.func1 (3 handlers)
  2060[GIN-debug] GET    /bar                      --> main.main.func2 (3 handlers)
  2061[GIN-debug] GET    /status                   --> main.main.func3 (3 handlers)
  2062```
  2063
  2064If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`.
  2065In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
  2066
  2067```go
  2068import (
  2069  "log"
  2070  "net/http"
  2071
  2072  "github.com/gin-gonic/gin"
  2073)
  2074
  2075func main() {
  2076  r := gin.Default()
  2077  gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
  2078    log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
  2079  }
  2080
  2081  r.POST("/foo", func(c *gin.Context) {
  2082    c.JSON(http.StatusOK, "foo")
  2083  })
  2084
  2085  r.GET("/bar", func(c *gin.Context) {
  2086    c.JSON(http.StatusOK, "bar")
  2087  })
  2088
  2089  r.GET("/status", func(c *gin.Context) {
  2090    c.JSON(http.StatusOK, "ok")
  2091  })
  2092
  2093  // Listen and Server in http://0.0.0.0:8080
  2094  r.Run()
  2095}
  2096```
  2097
  2098### Set and get a cookie
  2099
  2100```go
  2101import (
  2102  "fmt"
  2103
  2104  "github.com/gin-gonic/gin"
  2105)
  2106
  2107func main() {
  2108  router := gin.Default()
  2109
  2110  router.GET("/cookie", func(c *gin.Context) {
  2111
  2112      cookie, err := c.Cookie("gin_cookie")
  2113
  2114      if err != nil {
  2115          cookie = "NotSet"
  2116          c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
  2117      }
  2118
  2119      fmt.Printf("Cookie value: %s \n", cookie)
  2120  })
  2121
  2122  router.Run()
  2123}
  2124```
  2125
  2126## Don't trust all proxies
  2127
  2128Gin lets you specify which headers to hold the real client IP (if any),
  2129as well as specifying which proxies (or direct clients) you trust to
  2130specify one of these headers.
  2131
  2132Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
  2133or network CIDRs from where clients which their request headers related to client
  2134IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
  2135IPv6 CIDRs.
  2136
  2137**Attention:** Gin trust all proxies by default if you don't specify a trusted 
  2138proxy using the function above, **this is NOT safe**. At the same time, if you don't
  2139use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
  2140then `Context.ClientIP()` will return the remote address directly to avoid some
  2141unnecessary computation.
  2142
  2143```go
  2144import (
  2145  "fmt"
  2146
  2147  "github.com/gin-gonic/gin"
  2148)
  2149
  2150func main() {
  2151  router := gin.Default()
  2152  router.SetTrustedProxies([]string{"192.168.1.2"})
  2153
  2154  router.GET("/", func(c *gin.Context) {
  2155    // If the client is 192.168.1.2, use the X-Forwarded-For
  2156    // header to deduce the original client IP from the trust-
  2157    // worthy parts of that header.
  2158    // Otherwise, simply return the direct client IP
  2159    fmt.Printf("ClientIP: %s\n", c.ClientIP())
  2160  })
  2161  router.Run()
  2162}
  2163```
  2164
  2165**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
  2166to skip TrustedProxies check, it has a higher priority than TrustedProxies. 
  2167Look at the example below:
  2168
  2169```go
  2170import (
  2171  "fmt"
  2172
  2173  "github.com/gin-gonic/gin"
  2174)
  2175
  2176func main() {
  2177  router := gin.Default()
  2178  // Use predefined header gin.PlatformXXX
  2179  router.TrustedPlatform = gin.PlatformGoogleAppEngine
  2180  // Or set your own trusted request header for another trusted proxy service
  2181  // Don't set it to any suspect request header, it's unsafe
  2182  router.TrustedPlatform = "X-CDN-IP"
  2183
  2184  router.GET("/", func(c *gin.Context) {
  2185    // If you set TrustedPlatform, ClientIP() will resolve the
  2186    // corresponding header and return IP directly
  2187    fmt.Printf("ClientIP: %s\n", c.ClientIP())
  2188  })
  2189  router.Run()
  2190}
  2191```
  2192
  2193## Testing
  2194
  2195The `net/http/httptest` package is preferable way for HTTP testing.
  2196
  2197```go
  2198package main
  2199
  2200import (
  2201  "net/http"
  2202
  2203  "github.com/gin-gonic/gin"
  2204)
  2205
  2206func setupRouter() *gin.Engine {
  2207  r := gin.Default()
  2208  r.GET("/ping", func(c *gin.Context) {
  2209    c.String(http.StatusOK, "pong")
  2210  })
  2211  return r
  2212}
  2213
  2214func main() {
  2215  r := setupRouter()
  2216  r.Run(":8080")
  2217}
  2218```
  2219
  2220Test for code example above:
  2221
  2222```go
  2223package main
  2224
  2225import (
  2226  "net/http"
  2227  "net/http/httptest"
  2228  "testing"
  2229
  2230  "github.com/stretchr/testify/assert"
  2231)
  2232
  2233func TestPingRoute(t *testing.T) {
  2234  router := setupRouter()
  2235
  2236  w := httptest.NewRecorder()
  2237  req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
  2238  router.ServeHTTP(w, req)
  2239
  2240  assert.Equal(t, http.StatusOK, w.Code)
  2241  assert.Equal(t, "pong", w.Body.String())
  2242}
  2243```

View as plain text