Go Swagger 사용 예

REST API를 위하여 Go 언어에서 Swagger를 사용해 보았고, 간단히 사용 방법을 정리해 본다.


예전에 Go 언어로 구축해 준 Web 서버에서 REST API 구현 요청을 받아서, 처음으로 Swagger를 이용하여 코드에서 주석을 달고, 이를 이용하여 REST API 명세서를 얻었고, 바로 테스트까지 할 수 있었다. So cool 👍🏻

Swagger

Swagger 관련은 Swagger 홈페이지에 자세한 설명이 있다. OAS(OpenAPI Specification)는 RESTful API의 표준이고, 현재 OAS 2.0, OAS 3.0이 많이 쓰이고 있다. Swagger는 OAS를 구현하기 위한 문서화, 빌드 및 테스트를 위한 프레임워크이다.
Swagger 명세서는 yaml이나 json 파일로 구성되고, swagger 필드로 버전을 명시한다.

보통 REST API 코드를 수정할 때 문서도 따라서 수정해야 하는데, 이 과정을 빼먹으면 코드와 문서가 불일치하는 문제가 발생할 수 있다. 이때 Swagger를 사용하게 되면 코드와 문서를 쉽게 일치시킬 수 있는 이점이 있다 (코드 주석에서 Swagger 명세서를 만드거나, Swagger 명세서로부터 코드를 생성할 수 있으므로).
또한 Swagger UI에서 API 명세서를 편리하게 확인할 수 있고, 바로 REST API 테스트도 할 수 있다.

Swagger editorSwagger Codegen 툴을 사용하면 Swagger 파일(json 또는 yaml)로부터 원하는 언어를 사용한 서버/클라이언트 코드를 얻을 수 있다. (자바, 파이썬, NodeJS, Go, Rust 등 여러 언어 지원)
본 포스팅에서는 이 방법 대신에 코드 내 Swagger 주석으로부터 Swagger 명세서를 생성하는 방법을 기술할 것이다.

Swagger UI는 Swagger UI GitHub 페이지 내용에 따르면 NPM이나 Docker 등을 설치하면 볼 수 있다. 예를 들어 Docker를 이용하려면 먼저 아래와 같이 해당 Docker 이미지를 다운받는다.

$ docker pull swaggerapi/swagger-ui

이제 swagger.json 파일로 Swagger UI를 보려면 이 파일을 ~/tmp/ 파일에 넣은 후, 아래 예와 Docker 컨테이너를 실행시키면 된다. (--rm 옵션을 주어 컨테이너 실행을 중지시키면 자동으로 컨테이너가 삭제되도록 했음, 호스트 포트는 예로 8080 포트를 사용했음)

$ docker run --rm -p 8080:8080 -e SWAGGER_JSON=/swagger/swagger.json -v ~/tmp:/swagger swaggerapi/swagger-ui

이후 웹브라우저에서 http://localhost:8080 페이지에 접속하면 Swagger UI 페이지가 보여지고, 서버 프로그램을 실행시켜 놓은 상태에서 테스트도 할 수 있다.

Go 언어에서 Swagger 사용하기

Go 언어에서는 Go Swagger 패키지를 사용하면 된다. 또 아래와 같이 많이 사용되는 EchoGin 프레임워크에서 사용할 수 있는 패키지도 있으므로 사용하는 프레임워크에 따라서 추가로 사용하면 된다.

Swagger 문서는 swag 명령을 이용하는데, 이를 위해 아래와 같이 설치하고, 정상적으로 실행되는지 확인해 본다.

$ go get -u github.com/swaggo/swag/cmd/swag
$ swag

Swagger를 적용할 프로젝트 소스의 base 경로에서 아래와 같이 현재 프로젝트에 대한 Go 모듈을 설정한다.

$ go mod init <모듈이름>

Swagger UI 테스트를 위해서는 Go 소스에서 아래와 같이 import를 추가하고, 라우터를 설정해 주면 된다. (아래에서 import의 {모듈이름} 부분은 본인의 모듈 이름으로 대체해야 함)

