Go 실행 파일에 리소스 파일 포함시키기

Go 실행 파일에서 리소스 파일들을 포함시키는 방법을 알아본다.

필요성

팀 서버에 submit 된 PGP 파일을 decrypt 해 주는 서비스를 구현하였는데, 리소스 파일들을 파일 구조로 함께 배포할 수도 있지만 변경될 필요가 없는 리소스들은 편의상 실행 파일에 포함시켜서 배포하기로 하였다.
이렇게 하면 리소스 파일을 임의로 변경하는 것을 방지할 수 있고, 이후 서버 프로그램이나 리소스 파일들이 변경된 경우에도 실행 파일 한 개만 배포하면 되므로 아주 편리하다.
Go 언어는 특히 이런 경우에 유리한 점이 있는 것 같다.

기존 방법

Go 언어 자체적으로 embed 시켜주는 모듈이 없었으므로, 외부 패키지를 이용했어야 됐다.
이를 위한 여러 패키지들이 있었지만, 그 중에서 나는 주로 Go rice 패키지를 이용했었다. 아래는 간단한 사용 예이다.

  1. 아래와 같이 디렉구조와 파일을 생성하였다.
    ├─templates/index.html
    └─main.go
    
  2. 아래와 같이 실행하면 templates 디렉토리의 파일들의 내용을 포함하는 rice-box.go 파일이 생성된다.
    $ rice embed-go
    
  3. 아래는 main.go 파일에서의 사용법 예이다.
    package main
    
    import (
        "html/template"
        "net/http"
    
        rice "github.com/GeertJohan/go.rice"
        "github.com/gin-gonic/contrib/renders/multitemplate"
        "github.com/gin-gonic/contrib/sessions"
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        router := gin.New()
    
        // Home 핸들러
        router.GET("/", homeHandler)
    
        // 템플릿 html 파일들을 로딩
        router.HTMLRender = loadTemplates()
    
        router.Run(":8080")
    }
    
    // 템플릿 html 파일들을 embedding해서 로딩한다.
    func loadTemplates() multitemplate.Render {
        r := multitemplate.New()
    
        templateBox, err := rice.FindBox("templates")
        if err != nil {
            return r
        }
    
        list := [...]string{"index.html"}
        for _, x := range list {
            templateString, err := templateBox.String(x)
            if err != nil {
                continue
            }
            tmplMessage, err := template.New(x).Parse(templateString)
            if err != nil {
                continue
            }
            r.Add(x, tmplMessage)
        }
    
       return r
    }
    
    func homeHandler(ctx *gin.Context) {
        ctx.HTML(http.StatusOK, "index.html", nil)
    }
    

그런데 이번에 PGP 파일을 decrypt 해 주는 서비스를 구현할 때에도 기존 rice 패키지를 이용하는 방법을 이용하여 금방 구현은 했는데, 기존에 없던 favicon을 이 방법으로 포함시켜려고 하니깐 의외로 잘 되지 않았다.
그래서 다른 방법은 없을까 하고 구글링 해보니, 최근에 나온 Go 버전 1.16 부터는 embed 패키지가 기본 내장되었고 이를 이용하여 쉽게 구현할 수 있을 것 같아서 시도해 보았다. 🤔

embed 모듈을 이용한 방법

앞서 언급했듯이 Go 버전 1.16부터는 자체적으로 embed 모듈을 지원하므로, 외부 패키지를 이용하지 않아도 된다. Go embed 패키지 페이지에 기본적인 사용법이 나와 있다.

Gin에 적용 예제

  1. 아래와 같이 디렉구조와 파일을 생성하였다.
    ├─static
    │   ├─css/style.css
    │   ├─icon/favicon.png
    │   └─template/index.html
    └─main.go
    
  2. style.css 파일의 내용은 아래와 같이 작성하였다.
    body {
        background-color: lightgray;
    }
    
  3. 아래와 같이 main.go 파일을 작성하였다.
    package main
    
    import (
        "embed"
        "fmt"
        "html/template"
        "net/http"
        "github.com/gin-gonic/gin"
    )
    
    // "static" 디렉토리 embed
    var (
        //go:embed static
        embedFS embed.FS
    )
    
    func main() {
        router := gin.Default()
    
        // HTML 템플릿 파일들을 로드해서 세팅한다.
        template, err := template.ParseFS(embedFS, "static/template/*")
        if err != nil {
            fmt.Println("Fail to load template.", err)
            return
        }
        router.SetHTMLTemplate(template)
    
        // Home 핸들러
        router.GET("/", homeHandler)
    
        // Static embedded 파일들을 serve
        router.GET("/static/*filepath", func(c *gin.Context) {
            c.FileFromFS(c.Request.URL.Path, http.FS(embedFS))
        })
        router.Run()
    }
    
    func homeHandler(ctx *gin.Context) {
        ctx.HTML(http.StatusOK, "index.html", nil)
    }
    

    위의 코드에서처럼 아래와 같이 //go:embed 디렉토리 내용을 추가해 주면 이 디렉토리의 모든 파일들이 포함된다.

    //go:embed static
    embedFS embed.FS
    

    이후 embed.FS를 이용하여 SetHTMLTemplate() 함수를 수행해 주고, 아래와 같이 embedded 파일들을 serve 해 주면, 이후부터는 embedded 된 파일들을 바로 엑세스할 수 있게 된다.

    router.GET("/static/*filepath", func(c *gin.Context) {
        c.FileFromFS(c.Request.URL.Path, http.FS(embedFS))
    })
    
  4. index.html 파일의 내용은 아래와 같이 작성하였다.
    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <title>Go gin embed test</title>
            <link rel="stylesheet" href="/static/css/style.css">
            <link rel="icon" href="/static/icon/favicon.png" type="image/png">
        </head>
        <body>
            <h1>Hello! Go gin embed.</h1>
        </body>
    </html>
    

    HTML 템플릿 파일에서도 위의 예에서 보듯이 /static/와 같이 리소스 파일들의 경로를 디렉토리와 파일 경로로 엑세스할 수 있게 된다. (위의 예에서는 style.css, favicon.png 파일 사용)

빌드하면 결과로 실행 파일 한 개만 나오고, 이 파일을 실행한 후에 웹 페이지에 접속해 보면 index.html, style.css, favicon.png 파일들이 내용이 정상적으로 반영되어서 나옴을 확인하였다.
Go 자체적으로 지원하는 embed 패키지를 사용하니 확실히 rice 패키지를 사용할 때마다 편리해졌다. 🍻

카테고리:

업데이트: