Skip to content

Commit

Permalink
Реализовал функционал икремента
Browse files Browse the repository at this point in the history
  • Loading branch information
StasMerzlyakov committed Feb 6, 2024
1 parent f5a37ca commit f740df6
Show file tree
Hide file tree
Showing 10 changed files with 840 additions and 373 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.19

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-chi/chi/v5 v5.0.11 // indirect
github.com/go-resty/resty/v2 v2.11.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
15 changes: 13 additions & 2 deletions internal/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ func BadRequestHandler(w http.ResponseWriter, req *http.Request) {
http.Error(w, "BadRequest", http.StatusBadRequest)
}

func StatusMethodNotAllowedHandler(w http.ResponseWriter, req *http.Request) {
http.Error(w, "StatusMethodNotAllowed", http.StatusMethodNotAllowed)
}
func StatusNotImplemented(w http.ResponseWriter, req *http.Request) {
http.Error(w, "StatusMethodNotAllowed", http.StatusNotImplemented)
}

func StatusNotFound(w http.ResponseWriter, req *http.Request) {
http.Error(w, "StatusNotFound", http.StatusNotFound)
}

func TodoResponse(res http.ResponseWriter, message string) {
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusNotImplemented)
Expand All @@ -22,9 +33,9 @@ func TodoResponse(res http.ResponseWriter, message string) {
`, message)
}

type Middleware func(http.Handler) http.Handler
type Middleware func(http.HandlerFunc) http.HandlerFunc

func Conveyor(h http.Handler, middlewares ...Middleware) http.Handler {
func Conveyor(h http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for _, middleware := range middlewares {
h = middleware(h)
}
Expand Down
177 changes: 126 additions & 51 deletions internal/server/handlers.go
Original file line number Diff line number Diff line change
@@ -1,58 +1,94 @@
package server

import (
"fmt"
"github.com/StasMerzlyakov/go-metrics/internal"
"github.com/StasMerzlyakov/go-metrics/internal/storage"
"github.com/go-chi/chi/v5"
"html/template"
"net/http"
"regexp"
"strconv"
"strings"
)

func CreateCounterHandler(storage storage.MetricsStorage[int64]) http.Handler {
return internal.Conveyor(CounterHandlerCreator(storage), CheckInputMiddleware)
func CreateFullPostCounterHandler(counterHandler http.HandlerFunc) http.HandlerFunc {
return internal.Conveyor(
counterHandler,
CheckIntegerMiddleware,
CheckMetricNameMiddleware,
CheckContentTypeMiddleware,
CheckMethodPostMiddleware,
)
}

func CreateGaugeHandler(storage storage.MetricsStorage[float64]) http.Handler {
return internal.Conveyor(GaugeHandlerCreator(storage), CheckInputMiddleware)
func CreateFullPostGaugeHandler(gaugeHandler http.HandlerFunc) http.HandlerFunc {
return internal.Conveyor(
gaugeHandler,
CheckDigitalMiddleware,
CheckMetricNameMiddleware,
CheckContentTypeMiddleware,
CheckMethodPostMiddleware,
)
}

// 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) {
func CheckMethodPostMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
http.Error(res, "only post methods", http.StatusMethodNotAllowed)
http.Error(w, "only post methods", http.StatusMethodNotAllowed)
return
}
next.ServeHTTP(w, req)
}
}

func CheckContentTypeMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
contentType := req.Header.Get("Content-Type")
if contentType != "" && !strings.HasPrefix(contentType, "text/plain") {
http.Error(res, "only post methods", http.StatusUnsupportedMediaType)
http.Error(w, "only 'text/plain' supported", http.StatusUnsupportedMediaType)
return
}
next.ServeHTTP(w, req)
}
}

url := req.URL.Path
if len(strings.Split(url, "/")) != 3 {
http.Error(res, "wrong url", http.StatusNotFound)
func CheckDigitalMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
valueStr := chi.URLParam(req, "value")
if !CheckDecimal(valueStr) {
http.Error(w, "wrong decimal value", http.StatusBadRequest)
return
}
next.ServeHTTP(w, req)
}
}

if !pathPattern.MatchString(url) {
http.Error(res, "wrong url", http.StatusBadRequest)
func CheckIntegerMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
valueStr := chi.URLParam(req, "value")
if !CheckInteger(valueStr) {
http.Error(w, "wrong integer value", http.StatusBadRequest)
return
}
next.ServeHTTP(w, req)
}
}

next.ServeHTTP(res, req)
})
func CheckMetricNameMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
name := chi.URLParam(req, "name")
if !CheckName(name) {
http.Error(w, "wrong name value", http.StatusBadRequest)
return
}
next.ServeHTTP(w, req)
}
}

func GaugeHandlerCreator(storage storage.MetricsStorage[float64]) http.HandlerFunc {
func GaugePostHandlerCreator(storage storage.MetricsStorage[float64]) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) {
name, value, err := extractFloat64(req)
name := chi.URLParam(req, "name")
valueStr := chi.URLParam(req, "value")
value, err := ExtractFloat64(valueStr)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return
Expand All @@ -62,9 +98,25 @@ func GaugeHandlerCreator(storage storage.MetricsStorage[float64]) http.HandlerFu
}
}

func CounterHandlerCreator(storage storage.MetricsStorage[int64]) http.HandlerFunc {
func GaugeGetHandlerCreator(gaugeStorage storage.MetricsStorage[float64]) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
name := chi.URLParam(req, "name")
if v, ok := gaugeStorage.Get(name); !ok {
w.WriteHeader(http.StatusNotFound)
return
} else {
w.Write([]byte(fmt.Sprintf("%v", v)))
}
w.WriteHeader(http.StatusOK)
}
}

func CounterPostHandlerCreator(storage storage.MetricsStorage[int64]) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) {
name, value, err := extractInt64(req)
name := chi.URLParam(req, "name")
valueStr := chi.URLParam(req, "value")
value, err := ExtractInt64(valueStr)
if err != nil {
http.Error(res, err.Error(), http.StatusBadRequest)
return
Expand All @@ -74,35 +126,58 @@ func CounterHandlerCreator(storage storage.MetricsStorage[int64]) http.HandlerFu
}
}

// getURLRegexp
// возвращает regexp для проверки url вида /<ИМЯ_МЕТРИКИ>/<ЗНАЧЕНИЕ_МЕТРИКИ>
func getURLRegexp() *regexp.Regexp {
return regexp.MustCompile("^/[a-zA-Z][a-zA-Z0-9_]*/-?([1-9][0-9]*|0)([.][0-9]+)?$")
func CounterGetHandlerCreator(counterStorage storage.MetricsStorage[int64]) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
name := chi.URLParam(req, "name")
if v, ok := counterStorage.Get(name); !ok {
w.WriteHeader(http.StatusNotFound)
return
} else {
w.Write([]byte(fmt.Sprintf("%v", v)))
}
w.WriteHeader(http.StatusOK)
}
}

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

func extractFloat64(req *http.Request) (string, float64, error) {
name, valueStr := nameValueExtractor(req)
value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return name, -1, err
}
return name, value, nil
type MetricModel struct {
Items []MetricsData
}

func extractInt64(req *http.Request) (string, int64, error) {
name, valueStr := nameValueExtractor(req)
value, err := strconv.ParseInt(valueStr, 10, 64)
if err != nil {
return name, -1, err
// AllMetricsViewHandlerCreator
// по мотивам https://stackoverflow.com/questions/56923511/go-html-template-table
func AllMetricsViewHandlerCreator(
counterStorage storage.MetricsStorage[int64],
gaugeStorage storage.MetricsStorage[float64]) http.HandlerFunc {
return func(w http.ResponseWriter, request *http.Request) {
metrics := GetAllMetrics(counterStorage, gaugeStorage)
allMetricsViewTmpl.Execute(w, metrics)
}
return name, value, nil
}

var allMetricsViewTmpl, _ = template.New("allMetrics").Parse(`<!DOCTYPE html>
<html lang="en">
<body>
<table>
<tr>
<th>Type</th>
<th>Name</th>
<th>Value</th>
</tr>
{{ range .Items}}
<tr>
<td>{{ .Type }}</td>
<td>{{ .Name }}</td>
<td>{{ .Value }}</td>
</tr>
{{ end}}
</table>
</body>
</html>
`)
Loading

0 comments on commit f740df6

Please sign in to comment.