import (
    ginSwagger "github.com/swaggo/gin-swagger"
    swaggerFiles "github.com/swaggo/gin-swagger/swaggerFiles"
    _ "{모듈이름}/docs"
)
func main() {
    // ...
    router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    // ...
}

Go Swagger 예제에 따라 main 함수에는 아래 예와 같이 주석을 붙일 수 있다. (여기 내용은 Swagger UI 맨 윗 부분에 출력됨)
테스트 주소는 host 주석에 명시된 주소와 포트 번호를 사용하는데, 만약 host 주석을 주지 않으면 swagger 페이지에 접속된 주소를 그대로 사용한다. (테스트 서버 주소가 여러 개인 경우에는 이 방식이 편리할 수 있음)

// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @host localhost:443
// @basePath /api/v1
func main() {
    // ...
}

각 라우터 핸들러에는 아래 예와 같이 주석을 붙일 수 있다. (여기 내용은 Swagger UI에 각 메쏘드 별로 출력됨)

// @summary Host information collection.
// @description If it already exists, the changeable information is updated.
// @accept json
// @produce json
// @param apiver path string true "API version"
// @param project_id path string true "Project ID"
// @response 200 boolean body "Success"
// @response 400 boolean body "Fail"
// @router /{apiver}/projects/{project_id} [post]
func (o *Handler) HandlerName(c echo.Context) error {
    // ...
}

Go Swagger 주석 사용 규칙은 [Go Swagger 페이지 내의 Declarative Comments Format] 단락에 나와 있다.

참고로 아래는 내가 이번에 작성한 REST API에서 발췌한 내용이다. (3개의 파라미터를 받는데 그 중 하나는 파일임)

// @summary test
// @description my test
// @tags mytag
// @accept multipart/form-data
// @produce plain
// @param id formData string true "User ID"
// @param pw formData string true "Password"
// @param fileName formData file false "File name"
// @response 200 file formData
// @response 400 string body
// @router /test [post]
func RestApiTest(ctx *gin.Context) {
    // ...
}

소스를 작성했으면 아래와 같이 실행하면 필요한 패키지들이 자동으로 다운로드된다.

$ go mod tidy

Go swagger는 Go 소스의 Swagger 주석으로부터 Swagger 문서를 생성해 주는데, 아래와 같이 실행하면 결과로 docs 디렉토리 밑에 docs.go, swagger.json, swagger.yaml 파일들이 생성된다. (이후 Swagger 주석을 변경한 경우에는 다시 위 명령을 실행하면, 이 파일들이 업데이트됨)

$ swag init

이후 아래와 같이 빌드하면 Swagger가 포함된 실행 파일이 나온다.

$ go build

Swagger UI에서 테스트하기

Swagger UI로 테스트하려면 Swagger를 포함시켜서 서버 프로그램을 빌드한 후, 서버 프로그램을 실행시킨다. 이후 브라우저에서 https://localhost/swagger/index.html 주소에 접속하면 Swagger UI가 보여진다.
이 UI에서는 특히 tag 이름으로 그루핑되고, POST/PUT/DELETE/GET 등의 메쏘드가 다른 색깔로 표시되어 쉽게 식별할 수 있고, API 테스트도 가능하다. (테스트하면 해당 curl 명령으로도 보여줌)

기타 REST API 테스트 방법

