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
*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
이라는 함수를 만들어서 위와 같은 기능을 수행할 수 있다. CopyString
은 gofiber/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,
})
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)
app
은 fiber
인스턴스이다.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
})
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 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
는 새로운 HTTPError 인스턴스를 만들고 추가적인 message를 포함한다.
func NewError(code int, message ...string) *Error
app.Get("/", func(c *fiber.Ctx) error {
return fiber.NewError(782, "Custom error message")
})
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")
}
// ...
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,
})
특정 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()
})
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"))
}
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"))
}
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은 주어진 address로 HTTP request를 제공한다.
// Listen on port :8080
app.Listen(":8080")
// Custom host
app.Listen("127.0.0.1:8080")
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
메서드를 사용하여, 사용자의 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)
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 개발할 때 필요한 부분들을 정리해보도록 하자.