Fiber¶
Вебфреймворк, побудований на fasthttp, а не на net/http. Він міняє
сумісність зі стандартною бібліотекою на швидкість і компактніший API
обробників. Усе зі статті про HTTP-сервер
все ще застосовується концептуально — просто форми змінюються.
Модуль:
github.com/gofiber/fiber/v3.
app := fiber.New()
app.Get("/users/:id", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{"id": c.Params("id")})
})
app.Listen(":8080")
v3 змінила сигнатуру обробника¶
У v2 обробник брав *fiber.Ctx. У v3 fiber.Ctx — інтерфейс, що
передається за значенням:
Більшість прикладів v2 в інтернеті не скомпілюються. Якщо в фрагменті
написано *fiber.Ctx — це v2.
Справжня перевага цієї сигнатури над net/http — повернена error.
Замість того, щоб писати відповідь і пам'ятати про return, ви
повертаєте помилку, а центральний обробник розбирається з нею — що
прибирає найпоширенішу помилку обробників net/http.
Маршрутизація¶
Функції, названі за методом, а не патерни, і :id для параметрів.
Читайте їх через Params, Query і FormValue:
c.Get читає заголовок запиту; c.Set пише заголовок відповіді.
Ця асиметрія плутає людей.
Групи тримають префікси й middleware разом:
Незбіжні маршрути й неправильні методи поводяться розумно без жодних зусиль:
Обробники як методи¶
Та сама дисципліна залежностей, що й будь-де ще — структура, що тримає потрібні їй вузькі інтерфейси:
type Handler struct{ users Store }
func (h Handler) get(c fiber.Ctx) error {
u, err := h.users.ByID(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "not found"})
}
return c.JSON(u)
}
c.JSON встановлює тип вмісту й кодує. fiber.Map — це
map[string]any з коротшим ім'ям.
Прив'язка¶
var u User
if err := c.Bind().Body(&u); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "bad body"})
}
Bind().Body обирає декодер за типом вмісту. Ще є Bind().Query,
Bind().Params і Bind().Header.
Прив'язка — не валідація. Вона заповнює структуру; вона не перевіряє,
що Age правдоподібний, а Name непорожній. Валідуйте після
прив'язки — вручну або з бібліотекою валідації.
Одне місце для помилок¶
Повернена помилка направляється до ErrorHandler застосунку:
app := fiber.New(fiber.Config{
ErrorHandler: func(c fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
if e, ok := err.(*fiber.Error); ok {
code = e.Code
}
return c.Status(code).JSON(fiber.Map{"error": err.Error()})
},
})
Саме тут окупається повернення error: одне місце вирішує форму
відповіді, а обробники просто провалюються. Розширте його, щоб
розпізнавати ваші власні доменні помилки — errors.Is(err, ErrNotFound),
що стає 404 — і обробники взагалі перестають конструювати відповіді.
Будьте обережні з тим, що доходить до клієнта. Стандартний обробник
вище відлунює err.Error(), що охоче витече внутрішнє повідомлення.
Зіставляйте відомі помилки з безпечним текстом і повертайте загальне
повідомлення для решти.
Middleware¶
app.Use(requestid.New())
app.Use(recover.New())
app.Use(compress.New(compress.Config{
Next: func(c fiber.Ctx) bool {
return strings.HasSuffix(c.Path(), "/stream") // never compress SSE
},
}))
Middleware — це func(c fiber.Ctx) error, та сама форма, що й
обробник. Викличте c.Next(), щоб продовжити; поверніть без нього,
щоб зупинитися.
recover.New() перетворює паніку на 500 через ваш обробник помилок:
Більшість вбудованих middleware бере предикат Next, щоб пропускати
конкретні маршрути — приклад зі стисненням вище — саме той, що має
значення, якщо ви стрімите, бо буферизація для стиснення зводить нанівець
flush.
Чого вам коштує fasthttp¶
Fiber не використовує net/http, і це має наслідки:
- Middleware
net/httpне працює. Жодногоotelhttp, жодних сторонніх обгорток обробників, без адаптера. httptestне працює. Використовуйтеapp.Test(req), що бере*http.Requestі повертає*http.Response— зручно, і саме це використовують приклади тут.- Жоден контекст не завершується, коли клієнт відключається.
c.Context()повертаєcontext.Context, який ви зберегли черезc.SetContext, або порожнійcontext.Background(), якщо ви нічого не зберігали.c.RequestCtx()повертає власний об'єкт fasthttp —*fasthttp.RequestCtx. Він теж задовольняєcontext.Context, але його каналDoneзакривається лише тоді, коли сервер завершує роботу. Уnet/httpr.Context()завершується разом із запитом; у Fiber додавайте власний дедлайн черезcontext.WithTimeout. - Значення запиту й відповіді повторно використовуються між запитами.
[]byteабо рядок зc.Paramsчиc.Bodyдійсні лише під час обробника. Зберегти щось за межами повернення — у горутині, кеші, полі структури — дасть вам дані з непов'язаного запиту пізніше. Скопіюйте:string(append([]byte(nil), b...)). Цейappendнаnil-зріз будує новий масив, який нічого не поділяє з буфером fasthttp. Або просто використайте рядок відc.Params, якщо ваша версія вже копіює. Це найважче знайти помилку, бо вона проявляється лише під конкурентністю.
Останній пункт — справжній компроміс. Fiber швидкий частково тому, що повторно використовує буфери, а повторно використовувані буфери вимагають дисципліни.
Стрімінг: SSE — це fasthttp, а не http.Flusher¶
Стаття про події, надіслані сервером
навчає механізму net/http: отримати http.ResponseController чи
http.Flusher і викликати Flush після кожного повідомлення. У
Fiber цього інтерфейсу просто немає:
fasthttp натомість стрімить через body-stream writer. c.RequestCtx()
дає вам об'єкт запиту fasthttp, і його метод SetBodyStreamWriter
бере fasthttp.StreamWriter: функцію, що отримує *bufio.Writer і
скидає його. Обидві назви належать fasthttp, а не Fiber, тож
документацію варто шукати саме в fasthttp:
app.Get("/stream", func(c fiber.Ctx) error {
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.RequestCtx().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
for i := 1; i <= 3; i++ {
fmt.Fprintf(w, "event: tick\ndata: %d\n\n", i)
if err := w.Flush(); err != nil {
return // client went away
}
time.Sleep(10 * time.Millisecond)
}
}))
return nil
})
Усе інше з базової статті все ще діє: формат передачі, порожній рядок, що завершує кожне повідомлення, keepalive-коментарі, і скидання повільних підписників неблокувальним надсиланням.
Три моменти, специфічні для Fiber. Обробник повертається негайно
— SetBodyStreamWriter реєструє колбек, що виконується після вашого
повернення, тож усе потрібне має бути захоплене до того, а значення,
взяте з c, має бути скопійоване з причини вище. Провалений
w.Flush() — це ваш сигнал відключення, оскільки скасування
з'єднання fasthttp не поводиться як r.Context(). І виключіть
маршрут зі стиснення, інакше буферизація зведе flush нанівець.
Чи варто його використовувати¶
Якщо вам потрібна екосистема net/http — інструментація OpenTelemetry,
наявні middleware, httptest — стандартна бібліотека з роутером
спокійніший вибір, і достатньо швидка майже для всього.
Fiber має сенс, коли його ергономіка підходить команді, або коли частота запитів справді виправдовує fasthttp. Вирішуйте один раз, бо змішувати непрактично.
З досвіду Python: Fiber схожий формою на FastAPI — маршрутизація в стилі декораторів, прив'язка в типізовану структуру, центральний обробник винятків — що працює на нестандартному сервері, так само як
uvloopзаміняє цикл asyncio. Правило про повторне використання буферів не має аналога в Python, і саме його варто пам'ятати.
Швидка довідка¶
| Задача | Форма |
|---|---|
| застосунок | fiber.New(fiber.Config{...}) |
| обробник | func(c fiber.Ctx) error — значення, не вказівник, у v3 |
| маршрути | app.Get("/users/:id", h), app.Group("/api") |
| шлях / query | c.Params("id"), c.Query("q") |
| заголовок запиту чи відповіді | c.Get(...) проти c.Set(...) |
| декодувати тіло | c.Bind().Body(&v) — потім валідувати |
| відповісти | c.JSON(v), c.Status(code).JSON(...) |
| провалитися | return fiber.NewError(code, msg) |
| одне місце для помилок | Config.ErrorHandler |
| middleware | app.Use(...), з Next для пропуску маршрутів |
| тестування | app.Test(req) — не httptest |
| повторне використання буферів | копіюйте все, що зберігаєте поза обробником |