[golang] http server (p. 1)
![[golang] http server (p. 1)](/content/images/size/w1200/2025/01/6b9b7223c83111efa0882a787e8844a7_1.jpeg)
Всем привет! В этой статье будет разобрано, как написать веб сервер на golang с помощью стандартной библиотеки http. В качестве примера будет создаваться простая версия приложения ToDo для управления задачами.
Содание проекта
Первое, что необходимо сделать, это выполнить команды для инициализации проекта и для создания структуры проекта, для чего можно воспользоваться инструкциями, приведенными ниже.
# инициализация проекта
go mod init hw
# создание директории, точки входа в приложение, и файла main.go
mkdir cmd
touch cmd/main.go
# создание директории, содержащей внутренню логику приложения
# а также вложенной директории todo с файлами handler.go, repository.go, model.go
mkdir internal
mkdir internal/todo
touch internal/todo/handler.go
touch internal/todo/repository.go
touch internal/todo/model.go
# создание директории, содержащей пакеты общего использования
# а также вложенной директории db с файлом db.go
mkdir pkg
mkdir pkg/db
touch pkg/db/db.go
# Таким образом, мы получим следующую структуру нашего проекта
.
├── cmd
│ └── main.go
├── go.mod
├── internal
│ └── todo
│ ├── handler.go
│ ├── model.go
│ └── repository.go
└── pkg
└── db
└── db.go
Пока что в нашем проекте не будет следующих директорий:
Они будут добавлены позже по мере развития приложения в следующих статьях.
Хранение данных будет осуществляться в оперативной памяти.
Написание структуры для хранения данных - internal/todo/model.go
Пока что в нашем проекте не будет использоваться какая-либо база данных - она будет добавлена в последующих статьях. А в настоящий момент информацию будем хранить с помощью созданной нами структуры Task и такого типа данных как map.
// internal/todo/model.go
package todo
import "time"
type Task struct {
ID string
Title string
Description string
CreateadAt time.Time
UpdatedAt time.Time
Done bool
}
// Task - структура
// с помощью которой будет передаваться и храниться информация о задаче
// из запроса со стороны клиента
Написание структуры и функционала хранения данных в оперативной памяти - pkg/db/db.go
На первой итерации реализуем хранение данных в оперативной памяти.
// pkg/db/db.go
package db
import "time"
type TaskDb struct {
ID string
Title string
Description string
CreateadAt time.Time
UpdatedAt time.Time
Done bool
}
// Task - структура
// с помощью которой будет передаваться и храниться информация о задаче
type Db struct {
Tasks map[string]TaskDb
}
// Db - база данных с хранением в оперативной памяти
func NewDb() *Db {
return &Db{
Tasks: map[string]TaskDb{},
}
}
// NewDb создает новый экземпляр базы данных
Написание репозитория - internal/todo/repository.go
Репозиторий будет помогать нам осуществлять работу с базой данных
// internal/todo/repository.go
package todo
import (
"errors"
"hw/pkg/db"
)
type TaskRepository struct {
Database *db.Db
}
// TaskRepository - репозиторий для работы с задачами
func NewTaskRepository(database *db.Db) *TaskRepository {
return &TaskRepository{
Database: database,
}
}
// NewTaskRepository создает новый экземпляр репозитория
func (repo *TaskRepository) Create(task *Task) (*Task, error) {
repo.Database.Tasks[task.ID] = db.TaskDb{
ID: task.ID,
Title: task.Title,
Description: task.Description,
CreateadAt: task.CreateadAt,
UpdatedAt: task.UpdatedAt,
Done: task.Done,
}
return task, nil
}
// Create добавляет задачу в базу данных
func (repo *TaskRepository) GetByID(id string) (*Task, error) {
task, ok := repo.Database.Tasks[id]
if !ok {
return nil, errors.New("task not found")
}
data := Task{
ID: task.ID,
Title: task.Title,
Description: task.Description,
CreateadAt: task.CreateadAt,
UpdatedAt: task.UpdatedAt,
Done: task.Done,
}
return &data, nil
}
// GetByID возвращает задачу из базы данных по идентификатору
func (repo *TaskRepository) GetAll() ([]Task, error) {
var tasks []Task
for _, task := range repo.Database.Tasks {
tasks = append(tasks, Task{
ID: task.ID,
Title: task.Title,
Description: task.Description,
CreateadAt: task.CreateadAt,
UpdatedAt: task.UpdatedAt,
Done: task.Done,
})
}
return tasks, nil
}
// GetAll возвращает все задачи из базы данных
func (repo *TaskRepository) Update(task *Task) (*Task, error) {
_, ok := repo.Database.Tasks[task.ID]
if !ok {
return nil, errors.New("task not found")
}
repo.Database.Tasks[task.ID] = db.TaskDb{
ID: task.ID,
Title: task.Title,
Description: task.Description,
CreateadAt: task.CreateadAt,
UpdatedAt: task.UpdatedAt,
Done: task.Done,
}
return task, nil
}
// Update обновляет задачу в базе данных
func (repo *TaskRepository) Delete(id string) error {
_, ok := repo.Database.Tasks[id]
if !ok {
return errors.New("task not found")
}
delete(repo.Database.Tasks, id)
return nil
}
// Delete удаляет задачу из базы данных
Написание обработчика запросов - internal/todo/handler.go
Прописываем логику работы ручек нашего приложения
// internal/todo/handler.go
package todo
import (
"encoding/json"
"net/http"
"time"
"github.com/google/uuid"
)
type TaskHandler struct {
TaskRepository *TaskRepository
}
func NewTaskHandler(router *http.ServeMux, taskRepository *TaskRepository) {
handler := &TaskHandler{
TaskRepository: taskRepository,
}
router.HandleFunc("GET /tasks", handler.GetAll())
router.HandleFunc("GET /tasks/{id}", handler.GetByID())
router.HandleFunc("POST /tasks", handler.Create())
router.HandleFunc("PUT /tasks/{id}", handler.Update())
router.HandleFunc("DELETE /tasks/{id}", handler.Delete())
}
func (h *TaskHandler) GetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := h.TaskRepository.GetAll()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(data)
}
}
func (h *TaskHandler) GetByID() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
data, err := h.TaskRepository.GetByID(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(data)
}
}
func (h *TaskHandler) Create() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
task.ID = uuid.New().String()
task.CreateadAt = time.Now()
task.UpdatedAt = time.Now()
task.Done = false
data, err := h.TaskRepository.Create(&task)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(data)
}
}
func (h *TaskHandler) Update() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var taskDb *Task
taskDb, err := h.TaskRepository.GetByID(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
task.ID = id
task.CreateadAt = taskDb.CreateadAt
task.UpdatedAt = time.Now()
data, err := h.TaskRepository.Update(&task)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(data)
}
}
func (h *TaskHandler) Delete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
err := h.TaskRepository.Delete(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
Написание точки входа - cmd/main.go
// cmd/main.go
package main
import (
"fmt"
"hw/internal/todo"
"hw/pkg/db"
"net/http"
)
func main() {
db := db.NewDb()
router := http.NewServeMux()
taskRepository := todo.NewTaskRepository(db)
todo.NewTaskHandler(router, taskRepository)
server := http.Server{
Addr: ":8080",
Handler: router,
}
fmt.Println("Server is listening on port 8080")
server.ListenAndServe()
}