Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 85 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,105 @@
# Тестовое задание
# Queue Server on Windows

## Очередь на go с REST интерфейсом
Это краткое руководство по запуску проекта на Windows.

Реализовать брокер очередей в виде веб сервиса. Сервис должен обрабатывать 2 метода:
---

`PUT /queue/:queue`
## 1. Установка Go

```json
{
"message": "data"
}
1. Скачайте и установите Go с официального сайта: [https://golang.org/dl/](https://golang.org/dl/).
2. Проверьте установку:
```cmd
go version
```
Вы должны увидеть что-то вроде:
```
go version go1.20.5 windows/amd64
```

---

## 2. Клонирование проекта

1. Создайте папку для проекта, например `C:\queue-server`.
2. Скопируйте все файлы проекта (`main.go`, `queue.go`, `server.go`) в эту папку.

---

## 3. Компиляция программы

Откройте командную строку (`cmd`) или PowerShell и перейдите в папку проекта:
```cmd
cd C:\queue-server
```

Скомпилируйте программу:
```cmd
go build -o queue-server.exe
```

---

## 4. Запуск сервера

Запустите скомпилированный файл с параметрами:
```cmd
queue-server.exe -port=8080 -max-queues=10 -default-size=100 -timeout=5
```

- `-port`: порт, на котором будет работать сервер (например, `8080`).
- `-max-queues`: максимальное количество очередей (например, `10`).
- `-default-size`: размер очереди по умолчанию (например, `100`).
- `-timeout`: таймаут ожидания сообщения в секундах (например, `5`).

Если все настроено правильно, вы увидите сообщение:
```
Starting server on port 8080
```

Положить сообщение message в очередь с именем `:queue` (имя очереди может быть любое), пример:
---

`curl -XPUT http://localhost/queue/pet`
`curl -XPUT http://localhost/queue/role`
## 5. Тестирование

в ответ {пустое тело + статус 200 (ok)}
в случае если тело не по формату или отсутствует отдавать статус 400 (bad request)
Для тестирования можно использовать `curl` или Postman.

Желательно предусмотреть настройку максимального количества сообщений в очереди и общее количество очередей.
### Добавить сообщение в очередь:
```cmd
curl -XPUT http://localhost:8080/queue/pet -d "{\"message\":\"hello\"}"
```

`GET /queue/:queue`
### Получить сообщение из очереди:
```cmd
curl http://localhost:8080/queue/pet?timeout=10
```

Забрать (по принципу FIFO) из очереди с названием `:queue` сообщение и вернуть в теле http запроса, пример (результат, который должен быть при выполненных put’ах выше)
---

при GET-запросах сделать возможность задавать аргумент timeout
## 6. Альтернатива: Использование PowerShell

`curl http://localhost/queue/pet?timeout=N`
Если `curl` не работает, используйте PowerShell:

если в очереди нет готового сообщения получатель должен ждать либо до момента прихода сообщения либо до истечения таймаута (N - кол-во секунд). В случае, если сообщение так и не появилось - возвращать код 404. Получатели должны получать сообщения в том же порядке как от них поступал запрос, если 2 получателя ждут сообщения (используют таймаут), то первое сообщение должен получить тот, кто первый запросил.
### Добавить сообщение в очередь:
```powershell
curl -Method PUT -Uri "http://localhost:8080/queue/pet" -Body '{"message":"hello"}' -ContentType "application/json"
```

Порт, на котором будет слушать сервис, должен задаваться в аргументах командной строки.
Настройка таймаута по умолчанию, должена задаваться или отключаться в аргументах командной строки как и остальные параметры.
### Получить сообщение из очереди:
```powershell
curl -Uri "http://localhost:8080/queue/pet?timeout=10"
```

> Запрещается пользоваться какими либо сторонними пакетами кроме стандартных библиотек. (задача в написании кода, а не в использовании чужого)
---

Желательно организовать код используя принципы гексоганальной архитектуры.
Комментарии приветствуются и помогут нам понять ход Ваших мыслей при разработке.
## 7. Возможные проблемы

Лаконичность кода будет восприниматься крайне положительно, не нужна "гибкость" больше, чем требуется для решения именно этой задачи, не нужны логи процесса работы программы (только обработка ошибок), никакого дебага и т.д... чем меньше кода - тем лучше!
1. **Go не найден**:
- Убедитесь, что Go добавлен в переменную среды `PATH`. Проверьте это командой:
```cmd
echo %PATH%
```
- Если Go отсутствует в `PATH`, добавьте путь к папке `bin` Go (например, `C:\Go\bin`) в системные переменные среды.

