Gin은 Go(Golang)로 작성된 HTTP 웹 프레임워크입니다. Martini와 유사한 API를 제공하지만 Martini보다 성능이 최대 40배 빠릅니다. 뛰어난 성능이 필요하다면 진을 구입하세요.
Gin 공식 홈페이지에서는 "고성능", "좋은 생산성"을 갖춘 웹 프레임워크라고 소개하고 있습니다. 또한 두 개의 다른 라이브러리도 언급합니다. 첫 번째는 마티니(Martini)인데, 역시 웹 프레임워크이며 술의 이름을 가지고 있습니다. Gin은 API를 사용하지만 40배 더 빠르다고 말합니다. httprouter를 사용하는 것은 Martini보다 40배 더 빠른 중요한 이유입니다.
공식 홈페이지의 '기능' 중에는 8가지 주요 기능이 나열되어 있으며, 추후 이러한 기능의 구현을 점진적으로 지켜볼 예정입니다.
공식 문서에 나와 있는 가장 작은 예를 살펴보겠습니다.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
이 예제를 실행한 다음 브라우저를 사용하여 http://localhost:8080/ping을 방문하면 "pong"이 표시됩니다.
이 예는 매우 간단합니다. 세 단계로만 나눌 수 있습니다:
위 작은 예의 GET 메소드를 보면 Gin에서 HTTP 메소드의 처리 메소드를 동일한 이름의 해당 함수를 사용하여 등록해야 함을 알 수 있습니다.
HTTP 메소드에는 9가지가 있으며 가장 일반적으로 사용되는 4가지 메소드는 GET, POST, PUT, DELETE이며 각각 쿼리, 삽입, 업데이트, 삭제의 4가지 기능에 해당합니다. Gin은 모든 HTTP 메서드 처리 방법을 하나의 주소에 직접 바인딩할 수 있는 Any 인터페이스도 제공한다는 점에 유의하세요.
반환된 결과에는 일반적으로 두 개 또는 세 개의 부분이 포함됩니다. 코드와 메시지는 항상 존재하며, 데이터는 일반적으로 추가 데이터를 나타내는 데 사용됩니다. 반환할 추가 데이터가 없으면 생략할 수 있습니다. 예시에서 200은 코드 필드의 값이고, "pong"은 메시지 필드의 값입니다.
위의 예에서는 gin.Default()를 사용하여 엔진을 생성했습니다. 그러나 이 함수는 New에 대한 래퍼입니다. 실제로 엔진은 New 인터페이스를 통해 생성됩니다.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
지금은 생성 과정을 간단히 살펴보고 엔진 구조의 다양한 멤버 변수의 의미에 집중하지 마세요. New는 Engine 유형의 엔진 변수를 생성하고 초기화하는 것 외에도 Engine.pool.New를 Engine.allocateContext()를 호출하는 익명 함수로 설정한다는 것을 알 수 있습니다. 이 기능의 기능에 대해서는 나중에 다루도록 하겠습니다.
엔진에는 RouterGroup 구조체가 내장되어 있습니다. 엔진의 HTTP 메소드와 관련된 인터페이스는 모두 RouterGroup에서 상속됩니다. 공식 웹사이트에 언급된 기능 포인트의 "경로 그룹화"는 RouterGroup 구조체를 통해 이루어집니다.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
각 RouterGroup은 기본 경로 basePath와 연결됩니다. 엔진에 내장된 RouterGroup의 basePath는 "/"입니다.
처리 함수 핸들러 세트도 있습니다. 이 그룹과 연관된 경로 아래의 모든 요청은 주로 미들웨어 호출에 사용되는 이 그룹의 처리 기능을 추가로 실행합니다. 엔진이 생성될 때 핸들러는 0이며 Use 메서드를 통해 일련의 기능을 가져올 수 있습니다. 이 사용법은 나중에 살펴보겠습니다.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
RouterGroup의 핸들 메소드는 모든 HTTP 메소드 콜백 함수를 등록하기 위한 마지막 항목입니다. 초기 예제에서 호출된 GET 메소드 및 HTTP 메소드와 관련된 기타 메소드는 핸들 메소드에 대한 래퍼일 뿐입니다.
핸들 메소드는 RouterGroup의 basePath와 상대 경로 매개변수에 따라 절대 경로를 계산하고 동시에 CombineHandlers 메소드를 호출하여 최종 핸들러 배열을 가져옵니다. 이러한 결과는 처리 기능을 등록하기 위해 엔진의 addRoute 메소드에 매개변수로 전달됩니다.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
combineHandlers 메소드가 수행하는 작업은 mergedHandler 조각을 생성한 다음 RouterGroup 자체의 핸들러를 여기에 복사하고 매개변수의 핸들러를 복사한 다음 마지막으로 mergedHandler를 반환하는 것입니다. 즉, 핸들을 사용하여 메소드를 등록하면 실제 결과에는 RouterGroup 자체의 핸들러가 포함됩니다.
공식 홈페이지에 언급된 '빠른' 기능 포인트에는 네트워크 요청 라우팅이 기수 트리(Radix Tree)를 기반으로 구현된다고 언급되어 있습니다. 이 부분은 Gin에서 구현한 것이 아니고, 처음에 Gin 소개에서 언급한 httprouter에서 구현한 것입니다. Gin은 기능의 이 부분을 달성하기 위해 httprouter를 사용합니다. 기수 트리의 구현은 당분간 여기에서 언급되지 않습니다. 지금은 사용법에만 집중하겠습니다. 나중에 기수 트리 구현에 대한 별도의 기사를 작성하게 될 것입니다.
엔진에는 methodTree 구조의 일부인 tree 변수가 있습니다. 모든 기수 트리에 대한 참조를 보유하는 것은 이 변수입니다.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
엔진은 각 HTTP 메소드에 대해 기수 트리를 유지합니다. 이 트리의 루트 노드와 메소드 이름은 methodTree 변수에 함께 저장되며, 모든 methodTree 변수는 트리에 저장됩니다.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
엔진의 addRoute 메소드에서 먼저 트리의 get 메소드를 사용하여 해당 메소드에 해당하는 기수 트리의 루트 노드를 가져오는 것을 볼 수 있습니다. 기수 트리의 루트 노드를 얻지 못했다면 이는 이전에 이 메서드에 대해 등록된 메서드가 없다는 의미이며 트리 노드가 트리의 루트 노드로 생성되어 트리에 추가됩니다.
루트 노드를 가져온 후 루트 노드의 addRoute 메서드를 사용하여 경로 경로에 대한 처리 함수 핸들러 집합을 등록합니다. 이 단계에서는 경로와 핸들러에 대한 노드를 생성하고 이를 기수 트리에 저장합니다. 이미 등록된 주소를 등록하려고 하면 addRoute에서 바로 패닉 오류가 발생합니다.
HTTP 요청을 처리할 때 해당 경로를 통해 해당 노드의 값을 찾아야 합니다. 루트 노드에는 쿼리 작업 처리를 담당하는 getValue 메서드가 있습니다. Gin이 HTTP 요청을 처리하는 것에 관해 이야기할 때 이 점을 언급하겠습니다.
RouterGroup의 Use 메소드는 미들웨어 처리 기능 세트를 가져올 수 있습니다. 공식 홈페이지에 언급된 기능 중 "미들웨어 지원"은 Use 메소드를 통해 이루어집니다.
초기 예제에서는 Engine 구조체 변수 생성 시 New를 사용하지 않고 Default를 사용했습니다. Default가 추가로 수행하는 작업을 살펴보겠습니다.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
매우 간단한 기능임을 알 수 있습니다. New를 호출하여 Engine 개체를 생성하는 것 외에도 Use를 호출하여 두 미들웨어 함수인 Logger 및 Recovery의 반환 값을 가져옵니다. Logger의 반환 값은 로깅을 위한 함수이고, Recovery의 반환 값은 패닉 처리를 위한 함수입니다. 지금은 이 내용을 건너뛰고 나중에 이 두 가지 기능에 대해 살펴보겠습니다.
엔진에 RouterGroup이 포함되어 있지만 Use 메서드도 구현하지만 이는 RouterGroup의 Use 메서드와 일부 보조 작업에 대한 호출일 뿐입니다.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
RouterGroup의 사용 방법도 매우 간단하다는 것을 알 수 있습니다. 단지 Append를 통해 매개변수의 미들웨어 처리 기능을 자체 Handler에 추가할 뿐입니다.
작은 예에서 마지막 단계는 매개변수 없이 엔진의 Run 메소드를 호출하는 것입니다. 호출 후 전체 프레임워크가 실행되기 시작하며, 등록된 주소를 브라우저로 방문하면 콜백이 올바르게 실행됩니다.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Run 메서드는 주소를 구문 분석하고 서비스를 시작하는 두 가지 작업만 수행합니다. 여기서 주소는 실제로 문자열만 전달하면 되지만, 통과할 수 있거나 통과할 수 없는 효과를 얻기 위해 가변 매개변수가 사용됩니다. ResolveAddress 메소드는 addr의 다양한 상황의 결과를 처리합니다.
서비스를 시작하려면 표준 라이브러리의 net/http 패키지에 있는 ListenAndServe 메서드를 사용합니다. 이 메소드는 핸들러 인터페이스의 청취 주소와 변수를 승인합니다. 핸들러 인터페이스의 정의는 단 하나의 ServeHTTP 메소드로 매우 간단합니다.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
엔진이 ServeHTTP를 구현하기 때문에 여기에서는 엔진 자체가 ListenAndServe 메서드로 전달됩니다. 모니터링되는 포트에 새로운 연결이 있으면 ListenAndServe가 연결 수락 및 설정을 담당하고, 연결에 데이터가 있으면 핸들러의 ServeHTTP 메서드를 호출하여 처리합니다.
엔진의 ServeHTTP는 메시지 처리를 위한 콜백 함수입니다. 내용을 살펴보겠습니다.
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) assert1(finalSize < int(abortIndex), "too many handlers") mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
콜백 함수에는 두 개의 매개변수가 있습니다. 첫 번째는 요청 응답을 수신하는 데 사용되는 w입니다. w에 응답 데이터를 씁니다. 다른 하나는 이 요청의 데이터를 보유하는 req입니다. 후속 처리에 필요한 모든 데이터는 req에서 읽을 수 있습니다.
ServeHTTP 메서드는 네 가지 작업을 수행합니다. 먼저 풀 풀에서 Context를 가져온 다음 Context를 콜백 함수의 매개 변수에 바인딩한 다음 Context를 매개 변수로 사용하여 handlerHTTPRequest 메서드를 호출하여 이 네트워크 요청을 처리하고 마지막으로 Context를 풀에 다시 넣습니다. 🎜>
먼저 HandleHTTPRequest 메소드의 핵심 부분만 살펴보겠습니다.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
handleHTTPRequest 메소드는 주로 두 가지 작업을 수행합니다. 먼저 요청 주소에 따라 기수 트리에서 이전에 등록된 메소드를 가져옵니다. 여기서 핸들러는 이 처리를 위해 Context에 할당된 다음 Context의 Next 함수를 호출하여 핸들러의 메서드를 실행합니다. 마지막으로 Context의 responseWriter 유형 객체에 이 요청의 반환 데이터를 씁니다.
HTTP 요청을 처리할 때 모든 컨텍스트 관련 데이터는 Context 변수에 있습니다. 저자는 또한 Context 구조체의 주석에 "Context는 gin의 가장 중요한 부분입니다"라고 썼는데, 이는 그 중요성을 보여줍니다.
위의 Engine의 ServeHTTP 메소드를 살펴보면 Context가 직접 생성되는 것이 아니라 Engine의 pool 변수의 Get 메소드를 통해 얻어지는 것을 알 수 있습니다. 꺼낸 후 사용 전 상태가 초기화되고, 사용 후 다시 풀에 투입됩니다.
엔진의 풀 변수는 sync.Pool 유형입니다. 지금은 동시 사용을 지원하는 Go 공식에서 제공하는 개체 풀이라는 것만 알아두세요. Get 메서드를 통해 풀에서 개체를 가져올 수 있으며 Put 메서드를 사용하여 풀에 개체를 넣을 수도 있습니다. 풀이 비어 있고 Get 메소드를 사용하면 자체 New 메소드를 통해 객체를 생성하고 반환합니다.
이 New 메서드는 엔진의 New 메서드에 정의되어 있습니다. 엔진의 새로운 방식을 다시 한번 살펴보겠습니다.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
코드를 보면 Context 생성 방식이 Engine의 할당Context 방식임을 알 수 있습니다. 할당컨텍스트 메소드에는 미스터리가 없습니다. 단지 슬라이스 길이를 2단계로 사전 할당한 다음 객체를 생성하고 반환합니다.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
위에 언급된 Context의 Next 메소드는 핸들러의 모든 메소드를 실행합니다. 구현 방법을 살펴보겠습니다.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
핸들러는 슬라이스이지만 Next 메소드는 단순히 핸들러의 순회로 구현되는 것이 아니라 0으로 초기화되어 메소드 시작 시 증가하고 메소드 이후에 다시 증가하는 처리 진행 기록 인덱스를 도입합니다. 실행이 완료되었습니다.
Next의 디자인은 주로 일부 미들웨어 기능과 협력하기 위해 사용과 큰 관계가 있습니다. 예를 들어 특정 핸들러 실행 중 패닉이 발생하면 미들웨어의 복구를 사용하여 오류를 포착한 후 다시 Next를 호출하여 문제로 인해 전체 핸들러 배열에 영향을 주지 않고 후속 핸들러를 계속 실행할 수 있습니다. 한 명의 핸들러입니다.
Gin에서는 특정 요청의 처리 기능이 패닉을 유발하는 경우 전체 프레임워크가 직접 충돌하지 않습니다. 대신 오류 메시지가 출력되며, 서비스는 계속 제공됩니다. 이는 Lua 프레임워크가 일반적으로 xpcall을 사용하여 메시지 처리 기능을 실행하는 방식과 다소 유사합니다. 이 작업은 공식 문서에 언급된 "충돌 방지" 기능 포인트입니다.
위에서 언급한 것처럼 gin.Default를 사용하여 Engine을 생성할 때 Engine의 Use 메서드가 실행되어 두 가지 함수를 가져옵니다. 그 중 하나는 다른 함수의 래퍼인 Recovery 함수의 반환 값입니다. 마지막으로 호출되는 함수는 CustomRecoveryWithWriter입니다. 이 기능의 구현을 살펴보겠습니다.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
여기서는 오류 처리의 세부 사항에 초점을 맞추지 않고 오류 처리가 수행하는 작업만 살펴보겠습니다. 이 함수는 익명 함수를 반환합니다. 이 익명 함수에는 defer를 사용하여 또 다른 익명 함수가 등록됩니다. 이 내부 익명 함수에서는 복구를 사용하여 패닉을 포착한 다음 오류 처리를 수행합니다. 처리가 완료되면 Context의 Next 메소드를 호출하여 원래 순차적으로 실행되던 Context의 핸들러를 계속해서 실행할 수 있게 된다.
마지막으로 Gin 서비스 배포를 위한 최고의 플랫폼인 Leapcell을 소개하겠습니다.
문서에서 더 자세히 알아보세요!
리프셀 트위터: https://x.com/LeapcellHQ
위 내용은 Gin 심층 분석: Golang의 주요 프레임워크의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!