Skip to content

Gin

Offizielle Dokumentation: Gin Web Framework (gin-gonic.com)

Repository-Adresse: gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang)

Offizielle Beispiele: gin-gonic/examples: A repository to host examples and tutorials for Gin. (github.com)

Einführung

Gin ist ein in Go (Golang) geschriebenes Web-Framework. Es verfügt über eine martini-ähnliche API mit deutlich besserer Leistung – dank httprouter ist es bis zu 40-mal schneller. Wenn Sie Leistung und gute Produktivität benötigen, werden Sie Gin lieben. Im Vergleich zu Iris und Beego ist Gin eher ein leichtgewichtiges Framework, das sich nur um den Web-Teil kümmert und极致 Routing-Leistung anstrebt. Die Funktionen sind vielleicht nicht so umfassend, aber es ist leichtgewichtig und einfach zu erweitern, was auch sein Vorteil ist. Daher ist Gin unter allen Web-Frameworks am einfachsten zu erlernen und zu verwenden.

Eigenschaften

  • Schnell: Auf Radix-Baum basierendes Routing, geringer Speicherverbrauch. Keine Reflexion. Vorhersehbare API-Leistung.
  • Middleware-Unterstützung: Eingehende HTTP-Anfragen können von einer Reihe von Middlewares und endgültigen Operationen verarbeitet werden. Zum Beispiel: Logger, Authorization, GZIP, endgültige DB-Operation.
  • Crash-Behandlung: Gin kann eine Panic abfangen, die während einer HTTP-Anfrage auftritt, und sie recoveren. Somit ist Ihr Server immer verfügbar.
  • JSON-Validierung: Gin kann JSON-Anfragen parsen und validieren, zum Beispiel das Vorhandensein erforderlicher Werte überprüfen.
  • Routing-Gruppen: Bessere Organisation von Routen. Ob Autorisierung erforderlich ist, verschiedene API-Versionen... Außerdem können diese Gruppen unbegrenzt verschachtelt werden, ohne die Leistung zu beeinträchtigen.
  • Fehlerverwaltung: Gin bietet eine bequeme Methode, um alle während einer HTTP-Anfrage aufgetretenen Fehler zu sammeln. Schließlich kann die Middleware sie in eine Protokolldatei, Datenbank schreiben und über das Netzwerk senden.
  • Eingebautes Rendering: Gin bietet einfach zu verwendende APIs für JSON-, XML- und HTML-Rendering.
  • Erweiterbarkeit: Das Erstellen einer neuen Middleware ist sehr einfach

Installation

Stand 2022/11/22 unterstützt Gin die Go-Mindestversion 1.16, es wird empfohlen, go mod zur Verwaltung von Projektabhängigkeiten zu verwenden.

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

Import

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

Schnellstart

go
package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

func main() {
   engine := gin.Default() // Gin-Engine erstellen
   engine.GET("/ping", func(context *gin.Context) {
      context.JSON(http.StatusOK, gin.H{
         "message": "pong",
      })
   })
   engine.Run() // Server starten, standardmäßig Überwachung von localhost:8080
}

Anfrage-URL

http
GET localhost:8080/ping

Antwort

http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 22 Nov 2022 08:47:11 GMT
Content-Length: 18

{
  "message": "pong"
}
Response file saved.
> 2022-11-22T164711.200.json

Dokumentation

Eigentlich enthält die offizielle Gin-Dokumentation nicht viele Tutorials, meist nur einige Einführungen und grundlegende Verwendungen sowie einige Beispiele. Aber unter der gin-gonic/-Organisation gibt es ein gin-gonic/examples-Repository, ein von der Community gemeinsam gepflegtes gin-Beispiel-Repository. Alles ist auf Englisch, die Aktualisierungshäufigkeit ist nicht besonders hoch, auch der Autor hat von hier aus langsam das gin-Framework erlernt.

Beispiel-Repository-Adresse: gin-gonic/examples: A repository to host examples and tutorials for Gin. (github.com)

TIP

Vor dem Start wird empfohlen, HttpRouter zu lesen: HttpRouter

Parameter-Parsing

Die Parameter-Parsing in gin unterstützt insgesamt drei Methoden: Routenparameter, URL-Parameter, Formularparameter. Diese werden unten einzeln mit Code-Beispielen erklärt, relativ einfach und verständlich.

Routenparameter

Routenparameter sind tatsächlich eine Kapselung der Parameter-Parsing-Funktionalität von HttpRouter, die Verwendungsmethode ist im Wesentlichen mit HttpRouter identisch.

go
package main

import (
   "github.com/gin-gonic/gin"
   "log"
   "net/http"
)

func main() {
   e := gin.Default()
   e.GET("/findUser/:username/:userid", FindUser)
   e.GET("/downloadFile/*filepath", UserPage)

   log.Fatalln(e.Run(":8080"))
}

// Beispiel für benannte Parameter
func FindUser(c *gin.Context) {
   username := c.Param("username")
   userid := c.Param("userid")
   c.String(http.StatusOK, "username is %s\n userid is %s", username, userid)
}

// Beispiel für Pfadparameter
func UserPage(c *gin.Context) {
   filepath := c.Param("filepath")
   c.String(http.StatusOK, "filepath is  %s", filepath)
}

Beispiel 1

bash
curl --location --request GET '127.0.0.1:8080/findUser/jack/001'
username is jack
 userid is 001

Beispiel 2

bash
curl --location --request GET '127.0.0.1:8080/downloadFile/img/fruit.png'
filepath is  /img/fruit.png

URL-Parameter

Traditionelle URL-Parameter, das Format ist /url?key=val&key1=val1&key2=val2.

go
package main

import (
   "github.com/gin-gonic/gin"
   "log"
   "net/http"
)

func main() {
   e := gin.Default()
   e.GET("/findUser", FindUser)
   log.Fatalln(e.Run(":8080"))
}

func FindUser(c *gin.Context) {
   username := c.DefaultQuery("username", "defaultUser")
   userid := c.Query("userid")
   c.String(http.StatusOK, "username is %s\nuserid is %s", username, userid)
}

Beispiel 1

