-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Iter5 #3
Iter5 #3
Changes from 12 commits
7d47269
bc38f9a
bbb404c
0d3cc7b
4df6ae4
e9e515c
7dfe8c7
fec9b71
be9c58e
f4a3084
44f17b7
49684d6
91dd87f
ef68905
9bee7ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package client | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
endpoint := "http://localhost:8080/" | ||
// контейнер данных для запроса | ||
data := url.Values{} | ||
// приглашение в консоли | ||
fmt.Println("Введите длинный URL") | ||
// открываем потоковое чтение из консоли | ||
reader := bufio.NewReader(os.Stdin) | ||
// читаем строку из консоли | ||
long, err := reader.ReadString('\n') | ||
if err != nil { | ||
panic(err) | ||
} | ||
long = strings.TrimSuffix(long, "\n") | ||
// заполняем контейнер данными | ||
data.Set("url", long) | ||
// добавляем HTTP-клиент | ||
client := &http.Client{} | ||
// пишем запрос | ||
// запрос методом POST должен, помимо заголовков, содержать тело | ||
// тело должно быть источником потокового чтения io.Reader | ||
request, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(data.Encode())) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// в заголовках запроса указываем кодировку | ||
request.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||
// отправляем запрос и получаем ответ | ||
response, err := client.Do(request) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// выводим код ответа | ||
fmt.Println("Статус-код ", response.Status) | ||
defer response.Body.Close() | ||
// читаем поток из тела ответа | ||
body, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// и печатаем его | ||
fmt.Println(string(body)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
"github.com/nabbat/23_kogorta_shotener/internal/envirements" | ||
"github.com/nabbat/23_kogorta_shotener/internal/flags" | ||
) | ||
|
||
// Config структура для хранения настроек | ||
type Config struct { | ||
RunAddr string | ||
ResultURL string | ||
} | ||
|
||
func SetEnv() *Config { | ||
fl := flags.ParseFlags() | ||
en := envirements.ParseEnv() | ||
c := &Config{} | ||
|
||
if en.EnvRunAddr != "" { | ||
c.RunAddr = en.EnvRunAddr | ||
} else { | ||
c.RunAddr = fl.RunAddr | ||
} | ||
|
||
if en.EnvResultURL != "" && en.EnvRunAddr != "http://" { | ||
c.ResultURL = en.EnvResultURL | ||
} else { | ||
c.ResultURL = fl.ResultURL | ||
} | ||
fmt.Println(c) | ||
return c | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,91 @@ | ||
package main | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Комментарии разделены на несколько типов: |
||
|
||
func main() {} | ||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
"github.com/gorilla/mux" | ||
"github.com/nabbat/23_kogorta_shotener/cmd/config" | ||
"io" | ||
"log" | ||
"net/http" | ||
) | ||
|
||
// Словарь для хранения соответствий между сокращёнными и оригинальными URL | ||
// TODO Создать хранилище | ||
var urlMap = map[string]string{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BLOCKER] Есть urlstorage.go, давай использовать его. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// Перенаправляем по полной ссылке | ||
func redirectHandler(w http.ResponseWriter, r *http.Request) { | ||
if r.Method != http.MethodGet { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BLOCKER] при регистрации функции указывается какой метод использовать. Эту проверку от сюда можно убрать There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это вообще удалил. Ушел в internal/handlers |
||
http.Error(w, "invalid request type", http.StatusBadRequest) | ||
return | ||
} | ||
// Добавляем тестовое соответствие в словарь | ||
urlMap["aHR0cH"] = "https://practicum.yandex.ru/" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [LINT] Зачем здесь нужно добавлять тестовые данные? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А загадку я так и не решил. Без этого тесты не прошёл |
||
// Получаем идентификатор из URL-пути | ||
id := r.URL.Path[1:] | ||
|
||
// Получаем оригинальный URL из словаря | ||
|
||
if originalURL, found := urlMap[id]; found { | ||
// Устанавливаем заголовок Location и возвращаем ответ с кодом 307 | ||
w.Header().Set("Location", originalURL) | ||
w.WriteHeader(http.StatusTemporaryRedirect) | ||
return | ||
} | ||
http.Error(w, "Ссылка не найдена", http.StatusBadRequest) | ||
|
||
} | ||
|
||
func shortenURLHandler(w http.ResponseWriter, r *http.Request, c *config.Config) { | ||
// Читаем тело запроса (URL) | ||
urlBytes, err := io.ReadAll(r.Body) | ||
if err != nil { | ||
http.Error(w, "Ошибка чтения запроса", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Преобразуем в строку | ||
url := string(urlBytes) | ||
|
||
// Генерируем уникальный идентификатор сокращённого URL | ||
id := generateID(url) | ||
|
||
// Добавляем соответствие в словарь | ||
urlMap[id] = url | ||
|
||
// Отправляем ответ с сокращённым URL | ||
shortenedURL := fmt.Sprintf("%s/%s", c.ResultURL, id) | ||
w.Header().Set("Content-Type", "text/plain") | ||
w.WriteHeader(http.StatusCreated) | ||
if _, err := io.WriteString(w, shortenedURL); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
// Простая функция для генерации уникального идентификатора | ||
func generateID(fullURL string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BLOCKER] Унести в internal, в какой-нибудь сервис и перенести unit тесты туда же There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
encodedStr := base64.URLEncoding.EncodeToString([]byte(fullURL)) | ||
// Возвращаем первые 6 символов закодированной строки | ||
if len(encodedStr) > 6 { | ||
return encodedStr[:6] | ||
} | ||
return encodedStr | ||
} | ||
|
||
func main() { | ||
c := config.SetEnv() | ||
|
||
// Run server | ||
r := mux.NewRouter() | ||
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
shortenURLHandler(w, r, c) | ||
}).Methods("POST") | ||
r.HandleFunc("/{idShortenURL}", redirectHandler).Methods("GET") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BLOCKER] в internal есть handlers/urlhandlers.go давай переделаем на него There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
fmt.Println("RunAddr: ResultURL: ", c.RunAddr, c.ResultURL) | ||
fmt.Println("Running server on", c.RunAddr) | ||
err := http.ListenAndServe(c.RunAddr, r) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [FYI] Здесь так же было бы хорошо слушать сигналы ОС и корректно завершать http сервис чтобы пользователи в момент редеплоя приложения или принудительного завершения получили ответ. |
||
if err != nil { | ||
panic(err) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/nabbat/23_kogorta_shotener/cmd/config" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func Test_generateID(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
fullURL string | ||
want string | ||
}{ | ||
{name: "Проверим генератор на пустую строку", fullURL: "", want: ""}, | ||
{name: "Проверим генератор на НЕпустую строку", fullURL: "https://practicum.yandex.ru/", want: "aHR0cH"}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := generateID(tt.fullURL); got != tt.want { | ||
t.Errorf("generateID() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_shortenURLHandler(t *testing.T) { | ||
type want struct { | ||
code int | ||
response string | ||
contentType string | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
bodyURL string | ||
responseContentType string | ||
targetURL string | ||
want want | ||
}{ | ||
{name: "positive POST shortenURLHandler", | ||
bodyURL: "https://practicum.yandex.ru/", | ||
responseContentType: "text/plain", | ||
targetURL: "/", | ||
want: want{ | ||
code: 201, | ||
response: `http://localhost:8080/aHR0cH`, | ||
contentType: "text/plain", | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
bodyURLTesting := strings.NewReader(test.bodyURL) | ||
request := httptest.NewRequest(http.MethodPost, test.targetURL, bodyURLTesting) | ||
request.Header.Add("Content-Type", test.responseContentType) | ||
// создаём новый Recorder | ||
w := httptest.NewRecorder() | ||
c := config.SetEnv() | ||
|
||
shortenURLHandler(w, request, c) | ||
res := w.Result() | ||
// проверяем код ответа | ||
assert.Equal(t, res.StatusCode, test.want.code) | ||
// получаем и проверяем тело запроса | ||
defer res.Body.Close() | ||
resBody, err := io.ReadAll(res.Body) | ||
|
||
require.NoError(t, err) | ||
assert.Equal(t, string(resBody), test.want.response) | ||
assert.Equal(t, res.Header.Get("Content-Type"), test.want.contentType) | ||
}) | ||
} | ||
} | ||
|
||
func Test_redirectHandler(t *testing.T) { | ||
type want struct { | ||
code int | ||
response string | ||
} | ||
tests := []struct { | ||
name string | ||
want want | ||
}{ | ||
{ | ||
name: "Проверяем redirect", | ||
want: want{ | ||
code: 307, | ||
response: `https://practicum.yandex.ru/`, | ||
}, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
request := httptest.NewRequest(http.MethodGet, "/aHR0cH", nil) | ||
// создаём новый Recorder | ||
w := httptest.NewRecorder() | ||
redirectHandler(w, request) | ||
|
||
res := w.Result() | ||
// проверяем код ответа | ||
assert.Equal(t, res.StatusCode, test.want.code) | ||
// получаем и проверяем тело запроса | ||
defer res.Body.Close() | ||
resBody, err := io.ReadAll(res.Body) | ||
|
||
require.NoError(t, err, string(resBody)) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module github.com/nabbat/23_kogorta_shotener | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/gorilla/mux v1.8.0 | ||
github.com/spf13/pflag v1.0.5 | ||
github.com/stretchr/testify v1.8.4 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
go.uber.org/multierr v1.10.0 // indirect | ||
go.uber.org/zap v1.25.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||
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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= | ||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= | ||
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= | ||
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= | ||
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= |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package envirements | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
) | ||
|
||
type EnvConfig struct { | ||
EnvRunAddr string | ||
EnvResultURL string | ||
} | ||
|
||
// ParseEnv Get system environments | ||
func ParseEnv() *EnvConfig { | ||
env := &EnvConfig{} | ||
env.EnvRunAddr = os.Getenv("RUN_ADDR") | ||
env.EnvResultURL = os.Getenv("SERVER_ADDRESS") | ||
// парсим переданные серверу аргументы в зарегистрированные переменные | ||
if !strings.HasPrefix(env.EnvResultURL, "http://") && env.EnvResultURL != "" { | ||
env.EnvResultURL = "http://" + env.EnvResultURL | ||
} | ||
return env | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package flags | ||
|
||
import ( | ||
flag "github.com/spf13/pflag" | ||
"strings" | ||
) | ||
|
||
// Flags структура для хранения настроек | ||
type Flags struct { | ||
RunAddr string | ||
ResultURL string | ||
} | ||
|
||
// ParseFlags обрабатывает аргументы командной строки | ||
// и сохраняет их значения в соответствующих переменных | ||
func ParseFlags() *Flags { | ||
// Create a Config instance | ||
flg := &Flags{} | ||
flag.StringVarP(&flg.RunAddr, "a", "a", "localhost:8080", "Адрес запуска HTTP-сервера.") | ||
flag.StringVarP(&flg.ResultURL, "b", "b", "http://localhost:8080", "Адрес результирующего сокращённого URL.") | ||
// парсим переданные серверу аргументы в зарегистрированные переменные | ||
flag.Parse() | ||
if !strings.HasPrefix(flg.ResultURL, "http://") { | ||
flg.ResultURL = "http://" + flg.ResultURL | ||
} | ||
return flg | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[BLOCKER] так же в internal есть envirements.go, давай это уберем
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.