Оцениваться корректность реализации (заданные условия выполняются),архитектурная составляющая (нет лишних действий в программе, только решающие задачи программы), лаконичность и понятность кода (субъективно, конечно, но думайте о том, насколько будет понятен ваш. код для других, это куда более важно в командной разработке, чем сложный "крутой" код).
2. **Проблемы с curl**:
- Если `curl` не работает, скачайте его с [https://curl.se/download.html](https://curl.se/download.html) или используйте PowerShell.

Результаты прислать пул-реквестом в данный репозиторий.
---
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module go-queue-broker

go 1.24.2
24 changes: 24 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"flag"
"log"
"time"
)

func main() {
// Обрабатываются аргументы командной строки
port := flag.String("port", "8080", "port to listen on")
maxQueues := flag.Int("max-queues", 10, "maximum number of queues")
defaultSize := flag.Int("default-size", 100, "default queue size")
timeout := flag.Int("timeout", 5, "default timeout in seconds")
flag.Parse()
// Создается менеджер очередей и HTTP-сервер, запускается сервер
queueManager := NewQueueManager(*maxQueues, *defaultSize)
server := NewServer(queueManager, *port, time.Duration(*timeout)*time.Second)

log.Printf("Starting server on port %s", *port)
if err := server.Start(); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}
Binary file added queue-server.exe
Binary file not shown.
131 changes: 131 additions & 0 deletions queue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package main

import (
"errors"
"sync"
"time"
)

// Queue представляет одну очередь сообщений.
type Queue struct {
messages []string
maxSize int
mu sync.Mutex
waiters []*waiter
}

// waiter представляет ожидающего получателя.
type waiter struct {
ch chan string
}

// NewQueue создает новую очередь с максимальным размером maxSize.
func NewQueue(maxSize int) *Queue {
return &Queue{
messages: []string{},
maxSize: maxSize,
}
}

// Add добавляет сообщение в очередь.
func (q *Queue) Add(message string) error {
q.mu.Lock()
defer q.mu.Unlock()

if len(q.messages) >= q.maxSize {
return errors.New("queue is full")
}

q.messages = append(q.messages, message)
q.notifyWaiters()
return nil
}

// Get извлекает сообщение из очереди по принципу FIFO.
func (q *Queue) Get(timeout time.Duration) (string, error) {
q.mu.Lock()
defer q.mu.Unlock()

if len(q.messages) > 0 {
message := q.messages[0]
q.messages = q.messages[1:]
return message, nil
}

if timeout == 0 {
return "", errors.New("no messages available")
}

// Создаем нового ожидающего получателя.
waiter := &waiter{ch: make(chan string)}
q.waiters = append(q.waiters, waiter)
q.mu.Unlock()

select {
case message := <-waiter.ch:
q.mu.Lock()
return message, nil
case <-time.After(timeout):
q.mu.Lock()
q.removeWaiter(waiter)
return "", errors.New("timeout waiting for message")
}
}

// notifyWaiters уведомляет всех ожидающих получателей о новом сообщении.
func (q *Queue) notifyWaiters() {
for len(q.waiters) > 0 && len(q.messages) > 0 {
// Переименуем переменную waiter в w, чтобы избежать конфликта имен.
w := q.waiters[0]
q.waiters = q.waiters[1:]
message := q.messages[0]
q.messages = q.messages[1:]
go func(waiter *waiter, msg string) {
waiter.ch <- msg
}(w, message)
}
}

// removeWaiter удаляет ожидающего получателя из списка.
func (q *Queue) removeWaiter(w *waiter) {
for i, waiter := range q.waiters {
if waiter == w {
q.waiters = append(q.waiters[:i], q.waiters[i+1:]...)
break
}
}
}

// QueueManager управляет всеми очередями.
type QueueManager struct {
queues map[string]*Queue
maxQueues int
defaultSize int
mu sync.Mutex
}

// NewQueueManager создает новый менеджер очередей.
func NewQueueManager(maxQueues, defaultSize int) *QueueManager {
return &QueueManager{
queues: make(map[string]*Queue),
maxQueues: maxQueues,
defaultSize: defaultSize,
}
}

// GetOrCreateQueue возвращает существующую очередь или создает новую.
func (qm *QueueManager) GetOrCreateQueue(name string) (*Queue, error) {
qm.mu.Lock()
defer qm.mu.Unlock()

if len(qm.queues) >= qm.maxQueues {
return nil, errors.New("maximum number of queues reached")
}

queue, exists := qm.queues[name]
if !exists {
queue = NewQueue(qm.defaultSize)
qm.queues[name] = queue
}
return queue, nil
}
Loading