bash
curl --location --request GET '127.0.0.1:8080/findUser?username=jack&userid=001'
username is jack
userid is 001

Beispiel 2

bash
curl --location --request GET '127.0.0.1:8080/findUser'
username is defaultUser
userid is

Formularparameter

Der Inhaltstyp von Formularen ist normalerweise application/json, application/x-www-form-urlencoded, application/xml, multipart/form-data.

go
package main

import (
  "github.com/gin-gonic/gin"
  "net/http"
)

func main() {
  e := gin.Default()
  e.POST("/register", RegisterUser)
  e.POST("/update", UpdateUser)
  e.Run(":8080")
}

func RegisterUser(c *gin.Context) {
  username := c.PostForm("username")
  password := c.PostForm("password")
  c.String(http.StatusOK, "successfully registered,your username is [%s],password is [%s]", username, password)
}

func UpdateUser(c *gin.Context) {
  var form map[string]string
  c.ShouldBind(&form)
  c.String(http.StatusOK, "successfully update,your username is [%s],password is [%s]", form["username"], form["password"])
}

Beispiel 1: Verwendung von form-data

bash
curl --location --request POST '127.0.0.1:8080/register' \
--form 'username="jack"' \
--form 'password="123456"'
successfully registered,your username is [jack],password is [123456]

Die PostForm-Methode analysiert standardmäßig Formulare vom Typ application/x-www-form-urlencoded und multipart/form-data.

Beispiel 2: Verwendung von json

bash
curl --location --request POST '127.0.0.1:8080/update' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username":"username",
    "password":"123456"
}'
successfully update,your username is [username],password is [123456]

Daten-Parsing

In den meisten Fällen verwenden wir Strukturen, um Daten zu speichern, anstatt Parameter direkt zu parsen. In gin sind die Hauptmethoden für die Datenbindung Bind() und ShouldBind(). Der Unterschied zwischen beiden besteht darin, dass erstere intern auch ShouldBind() aufruft, aber bei Rückgabe von err direkt mit 400 antwortet, während letztere dies nicht tut. Wenn Sie flexiblere Fehlerbehandlung wünschen, wird letztere empfohlen. Diese beiden Funktionen schließen automatisch anhand des content-type der Anfrage darauf, wie sie geparst werden soll.

go
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
    // Ruft ShouldBindWith() auf
  if err := c.ShouldBindWith(obj, b); err != nil {
    c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // Direkte 400 BadRequest-Antwort
    return err
  }
  return nil
}

Wenn Sie selbst auswählen möchten, können Sie BindWith() und ShouldBindWith() verwenden, zum Beispiel:

go
c.MustBindWith(obj, binding.JSON) //json
c.MustBindWith(obj, binding.XML) //xml

Gin unterstützt folgende Bindungstypen:

go
var (
   JSON          = jsonBinding{}
   XML           = xmlBinding{}
   Form          = formBinding{}
   Query         = queryBinding{}
   FormPost      = formPostBinding{}
   FormMultipart = formMultipartBinding{}
   ProtoBuf      = protobufBinding{}
   MsgPack       = msgpackBinding{}
   YAML          = yamlBinding{}
   Uri           = uriBinding{}
   Header        = headerBinding{}
   TOML          = tomlBinding{}
)

Beispiel

go
package main

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

type LoginUser struct {
  Username string `binding:"required" json:"username" form:"username" uri:"username"`
  Password string `binding:"required" json:"password" form:"password" uri:"password"`
}

func main() {
  e := gin.Default()
  e.POST("/loginWithJSON", Login)
  e.POST("/loginWithForm", Login)
  e.GET("/loginWithQuery/:username/:password", Login)
  e.Run(":8080")
}

func Login(c *gin.Context) {
  var login LoginUser
    // ShouldBind verwenden, damit gin automatisch ableitet
  if c.ShouldBind(&login) == nil && login.Password != "" && login.Username != "" {
    c.String(http.StatusOK, "login successfully !")
  } else {
    c.String(http.StatusBadRequest, "login failed !")
  }
  fmt.Println(login)
}

JSON-Datenbindung

bash
curl --location --request POST '127.0.0.1:8080/loginWithJSON' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username":"root",
    "password":"root"
}'
login successfully !

Formular-Datenbindung

go
curl --location --request POST '127.0.0.1:8080/loginWithForm' \
--form 'username="root"' \
--form 'password="root"'
login successfully !

URL-Datenbindung

go
curl --location --request GET '127.0.0.1:8080/loginWithQuery/root/root'
login failed !

Hier tritt ein Fehler auf, da der ausgegebene content-type ein leerer String ist und nicht abgeleitet werden kann, wie die Daten geparst werden sollen. Wenn Sie also URL-Parameter verwenden, sollten Sie die Parsing-Methode manuell angeben, zum Beispiel:

go
if err := c.ShouldBindUri(&login); err == nil && login.Password != "" && login.Username != "" {
   c.String(http.StatusOK, "login successfully !")
} else {
   fmt.Println(err)
   c.String(http.StatusBadRequest, "login failed !")
}

Mehrfaches Binden

Allgemeine Methoden binden Daten durch Aufrufen der c.Request.Body-Methode, aber diese Methode kann nicht mehrmals aufgerufen werden, zum Beispiel c.ShouldBind, nicht wiederverwendbar. Wenn Sie mehrmals binden möchten, können Sie c.ShouldBindBodyWith verwenden.

go
func SomeHandler(c *gin.Context) {
  objA := formA{}
  objB := formB{}
  // Liest c.Request.Body und speichert das Ergebnis im Kontext.
  if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
    c.String(http.StatusOK, `the body should be formA`)
  // Hier wird der im Kontext gespeicherte Body wiederverwendet.
  }
  if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
    c.String(http.StatusOK, `the body should be formB JSON`)
  // Kann auch andere Formate akzeptieren
  }
  if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
    c.String(http.StatusOK, `the body should be formB XML`)
  }
}

TIP

c.ShouldBindBodyWith speichert den Body vor dem Binden im Kontext. Dies hat leichte Auswirkungen auf die Leistung. Wenn ein Aufruf zum Binden ausreicht, sollten Sie diese Methode nicht verwenden. Nur bestimmte Formate benötigen diese Funktionalität, wie JSON, XML, MsgPack, ProtoBuf. Für andere Formate wie Query, Form, FormPost, FormMultipart kann c.ShouldBind() mehrmals aufgerufen werden, ohne Leistungseinbußen.

Datenvalidierung

Das in gin eingebaute Validierungstool ist tatsächlich github.com/go-playground/validator/v10, die Verwendung ist fast identisch, Validator

Einfaches Beispiel

go
type LoginUser struct {
   Username string `binding:"required"  json:"username" form:"username" uri:"username"`
   Password string `binding:"required" json:"password" form:"password" uri:"password"`
}

func main() {
   e := gin.Default()
   e.POST("/register", Register)
   log.Fatalln(e.Run(":8080"))
}

func Register(ctx *gin.Context) {
   newUser := &LoginUser{}
   if err := ctx.ShouldBind(newUser); err == nil {
      ctx.String(http.StatusOK, "user%+v", *newUser)
   } else {
      ctx.String(http.StatusBadRequest, "invalid user,%v", err)
   }
}

Test

curl --location --request POST 'http://localhost:8080/register' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username":"jack1"

}'

Ausgabe

invalid user,Key: 'LoginUser.Password' Error:Field validation for 'Password' failed on the 'required' tag

TIP

Zu beachten ist, dass in gin das Validierungs-Tag des Validators binding ist, während bei alleiniger Verwendung von validator das Validierungs-Tag validator ist

Datenantwort

Die Datenantwort ist der letzte Schritt bei der Verarbeitung einer Schnittstelle. Das Backend gibt alle verarbeiteten Daten über das HTTP-Protokoll an den Aufrufer zurück. Gin bietet umfangreiche eingebaute Unterstützung für Datenantworten, die Verwendung ist einfach und klar, sehr einfach zu erlernen.

Einfaches Beispiel

go
func Hello(c *gin.Context) {
    // Gibt Daten im reinen String-Format zurück, http.StatusOK repräsentiert den 200-Statuscode, Daten sind "Hello world !"
  c.String(http.StatusOK, "Hello world !")
}

HTML-Rendering

TIP

Beim Laden von Dateien ist der Standard-Stammpfad der Projektpfad, also der Pfad, in dem sich die go.mod-Datei befindet. Die index.html im folgenden Beispiel befindet sich unter index.html im Stammpfad. Normalerweise werden diese Template-Dateien nicht im Stammpfad gespeichert, sondern in einem statischen Ressourcenordner

go
func main() {
   e := gin.Default()
    // HTML-Datei laden, kann auch Engine.LoadHTMLGlob() verwenden
   e.LoadHTMLFiles("index.html")
   e.GET("/", Index)
   log.Fatalln(e.Run(":8080"))
}

func Index(c *gin.Context) {
   c.HTML(http.StatusOK, "index.html", gin.H{})
}

Test

curl --location --request GET 'http://localhost:8080/'

Rückgabe

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>GinLearn</title>
  </head>

  <body>
    <h1>Hello World!</h1>
    <h1>This is a HTML Template Render Example</h1>
  </body>
</html>

Schnelle Antwort

Zuvor wurde häufig die context.String()-Methode zur Datenantwort verwendet. Dies ist die ursprünglichste Antwortmethode, die direkt einen String zurückgibt. In gin sind tatsächlich viele schnelle Antwortmethoden eingebaut, zum Beispiel:

go
// Verwendet Render zum Schreiben des Response-Headers und zum Rendern der Daten
func (c *Context) Render(code int, r render.Render)

// Rendert ein HTML-Template, name ist der HTML-Pfad, obj ist der Inhalt
func (c *Context) HTML(code int, name string, obj any)

// Rendert Daten als eingerückten JSON-String, wird normalerweise nicht empfohlen, da dies mehr Übertragungsaufwand verursacht
func (c *Context) IndentedJSON(code int, obj any)

// Sicheres JSON, kann JSON-Hijacking verhindern, Details unter: https://www.cnblogs.com/xusion/articles/3107788.html
func (c *Context) SecureJSON(code int, obj any)

// Rendert im JSONP-Verfahren
func (c *Context) JSONP(code int, obj any)

// Rendert im JSON-Verfahren
func (c *Context) JSON(code int, obj any)

// Rendert im JSON-Verfahren, konvertiert Unicode-Code in ASCII-Code
func (c *Context) AsciiJSON(code int, obj any)

// Rendert im JSON-Verfahren, ohne Escape von HTML-Sonderzeichen
func (c *Context) PureJSON(code int, obj any)

// Rendert im XML-Verfahren
func (c *Context) XML(code int, obj any)

// Rendert im YML-Verfahren
func (c *Context) YAML(code int, obj any)

// Rendert im TOML-Verfahren
func (c *Context) TOML(code int, obj interface{})

// Rendert im ProtoBuf-Verfahren
func (c *Context) ProtoBuf(code int, obj any)

// Rendert im String-Verfahren
func (c *Context) String(code int, format string, values ...any)

// Leitet zu einem bestimmten Ort um
func (c *Context) Redirect(code int, location string)

// Schreibt Daten in den Antwort-Stream
func (c *Context) Data(code int, contentType string, data []byte)

// Liest Stream über Reader und schreibt in den Antwort-Stream
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)

// Schreibt Datei effizient in den Antwort-Stream
func (c *Context) File(filepath string)

// Schreibt Datei-Stream aus fs auf effiziente Weise in den Antwort-Stream
func (c *Context) FileFromFS(filepath string, fs http.FileSystem)

// Schreibt Datei-Stream aus fs auf effiziente Weise in den Antwort-Stream und wird beim Client mit dem angegebenen Dateinamen heruntergeladen
func (c *Context) FileAttachment(filepath, filename string)

// Schreibt Server-Push-Stream in den Antwort-Stream
func (c *Context) SSEvent(name string, message any)

// Sendet eine Stream-Antwort und gibt einen booleschen Wert zurück, um zu bestimmen, ob der Client den Stream unterbrochen hat
func (c *Context) Stream(step func(w io.Writer) bool) bool