Swagger UI를 사용하는 방법 외에도 기존처럼 아래와 같은 방법 등을 사용하여 테스트할 수도 있다. (물론 이 방법들은 Swagger UI를 이용하지 않으므로 빌드시에 Swagger를 포함하지 않아도 됨)

  • curl 이용
    가장 기본적이고 범용적인 방법이다. 아래 예와 같이 테스트할 수 있다. (SSL verify를 끄려면 -k 파라미터를 추가하면 됨)
    $ curl -X 'POST' https://localhost/api -H 'accept: text/plain' -H 'Content-Type: multipart/form-data' -F 'id=xxx' -F 'pw=yyy' -F file=@test.bin -O -J
    

    추가로 -i 옵션을 추가하면 서버가 보낸 HTTP 헤더 내용도 출력되므로 디버깅시 편리하다. 또, 파일 다운로드시 서버에서 세팅한 Content-Dispositionfilename으로 지정된 이름으로 저장하려면 "-J -O" 옵션을 추가하면 된다. (-J 옵션은 --remote-header-name 옵션과 같고, -O 옵션은 --remote-name 옵션과 같음)

  • httpie 이용
    curl과 비슷하지만 조금 더 편리할 수 있다. 아래와 같이 httpie 패키지를 설치한 후 테스트할 수 있다. (SSL verify를 끄려면 --verify no 파라미터를 추가하면 됨)
    $ sudo apt install httpie
    $ http -f POST https://localhost/api 'id=xxx' 'pw=yyy' file@test.bin -d
    
  • Postman 이용
    Postman을 다운받아서 설치한 후, 실행하면 GUI 화면이 나오므로 쉽게 테스트할 수 있다. 테스트하려는 메쏘드(POST 등)를 선택하고 URI를 입력하고 원하는 파라미터를 세팅한 후, Send 버튼을 누르면 된다.
    또 Swagger API 명세서를 읽어들여서 테스트할 수도 있다. (메뉴 File -> Import -> File 탭에서 upload 할 수 있음)

  • VS Code 익스텐션 이용
    Swagger 파일(yaml 또는 json)을 미리 보기할 수 있는 익스텐션에는 Swagger Viewer 등이 있다. (미리 보기는 잘 되는데, 테스트는 안되었음)
    REST API를 테스트 할 수 있는 익스텐션에는 REST Client, Thunder Client 등과 같은 익스텐션을 사용할 수 있다.

  • 파이썬 코드 이용 예
    import requests
    import urllib3
    
    urls = 'https://localhost/api'
    data = {
        'id': 'xxx',
        'pw': 'yyy',
    }
    files = {
        'file': ('test.bin', open('test.bin', 'rb'))
    }
    
    urllib3.disable_warnings()
    response = requests.post(urls, data=data, files=files, verify=False)
    
    if response.status_code == 200:
        contextDispos = response.headers['Content-Disposition']
        pos = contextDispos.find("filename=")
        fileName = contextDispos[pos + 10:-1]
        with open(fileName, 'wb') as f:
            f.write(response.content)
        print("Success:", fileName)
    else:
        print("Error.", response.text)
    

Go Swagger 빌드 팁

Swagger UI로 테스트할 때에는 포함시켜야 하지만, 릴리즈 용으로는 포함되면 안된다. 이를 편리하게 하게 위하여 main.go 파일에서는 setupSwagger() 함수를 호출하였고, 아래와 같이 setupSwagger() 함수는 2개의 파일에 각각 구현하였다(즉, swagEnable.go 파일에서만 Swagger를 세팅함).

  1. swagDisable.go
    // +build !swagger
    
    package main
    
    import (
        "github.com/gin-gonic/gin"
    )
    
    // setup swagger
    func setupSwagger(router *gin.Engine) {
    }
    
  2. swagEnable.go
    // +build swagger
    
    package main
       
    import (
        "github.com/gin-gonic/gin"
        ginSwagger "github.com/swaggo/gin-swagger"
        swaggerFiles "github.com/swaggo/gin-swagger/swaggerFiles"
    )
    
    // setup swagger
    func setupSwagger(router *gin.Engine) {
        router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
        restAuth := router.Group("/rest")
        restAuth.POST("/signrequest", RestApiSignRequest)
    }
    

Makefile을 아래와 같이 작성하여 빌드시에 -tags 옵션을 이용하여 swagDisable.go 또는 swagEnable.go 둘 중 하나의 파일이 선택되어 빌드되도록 하였다.

ifeq ($(SWAGGER), y)
TAGS = -tags swagger
endif

all:
ifeq ($(SWAGGER), y)
    @swag init
endif
    go build -ldflags "-s" $(TAGS)

이제 빌드하면 SWAGGER 환경 변수가 y 인 경우에만 Swagger UI가 포함되어 빌드되므로 아주 편리해졌다. 😛

카테고리:

업데이트: