Go fiber를 배워보자 1일차 - overview

1

GO-fiber

목록 보기
1/1

fiber

docs: https://docs.gofiber.io/
dev blog: https://dev.to/koddr/go-fiber-by-examples-how-can-the-fiber-web-framework-be-useful-487a

fiber는 nodejs의 Express에 영감을 받아 가장 상위에 golang의 fasthttp(go에서 가장 빠른 http engine)을 탑재한 웹 프레임워크이다. fiber는 zero memory allocation과 performance를 염두해 빠른 개발을 위해 디자인되었다.

설치 방법

먼저 go 1.16 버전 이상을 사용하는 것을 전제로 한다.

go get github.com/gofiber/fiber/v2

Zero Allocation

*fiber.Ctx로 부터 반환되는 일부 값들은 기본적으로 immutable이 아니다.

왜냐면 fiber는 high-performance에 최적화되었기 때문에 fiber.Ctx로부터 반환된 값들은 기본적으로 immutable이 아니다. 즉, 값이 변경된다. 이는 일부 값들이 requests 사이에 재사용될 수 있다. 따라서, 오직 handler 안에서만 context value들을 사용해야 하고, 어떠한 reference(참조)들을 유지하지 말아야 한다. handler에서 return을 하면, context로 부터 얻은 어떠한 값들도 미래의 requests에 다시 재사용될 것이고 사용자의 제어에 벗어나 변경될 것이다. 여기 다음의 예제가 있다.

func handler(c *fiber.Ctx) error {
    // Variable is only valid within this handler
    result := c.Params("foo")
    /// ...
}

위의 예제를

만약, 해당 값들을 handler 밖에서도 보존하고 싶다면 copy를 사용하여 underlying buffer의 복사본을 만들어야 한다. 여기 string을 보존하는 방법에 대한 예제가 있다.

func handler(c *fiber.Ctx) error {
    // Variable is only valid within this handler
    result := c.Params("foo")

    // Make a copy
    buffer := make([]byte, len(result))
    copy(buffer, result)
    resultCopy := string(buffer) 
    // Variable is now valid forever

    // ...
}

fiber에서 custom하게 CopyString이라는 함수를 만들어서 위와 같은 기능을 수행할 수 있다. CopyStringgofiber/utils에 있다.

app.Get("/:foo", func(c *fiber.Ctx) error {
    // Variable is now immutable
    result := utils.CopyString(c.Params("foo")) 

    // ...
})

golang의 string은 immutable이라고 배워서, 위의 사용법이 조금 의아할 수 있다. 그런데 사실 golang의 string은 완전히 immutable은 아니다. 정확히 말하자면 문자열 자체는 mutable이지만, 사용하는 입장에서 편하게 사용하라고 immutable인 string을 제공하는 것이다. string[]rune, []byte로 변환하여 수정할 수 있다는 사실에 주목하면 그닥 놀라울게 없다. 즉, string에는 buffer가 있고, 이 buffer 내용을 변경하면 immutable한 string도 변경된다.

