Привет, Хабр!
Когда речь заходит о создании HTTP-серверов на Go, большинство сразу думают о привычных решениях, таких как net/http или Gin. Эти инструменты проверены временем, но что, если нужен сервер, который не просто стабилен, а работает очень быстро? Здесь помогает Fiber — лёгкий, но невероятно мощный HTTP-фреймворк, способный вывести производительность сервера на новый уровень.
С синтаксисом, знакомым всем юзерам Express.js, Fiber избавляет от лишней сложности и позволяет сосредоточиться на главном — максимальной скорости и эффективности.
Основные концепции Fiber
Асинхронность — это база Fiber. Он построен поверх Go‑рутин, что делает его нативно асинхронным, давая возможность обрабатывать множество запросов параллельно с минимальной нагрузкой на систему. Фича в том, что Go‑рутины крайне легковесны — они быстрее и требуют меньше ресурсов, чем традиционные потоки в других ЯП.
Fiber использует goroutine pooling — технику, при которой набор Go‑рутин заранее подготовлен для обработки запросов.
Пример простой асинхронной обработки запросов:
package main
import (
"github.com/gofiber/fiber/v2"
"time"
)
func main() {
app := fiber.New()
app.Get("/async", func(c *fiber.Ctx) error {
go func() {
time.Sleep(2 * time.Second)
println("Асинхронная задача завершена")
}()
return c.SendString("Запрос принят, задача выполняется в фоне!")
})
app.Listen(":3000")
}
Запрос на маршрут /async
возвращает результат сразу, не ожидая завершения асинхронной задачи, которая работает в фоновом режиме.
Высокой производительность достигается благодаря нескольким факторам:
Zero allocation routing: маршрутизация запросов в Fiber выполняется без дополнительных аллокаций, что минимизирует накладные расходы на обработку запросов.
Многопоточность через Go‑рутины: благодаря встроенным возможностям Go для работы с многопоточностью, Fiber масштабируется горизонтально и может обрабатывать тысячи запросов параллельно.
Пример конфигурации Fiber для макс. производительности:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
)
func main() {
app := fiber.New(fiber.Config{
Prefork: true, // включаем предварительное форкование для увеличения производительности на многоядерных процессорах
ServerHeader: "Fiber", // добавляем заголовок для идентификации сервера
CaseSensitive: true, // включаем чувствительность к регистру в URL
StrictRouting: true, // включаем строгую маршрутизацию
})
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, Fiber!")
})
app.Listen(":3000")
}
Здесь Prefork позволяет создавать несколько процессов.
Обзор основного синтаксиса
Маршрутизация в Fiber невероятно проста и быстра благодаря своей zero allocation архитектуре. Fiber поддерживает методы GET
, POST
, PUT
, DELETE
, а также динамическую маршрутизацию:
app.Get("/users/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
return c.SendString("User ID: " + id)
})
Маршрут с динамическим параметром :id
позволяет легко работать с URL и передавать данные в обработчик.
Middleware в Fiber подключается интуитивно и работает аналогично Express.js:
app.Use(func(c *fiber.Ctx) error {
println("Запрос получен")
return c.Next() // передаем управление дальше
})
Можно легко добавить middleware для обработки авторизации, логирования или различной защиты.
Обработка запросов в Fiber поддерживает работу с телом запроса, заголовками и файлами. Пример работы с телом запроса:
app.Post("/submit", func(c *fiber.Ctx) error {
data := new(struct {
Name string `json:"name"`
})
if err := c.BodyParser(data); err != nil {
return err
}
return c.JSON(fiber.Map{"message": "Привет, " + data.Name})
})
Здесь Fiber позволяет парсить тело запроса и отправлять ответ в формате JSON.
Одним из больших плюсов Fiber является его совместимость с низкоуровневыми возможностями Go. Fiber предоставляет интерфейс для работы с нативными net/http хендлерами, что позволяет комбинировать его с уже существующими решениями:
app := fiber.New()
httpHandler := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Привет из net/http"))
}
app.Get("/legacy", func(c *fiber.Ctx) error {
httpHandler(c.Context().Response().Writer, c.Context().Request())
return nil
})
Продолжим разбор синтаксиса Fiber — фреймворка, который делает работу с HTTP-серверами на Go интуитивно понятной и невероятно производительной.
Извлечение динамических параметров из URL осуществляется очень просто. Например, при создании маршрута с динамическим сегментом можно легко получить значение параметра:
app.Get("/products/:id", func(c *fiber.Ctx) error {
id := c.Params("id") // извлечение параметра "id" из URL
return c.SendString("Product ID: " + id)
})
Так можно передавать идентификатор продукта прямо в URL и обрабатывать его в функции-обработчике.
Query-параметры, которые передаются в строке запроса (например, ?sort=desc
), также легко извлекаются через Fiber:
app.Get("/search", func(c *fiber.Ctx) error {
query := c.Query("q", "default") // получаем значение параметра "q", задаём "default" как значение по умолчанию
return c.SendString("Searching for: " + query)
})
Если нужно работать с заголовками запроса, Fiber имеет удобный API для их извлечения:
app.Get("/headers", func(c *fiber.Ctx) error {
userAgent := c.Get("User-Agent") // извлекаем заголовок User-Agent
return c.SendString("Your User-Agent is: " + userAgent)
})
Пример сервера
Теперь создадим полноценный сервер на Fiber. Реализуем HTTP-сервер для онлайн-магазина корма для котиков.
Структура проекта
Определим структуру проекта, чтобы она была удобной для разработки и масштабирования:
cat-food-store/
│
├── main.go // Главная точка входа
├── routes/ // Каталог с файлами маршрутов
│ └── products.go // Маршруты для работы с продуктами (корм)
├── handlers/ // Обработчики для запросов
│ └── product.go // Логика обработки продуктов
├── models/ // Модели для базы данных
│ └── product.go // Модель данных для корма
└── database.go // Подключение к базе данных
Теперь можно начинать с основного файла main.go
.
Реализация главного файла main.go
Этот файл будет отвечать за инициализацию приложения, подключение к БД, настройку middleware и запуск сервера:
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/gofiber/fiber/v2/middleware/recover"
"cat-food-store/database"
"cat-food-store/routes"
)
func main() {
// инициализируем базу данных
if err := database.Connect(); err != nil {
log.Fatalf("Ошибка подключения к базе данных: %v", err)
}
// создаём новое приложение Fiber
app := fiber.New(fiber.Config{
Prefork: true, // используем предварительное форкование для увеличения производительности
})
// Подключаем middleware
app.Use(logger.New()) // Логирование запросов
app.Use(compress.New()) // Сжатие ответов
app.Use(recover.New()) // Восстановление после паники
app.Use(limiter.New()) // Лимит запросов для предотвращения DDOS атак
// Регистрация маршрутов
routes.RegisterProductRoutes(app)
// Запускаем сервер
log.Fatal(app.Listen(":3000"))
}
Подключение к БД
Будем использовать PostgreSQL в качестве БД для хранения информации о продуктах (корме). Для подключения к БД создадим файл database.go
:
package database
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
var DB *sql.DB
// функция подключения к базе данных
func Connect() error {
connStr := "user=username dbname=catfoodstore sslmode=disable password=yourpassword"
db, err := sql.Open("postgres", connStr)
if err != nil {
return fmt.Errorf("ошибка подключения к БД: %v", err)
}
if err := db.Ping(); err != nil {
return fmt.Errorf("не удалось подключиться к базе данных: %v", err)
}
DB = db
log.Println("Успешно подключились к базе данных")
return nil
}
Модель данных для продуктов (models/product.go)
Теперь создадим модель данных для продукта, которая будет хранить информацию о кормах для котиков:
package models
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
Stock int `json:"stock"`
ImageURL string `json:"image_url"`
}
Обработчик запросов для продуктов (handlers/product.go)
Обработчик будет содержать логику для взаимодействия с базой данных: создание, получение, обновление и удаление продуктов:
package handlers
import (
"cat-food-store/database"
"cat-food-store/models"
"github.com/gofiber/fiber/v2"
"strconv"
)
// получение списка всех продуктов
func GetProducts(c *fiber.Ctx) error {
rows, err := database.DB.Query("SELECT id, name, description, price, stock, image_url FROM products")
if err != nil {
return c.Status(500).SendString("Ошибка выполнения запроса к базе данных")
}
defer rows.Close()
var products []models.Product
for rows.Next() {
var product models.Product
err := rows.Scan(&product.ID, &product.Name, &product.Description, &product.Price, &product.Stock, &product.ImageURL)
if err != nil {
return c.Status(500).SendString("Ошибка сканирования данных")
}
products = append(products, product)
}
return c.JSON(products)
}
// создание нового продукта
func CreateProduct(c *fiber.Ctx) error {
product := new(models.Product)
if err := c.BodyParser(product); err != nil {
return c.Status(400).SendString("Неверный формат запроса")
}
_, err := database.DB.Exec("INSERT INTO products (name, description, price, stock, image_url) VALUES ($1, $2, $3, $4, $5)",
product.Name, product.Description, product.Price, product.Stock, product.ImageURL)
if err != nil {
return c.Status(500).SendString("Ошибка вставки данных в базу")
}
return c.Status(201).SendString("Продукт успешно создан")
}
// получение продукта по ID
func GetProduct(c *fiber.Ctx) error {
id := c.Params("id")
row := database.DB.QueryRow("SELECT id, name, description, price, stock, image_url FROM products WHERE id = $1", id)
var product models.Product
err := row.Scan(&product.ID, &product.Name, &product.Description, &product.Price, &product.Stock, &product.ImageURL)
if err != nil {
return c.Status(404).SendString("Продукт не найден")
}
return c.JSON(product)
}
// обновление продукта
func UpdateProduct(c *fiber.Ctx) error {
id := c.Params("id")
product := new(models.Product)
if err := c.BodyParser(product); err != nil {
return c.Status(400).SendString("Неверный формат запроса")
}
_, err := database.DB.Exec("UPDATE products SET name = $1, description = $2, price = $3, stock = $4, image_url = $5 WHERE id = $6",
product.Name, product.Description, product.Price, product.Stock, product.ImageURL, id)
if err != nil {
return c.Status(500).SendString("Ошибка обновления данных")
}
return c.SendString("Продукт успешно обновлён")
}
// удаление продукта
func DeleteProduct(c *fiber.Ctx) error {
id := c.Params("id")
_, err := database.DB.Exec("DELETE FROM products WHERE id = $1", id)
if err != nil {
return c.Status(500).SendString("Ошибка удаления продукта")
}
return c.SendString("Продукт успешно удалён")
}
Маршруты для продуктов (routes/products.go)
Теперь зарегистрируем маршруты для работы с продуктами в отдельном файле:
package routes
import (
"cat-food-store/handlers"
"github.com/gofiber/fiber/v2"
)
func RegisterProductRoutes(app *fiber.App) {
api := app.Group("/api")
api.Get("/products", handlers.GetProducts) // Получить все продукты
api.Post("/products", handlers.CreateProduct) // Создать новый продукт
api.Get("/products/:id", handlers.GetProduct) // Получить продукт по ID
api.Put("/products/:id", handlers.UpdateProduct) // Обновить продукт
api.Delete("/products/:id", handlers.DeleteProduct) // Удалить продукт
}
Создали полноценный сервер для магазина корма для котиков на Fiber, который включает в себя:
Логирование запросов
Сжатие ответов для повышения производительности
Ограничение запросов для предотвращения атак
Подключение к базе данных PostgreSQL
Полноценную маршрутизацию для работы с продуктами (кормом для котиков)
CRUD-операции
Подробнее с Fiber можно ознакомиться здесь.
А по ссылке вы можете зарегистрироваться на бесплатный вебинар курса "Golang Developer. Professional".
Комментарии (7)
Sly_tom_cat
09.09.2024 17:38Преподносить много-поточность через go-routine как какую-то особую фичу фреймворка выглядит немного глупо. Стандартный http в это тоже может как и любая другая библиотека для построения HTTP сервисов.
Прикрутить пулл горутин - ну такое себе... Пулл это всегда преолокация (что есть относительно хорошо для производительности, но не гуд по ресурсам), но в то же время они дают и лимиты: что делать если пре-аллокированные кончились? Ставить прием запросов в ожидание? Начинать возвращать ошибки? Срочно аллокировать новые хендлеры?
В целом библиотечка выглядит не так и плохо - стоит посмотреть на досуге. Но что там с HTTP2/chunked - есть поддержка или навешивать снаружи?
mrobespierre
09.09.2024 17:38+1Если у вас тормозит net/http - вы делаете что-то не то. Тормозить должна база, ответы других сервисов или БЛ (зависит от задачи), а не протокол передачи данных. Если он правда тормозит, вы что-то неправильно спроектировали (не взяли grpc или nginx там где надо было). А ещё оно иногда падучее (zero allocation не просто даётся).
Итого: актуально для школьников-бенчмаркеров, а для бизнеса не оч.
olivera507224
09.09.2024 17:38+1Я, конечно, извиняюсь, но мне всегда казалось, что если ты утверждаешь, что один фреймворк быстрее другого, то тебе стоило бы привести хоть какую-то сравнительную характеристику, выраженную в абсолютных величинах. Ну или результаты бэнчмарков, на худой конец.
ddwu
09.09.2024 17:38Поворчу: подключение к bd без пула, ну такое.
К сожалению не упомянут gofiber Client.. отличная штука! :)
Про базовый fasthttp, выше уже написали.
Забыли так же про длинный список поддерживаемых `template engines`..
напримерDjango
иpug
.
gohrytt
Префоркование