golang

HTTP-серверы на Fiber в Golang

  • вторник, 10 сентября 2024 г. в 00:00:07
https://habr.com/ru/companies/otus/articles/841194/

Привет, Хабр!

Когда речь заходит о создании 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 возвращает результат сразу, не ожидая завершения асинхронной задачи, которая работает в фоновом режиме.

Высокой производительность достигается благодаря нескольким факторам:

  1. Zero allocation routing: маршрутизация запросов в Fiber выполняется без дополнительных аллокаций, что минимизирует накладные расходы на обработку запросов.

  2. Многопоточность через 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, который включает в себя:

  1. Логирование запросов

  2. Сжатие ответов для повышения производительности

  3. Ограничение запросов для предотвращения атак

  4. Подключение к базе данных PostgreSQL

  5. Полноценную маршрутизацию для работы с продуктами (кормом для котиков)

  6. CRUD-операции


Подробнее с Fiber можно ознакомиться здесь.

А по ссылке вы можете зарегистрироваться на бесплатный вебинар курса "Golang Developer. Professional".