当前位置:网站首页>[gin] gin framework for golang web development
[gin] gin framework for golang web development
2022-06-12 05:32:00 【cbdgz】
Golang Web Development of Gin frame
- summary
- install Gin frame
- Custom HTTP configuration Customize HTTP To configure
- Gin URL
- Gin File upload service
- Gin middleware
- Custom log output format Custom Log Format
- Model binding for request parameters Model binding and validation
- Custom request parameter validation
- Parameter binding Uri (Bind Uri)
- Parameter request header (Bind Header)
- Bind HTML checkboxes
- Gin Framework of the JSON Introduce
- Gin file management
- HTML Template
- Custom Template renderer
- Custom Delimiters
- Custom Template Funcs
- Multitemplate
- Gin Redirect
- Support Let’s Encrypt
- utilize Gin Open multiple services
- Set and get cookie
- Test paper
summary
gin The frame is Go web Among all the popular frameworks in development, the better ones
Github Address :
install Gin frame
$ go get -u github.com/gin-gonic/gin

Custom HTTP configuration Customize HTTP To configure
Use http.ListenAndServe() directly, like this:
func main() {
router := gin.Default()
http.ListenAndServe(":8080", router)
}
or
func main() {
router := gin.Default()
s := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}
Gin URL
For me gin Of url Absolutely all used web The most coquettish and powerful development framework
// With parameters url parameters in path
r.GET("/user/:name", func(context *gin.Context) {
name := context.Param("name")
context.String(http.StatusOK, "Hello %s", name)
})
// Gin The framework supports the setting of default parameters
r.GET("/index", func(c *gin.Context) {
f := c.DefaultQuery("f", " The default value is ") // Set the default value of the target parameter
l := c.Query("l")
c.String(http.StatusOK, "Hello %s %s", f, l)
})
r.POST("/post", func(c *gin.Context) {
m := c.PostForm("m")
n := c.DefaultPostForm("n", " The default value is ")
c.JSON(http.StatusOK, gin.H{
"status": "success",
"msg": m,
"nick": n,
})
})
- Gin One of the coquettish : Support query parameters and post Parameters used together
// query+post form The query parameter is ?a=1&b=2 Type of ,post form Is in body Inside
r.POST("/post2", func(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
msg := c.PostForm("msg")
fmt.Printf("ID:%s page:%s name:%s msg:%s\n", id, page, name, msg)
})
- Gin Coquettish two : Support array type request parameters ( The first time I saw you, I was really shocked )
/* Array type post POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 Content-Type: application/x-www-form-urlencoded names[first]=thinkerou&names[second]=tianou */
r.POST("/post3", func(c *gin.Context) {
ids := c.QueryMap("ids")
names := c.PostFormMap("names")
fmt.Printf("ids: %v; names: %v\n", ids, names)
})
Gin File upload service
// Upload files The default file size limit is 32MiB
r.MaxMultipartMemory = 8 << 20 //8MiB
r.POST("/upload", func(c *gin.Context) {
file, _ := c.FormFile("Filename")
log.Println(file.Filename)
// Save the file to the destination path
err := c.SaveUploadedFile(file, "your save path")
if err != nil {
return
}
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
// Upload multiple files
r.POST("/uploads", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["Filename[]"]
for _, file := range files {
log.Println(file.Filename)
// Upload the file to specific dst.
err := c.SaveUploadedFile(file, "your save path")
if err != nil {
return
}
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
Gin middleware
- Do not use any middleware by default (Blank Gin without middleware by default)
r := gin.New() // Initialize routing without middleware
r := gin.Default() // Use default logging and Recovery middleware The routing
r.Run() // Default to 8080 Port boot web service
r.Run(":9020") // With 9020 Port boot web service
- How to use middleware
r := gin.New()
r.Use(gin.Logger()) // Use gin Framework's logging middleware ,By default gin.DefaultWriter = os.Stdout
// Use error handling middleware
// Recovery middleware recovers from any panics and writes a 500 if there was one
r.Use(gin.Recovery())
// For specific routes , Any number of middleware can be added
r.GET("/exp",MyBenchLogger(),benchEndPoint)//first para is path,latest is handle func
// You can add middleware to the routing group
v1 := r.Group("/v1")
v1.Use(AuthRequired()){
v1.POST("/login",loginHandle)
v1.POST("/logout",loginOutHandle)
// Groups can also be nested within groups nested group
testing := v1.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
- Define middleware
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// Set example variable
c.Set("example", "12345")
// before request
c.Next()
// after request
latency := time.Since(t)
log.Print(latency)
// access the status we are sending
status := c.Writer.Status()
log.Println(status)
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
- Validate simple authentication middleware
// simulate some private data
// Record the information of the corresponding user
var secrets = gin.H{
"foo": gin.H{
"email": "[email protected]", "phone": "123433"},
"austin": gin.H{
"email": "[email protected]", "phone": "666"},
"lena": gin.H{
"email": "[email protected]", "phone": "523443"},
}
func main() {
r := gin.Default()
// Group using gin.BasicAuth() middleware
// gin.Accounts is a shortcut for map[string]string
// Set the user name and password allowed to log in
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
// /admin/secrets endpoint
// hit "localhost:8080/admin/secrets
authorized.GET("/secrets", func(c *gin.Context) {
// get user, it was set by the BasicAuth middleware
user := c.MustGet(gin.AuthUserKey).(string)
if secret, ok := secrets[user]; ok {
c.JSON(http.StatusOK, gin.H{
"user": user, "secret": secret})
} else {
c.JSON(http.StatusOK, gin.H{
"user": user, "secret": "NO SECRET :("})
}
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
Goroutines inside a middleware
When starting new Goroutines inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.— Start a new... In middleware or handler goroutine when , you Should not Use the original context , You must use a read-only copy .
func main() {
r := gin.Default()
r.GET("/long_async", func(c *gin.Context) {
// create copy to be used inside the goroutine
cCp := c.Copy()
go func() {
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// note that you are using the copied context "cCp", IMPORTANT
log.Println("Done! in path " + cCp.Request.URL.Path)
}()
})
r.GET("/long_sync", func(c *gin.Context) {
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// since we are NOT using a goroutine, we do not have to copy the context
log.Println("Done! in path " + c.Request.URL.Path)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
Custom log output format Custom Log Format
func main() {
router := gin.New()
// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
// By default gin.DefaultWriter = os.Stdout
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// your custom format
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
router.Use(gin.Recovery())
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080")
}
Model binding for request parameters Model binding and validation
// Binding from JSON,binding:"required"// Indicates that the parameter must be submitted
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
// Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error()})
return
}
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{
"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "you are logged in"})
})
// Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>manu</user>
// <password>123</password>
// </root>)
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
if err := c.ShouldBindXML(&xml); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error()})
return
}
if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{
"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "you are logged in"})
})
// Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error()})
return
}
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{
"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "you are logged in"})
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
- Parameter validation fails
$ curl -v -X POST \
http://localhost:8080/loginJSON \
-H 'content-type: application/json' \
-d '{ "user": "manu" }'
> POST /loginJSON HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Fri, 04 Aug 2017 03:51:31 GMT
< Content-Length: 100
<
{
"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
Custom request parameter validation
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
// Booking contains binded and validated data.
type Booking struct {
//binding Set the parameter to must and must follow bookabledate The rules of ,time_format Set the date format
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
//gifield=CheckIn Express check_out The date of must be earlier than check_in On the evening of
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
// Must return if the rules of the custom validator are met true
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
date, ok := fl.Field().Interface().(time.Time)
if ok {
today := time.Now()
if today.After(date) {
return false
}
}
return true
}
func main() {
route := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
route.GET("/bookable", getBookable)
route.Run(":8085")
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{
"message": "Booking dates are valid!"})
} else {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error()})
}
}
- The test sample
$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
{
"message":"Booking dates are valid!"}
$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
{
"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
{
"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
Parameter binding Uri (Bind Uri)
package main
import "github.com/gin-gonic/gin"
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func main() {
route := gin.Default()
route.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{
"msg": err.Error()})
return
}
c.JSON(200, gin.H{
"name": person.Name, "uuid": person.ID})
})
route.Run(":8088")
}
Parameter request header (Bind Header)
Little cheese : Request header verification can be used to block most crawlers lacking request headers
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
}
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
h := testHeader{
}
if err := c.ShouldBindHeader(&h); err != nil {
c.JSON(200, err)
}
fmt.Printf("%#v\n", h)
c.JSON(200, gin.H{
"Rate": h.Rate, "Domain": h.Domain})
})
r.Run()
}
Use curl Access the target interface
*client curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ output { "Domain":"music","Rate":300}*
Bind HTML checkboxes
In fact, it's just , Receive parameters of array type
type myForm struct { Colors []string `form:"colors[]"` } ... func formHandler(c *gin.Context) { var fakeForm myForm c.ShouldBind(&fakeForm) c.JSON(200, gin.H{ "color": fakeForm.Colors}) }
Gin Framework of the JSON Introduce
relevant json Introduction to hijacking :
JSON hijacked
Using SecureJSON to prevent json hijacking. Default prepends "while(1)," to response body if the given struct is array values.
func main() {
r := gin.Default()
// You can also use your own secure json prefix
// r.SecureJsonPrefix(")]}',\n")
r.GET("/someJSON", func(c *gin.Context) {
names := []string{
"lena", "austin", "foo"}
// Will output : while(1);["lena","austin","foo"]
c.SecureJSON(http.StatusOK, names)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
func main() {
r := gin.Default()
r.GET("/JSONP", func(c *gin.Context) {
data := gin.H{
"foo": "bar",
}
//callback is x
// Will output : x({\"foo\":\"bar\"})
c.JSONP(http.StatusOK, data)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
// client
// curl http://127.0.0.1:8080/JSONP?callback=x
}
unc main() {
r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) {
data := gin.H{
"lang": "GO Language ",
"tag": "<br>",
}
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
c.AsciiJSON(http.StatusOK, data)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
Normally, JSON replaces special HTML characters with their unicode entities, e.g.< becomes \u003c. If you want to encode such characters literally, you can use PureJSON instead.
This feature is unavailable in Go 1.6 and lower.
Usually ,JSON Use it unicode Entities replace special HTML character , for example “<` become “\u003c”. If you want to encode these characters literally , have access to PureJSON.
This function is available in Go 1.6 Not available in and earlier versions .
func main() {
r := gin.Default()
// Serves unicode entities
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// Serves literal characters
r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
Gin file management
func main() {
router := gin.Default()
router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
func main() {
router := gin.Default()
router.GET("/local/file", func(c *gin.Context) {
c.File("local/file.go")
})
var fs http.FileSystem = // ...
router.GET("/fs/file", func(c *gin.Context) {
c.FileFromFS("fs/file.go", fs)
})
}
func main() {
router := gin.Default()
router.GET("/someDataFromReader", func(c *gin.Context) {
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
reader := response.Body
defer reader.Close()
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
extraHeaders := map[string]string{
"Content-Disposition": `attachment; filename="gopher.png"`,
}
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
router.Run(":8080")
}
HTML Template
Using LoadHTMLGlob() or LoadHTMLFiles()
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
})
router.Run(":8080")
}
templates/index.tmpl
<html>
<h1>
{
{ .title }}
</h1>
</html>
Using templates with same name in different directories
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "Posts",
})
})
router.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "Users",
})
})
router.Run(":8080")
}
templates/posts/index.tmpl
{
{ define "posts/index.tmpl" }}
<html><h1>
{
{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{
{ end }}
templates/users/index.tmpl
{
{ define "users/index.tmpl" }}
<html><h1>
{
{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{
{ end }}
Custom Template renderer
You can also use your own html template render
import "html/template"
func main() {
router := gin.Default()
html := template.Must(template.ParseFiles("file1", "file2"))
router.SetHTMLTemplate(html)
router.Run(":8080")
}
Custom Delimiters
You may use custom delims
r := gin.Default()
r.Delims("{[{", "}]}")
r.LoadHTMLGlob("/path/to/templates")
Custom Template Funcs
See the detail example code.
main.go
import (
"fmt"
"html/template"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d%02d/%02d", year, month, day)
}
func main() {
router := gin.Default()
router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
router.LoadHTMLFiles("./testdata/template/raw.tmpl")
router.GET("/raw", func(c *gin.Context) {
c.HTML(http.StatusOK, "raw.tmpl", gin.H{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
})
router.Run(":8080")
}
raw.tmpl
Date: {[{.now | formatAsDate}]}
Result:
Date: 2017/07/01
Multitemplate
Gin allow by default use only one html.Template. Check a multitemplate render for using features like go 1.6 block template.
Gin Redirect
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})
Issuing a HTTP redirect from POST. Refer to issue: #444
r.POST("/test", func(c *gin.Context) {
c.Redirect(http.StatusFound, "/foo")
})
Issuing a Router redirect, use HandleContext like below.
r.GET("/test", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{
"hello": "world"})
})
Support Let’s Encrypt
Let’s Encrypt Introduction to :
- example for 1-line LetsEncrypt HTTPS servers
package main
import (
"log"
"github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// Ping handler
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
}
- example for custom autocert manager
package main
import (
"log"
"github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/acme/autocert"
)
func main() {
r := gin.Default()
// Ping handler
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
Cache: autocert.DirCache("/var/www/.cache"),
}
log.Fatal(autotls.RunWithManager(r, &m))
}
utilize Gin Open multiple services
Take a chestnut
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
g.Go(func() error {
err := server01.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
return err
})
g.Go(func() error {
err := server02.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
return err
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
Set and get cookie
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("gin_cookie")
if err != nil {
cookie = "NotSet"
c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
}
fmt.Printf("Cookie value: %s \n", cookie)
})
router.Run()
}
Test paper
package main
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
return r
}
func main() {
r := setupRouter()
r.Run(":8080")
}
Test for code example above
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, "pong", w.Body.String())
}
边栏推荐
- Greenplum [question 05] Greenplum streaming server custom client problem handling (increasing)
- Classes and objects, methods and encapsulation
- Detailed analysis of mathematical modeling problem a (vaccine production scheduling problem) of May Day cup in 2021
- Caused by: org. h2.jdbc. JdbcSQLSyntaxErrorException: Table “USER“ not found; SQL statement:
- About architecture (in no particular order)
- 国企为什么要上市
- Go 接口实现原理【高阶篇】
- Reason: Canonical names should be kebab-case (‘-‘ separated), lowercase alpha-numeric characters and
- MySQL Linux Installation mysql-5.7.24
- Field xxxxDAO in com. nero. hua. service. impl. LoginServiceImpl required a bean of type
猜你喜欢

ESP8266 Arduino OLED

yolov5

Serial port oscilloscope_ port_ Setup of plotter secondary development environment (including QT setup)

Details of FPGA syntax

Project requirements specification

公司注册认缴资金多久

Deep understanding of asynchronous programming

Multi thread learning III. classification of threads

DMA RDMA technology details

The combined application of TOPSIS and fuzzy borde (taking the second Dawan District cup and the national championship as examples, it may cause misunderstanding, and the Dawan District cup will be up
随机推荐
按键精灵的简单入门
tkinter使用WebView2网页组件(续篇)
Yolov5 realizes road crack detection
ESP8266 Arduino OLED
12.26 exercise summary
38. appearance series
PHP实现图片登录验证码的解决方案
Thesis reading_ Figure neural network gin
Legal liabilities to be borne by the person in charge of the branch
Development of video preview for main interface of pupanvr-ui
Caused by: org. h2.jdbc. JdbcSQLSyntaxErrorException: Table “USER“ already exists; SQL statement:
What is the project advance payment
Qs100 at command mqtt access thingsboard
Go interface implementation principle [advanced level]
Role and understanding of proc/cmdline
4.3 模拟浏览器操作和页面等待(显示等待和隐式等待、句柄)
MySQL Linux Installation mysql-5.7.24
How long is the company's registered capital subscribed
Greenplum [question 05] Greenplum streaming server custom client problem handling (increasing)
C语言-数组的定义方式