Skip to content

Commit

Permalink
Iter1 (#3)
Browse files Browse the repository at this point in the history
* go mod

* increment 1

* изменения по результатам работы статического анализатора

* ещё одни Close

* Поправил функцию
  • Loading branch information
StasMerzlyakov authored Feb 4, 2024
1 parent 0941caa commit e056a6c
Show file tree
Hide file tree
Showing 10 changed files with 547 additions and 3 deletions.
File renamed without changes.
3 changes: 0 additions & 3 deletions cmd/server/main.go

This file was deleted.

11 changes: 11 additions & 0 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"github.com/StasMerzlyakov/go-metrics/internal/server"
)

func main() {
if err := server.CreateServer(); err != nil {
panic(err)
}
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/StasMerzlyakov/go-metrics

go 1.19

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
32 changes: 32 additions & 0 deletions internal/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package internal

import (
"fmt"
"net/http"
)

func BadRequestHandler(w http.ResponseWriter, req *http.Request) {
http.Error(w, "BadRequest", http.StatusBadRequest)
}

func TodoResponse(res http.ResponseWriter, message string) {
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusNotImplemented)
fmt.Fprintf(res, `
{
"response": {
"text": "%v"
},
"version": "1.0"
}
`, message)
}

type Middleware func(http.Handler) http.Handler

func Conveyor(h http.Handler, middlewares ...Middleware) http.Handler {
for _, middleware := range middlewares {
h = middleware(h)
}
return h
}
58 changes: 58 additions & 0 deletions internal/server/mem_storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package server

import "sync"

type MemValue interface {
int64 | float64
}

func NewFloat64Storage() MemStorage[float64] {
return &memStorage[float64]{}
}

func NewInt64Storage() MemStorage[int64] {
return &memStorage[int64]{}
}

type MemStorage[T MemValue] interface {
Set(key string, value T)
Add(key string, value T)
Get(key string) (T, bool)
}

type memStorage[T MemValue] struct {
mtx sync.Mutex
storage map[string]T
}

func (ms *memStorage[T]) Set(key string, value T) {
ms.mtx.Lock()
defer ms.mtx.Unlock()
if ms.storage == nil {
ms.storage = make(map[string]T)
}
ms.storage[key] = value
}

func (ms *memStorage[T]) Add(key string, value T) {
ms.mtx.Lock()
defer ms.mtx.Unlock()
if ms.storage == nil {
ms.storage = make(map[string]T)
}
if curVal, ok := ms.storage[key]; ok {
ms.storage[key] = curVal + value
} else {
ms.storage[key] = value
}
}

func (ms *memStorage[T]) Get(key string) (T, bool) {
ms.mtx.Lock()
defer ms.mtx.Unlock()
if ms.storage == nil {
ms.storage = make(map[string]T)
}
curVal, ok := ms.storage[key]
return curVal, ok
}
45 changes: 45 additions & 0 deletions internal/server/mem_storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package server

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestInt64MemStorage(t *testing.T) {
ms := NewInt64Storage()
ms.Add("m", 1)
ms.Add("m", 2)
k, v := ms.Get("m")
assert.True(t, v)
assert.Equal(t, int64(3), k)

ms.Set("m", 5)
k, v = ms.Get("m")
assert.True(t, v)
assert.Equal(t, int64(5), k)

_, v = ms.Get("m2")
assert.False(t, v)
}

func TestFloat64MemStorage(t *testing.T) {
ms := NewFloat64Storage()
ms.Add("m", 1)
ms.Add("m", 2)
k, v := ms.Get("m")
assert.True(t, v)
assert.Equal(t, float64(3), k)

ms.Set("m", 5)
k, v = ms.Get("m")
assert.True(t, v)
assert.Equal(t, float64(5), k)

ms.Set("m", 7)
k, v = ms.Get("m")
assert.True(t, v)
assert.Equal(t, float64(7), k)

_, v = ms.Get("m2")
assert.False(t, v)
}
104 changes: 104 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package server

import (
"fmt"
"github.com/StasMerzlyakov/go-metrics/internal"
"net/http"
"regexp"
"strconv"
"strings"
)

// CreateServer
// TODO configuration
func CreateServer() error {
counterStorage := NewInt64Storage()
gaugeStorage := NewFloat64Storage()
sux := http.NewServeMux()
sux.HandleFunc("/", internal.BadRequestHandler)
sux.Handle("/update/gauge/", http.StripPrefix("/update/gauge", CreateGaugeConveyor(gaugeStorage)))
sux.Handle("/update/counter/", http.StripPrefix("/update/counter", CreateCounterConveyor(counterStorage)))

return http.ListenAndServe(`:8080`, sux)
}

func CreateCounterConveyor(storage MemStorage[int64]) http.Handler {
return internal.Conveyor(CounterHandlerCreator(storage), CheckInputMiddleware)
}

func CreateGaugeConveyor(storage MemStorage[float64]) http.Handler {
return internal.Conveyor(GaugeHandlerCreator(storage), CheckInputMiddleware)
}

func GetURLRegexp() *regexp.Regexp {
return regexp.MustCompile("^/[a-zA-Z][a-zA-Z0-9_]*/-?([1-9][0-9]*|0)([.][0-9]+)?$")
}

// CheckInputMiddleware
// принимаем только:
// - POST
// - Content-Type: text/plain
// - /<ИМЯ_МЕТРИКИ>/<ЗНАЧЕНИЕ_МЕТРИКИ>
func CheckInputMiddleware(next http.Handler) http.Handler {
pathPattern := GetURLRegexp()
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
http.Error(res, "only post methods", http.StatusMethodNotAllowed)
return
}
contentType := req.Header.Get("Content-Type")
if contentType != "" && !strings.HasPrefix(contentType, "text/plain") {
http.Error(res, "only post methods", http.StatusUnsupportedMediaType)
return
}

url := req.URL.Path

if len(strings.Split(url, "/")) != 3 {
http.Error(res, "wrong url", http.StatusNotFound)
return
}

if !pathPattern.MatchString(url) {
http.Error(res, "wrong url", http.StatusBadRequest)
return
}

next.ServeHTTP(res, req)
})
}

// nameValueExtractor
// разбивает сроку /<ИМЯ_МЕТРИКИ>/<ЗНАЧЕНИЕ_МЕТРИКИ> на имя и значение
// url уже валидирована!!
func nameValueExtractor(req *http.Request) (string, string) {
url := req.URL.Path
res := strings.Split(url, "/")[1:]
return res[0], res[1]
}

func GaugeHandlerCreator(storage MemStorage[float64]) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) {
name, valueStr := nameValueExtractor(req)
value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
http.Error(res, fmt.Sprintf("wrong float64 value: %v", valueStr), http.StatusBadRequest)
return
}
storage.Set(name, value)
res.WriteHeader(http.StatusOK)
}
}

func CounterHandlerCreator(storage MemStorage[int64]) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) {
name, valueStr := nameValueExtractor(req)
value, err := strconv.ParseInt(valueStr, 10, 64)
if err != nil {
http.Error(res, fmt.Sprintf("wrong int64 value: %v", valueStr), http.StatusBadRequest)
return
}
storage.Add(name, value)
res.WriteHeader(http.StatusOK)
}
}
Loading

0 comments on commit e056a6c

Please sign in to comment.