Für die meisten Anwendungen wird am häufigsten context.JSON verwendet, die anderen seltener. Hier werden keine Beispiele gezeigt, da alle relativ einfach und verständlich sind, fast alles ist direkter Aufruf.

Asynchrone Verarbeitung

In gin muss die asynchrone Verarbeitung in Kombination mit Goroutines verwendet werden, die Anwendung ist sehr einfach.

go
// copy gibt eine Kopie des aktuellen Context zurück, um sicher außerhalb des aktuellen Context-Gültigkeitsbereichs verwendet zu werden, kann an eine Goroutine übergeben werden
func (c *Context) Copy() *Context
go
func main() {
  e := gin.Default()
  e.GET("/hello", Hello)
  log.Fatalln(e.Run(":8080"))
}

func Hello(c *gin.Context) {
  ctx := c.Copy()
  go func() {
    // Sub-Routine sollte die Kopie des Context verwenden, nicht den ursprünglichen Context
    log.Println("Asynchrone Verarbeitungsfunktion: ", ctx.HandlerNames())
  }()
  log.Println("Schnittstellen-Verarbeitungsfunktion: ", c.HandlerNames())
  c.String(http.StatusOK, "hello")
}

Test

go
curl --location --request GET 'http://localhost:8080/hello'

Ausgabe

go
2022/12/21 13:33:47 Asynchrone Verarbeitungsfunktion:  []
2022/12/21 13:33:47 Schnittstellen-Verarbeitungsfunktion:  [github.com/gin-gonic/gin.LoggerWithConfig.func1 github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 main.Hello]
[GIN] 2022/12/21 - 13:33:47 | 200 |     11.1927ms |             ::1 | GET      "/hello"

Wie Sie sehen, ist die Ausgabe unterschiedlich. Beim Kopieren wurden aus Sicherheitsgründen viele Elementwerte gelöscht.

Dateiübertragung

Die Dateiübertragung ist eine unverzichtbare Funktion von Web-Anwendungen. Gin unterstützt dies mit einer sehr einfachen Kapselung, aber im Wesentlichen ist der Prozess derselbe wie bei der Verwendung der nativen net/http. Der Prozess besteht darin, den Dateistream aus dem Anfragekörper zu lesen und dann lokal zu speichern.

Einzeldatei-Upload

go
func main() {
  e := gin.Default()
  e.POST("/upload", uploadFile)
  log.Fatalln(e.Run(":8080"))
}

func uploadFile(ctx *gin.Context) {
  // Datei abrufen
  file, err := ctx.FormFile("file")
  if err != nil {
    ctx.String(http.StatusBadRequest, "%+v", err)
    return
  }
  // Lokal speichern
  err = ctx.SaveUploadedFile(file, "./"+file.Filename)
  if err != nil {
    ctx.String(http.StatusBadRequest, "%+v", err)
    return
  }
  // Ergebnis zurückgeben
  ctx.String(http.StatusOK, "upload %s size:%d byte successfully!", file.Filename, file.Size)
}

Test

curl --location --request POST 'http://localhost:8080/upload' \
--form 'file=@"/C:/Users/user/Pictures/Camera Roll/a.jpg"'

Ergebnis

upload a.jpg size:1424 byte successfully!

TIP

Normalerweise wird für Datei-Uploads POST als Method angegeben. Einige Unternehmen bevorzugen möglicherweise PUT. Ersteres ist eine einfache HTTP-Anfrage, letzteres eine komplexe HTTP-Anfrage. Der genaue Unterschied wird hier nicht erläutert. Wenn Sie letzteres verwenden, insbesondere bei Projekten mit getrenntem Frontend und Backend, ist eine entsprechende CORS-Konfiguration erforderlich, und Gin unterstützt standardmäßig kein CORS CORS-Konfiguration.

Mehrfachdatei-Upload

go
func main() {
   e := gin.Default()
   e.POST("/upload", uploadFile)
   e.POST("/uploadFiles", uploadFiles)
   log.Fatalln(e.Run(":8080"))
}

func uploadFiles(ctx *gin.Context) {
  // Von gin geparstes Multipart-Formular abrufen
  form, _ := ctx.MultipartForm()
  // Dateiliste basierend auf Schlüsselwert abrufen
  files := form.File["files"]
  // Dateiliste durchlaufen und lokal speichern
  for _, file := range files {
    err := ctx.SaveUploadedFile(file, "./"+file.Filename)
    if err != nil {
      ctx.String(http.StatusBadRequest, "upload failed")
      return
    }
  }
  // Ergebnis zurückgeben
  ctx.String(http.StatusOK, "upload %d files successfully!", len(files))
}

Test

curl --location --request POST 'http://localhost:8080/uploadFiles' \
--form 'files=@"/C:/Users/Stranger/Pictures/Camera Roll/a.jpg"' \
--form 'files=@"/C:/Users/Stranger/Pictures/Camera Roll/123.jpg"' \
--form 'files=@"/C:/Users/Stranger/Pictures/Camera Roll/girl.jpg"'

Ausgabe

upload 3 files successfully!

Datei-Download

Für den Datei-Download hat Gin die API der ursprünglichen Standardbibliothek erneut gekapselt, was den Datei-Download extrem einfach macht.

go
func main() {
  e := gin.Default()
  e.POST("/upload", uploadFile)
  e.POST("/uploadFiles", uploadFiles)
  e.GET("/download/:filename", download)
  log.Fatalln(e.Run(":8080"))
}

func download(ctx *gin.Context) {
    // Dateinamen abrufen
  filename := ctx.Param("filename")
    // Entsprechende Datei zurückgeben
  ctx.FileAttachment(filename, filename)
}

Test

curl --location --request GET 'http://localhost:8080/download/a.jpg'

Ergebnis

Content-Disposition: attachment; filename="a.jpg"
Date: Wed, 21 Dec 2022 08:04:17 GMT
Last-Modified: Wed, 21 Dec 2022 07:50:44 GMT

Finden Sie es nicht zu einfach? Versuchen wir, den Prozess ohne die Framework-Methoden selbst zu schreiben

