
요청-응답의 사이에 있어서 미들웨어라고 불린다.
미들웨어는 요청-응답 사이클 사이에서 추가적인 기능을 부여할 수 있고, 본 핸들러로 넘어가기 전에 전처리 과정을 거칠 수도 있다.
요청문을 수정할 수도 있고, 필터링하는 기능을 제공할 수도 있다.

최종적으로 요청을 다루는 미들웨어가 핸들러라고 할 수 있다.
이러한 미들웨어들은 체인처럼 연결되어서, 각 미들웨어는 요청을 처리하고 다음 미들웨어로 넘기거나, 중간에서 요청을 기각시킬 수 있다.
미들웨어들은 재사용이 가능하고, 유연하게 사용될 수 있다.
요청문의 json body를 파싱하고, 특정한 key값을 반드시 가져야 통과시켜주는 유효성 검사 미들웨어들을 만들어보자.
package middlewares
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// JSONParsingMiddleware - Parse JSON Body
func JSONParsingMiddleware(c *gin.Context) {
contentType := c.GetHeader("Content-Type")
if strings.HasPrefix(contentType, "application/json") {
var body map[string]interface{}
if err := c.ShouldBindJSON(&body); err != nil {
if err.Error() == "EOF" {
body = make(map[string]interface{})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
c.Abort()
return
}
}
c.Set("body", body)
}
c.Next()
}
Gin 프레임워크에서 미들웨어 함수로서 정의하려면, 매개변수로 Context 객체를 받아야 한다.
위 코드는 다음의 순서를 가진다:
Content-Type 헤더를 가져온다.application/json의 body를 가지고 있다면, 3번으로 간다.c.ShouldBindJSON()메서드를 이용해서 맵 자료구조에 json값을 담는다.4XX 에러를 반환하면서 요청을 기각시킨다.c.Set("body", body)를 통해서 Context 객체에서 "body"를 가져오라고 하면 파싱된 맵 자료구조를 불러올 수 있도록 한다.c.Next()로 다음 미들웨어에게 요청을 넘긴다.// ValidateBodyMiddleware - Null check the body
func ValidateBodyMiddleware(requiredFields ...string) gin.HandlerFunc {
return func(c *gin.Context) {
body, ok := c.Get("body")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error loading body:"})
c.Abort()
return
}
bodyMap := body.(map[string]interface{})
for _, field := range requiredFields {
if value, exists := bodyMap[field]; !exists || value == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": field + " is required and cannot be null"})
c.Abort()
return
}
}
c.Next()
}
}
이 위의 Json 파싱 미들웨어가 먼저 사용되어야 한다.
이 함수는 미들웨어함수를 콜백 함수로 가지고 있다.
이런 구조를 사용하는 이유는, 동적으로 처리하는 미들웨어를 구현하기 위해서이다.
원래의 미들웨어 함수는 매개변수로 Context만 받아야 하는데, 여기서는 필요한 key값들을 받아서 처리해야 한다.
보다시피, 미들웨어 함수의 자료형은 HandlerFunc형이다.
우선, c의 "body"를 key로 하는 값, 즉 이전에 파싱과정을 거치고 난 뒤 c에서 "body"를 가져오는 것이다.
이때, body의 자료형은 any이기 때문에, 따로 형변환이 필요하다.
Map에 대해서, requiredFields에서 말하는 필수 key들에 대해서 유효성 검사를 하고, 값이 없는 경우 4XX 응답과 함께 요청을 기각시킨다.
그리고, 다음 미들웨어에게 요청을 넘긴다.
이제, 미들웨어를 라우터에 장착시켜보자.
아래는 공지사항에 관련된 라우터들을 정리해놓은 것이다.
공지사항을 작성할 때, 제목과 내용은 필수이므로, title, content를 필수입력항목이라고 지정하여 유효성검사를 할 것이다.
미들웨어들은 체이닝이 가능하다.
router.Use(h *gin.Handler)를 통해서 미들웨어 함수를 범위 내에 전역으로 적용시킬 수 있다.
또는 직접 체이닝이 가능하다.
.Use()를 이용한 미들웨어 선언들은 위에서 아래로 선언한 순서대로 실행이 되고,
라우터에 직접 체이닝된 미들웨어들은 좌에서 우로 순차적으로 실행이 된다.
router := gin.Default()
router.Use(cors.Default())
router.Use(middlewares.JSONParsingMiddleware)
// 중략...
notice := router.Group("/notices")
{
notice.GET("/", getNoticesHandler)
notice.PUT("/:noticeId", middlewares.RequireAuth, middlewares.ValidateBodyMiddleware("title", "content"), modifyNoticeHandler)
notice.POST("/", middlewares.RequireAuth, middlewares.ValidateBodyMiddleware("title", "content"), createNoticeHandler)
notice.DELETE("/:noticeId", middlewares.RequireAuth, deleteNoticeHandler)
}
이 예시에서 게시글을 작성하는 POST 요청에 대해서는,
1. CORS
2. JSON바디 파싱
3. 인가 확인(토큰검증. 이는 추후 자세히 살펴볼 것이다.)
4. JSON바디에서 title, content값이 있는지 확인
5. 글 작성 관련 핸들러
이러한 순서로 실행된다.
