-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
245 lines (225 loc) · 7.18 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
)
func main() {
stor := NewStorage()
h := NewHandler(stor)
h.Init()
srv := &http.Server{
Addr: addr(),
}
log.Fatal(srv.ListenAndServe())
}
// handlers
type Handlers struct {
stor Stor
}
func NewHandler(stor Stor) *Handlers {
return &Handlers{
stor: stor,
}
}
func (h *Handlers) Init() {
http.HandleFunc("/", h.handle)
}
func (h *Handlers) handle(w http.ResponseWriter, r *http.Request) {
// проверяем если метод GET
if r.Method == http.MethodGet {
// создаем переменную значения ответа
var value string
// получаем и проверяем правильность запроса
path, err := path(r.URL.Path)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "")
return
}
// получаем, если есть query параметр timeout
timout := r.URL.Query().Get("timeout")
if timout != "" {
// если таймаут задан пробуем получить значение
t, err := strconv.Atoi(timout)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "")
return
}
// задаем контекст
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(t)*time.Second)
defer cancel()
// делаем подписку на значение
value = h.stor.Subscribe(ctx, path)
// если в ответ пришло пустое значение, считаем что значений не было
if value == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "")
return
}
} else {
// если таймаут не был задан, делаем запрос в хранилище
value, err = h.stor.Get(path)
if err != nil {
if errors.Is(err, ErrNotFound) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "")
return
}
}
}
// отправляем ответ
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, value)
return
}
// если метод запроса PUT
if r.Method == http.MethodPut {
// получаем и проверяем params запроса
path, err := path(r.URL.Path)
// получаем query параметр запроса
value := r.URL.Query().Get("v")
// если ошибка в params или не задан query параметр v
if err != nil || value == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "")
return
}
// добавляем новое значение в хранилище и отвечаем на запрос
h.stor.Add(path, value)
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "")
return
}
// ответ если использовались не предусмотренные метода запроса
w.WriteHeader(http.StatusNotImplemented)
fmt.Fprint(w, "")
}
// path метод получения и проверки params в path запроса
func path(URLPath string) (string, error) {
path := strings.Split(URLPath, "/")[1:]
if len(path) != 1 {
return "", errors.New("bad request")
}
return path[0], nil
}
// storage
type Storage struct {
// repo хранилище значений
repo map[string][]string
// хранилище каналов
ch map[string][]chan string
sync.Mutex
}
type Stor interface {
Add(name string, value string)
Get(name string) (string, error)
Subscribe(ctx context.Context, name string) string
}
var ErrNotFound = errors.New("not found")
// NewStorage конструктор хранилища
func NewStorage() *Storage {
return &Storage{
repo: map[string][]string{},
ch: map[string][]chan string{},
}
}
// Add метод добавления нового значения
func (s *Storage) Add(name string, value string) {
s.Lock()
defer s.Unlock()
// если есть каналы с таким именем, то значение сразу отправляем туда
channels, ok := s.ch[name]
// проверяем есть ли хранилище с каналам с таким именем
// а также длинна слайса с каналами не нулевая
if ok && len(channels) != 0 {
// получаем первый в очереди канал
ch := channels[0]
// сохраняем новый слайс без первого канала
s.ch[name] = channels[1:]
// отправляем в канал значение
ch <- value
return
}
// если каналов нет
// проверяем есть ли слуйс значение с таким именем
values, ok := s.repo[name]
if ok {
// если есть то добавляем новое значение в конец
s.repo[name] = append(values, value)
return
}
// если слайса еще не было, то создаем новый с полученным значениеы
s.repo[name] = []string{value}
}
// Get метод получения значения из хранилища значений
func (s *Storage) Get(name string) (string, error) {
s.Lock()
defer s.Unlock()
// проверяем есть ли слайсы в мапе с таким именем
values, ok := s.repo[name]
if !ok || (len(values) == 0) {
// если не нашли, то возвращаем ошибку
return "", ErrNotFound
}
// забираем первое значение
res := values[0]
// сохраняем новый слайс без первого значения
s.repo[name] = values[1:]
// возвращаем результат
return res, nil
}
// Subscribe метод получения значения по подсписке
func (s *Storage) Subscribe(ctx context.Context, name string) string {
s.Lock()
defer s.Unlock()
// проверяем если в хранилище есть значение, то его и возвращаем
val, err := s.Get(name)
if err == nil {
return val
}
// если значения не было
// создаем канал для получения новых значений
ch := make(chan string)
defer close(ch)
// ищем есть ли слайс каналов с таким именем в мапе
channels, ok := s.ch[name]
if ok {
// если есть то добавляем в конец новый
s.ch[name] = append(channels, ch)
} else {
// если нет, то создаем
s.ch[name] = []chan string{ch}
}
// запускаем бесконечный цикл и ждем что наступит раньше
// если раньше сработает отмена контекста, то вернем пустое значение
// если раньше сработает созданный канал, то вернем значение полученное в нем
for {
select {
case <-ctx.Done():
return ""
case val = <-ch:
return val
}
}
}
// utils
// addr метод получения порта из аргументов командной строки
func addr() string {
if len(os.Args) != 2 {
log.Fatal("args port is required")
}
p, err := strconv.Atoi(os.Args[1])
if err != nil {
log.Fatal("port must be a integer")
}
return fmt.Sprintf("127.0.0.1:%d", p)
}