go
func download(ctx *gin.Context) {
   // Parameter abrufen
   filename := ctx.Param("filename")

   // Anfrage-Antwort-Objekt und Anfrage-Objekt
   response, request := ctx.Writer, ctx.Request
   // In Response-Header schreiben
   // response.Header().Set("Content-Type", "application/octet-stream") Datei als Binärstream übertragen
   response.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) // Sicheres Escaping des Dateinamens
   response.Header().Set("Content-Transfer-Encoding", "binary")                                            // Übertragungskodierung
   http.ServeFile(response, request, filename)
}

Tatsächlich hat auch net/http bereits gut genug gekapselt

TIP

Sie können Engine.MaxMultipartMemory verwenden, um den maximalen Speicher für Dateiübertragungen festzulegen, standardmäßig 32 << 20 // 32 MB

Routing-Verwaltung

Die Routing-Verwaltung ist ein sehr wichtiger Teil eines Systems, es muss sichergestellt werden, dass jede Anfrage korrekt der entsprechenden Funktion zugeordnet wird.

Routing-Gruppen

Das Erstellen einer Routing-Gruppe dient dazu, Schnittstellen zu kategorisieren. Verschiedene Kategorien von Schnittstellen entsprechen verschiedenen Funktionen und sind einfacher zu verwalten.

go
func Hello(c *gin.Context) {

}

func Login(c *gin.Context) {

}

func Update(c *gin.Context) {

}

func Delete(c *gin.Context) {

}

Angenommen, wir haben die oben genannten vier Schnittstellen, vorerst unabhängig von der internen Implementierung. Hello, Login sind eine Gruppe, Update, Delete sind eine Gruppe.

go
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup

Beim Erstellen einer Gruppe können wir auch Handler für das Stammverzeichnis der Gruppe registrieren, aber meistens wird dies nicht getan.

go
func main() {
  e := gin.Default()
  v1 := e.Group("v1")
  {
    v1.GET("/hello", Hello)
    v1.GET("/login", Login)
  }
  v2 := e.Group("v2")
  {
    v2.POST("/update", Update)
    v2.DELETE("/delete", Delete)
  }
}

Wir haben sie in v1, v2 zwei Gruppen unterteilt. Die geschweiften Klammern {} dienen nur der Übersichtlichkeit und zeigen an, dass die in den Klammern registrierten Handler zur selben Routing-Gruppe gehören. Funktionell haben sie keine Wirkung. Ebenso unterstützt gin auch verschachtelte Gruppen, die Methode ist identisch mit dem obigen Beispiel, wird hier nicht demonstriert.

404-Routing

Die Engine-Struktur in gin bietet eine Methode NoRoute, um festzulegen, wie verarbeitet werden soll, wenn die aufgerufene URL nicht existiert. Entwickler können die Logik in diese Methode schreiben, damit sie automatisch aufgerufen wird, wenn ein Router nicht gefunden wird. Standardmäßig wird der Statuscode 404 zurückgegeben.

go
func (engine *Engine) NoRoute(handlers ...HandlerFunc)

Wir nehmen das vorherige Beispiel

go
func main() {
   e := gin.Default()
   v1 := e.Group("v1")
   {
      v1.GET("/hello", Hello)
      v1.GET("/login", Login)
   }
   v2 := e.Group("v2")
   {
      v2.POST("/update", Update)
      v2.DELETE("/delete", Delete)
   }
   // Handler registrieren
   e.NoRoute(func(context *gin.Context) { // Hier nur Demonstration, in Produktionsumgebungen keinen HTML-Code direkt zurückgeben
      context.String(http.StatusNotFound, "<h1>404 Page Not Found</h1>")
   })
   log.Fatalln(e.Run(":8080"))
}

Zufällige Anfrage senden

curl --location --request GET 'http://localhost:8080/'
<h1>404 Page Not Found</h1>

405-Routing

Im HTTP-Statuscode repräsentiert 405, dass die aktuelle Anfragemethode nicht erlaubt ist. Gin bietet folgende Methode

go
func (engine *Engine) NoMethod(handlers ...HandlerFunc)

um einen Handler zu registrieren, der automatisch aufgerufen wird, wenn dies eintritt. Voraussetzung ist, dass Engine.HandleMethodNotAllowed = true gesetzt ist.

go
func main() {
   e := gin.Default()
   // Muss auf true gesetzt werden
   e.HandleMethodNotAllowed = true
   v1 := e.Group("/v1")
   {
      v1.GET("/hello", Hello)
      v1.GET("/login", Login)
   }
   v2 := e.Group("/v2")
   {
      v2.POST("/update", Update)
      v2.DELETE("/delete", Delete)
   }
   e.NoRoute(func(context *gin.Context) {
      context.String(http.StatusNotFound, "<h1>404 Page Not Found</h1>")
   })
   // Handler registrieren
   e.NoMethod(func(context *gin.Context) {
      context.String(http.StatusMethodNotAllowed, "method not allowed")
   })
   log.Fatalln(e.Run(":8080"))
}

Nach der Konfiguration unterstützt gin standardmäßig keine OPTION-Anfragen im Header. Testen wir

curl --location --request OPTIONS 'http://localhost:8080/v2/delete'
method not allowed

Damit ist die Konfiguration erfolgreich

Umleitung

Die Umleitung in gin ist sehr einfach, rufen Sie einfach die gin.Context.Redirect()-Methode auf.

go
func main() {
  e := gin.Default()
  e.GET("/", Index)
  e.GET("/hello", Hello)
  log.Fatalln(e.Run(":8080"))
}

func Index(c *gin.Context) {
  c.Redirect(http.StatusMovedPermanently, "/hello")
}

func Hello(c *gin.Context) {
  c.String(http.StatusOK, "hello")
}

Test

curl --location --request GET 'http://localhost:8080/'

Ausgabe

hello

Middleware