자세한 내용은 아래를 참고하면 된다.
(https://github.com/gofiber/fiber/issues/426)[https://github.com/gofiber/fiber/issues/426]

추가적으로, Immutable 셋팅을 설정할 수 있다. 이는 context로 부터 반환되는 모든 값들을 immutable로 만들고, 이 값들을 사용자가 유지할 수 있도록 해준다. 물론 이는 성능과 어느정도 compensate가 있다.

app := fiber.New(fiber.Config{
    Immutable: true,
})

Basic routing

routing은 어떻게 application이 특정 endpoint에 대한 client request에 반응할 것인지 에 대해 참조한다. endpoint는 URI(or path)와 특정 HTTP request method들로 구성된다.(GET, POST, PUT, ...)

각 route는 여러 개의 handler function을 가질 수 있다. 이는 route path가 매칭되면 실행되는 함수이다.

route 정의는 다음의 구조를 따른다.

app.Method(path string, ...func(*fiber.Ctx) error)
  • appfiber 인스턴스이다.
  • method는 http request method로 GET, PUT, POST 등등이다.
  • path는 server에 대한 가상의 path이다.
  • func(*fiber.Ctx) error는 callback function으로 route가 매칭될 때 실행되는 Context를 포함한다.

간단한 route에 대한 예제는 다음과 같다.

// Respond with "Hello, World!" on root path, "/"
app.Get("/", func(c *fiber.Ctx) error {
    return c.SendString("Hello, World!")
})

parameter들을 사용할 수 있다.

// GET http://localhost:8080/hello%20world

app.Get("/:value", func(c *fiber.Ctx) error {
    return c.SendString("value: " + c.Params("value"))
    // => Get request with value: hello world
})

optional parameter를 넣을 수 있다.

// GET http://localhost:3000/john

app.Get("/:name?", func(c *fiber.Ctx) error {
    if c.Params("name") != "" {
        return c.SendString("Hello " + c.Params("name"))
        // => Hello john
    }
    return c.SendString("Where is john?")
})

wildcard를 넣을 수 있다.

// GET http://localhost:3000/api/user/john

app.Get("/api/*", func(c *fiber.Ctx) error {
    return c.SendString("API path: " + c.Params("*"))
    // => API path: user/john
})

Static files

images, CSS, JS와 같은 static file들을 제공하기 위해서는 function handler를 파일 또는 directory string으로 변환해야 한다.

함수 시그니처는 다음과 같다.

app.Static(prefix, root string, config ...Static)

./public에 정적 파일들이 있다고 하자.

app := fiber.New()

app.Static("/", "./public") 

app.Listen(":3000")

이제 ./public directory에 접근할 수 있다.

http://localhost:8080/hello.html
http://localhost:8080/js/jquery.js
http://localhost:8080/css/style.css

Fiber API

새로운 fiber app을 만들기 위해서 New 메서드를 사용하면 된다. 여기에 optional config를 추가할 수 있다.

func New(config ...Config) *App

사용법은 다음과 같다.

// Default config
app := fiber.New()

// ...

config 설정은 다음과 같다.

// Custom config
app := fiber.New(fiber.Config{
    Prefork:       true,
    CaseSensitive: true,
    StrictRouting: true,
    ServerHeader:  "Fiber",
    AppName: "Test App v1.0.1"
})
// ...

config fields를 간단히 설명하면 다음과 같다.
1. AppName: string으로 app name을 설정할 수 있다. default로 ""이다.
2. BodyLimit: int로 request body의 최대 size를 설정할 수 있다. 설정한 size가 넘으면 413, Request Entitiy Too Large라는 응답이 온다. default는 4 * 1024 * 1024이다.
3. CaseSensitive: bool로 true인 경우 /foo, /Foo는 다른 경로이다. false면 이들은 같게 처리된다. 기본적으로 false이다.
4. ColorSchema: Colors타입으로 프로그램을 처음 시작할 때 보이는 message와 route list, 일부 middleware에 쓰인다. 기본적으로 DefaultColors이다.
5. CompressedFileSuffix: string으로 원래 file name에 suffix를 추가한다. 그리고 새로운 파일 이름으로 압축한 파일을 저장한다. 기본적으로 ".fiber.gz접두사이다. 6. Concurrency:int형으로 concurrent connection의 최대 수를 조절한다. 기본적으로 256 * 1024이다. 7. DisableDefaultContentType: bool타입으로 true를 설정하면 기본 default Content-Type header가 응답에서 제외된다. 기본적으로 false이다.
8. DisableDefaultDate: bool타입으로 true로 설정하면 default date를 header에서 제외한다. 기본적으로 false이다.
9. DisableHeaderNormalizing: bool타입으로 기본적으로 모든 header 이름들이 nomalized된다. 가령 conteNT-tYPE의 경우 Content-Type이 된다. default로 false이다.
10. DisableKeepalive: bool타입으로 keep-alive connection 기능을 비활성화한다. server는 client에게 응답을 전송하고 connection을 끊어버리게 된다. 기본적으로 false이다.
11. DisablePreParseMultipartForm: bool타입으로 true일 때 multipart form data를 미리 parse하지 않는다. 이는 server가 multipart form을 binary blob으로 처리할 때 매우 좋다. 기본적으로 false이다.
12. DisableStartupMessage: bool타입으로 true 설정 시 debug information을 출력하지 않는다. 기본적으로 false이다. 13. ErrorHandler: ErrorHandler타입으로fiber.Handler로 부터 에러가 반환되면, ErrorHandler가 실행된다. 마운트된 fiber error handler들은 app의 최상위에서 관리되고, 관련된 request에 대한 prefix를 붙인다. 기본적으로 DefaultErrorHandler이다. 14. GETOnly: bool타입으로 모든 non-GET 요청들을 거절한다. 기본적으로false이다. 15. IdleTimeout: time.Duration으로 keep-alive http 요청을 때, 다음 request를 기다리는 최대 시간이다. 만약 IdleTimeout이 zero면 해당 값은 ReadTimeout이 사용된다. 15. Immutable: bool타입으로 true일 때 context methods에 의해 반환된 모든 값들은 모두 immutable이 된다. 기본적으로false이다. 16. ReadTimeout: time.Duration으로 body를 포함한 request를 읽는 최대 시간이다. 기본적으로 timeout은 제한되지 않는다. default값은 nil이다. 17. RequestMethods: []string타입으로 허용하는 HTTP method들을 설정할 수 있다. 기본적으로 DefaultMethods이다. 18. StrictRouting: bool타입으로 true일 때 route는 /foo/foo/를 다르게 구분한다. 기본적으로 false이기 때문에 /foo와 /foo/는 같게 취급한다.

이 밖에도 여러 개의 config가 있으니 찾아보도록 하자. https://docs.gofiber.io/api/fiber

NewError

NewError는 새로운 HTTPError 인스턴스를 만들고 추가적인 message를 포함한다.

func NewError(code int, message ...string) *Error
app.Get("/", func(c *fiber.Ctx) error {
    return fiber.NewError(782, "Custom error message")
})

IsChild

IsChild는 현재 process가 Prefork의 결과인지 아닌 지를 판단해준다.

func IsChild() bool
// Prefork will spawn child processes
app := fiber.New(fiber.Config{
    Prefork: true,
})

if !fiber.IsChild() {
    fmt.Println("I'm the parent process")
} else {
    fmt.Println("I'm a child process")
}

// ...

App

Static

Static 메서드를 사용하여 images,CSS,JS와 같은 static file들을 제공할 수 있다.

기본적으로 Static은 directory에 있는 index.html을 serve한다.

func (app *App) Static(prefix, root string, config ...Static) Router

위의 코드를 사용하여 ./public directory에 있는 파일들을 제공해주도록 하자.

app.Static("/", "./public")

// => http://localhost:3000/hello.html
// => http://localhost:3000/js/jquery.js
// => http://localhost:3000/css/style.css

아래와 같이 하나의 경로에 여러 개의 directory를 제공할 수도 있다.

// Serve files from multiple directories
app.Static("/", "./public")

// Serve files from "./files" directory:
app.Static("/", "./files")

또한, 가상한 path prefix를 설정할 수 있다. 이는 실제 filesystem에는 없지만 route과정에서의 path를 설정해놓는 것이다.

app.Static("/static", "./public")

// => http://localhost:3000/static/hello.html
// => http://localhost:3000/static/js/jquery.js
// => http://localhost:3000/static/css/style.css

만약, static files에 대한 셋팅에 관하여 약간의 조절이 필요하다면 fiber.Static 구조체를 사용하여 특정한 setting들을 조절할 수 있다.

// Static defines configuration options when defining static assets.
type Static struct {
    // When set to true, the server tries minimizing CPU usage by caching compressed files.
    // This works differently than the github.com/gofiber/compression middleware.
    // Optional. Default value false
    Compress bool `json:"compress"`

    // When set to true, enables byte range requests.
    // Optional. Default value false
    ByteRange bool `json:"byte_range"`

    // When set to true, enables directory browsing.
    // Optional. Default value false.
    Browse bool `json:"browse"`

    // When set to true, enables direct download.
    // Optional. Default value false.
    Download bool `json:"download"`

    // The name of the index file for serving a directory.
    // Optional. Default value "index.html".
    Index string `json:"index"`

    // Expiration duration for inactive file handlers.
    // Use a negative time.Duration to disable it.
    //
    // Optional. Default value 10 * time.Second.
    CacheDuration time.Duration `json:"cache_duration"`

    // The value for the Cache-Control HTTP-header
    // that is set on the file response. MaxAge is defined in seconds.
    //
    // Optional. Default value 0.
    MaxAge int `json:"max_age"`

    // ModifyResponse defines a function that allows you to alter the response.
    //
    // Optional. Default: nil
    ModifyResponse Handler

    // Next defines a function to skip this middleware when returned true.
    //
    // Optional. Default: nil
    Next func(c *Ctx) bool
}
// Custom config
app.Static("/", "./public", fiber.Static{
  Compress:      true,
  ByteRange:     true,
  Browse:        true,
  Index:         "john.html",
  CacheDuration: 10 * time.Second,
  MaxAge:        3600,
})

Route handlers

특정 http method에 대한 route를 등록할 수 있다.

// HTTP methods
func (app *App) Get(path string, handlers ...Handler) Router
func (app *App) Head(path string, handlers ...Handler) Router
func (app *App) Post(path string, handlers ...Handler) Router
func (app *App) Put(path string, handlers ...Handler) Router
func (app *App) Delete(path string, handlers ...Handler) Router
func (app *App) Connect(path string, handlers ...Handler) Router
func (app *App) Options(path string, handlers ...Handler) Router
func (app *App) Trace(path string, handlers ...Handler) Router
func (app *App) Patch(path string, handlers ...Handler) Router

// Add allows you to specifiy a method as value
func (app *App) Add(method, path string, handlers ...Handler) Router

// All will register the route on all HTTP methods
// Almost the same as app.Use but not bound to prefixes
func (app *App) All(path string, handlers ...Handler) Router
// Simple GET handler
app.Get("/api/list", func(c *fiber.Ctx)error{
  return c.SendString("I'm a GET request!")
})

// Simple POST handler
app.Post("/api/register", func(c *fiber.Ctx) error {
  return c.SendString("I'm a POST request!")
})

Use는 middleware 패키지들에 사용된고 prefix catchers로 사용된다. 이 routes는 오직 각 path의 첫부분이 맞을 때 동작한다. 예시로 /john이라면 /john/doe, /johnnnnnn 등등이 가능하다.

func (app *App) Use(args ...interface{}) Router
// Match any request
app.Use(func(c *fiber.Ctx) error {
    return c.Next()
})

// Match request starting with /api
app.Use("/api", func(c *fiber.Ctx) error {
    return c.Next()
})

// Match requests starting with /api or /home (multiple-prefix support)
app.Use([]string{"/api", "/home"}, func(c *fiber.Ctx) error {
    return c.Next()
})

// Attach multiple handlers 
app.Use("/api",func(c *fiber.Ctx) error {
  c.Set("X-Custom-Header", random.String(32))
    return c.Next()
}, func(c *fiber.Ctx) error {
    return c.Next()
})

Group

route들을 *Group 구조체를 만들어서 그룹핑 할 수 있다.

func (app *App) Group(prefix string, handlers ...Handler) Router
func main() {
  app := fiber.New()

  api := app.Group("/api", handler)  // /api

  v1 := api.Group("/v1", handler)   // /api/v1
  v1.Get("/list", handler)          // /api/v1/list
  v1.Get("/user", handler)          // /api/v1/user

  v2 := api.Group("/v2", handler)   // /api/v2
  v2.Get("/list", handler)          // /api/v2/list
  v2.Get("/user", handler)          // /api/v2/user

  log.Fatal(app.Listen(":3000"))
}

Route

common prefix로 route들을 만들어 common function 안에 넣을 수 있다.

func (app *App) Route(prefix string, fn func(router Router), name ...string) Router
func main() {
  app := fiber.New()

  app.Route("/test", func(api fiber.Router) {
      api.Get("/foo", handler).Name("foo") // /test/foo (name: test.foo)
    api.Get("/bar", handler).Name("bar") // /test/bar (name: test.bar)
  }, "test.")

  log.Fatal(app.Listen(":3000"))
}

Server Shutdown

shuudown은 어떠한 active connection을 방해하지않고 gracefully하게 server shutdown 시켜준다. shutdown은 모든 열려있는 listener을 닫고, shutting down되기 전에 모든 connection들에게 idle 응답을 전달할 때까지 끝없이 기다린다.

func (app *App) Shutdown() error
func (app *App) ShutdownWithTimeout(timeout time.Duration) error

Listen

Listen은 주어진 address로 HTTP request를 제공한다.

// Listen on port :8080 
app.Listen(":8080")

// Custom host
app.Listen("127.0.0.1:8080")

ListenTLS

ListenTLS은 주어진 address에 대한 HTTP의 요청들을 제공하는데, certFile과 keyFile path들을 TLS certificate로 설정하고 key file을 제공할 수 있다.

func (app *App) ListenTLS(addr, certFile, keyFile string) error

ListenTLS은 기본적으로 다음의 default tls config를 사용한다. 만약, 수정하고 싶다면 Listener를 사용하여 config를 제공하면 된다.

&tls.Config{
    MinVersion:               tls.VersionTLS12,
    Certificates: []tls.Certificate{
        cert,
    },
}

Listener

Listener 메서드를 사용하여, 사용자의 net.Listener를 전달할 수 있다. 이 메서드는 TLS/HTTPS를 custom tls.Config를 사용하여 enable할 수 있다.

func (app *App) Listener(ln net.Listener) error
ln, _ := net.Listen("tcp", ":3000")

cer, _:= tls.LoadX509KeyPair("server.crt", "server.key")

ln = tls.NewListener(ln, &tls.Config{Certificates: []tls.Certificate{cer}})

app.Listener(ln)

Tests

Test method를 통해서 application을 test할 수 있다. _test.go 파일들을 생성하기 위해서 이 method를 사용하거나, routing logic을 디버그할 필요가 있을 때 사용한다. 기본적으로 timeout은 1s로 되어있고 -1을 넣으면 timeout을 disable시킬 수 있다.

func (app *App) Test(req *http.Request, msTimeout ...int) (*http.Response, error)
// Create route with GET method for test:
app.Get("/", func(c *fiber.Ctx) error {
  fmt.Println(c.BaseURL())              // => http://google.com
  fmt.Println(c.Get("X-Custom-Header")) // => hi

  return c.SendString("hello, World!")
})

// http.Request
req := httptest.NewRequest("GET", "http://google.com", nil)
req.Header.Set("X-Custom-Header", "hi")

// http.Response
resp, _ := app.Test(req)

// Do something with results:
if resp.StatusCode == fiber.StatusOK {
  body, _ := ioutil.ReadAll(resp.Body)
  fmt.Println(string(body)) // => Hello, World!
}

다음은 fiber에 대해서 직접 사용해보면서 REST API 개발할 때 필요한 부분들을 정리해보도록 하자.

0개의 댓글