echo-swagger
Swagger는 API의 문서화를 자동으로 처리해주는 오픈 프로젝트다.
API가 변하면 자동으로 변한 API에 맞춰 문서화를 해주어 매우 편리하다.
시작
🖐️Swaggo
Swaggo는 이런 Swagger를 Golang에서 잘 활용할 수 있도록 도와준다.
- swaggo/swag : RESTful API 문서 자동생성
- swaggo/echo-swagger : Echo 프레임워크의 미들웨어로 동작하며 Swagger를 사용할 수 있도록 함.
🖐️Usage
우선 필요한 패키지의 설치를 한다.
1
2
go install github.com/swaggo/swag/cmd/swag@latest
go get -u github.com/swaggo/echo-swagger
이제 API 소스 코드에 Declartive Comments를 작성한다.
작성 방법은 공식 문서를 참고한다.
https://github.com/swaggo/swag?tab=readme-ov-file#declarative-comments-format
나는 BSMG 프로젝트에서 이렇게 사용하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
main.go
// @title BSMG Swagger API
// @version 1.0
// @host localhost:3000
// @BasePath /bsmg
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
func main() {
...
e := echo.New()
...
// Swagger Docs 추가
e.GET("/swagger/*", func(c echo.Context) error {
// CORS 헤더 추가
c.Response().Header().Set("Access-Control-Allow-Origin", "*")
c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Response().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Response().Header().Set("Access-Control-Max-Age", "86400")
return echoSwagger.WrapHandler(c)
})
}
참고로 Nginx등을 이용하여 프록시 혹은 리버스 프록시를 하고 있다면, @host
에도 맞게 변경해주어야 한다. (그렇지 않으면 Swagger 사용 시 Nginx conf를 아무리 고쳐도 CORS가 발생한다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
handler.go
// @Summary user's login check
// @Description check user is logined
// @Tags Login
// @Accept json
// @Produce json
// @Success 200 {object} define.BsmgMemberResponse
// @Router /login/chkLogin [get]
func (h *BsmgHandler) GetChkLoginRequest(c echo.Context) error {
log.Println("getChkLogin Req")
// JWT 검증 ------------------------------------------
apiResponse, resultCode := h.uc.CheckLoginIng(c)
if resultCode != define.Success {
apiResponse.Result.ResultCode = int32(resultCode)
return c.JSON(http.StatusOK, apiResponse)
}
return c.JSON(http.StatusOK, apiResponse)
}
// @Summary get report List summary
// @Description combo means 0:all, 1:title, 2:content, 3:reporter
// @Tags Report
// @Accept json
// @Produce json
// @Param searchData query define.SearchData true "Search Condition"
// @Param offset query int true "offset"
// @Param limit query int true "limit"
// @Success 200 {object} define.BsmgReportListResponse
// @Router /report/reportList [get]
func (h *BsmgHandler) GetReportSearchReq(c echo.Context) (err error) {
log.Println("getReportSearchReq")
// server 변수를 현재 context에서 꺼내쓰는데 타입에 대해서 불명확하기때문에 이건 지양해야 하는 방향.
server, _ := c.Get("Server").(*server.ServerProcessor)
server.Mutex.Lock()
defer server.Mutex.Unlock()
var searchData define.SearchData
searchCombo := c.Request().FormValue("@d1#search_combo")
combo, _ := strconv.Atoi(searchCombo)
searchData.SearchCombo = int32(combo)
searchData.SearchInput = c.Request().FormValue("@d1#search_input")
offset, _ := strconv.Atoi(c.Request().FormValue("offset"))
limit, _ := strconv.Atoi(c.Request().FormValue("limit"))
pageInfo := define.PageInfo{
Offset: int32(offset),
Limit: int32(limit),
}
apiResponse := h.uc.SelectReportListReq(searchData, pageInfo)
return c.JSON(http.StatusOK, apiResponse)
}
handler에서 Parameter로 콤보박스등을 지정하려면,
1
2
3
4
5
6
7
8
type SearchData struct {
// * 0 - All
// * 1 - Title
// * 2 - Content
// * 3 - Reporter
SearchCombo int32 `json:"@d1#search_combo" enums:"0,1,2,3"`
SearchInput string `json:"@d1#search_input"`
}
이렇게 정의에서 enums 태그를 달아주면 된다.
🖐️Docs 변환
main과 handler함수에 Declartive Comments를 달아주었으면, Swagger 문서로 변환할 차례다.
1
swag init
이 명령을 실행하면, 프로젝트의 root 디렉토리에 /docs 디렉토리가 생성된다.
1
2
3
4
5
mjy@mjyui-MacBookAir docs % tree
docs
├── docs.go
├── swagger.json
└── swagger.yaml
생성된 docs 패키지를 main.go에서 import 한다.
1
2
3
import (
_ "BsmgRefactoring/docs"
)
🖐️Swagger 확인
웹 URL에 /swagger/index.html
을 붙이면 된다.
API에 변화를 주었거나, Declartive Comments를 변경하였다면,
1
swag init
을 통해서 Swagger 문서를 다시 생성해야 한다.
🖐️Trouble Shooting
BSMG 프로젝트는 SPA로 개발하여 index.html이 존재하고, 모든 정적파일을 Echo 프레임워크에 올려놓고 사용한다.
1
2
3
4
e.Use(middleware.Static("views/webRoot")) // eXBuilder6 의존성 파일 추가
e.GET("/", func(c echo.Context) error {
return c.File("index.html")
})
내겐 이 부분이 문제가 되었다.
localhost/swagger/index.html
로 접속하면 계속 SPA의 시작 index 파일이 호출되었다.
처음엔 라우팅 순서 문제인줄 알고 여러 시도를 해보았지만 아니었다.
1
2
3
4
5
echo의 미들웨어는 먼저 작성된 순서를 따라가지만, 그보다 우선순위가 있었다.
// Swagger Docs 추가
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.Use(middleware.Static("views/webRoot")) // eXBuilder6 의존성 파일 추가
문제는 echo 프레임워크의 우선 순위
문제였다. echo 프레임워크는 정적 파일 서빙을 우선시 하여, echo-swagger의 라우팅을 다음과 같이 먼저 하여도 swagger가 아닌 정적 파일을 먼저 불러온다.
swagger UI의 index.html은 서버가 시작하고 생성된다.
echo-swagger를 사용하며 swagger UI를 들어가기 위해선 /{특정 URL}/index.html
이 고정값이다.
echo-swagger 패키지의 코드를 고치고 싶지 않아서 고민하다, 라우팅에 hook을 추가하여 해결하였다.
1
2
3
4
5
6
7
8
9
10
11
// Swagger Docs 추가
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Swagger UI를 불러오는 URL (/swagger/index.html)이 정적파일의 index.html과 충돌하여 동작하지 않으므로 후킹 추가
if c.Request().URL.Path == swaggerIndexURL {
return echoSwagger.WrapHandler(c)
}
return next(c)
}
})