Gin ist sehr leicht und flexibel, sehr erweiterbar, und die Unterstützung für Middleware ist ebenfalls sehr freundlich. In Gin müssen alle Schnittstellenanfragen durch Middleware gehen. Durch Middleware können Entwickler viele Funktionen und Logiken selbst implementieren. Gin hat zwar selbst wenige Funktionen, aber die von der Drittanbieter-Community entwickelten Gin-Erweiterungs-Middlewares sind sehr umfangreich.

Middleware ist im Wesentlichen immer noch ein Schnittstellen-Handler

go
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

In gewissem Sinne ist auch jeder Handler, der einer Anfrage entspricht, eine Middleware, nur mit einem sehr kleinen lokalen Geltungsbereich.

go
func Default() *Engine {
   debugPrintWARNINGDefault()
   engine := New()
   engine.Use(Logger(), Recovery())
   return engine
}

In der Quellcode von gin verwendet das im Default-Funktion zurückgegebene Standard-Engine zwei Standard-Middlewares Logger(), Recovery(). Wenn Sie die Standard-Middlewares nicht verwenden möchten, können Sie gin.New() verwenden.

Globale Middleware

Globale Middleware hat einen globalen Geltungsbereich, alle Anfragen im gesamten System durchlaufen diese Middleware.

go
func GlobalMiddleware() gin.HandlerFunc {
   return func(ctx *gin.Context) {
      fmt.Println("Globale Middleware ausgeführt...")
   }
}

Erstellen Sie zuerst eine Closure-Funktion, um die Middleware zu erstellen, und registrieren Sie dann die globale Middleware über Engine.Use().

go
func main() {
   e := gin.Default()
   // Globale Middleware registrieren
   e.Use(GlobalMiddleware())
   v1 := e.Group("/v1")
   {
      v1.GET("/hello", Hello)
      v1.GET("/login", Login)
   }
   v2 := e.Group("/v2")
   {
      v2.POST("/update", Update)
      v2.DELETE("/delete", Delete)
   }
   log.Fatalln(e.Run(":8080"))
}

Test

curl --location --request GET 'http://localhost:8080/v1/hello'

Ausgabe

[GIN-debug] Listening and serving HTTP on :8080
Globale Middleware ausgeführt...
[GIN] 2022/12/21 - 11:57:52 | 200 |       538.9µs |             ::1 | GET      "/v1/hello"

Lokale Middleware

Lokale Middleware hat einen lokalen Geltungsbereich, lokale Anfragen im System durchlaufen diese Middleware. Lokale Middleware kann auf einer einzelnen Route registriert werden, meistens wird sie jedoch auf einer Routing-Gruppe registriert.

go
func main() {
   e := gin.Default()
   // Globale Middleware registrieren
   e.Use(GlobalMiddleware())
   // Routing-Gruppen lokale Middleware registrieren
   v1 := e.Group("/v1", LocalMiddleware())
   {
      v1.GET("/hello", Hello)
      v1.GET("/login", Login)
   }
   v2 := e.Group("/v2")
   {
      // Einzelne Route lokale Middleware registrieren
      v2.POST("/update", LocalMiddleware(), Update)
      v2.DELETE("/delete", Delete)
   }
   log.Fatalln(e.Run(":8080"))
}

Test

curl --location --request POST 'http://localhost:8080/v2/update'

Ausgabe

Globale Middleware ausgeführt...
Lokale Middleware ausgeführt
[GIN] 2022/12/21 - 12:05:03 | 200 |       999.9µs |             ::1 | POST     "/v2/update"

Middleware-Prinzip

Die Verwendung und Anpassung von Middleware in Gin ist sehr einfach, die internen Prinzipien sind ebenfalls relativ einfach. Für das weitere Lernen ist ein einfaches Verständnis der internen Prinzipien erforderlich. Gin's Middleware verwendet das Verantwortlichkeitskettenmuster (Chain of Responsibility). Context verwaltet eine HandlersChain, im Wesentlichen ein []HandlerFunc, und einen index vom Datentyp int8. In der Engine.handlerHTTPRequest(c *Context)-Methode zeigt ein Codeabschnitt den Aufrufprozess: Nachdem gin die entsprechende Route im Routing-Baum gefunden hat, ruft es die Next()-Methode auf.

go
if value.handlers != nil {
   // Aufrufkette dem Context zuweisen
   c.handlers = value.handlers
   c.fullPath = value.fullPath
   // Middleware aufrufen
   c.Next()
   c.writermem.WriteHeaderNow()
   return
}

Der Aufruf von Next() ist der Schlüssel. Next() durchläuft die HandlerFunc in handlers der Route und führt sie aus. Hier sehen Sie, dass index dazu dient, die Aufrufposition der Middleware aufzuzeichnen. Dabei ist auch die für die Route registrierte Schnittstellenfunktion in handlers enthalten, weshalb wir zuvor gesagt haben, dass auch eine Schnittstelle eine Middleware ist.

go
func (c *Context) Next() {
   // Bei Eintritt +1, um endlose Rekursion zu vermeiden, Standardwert ist -1
   c.index++
   for c.index < int8(len(c.handlers)) {
      // HandlerFunc ausführen
      c.handlers[c.index](c)
      // Ausführung abgeschlossen, index+1
      c.index++
   }
}

Ändern wir die Logik von Hello(), um zu überprüfen, ob dies wirklich so ist

go
func Hello(c *gin.Context) {
   fmt.Println(c.HandlerNames())
}

Ausgabe

[github.com/gin-gonic/gin.LoggerWithConfig.func1 github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 main.GlobalMiddleware.func1 main.LocalMiddleware.func1 main.Hello]

Wie Sie sehen, ist die Reihenfolge der Middleware-Aufrufkette: Logger -> Recovery -> GlobalMiddleware -> LocalMiddleWare -> Hello. Das letzte Element der Aufrufkette ist die tatsächlich auszuführende Schnittstellenfunktion, alle vorherigen sind Middlewares.

TIP

Bei der Registrierung lokaler Routen gibt es folgende Assertion

go
finalSize := len(group.Handlers) + len(handlers) // Gesamtzahl der Middlewares
assert1(finalSize < int(abortIndex), "too many handlers")

Dabei ist abortIndex int8 = math.MaxInt8 >> 1 der Wert 63, d.h. die Anzahl der Routing-Registrierungen im System sollte 63 nicht überschreiten.

