diff --git a/cmd/shortener/main.go b/cmd/shortener/main.go index 38dd16d..5072b15 100644 --- a/cmd/shortener/main.go +++ b/cmd/shortener/main.go @@ -1,3 +1,97 @@ package main -func main() {} +import ( + "encoding/base64" + "errors" + "fmt" + "net/http" + "io" + "github.com/go-chi/chi/v5" + "log" +) + +var urls map[string]string // хранилище ссылок + +func main() { + + urls = make(map[string]string) + + r := chi.NewRouter() + r.Post("/", handlerPost) + r.Get("/{link}", handlerGet) + + + fmt.Println("Server is starter") + log.Fatal(http.ListenAndServe(":8080", r)) + +} + + +// Обрабатывает POST-запрос. Возвращает заголовок со статусом 201, если результат Ок +func handlerPost(rw http.ResponseWriter, rq *http.Request) { + fmt.Println("Отрабатывает метод", rq.Method) + // Проверяем, есть ли в теле запроса данные (ссылка) + body, _ := io.ReadAll(rq.Body) + + if string(body) != "" { + // Сокращаем принятую ссылку + res, err := encodeURL(string(body)) + + // Если ошибки нет, возвращаем результат сокращения в заголовке + // а также сохраняем короткую ссылку в хранилище + + if err == nil { + urls[res] = string(body) + rw.Header().Set("Content-Type", "text/plain") + rw.WriteHeader(201) + rw.Write([]byte("http://localhost:8080/" + res)) + } else { + panic("Something wrong in encoding") + } + + } else { + rw.WriteHeader(400) + rw.Write([]byte("No URL in request")) + } +} + +func handlerGet(rw http.ResponseWriter, rq *http.Request) { + fmt.Println("Отрабатывает метод", rq.Method) + // Получаем короткий URL из запроса + shortURL := rq.URL.String()[1:] + fmt.Println(urls) + + // Если URL уже имеется в хранилище, возвращем в браузер ответ и делаем редирект + if value, ok := urls[shortURL]; ok { + rw.Header().Set("Location", value) + rw.WriteHeader(307) + } else { + rw.Header().Set("Location", "URL not found") + rw.WriteHeader(400) + } + +} + +// Функция кодирования URL в сокращенный вид +func encodeURL(url string) (string, error) { + if url != "" { + var shortURL string + // кодируем URL по алгоритму base64 и сокращаем строку до 6 символов + fmt.Println("Закодированная ссылка =", base64.StdEncoding.EncodeToString([]byte(url))) + start := len(base64.StdEncoding.EncodeToString([]byte(url))) + shortURL = base64.StdEncoding.EncodeToString([]byte(url))[start-6:] + fmt.Println("Короткая ссылка =", shortURL) + return shortURL, nil + } else { + return "", errors.New("URL is empty") + } +} + +// Функция декодирования URL в адрес полной длины +func decodeURL(shortURL string) (string, error) { + // Ищем shortUrl в хранилище как ключ, если есть, позвращаем значение из мапы по данному ключу + if value, ok := urls[shortURL]; ok { + return value, nil + } + return "", errors.New("short URL not foud") +} diff --git a/cmd/shortener/main_test.go b/cmd/shortener/main_test.go new file mode 100644 index 0000000..2a97e90 --- /dev/null +++ b/cmd/shortener/main_test.go @@ -0,0 +1,107 @@ +package main + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" + "strings" + "fmt" +) + +func TestHandlerPost(t *testing.T){ + urls = make(map[string]string) + + type want struct { + code int + } + tests := []struct { + name string + param string + want want + }{ + { + name: "post test 1. body doesn't consist of data", + param: "", + want: want{ + code: 400, + }, + }, + { + name: "post test 2. body consist of data", + param: "http://ya.ru", + want: want{ + code: 201, + }, + + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fmt.Printf("\n\nTest %v Body %v\n", test.name, test.param) + param := strings.NewReader(test.param) + request := httptest.NewRequest(http.MethodPost, "/", param) + w := httptest.NewRecorder() + handlerPost(w, request) + + res := w.Result() + defer res.Body.Close() + fmt.Printf("want code = %d StatusCode %d\n", test.want.code, res.StatusCode) + assert.Equal(t, test.want.code, res.StatusCode) + }) + } + +} + +func TestHandlerGet(t *testing.T){ + type want struct { + code int + } + tests := []struct { + name string + body string + want want + }{ + { + name: "get test 1. body doesn't consist of data", + body: "", + want: want{ + code: 400, + }, + }, + { + name: "get test 2. body consist of data", + body: "http://ya.ru", + want: want{ + code: 307, + }, + + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var addr string + if test.body == "/"{ + addr ="" + } else { + for k, v := range urls { + if v == test.body { + addr = k + } + } + } + fmt.Printf("\n\nTest %v Body %v Addr %v\n", test.name, test.body, addr) + request := httptest.NewRequest(http.MethodGet, "/"+addr, nil) + w := httptest.NewRecorder() + handlerGet(w, request) + + res := w.Result() + defer res.Body.Close() + fmt.Printf("want code = %d StatusCode %d\n", test.want.code, res.StatusCode) + assert.Equal(t, test.want.code, res.StatusCode) + }) + } + +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b8c4047 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/evgenygavriluk/go-musthave-shortener-tpl + +go 1.21.3 + +require ( + github.com/go-chi/chi v1.5.5 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-chi/chi/v5 v5.0.10 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..230affe --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +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 v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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=