Timer-Middleware

Nachdem Sie die oben genannten Middleware-Prinzipien kennen, können Sie eine einfache Anfragezeit-Statistik-Middleware schreiben.

go
func TimeMiddleware() gin.HandlerFunc {
   return func(context *gin.Context) {
      // Startzeit aufzeichnen
      start := time.Now()
      // Nachfolgende Aufrufkette ausführen
      context.Next()
      // Zeitintervall berechnen
      duration := time.Since(start)
      // Nanosekunden ausgeben, um das Ergebnis zu beobachten
      fmt.Println("Anfragezeit: ", duration.Nanoseconds())
   }
}

func main() {
  e := gin.Default()
  // Globale Middleware registrieren, Timer-Middleware
  e.Use(GlobalMiddleware(), TimeMiddleware())
  // Routing-Gruppen lokale Middleware registrieren
  v1 := e.Group("/v1", LocalMiddleware())
  {
    v1.GET("/hello", Hello)
    v1.GET("/login", Login)
  }
  v2 := e.Group("/v2")
  {
    // Einzelne Route lokale Middleware registrieren
    v2.POST("/update", LocalMiddleware(), Update)
    v2.DELETE("/delete", Delete)
  }
  log.Fatalln(e.Run(":8080"))
}

Test

curl --location --request GET 'http://localhost:8080/v1/hello'

Ausgabe

Anfragezeit:  517600

Eine einfache Timer-Middleware wurde bereits fertig geschrieben. Später können Sie durch eigene Erkundung einige praktischere Middlewares schreiben.

Server-Konfiguration

Die Verwendung der Standardkonfiguration allein reicht bei weitem nicht aus. In den meisten Fällen müssen viele Server-Konfigurationen geändert werden, um die Anforderungen zu erfüllen.

HTTP-Konfiguration

Sie können Server über net/http erstellen und konfigurieren. Gin selbst unterstützt auch die Verwendung von Gin wie die native API.

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

Statische Ressourcen-Konfiguration

Statische Ressourcen waren früher ein unverzichtbarer Teil des Servers. Obwohl der Anteil jetzt allmählich abnimmt, gibt es immer noch viele Systeme, die eine monolithische Architektur verwenden.

Gin bietet drei Methoden zum Laden statischer Ressourcen

go
// Lädt einen bestimmten statischen Ordner
func (group *RouterGroup) Static(relativePath, root string) IRoutes

// Lädt ein bestimmtes fs
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes

// Lädt eine bestimmte statische Datei
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes

TIP

relativePath ist der relative Pfad, der auf die Web-URL abgebildet wird, root ist der tatsächliche Pfad der Datei im Projekt

Angenommen, die Projektstruktur sieht wie folgt aus

root
|
|-- static
|  |
|  |-- a.jpg
|  |
|  |-- favicon.ico
|
|-- view
  |
  |-- html
go
func main() {
   router := gin.Default()
   // Statisches Dateiverzeichnis laden
   router.Static("/static", "./static")
   // Statisches Dateiverzeichnis laden
   router.StaticFS("/view", http.Dir("view"))
   // Statische Datei laden
   router.StaticFile("/favicon", "./static/favicon.ico")

   router.Run(":8080")
}

CORS-Konfiguration

Gin selbst hat keine CORS-Konfiguration verarbeitet. Sie müssen selbst Middleware schreiben, um die entsprechenden Anforderungen zu implementieren. Das ist auch nicht schwierig. Jeder, der mit dem HTTP-Protokoll etwas vertraut ist, kann es schreiben. Die Logik ist fast immer dieselbe.

go
func CorsMiddle() gin.HandlerFunc {
   return func(c *gin.Context) {
      method := c.Request.Method
      origin := c.Request.Header.Get("Origin")
      if origin != "" {
         // In Produktionsumgebungen wird normalerweise nicht * ausgefüllt, sondern die angegebene Domain
         c.Header("Access-Control-Allow-Origin", origin)
         // Erlaubte HTTP METHOD
         c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
         // Erlaubte Anfrage-Header
         c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
         // Erlaubte Antwort-Header für Client-Zugriff
         c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
         // Ob Authentifizierungsinformationen Credentials benötigt werden, dies können Cookies, Authorization-Header oder TLS-Client-Zertifikate sein
         // Wenn auf true gesetzt, kann Access-Control-Allow-Origin nicht * sein
         c.Header("Access-Control-Allow-Credentials", "true")
      }
      // OPTION-Anfragen durchlassen, aber nachfolgende Methoden nicht ausführen
      if method == "OPTIONS" {
         c.AbortWithStatus(http.StatusNoContent)
      }
      // Durchlassen
      c.Next()
   }
}

Registrieren Sie die Middleware als globale Middleware

Sitzungssteuerung

In der heutigen Zeit gibt es drei beliebte Methoden zur Web-Sitzungssteuerung: Cookie, Session, JWT.

Die Informationen in Cookies werden als Schlüssel-Wert-Paare im Browser gespeichert, und die Daten können direkt im Browser eingesehen werden

Vorteile:

  • Einfache Struktur
  • Persistente Daten

Nachteile:

  • Größenbeschränkung
  • Klartextspeicherung
  • Anfällig für CSRF-Angriffe
go
import (
    "fmt"

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

func main() {

    router := gin.Default()

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

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

        if err != nil {
            cookie = "NotSet"
            // Cookie setzen Parameter: key, val, Lebensdauer, Pfad, Domain, ob anderen den Zugriff via JS erlaubt ist, nur http
            c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
        }

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

    router.Run()
}

Einfache Cookies wurden vor fünf oder sechs Jahren häufiger verwendet, aber der Autor verwendet selten nur Cookies zur Sitzungssteuerung, da dies tatsächlich nicht sehr sicher ist.

Session

Sessions werden auf dem Server gespeichert, dann wird ein Cookie an den Browser gesendet. Im Cookie wird die session_id gespeichert. Bei jeder nachfolgenden Anfrage kann der Server über die session_id die entsprechenden Session-Informationen abrufen

Vorteile:

  • Speicherung auf dem Server, erhöht die Sicherheit, erleichtert die Verwaltung

Nachteile:

  • Speicherung auf dem Server, erhöht den Server-Overhead, verringert die Leistung
  • Basiert auf Cookie-Erkennung, unsicher
  • Authentifizierungsinformationen sind in verteilten Umgebungen nicht synchronisiert

Session und Cookie sind untrennbar miteinander verbunden. Wann immer Session verwendet wird, wird standardmäßig auch Cookie verwendet. Gin unterstützt standardmäßig kein Session, da Cookie Teil des HTTP-Protokolls ist, Session aber nicht. Es gibt jedoch Drittanbieter-Middleware-Unterstützung. Installieren Sie einfach die Abhängigkeit: gin-contrib/sessions: Gin middleware for session management (github.com)

go get github.com/gin-contrib/sessions

Unterstützt Cookie, Redis, MongoDB, GORM, PostgreSQL

go
func main() {
   r := gin.Default()
   // Cookie-basierte Storage-Engine erstellen
   store := cookie.NewStore([]byte("secret"))
   // Session-Middleware setzen, mysession ist der Session-Name und auch der Cookie-Name
   r.Use(sessions.Sessions("mysession", store))
   r.GET("/incr", func(c *gin.Context) {
      // Session initialisieren
      session := sessions.Default(c)
      var count int
      // Wert abrufen
      v := session.Get("count")
      if v == nil {
         count = 0
      } else {
         count = v.(int)
         count++
      }
      // Setzen
      session.Set("count", count)
      // Speichern
      session.Save()
      c.JSON(200, gin.H{"count": count})
   })
   r.Run(":8000")
}

Es wird allgemein nicht empfohlen, Session über Cookie zu speichern. Redis wird empfohlen. Weitere Beispiele finden Sie im offiziellen Repository.

JWT

Vorteile:

  • Basiert auf JSON, mehrsprachig universell
  • Kann nicht-sensible Informationen speichern
  • Sehr kompakt, einfach zu übertragen
  • Server muss nicht speichern, vorteilhaft für verteilte Erweiterung

Nachteile:

  • Token-Aktualisierungsproblem
  • Einmal ausgestellt, kann nicht aktiv kontrolliert werden

Seit der Frontend-Revolution ist der Frontend-Programmierer nicht mehr nur ein "Seiten-Schreiber". Der Trend zur Trennung von Frontend und Backend wird immer stärker. JWT ist am besten geeignet für Frontend-Backend-Trennung und verteilte Systeme zur Sitzungssteuerung und hat große natürliche Vorteile. Da JWT vollständig vom Gin-Inhalt getrennt ist und keine Middleware-Unterstützung hat – weil JWT selbst nicht auf ein Framework oder eine Sprache beschränkt ist – wird hier nicht im Detail darauf eingegangen. Siehe anderes Dokument: JWT

Protokollverwaltung

Die von Gin standardmäßig verwendete Protokoll-Middleware verwendet os.Stdout mit nur den grundlegendsten Funktionen. Gin konzentriert sich nur auf Web-Services. In den meisten Fällen sollten Sie ein ausgereifteres Protokoll-Framework verwenden. Dies ist jedoch nicht Gegenstand dieses Kapitels, und Gin ist sehr erweiterbar und kann leicht andere Frameworks integrieren. Hier wird nur der eingebaute Protokoll-Service besprochen.

Konsolenfarbe

go
gin.DisableConsoleColor() // Konsolen-Protokollfarbe deaktivieren

Außer während der Entwicklung wird in den meisten Fällen nicht empfohlen, dies zu aktivieren

Protokoll in Datei schreiben

go
func main() {
  e := gin.Default()
    // Konsolenfarbe ausschalten
  gin.DisableConsoleColor()
    // Zwei Protokolldateien erstellen
  log1, _ := os.Create("info1.log")
  log2, _ := os.Create("info2.log")
    // Gleichzeitig in zwei Protokolldateien schreiben
  gin.DefaultWriter = io.MultiWriter(log1, log2)
  e.GET("/hello", Hello)
  log.Fatalln(e.Run(":8080"))
}

Das eingebaute Protokoll von gin unterstützt das Schreiben in mehrere Dateien, aber der Inhalt ist identisch, was nicht sehr praktisch ist. Außerdem werden Anfrage-Protokolle nicht in Dateien geschrieben.

go
func main() {
  router := gin.New()
  // LoggerWithFormatter Middleware schreibt Protokolle in gin.DefaultWriter
  // Standardmäßig gin.DefaultWriter = os.Stdout
  router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        //TODO Logik zum Schreiben in entsprechende Datei
        ......
    // Benutzerdefiniertes Format ausgeben
    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")
}

Durch benutzerdefinierte Middleware kann das Protokoll in Dateien geschrieben werden

Routing-Debug-Protokollformat

Hier wird nur das Protokoll geändert, das beim Start die Routing-Informationen ausgibt

go
func main() {
   e := gin.Default()
   gin.SetMode(gin.DebugMode)
   gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
      if gin.IsDebugging() {
         log.Printf("Route %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
      }
   }
   e.GET("/hello", Hello)
   log.Fatalln(e.Run(":8080"))
}

Ausgabe

2022/12/21 17:19:13 Route GET /hello main.Hello 3

Fazit: Gin ist eines der am einfachsten zu erlernenden Web-Frameworks in Go. Denn Gin hat es wirklich geschafft, die Verantwortung zu minimieren und sich nur um Web-Services zu kümmern. Andere Authentifizierungslogik, Daten-Caching und andere Funktionen werden den Entwicklern überlassen. Im Vergleich zu diesen großen und umfassenden Frameworks ist das leichtgewichtige und einfache Gin für Anfänger geeigneter und sollte auch erlernt werden. Denn Gin erzwingt nicht die Verwendung einer bestimmten Spezifikation. Wie das Projekt aufgebaut sein soll und welche Struktur verwendet werden soll, muss selbst entschieden werden. Für Anfänger ist dies besser für die Ausbildung der Fähigkeiten.

Golang by www.golangdev.cn edit