Skip to content

Commit e184a64

Browse files
authored
Merge pull request #18 from shekshuev/iter23
Iter23
2 parents 833d2cf + 3cd16d7 commit e184a64

File tree

10 files changed

+644
-6
lines changed

10 files changed

+644
-6
lines changed

cmd/shortener/main.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,31 @@ func main() {
6060
Addr: cfg.ServerAddress,
6161
Handler: urlHandler.Router,
6262
}
63+
6364
done := make(chan os.Signal, 1)
64-
signal.Notify(done, os.Interrupt, syscall.SIGTERM)
65+
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
6566

6667
go func() {
67-
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
68+
var err error
69+
if cfg.EnableHTTPS {
70+
l.Log.Info("Starting HTTPS server", zap.String("addr", cfg.ServerAddress))
71+
err = server.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile)
72+
} else {
73+
l.Log.Info("Starting HTTP server", zap.String("addr", cfg.ServerAddress))
74+
err = server.ListenAndServe()
75+
}
76+
if err != nil && err != http.ErrServerClosed {
6877
l.Log.Error("Error starting server", zap.Error(err))
6978
}
7079
}()
7180
go func() {
7281
http.ListenAndServe("localhost:6060", nil)
7382
}()
7483

75-
l.Log.Info("Server started")
84+
l.Log.Info("Server started. Waiting for shutdown signal...")
85+
7686
<-done
77-
l.Log.Info("Shutting down server...")
87+
l.Log.Info("Shutdown signal received")
7888
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
7989
defer cancel()
8090
if err := urlStore.Close(); err != nil {

internal/app/compress/gzip_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package compress
2+
3+
import (
4+
"bytes"
5+
"compress/gzip"
6+
"io"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestGzipWriter_WriteAndClose(t *testing.T) {
15+
rr := httptest.NewRecorder()
16+
gz := NewGzipWriter(rr)
17+
gz.WriteHeader(http.StatusOK)
18+
19+
data := []byte("hello, compressed world!")
20+
_, err := gz.Write(data)
21+
assert.NoError(t, err)
22+
23+
err = gz.Close()
24+
assert.NoError(t, err)
25+
26+
// проверим, что заголовок установлен
27+
assert.Equal(t, "gzip", rr.Header().Get("Content-Encoding"))
28+
29+
// пытаемся распаковать, чтобы проверить содержимое
30+
gr, err := gzip.NewReader(bytes.NewReader(rr.Body.Bytes()))
31+
assert.NoError(t, err)
32+
33+
uncompressed, err := io.ReadAll(gr)
34+
assert.NoError(t, err)
35+
assert.Equal(t, data, uncompressed)
36+
}
37+
38+
func TestGzipWriter_WriteHeader(t *testing.T) {
39+
rr := httptest.NewRecorder()
40+
gz := NewGzipWriter(rr)
41+
42+
gz.WriteHeader(http.StatusTeapot)
43+
assert.Equal(t, "gzip", rr.Header().Get("Content-Encoding"))
44+
45+
assert.Equal(t, http.StatusTeapot, rr.Code)
46+
_ = gz.Close()
47+
}
48+
49+
func TestGzipReader_ReadAndClose(t *testing.T) {
50+
var buf bytes.Buffer
51+
zw := gzip.NewWriter(&buf)
52+
original := []byte("this is a test")
53+
_, err := zw.Write(original)
54+
assert.NoError(t, err)
55+
err = zw.Close()
56+
assert.NoError(t, err)
57+
58+
gr, err := NewGzipReader(io.NopCloser(bytes.NewReader(buf.Bytes())))
59+
assert.NoError(t, err)
60+
61+
out, err := io.ReadAll(gr)
62+
assert.NoError(t, err)
63+
assert.Equal(t, original, out)
64+
65+
err = gr.Close()
66+
assert.NoError(t, err)
67+
}
68+
69+
func TestGzipReader_InvalidInput(t *testing.T) {
70+
_, err := NewGzipReader(io.NopCloser(bytes.NewReader([]byte("not gzip"))))
71+
assert.Error(t, err)
72+
}

internal/app/config/config.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package config
22

33
import (
4+
"encoding/json"
45
"flag"
6+
"os"
57

68
"github.com/caarlos0/env/v6"
79
"github.com/shekshuev/shortener/internal/app/logger"
@@ -14,17 +16,36 @@ type Config struct {
1416
BaseURL string // Базовый URL для сокращённых ссылок.
1517
FileStoragePath string // Путь к файлу для хранения сокращённых URL.
1618
DatabaseDSN string // Строка подключения к базе данных.
19+
EnableHTTPS bool // Включить HTTPS.
20+
CertFile string // Путь к файлу с сертификатом.
21+
KeyFile string // путь к файлу с ключом.
1722
DefaultServerAddress string // Значение по умолчанию для ServerAddress.
1823
DefaultBaseURL string // Значение по умолчанию для BaseURL.
1924
DefaultFileStoragePath string // Значение по умолчанию для FileStoragePath.
2025
DefaultDatabaseDSN string // Значение по умолчанию для DatabaseDSN.
26+
DefaultEnableHTTPS bool // Значение по умолчанию для EnableHTTPS.
27+
DefaultCertFile string // Значение по умолчанию для CertFile.
28+
DefaultKeyFile string // Значение по умолчанию для KeyFile.
2129
}
2230

2331
type envConfig struct {
2432
ServerAddress string `env:"SERVER_ADDRESS"`
2533
BaseURL string `env:"BASE_URL"`
2634
FileStoragePath string `env:"FILE_STORAGE_PATH"`
2735
DatabaseDSN string `env:"DATABASE_DSN"`
36+
EnableHTTPS string `env:"ENABLE_HTTPS"`
37+
CertFile string `env:"TLS_CERT"`
38+
KeyFile string `env:"TLS_KEY"`
39+
}
40+
41+
type jsonConfig struct {
42+
ServerAddress string `json:"server_address"`
43+
BaseURL string `json:"base_url"`
44+
FileStoragePath string `json:"file_storage_path"`
45+
DatabaseDSN string `json:"database_dsn"`
46+
EnableHTTPS bool `json:"enable_https"`
47+
CertFile string `json:"cert_file"`
48+
KeyFile string `json:"key_file"`
2849
}
2950

3051
// GetConfig возвращает экземпляр конфига
@@ -34,12 +55,21 @@ func GetConfig() Config {
3455
cfg.DefaultBaseURL = "http://localhost:8080"
3556
cfg.DefaultFileStoragePath = "./storage.txt"
3657
cfg.DefaultDatabaseDSN = ""
58+
cfg.DefaultEnableHTTPS = false
59+
cfg.DefaultCertFile = ""
60+
cfg.DefaultKeyFile = ""
3761
parseFlags(&cfg)
3862
parsEnv(&cfg)
3963
return cfg
4064
}
4165

4266
func parseFlags(cfg *Config) {
67+
var configPath string
68+
if f := flag.Lookup("c"); f == nil {
69+
flag.StringVar(&configPath, "c", "", "path to JSON config file")
70+
} else if f := flag.Lookup("config"); f == nil {
71+
flag.StringVar(&configPath, "config", "", "path to JSON config file")
72+
}
4373
if f := flag.Lookup("a"); f == nil {
4474
flag.StringVar(&cfg.ServerAddress, "a", cfg.DefaultServerAddress, "address and port to run server")
4575
} else {
@@ -60,7 +90,25 @@ func parseFlags(cfg *Config) {
6090
} else {
6191
cfg.DatabaseDSN = cfg.DefaultDatabaseDSN
6292
}
93+
if f := flag.Lookup("s"); f == nil {
94+
flag.BoolVar(&cfg.EnableHTTPS, "s", cfg.DefaultEnableHTTPS, "enable HTTPS")
95+
} else {
96+
cfg.EnableHTTPS = cfg.DefaultEnableHTTPS
97+
}
98+
99+
if f := flag.Lookup("cert"); f == nil {
100+
flag.StringVar(&cfg.CertFile, "cert", cfg.DefaultCertFile, "cert file")
101+
} else {
102+
cfg.CertFile = cfg.DefaultCertFile
103+
}
104+
if f := flag.Lookup("key"); f == nil {
105+
flag.StringVar(&cfg.KeyFile, "key", cfg.DefaultKeyFile, "key file")
106+
} else {
107+
cfg.KeyFile = cfg.DefaultKeyFile
108+
}
109+
63110
flag.Parse()
111+
parseJSON(configPath, cfg)
64112
parsEnv(cfg)
65113
}
66114

@@ -83,4 +131,57 @@ func parsEnv(cfg *Config) {
83131
if len(envCfg.DatabaseDSN) > 0 {
84132
cfg.DatabaseDSN = envCfg.DatabaseDSN
85133
}
134+
if envCfg.EnableHTTPS == "true" || envCfg.EnableHTTPS == "1" {
135+
cfg.EnableHTTPS = true
136+
}
137+
if len(envCfg.CertFile) > 0 {
138+
cfg.CertFile = envCfg.CertFile
139+
}
140+
if len(envCfg.KeyFile) > 0 {
141+
cfg.KeyFile = envCfg.KeyFile
142+
}
143+
}
144+
145+
func parseJSON(path string, cfg *Config) {
146+
if path == "" {
147+
path = os.Getenv("CONFIG")
148+
}
149+
if path == "" {
150+
return
151+
}
152+
153+
file, err := os.Open(path)
154+
if err != nil {
155+
logger.NewLogger().Log.Warn("Could not open config file", zap.Error(err))
156+
return
157+
}
158+
defer file.Close()
159+
160+
var jCfg jsonConfig
161+
if err := json.NewDecoder(file).Decode(&jCfg); err != nil {
162+
logger.NewLogger().Log.Warn("Could not decode config JSON", zap.Error(err))
163+
return
164+
}
165+
166+
if cfg.ServerAddress == cfg.DefaultServerAddress && jCfg.ServerAddress != "" {
167+
cfg.ServerAddress = jCfg.ServerAddress
168+
}
169+
if cfg.BaseURL == cfg.DefaultBaseURL && jCfg.BaseURL != "" {
170+
cfg.BaseURL = jCfg.BaseURL
171+
}
172+
if cfg.FileStoragePath == cfg.DefaultFileStoragePath && jCfg.FileStoragePath != "" {
173+
cfg.FileStoragePath = jCfg.FileStoragePath
174+
}
175+
if cfg.DatabaseDSN == cfg.DefaultDatabaseDSN && jCfg.DatabaseDSN != "" {
176+
cfg.DatabaseDSN = jCfg.DatabaseDSN
177+
}
178+
if cfg.EnableHTTPS == cfg.DefaultEnableHTTPS {
179+
cfg.EnableHTTPS = jCfg.EnableHTTPS
180+
}
181+
if cfg.CertFile == cfg.DefaultCertFile && jCfg.CertFile != "" {
182+
cfg.CertFile = jCfg.CertFile
183+
}
184+
if cfg.KeyFile == cfg.DefaultKeyFile && jCfg.KeyFile != "" {
185+
cfg.KeyFile = jCfg.KeyFile
186+
}
86187
}

internal/app/config/config_test.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,113 @@ func TestGetConfig_EnvPriority(t *testing.T) {
1313
baseURL := "http://localhost:3000"
1414
fileStoragePath := "./test.txt"
1515
databaseDSN := "host=test port=5432 user=test password=test dbname=test sslmode=disable"
16+
enableHTTPS := "true"
17+
cert := "cert"
18+
key := "key"
1619
os.Setenv("SERVER_ADDRESS", serverAddress)
1720
os.Setenv("BASE_URL", baseURL)
1821
os.Setenv("FILE_STORAGE_PATH", fileStoragePath)
1922
os.Setenv("DATABASE_DSN", databaseDSN)
23+
os.Setenv("ENABLE_HTTPS", enableHTTPS)
24+
os.Setenv("TLS_CERT", cert)
25+
os.Setenv("TLS_KEY", key)
2026
defer os.Unsetenv("SERVER_ADDRESS")
2127
defer os.Unsetenv("BASE_URL")
2228
defer os.Unsetenv("FILE_STORAGE_PATH")
29+
defer os.Unsetenv("DATABASE_DSN")
30+
defer os.Unsetenv("ENABLE_HTTPS")
31+
defer os.Unsetenv("TLS_CERT")
32+
defer os.Unsetenv("TLS_KEY")
2333
cfg := GetConfig()
2434
assert.Equal(t, cfg.BaseURL, baseURL)
2535
assert.Equal(t, cfg.ServerAddress, serverAddress)
2636
assert.Equal(t, cfg.FileStoragePath, fileStoragePath)
2737
assert.Equal(t, cfg.DatabaseDSN, databaseDSN)
38+
assert.Equal(t, cfg.EnableHTTPS, true)
39+
assert.Equal(t, cfg.CertFile, cert)
40+
assert.Equal(t, cfg.KeyFile, key)
2841
}
2942

3043
func TestGetConfig_FlagPriority(t *testing.T) {
3144
serverAddress := "localhost:3000"
3245
baseURL := "http://localhost:3000"
3346
fileStoragePath := "./test.txt"
3447
databaseDSN := "host=test port=5432 user=test password=test dbname=test sslmode=disable"
48+
enableHTTPS := true
49+
cert := "cert"
50+
key := "key"
3551
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
36-
os.Args = []string{"cmd", "-a", serverAddress, "-b", baseURL, "-f", fileStoragePath, "-d", databaseDSN}
52+
os.Args = []string{"cmd", "-a", serverAddress, "-b", baseURL, "-f", fileStoragePath, "-d", databaseDSN, "-s", "-cert", cert, "-key", key}
3753
defer func() { os.Args = os.Args[:1] }()
3854
cfg := GetConfig()
3955
assert.Equal(t, cfg.BaseURL, baseURL)
4056
assert.Equal(t, cfg.ServerAddress, serverAddress)
4157
assert.Equal(t, cfg.FileStoragePath, fileStoragePath)
4258
assert.Equal(t, cfg.DatabaseDSN, databaseDSN)
59+
assert.Equal(t, cfg.EnableHTTPS, enableHTTPS)
60+
assert.Equal(t, cfg.CertFile, cert)
61+
assert.Equal(t, cfg.KeyFile, key)
4362
}
4463

4564
func TestGetConfig_DefaultPriority(t *testing.T) {
4665
os.Unsetenv("SERVER_ADDRESS")
4766
os.Unsetenv("BASE_URL")
4867
os.Unsetenv("FILE_STORAGE_PATH")
4968
os.Unsetenv("DATABASE_DSN")
69+
os.Unsetenv("ENABLE_HTTPS")
70+
os.Unsetenv("TLS_CERT")
71+
os.Unsetenv("TLS_KEY")
5072
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
5173
os.Args = []string{"cmd"}
5274
cfg := GetConfig()
5375
assert.Equal(t, cfg.BaseURL, cfg.DefaultBaseURL)
5476
assert.Equal(t, cfg.ServerAddress, cfg.DefaultServerAddress)
5577
assert.Equal(t, cfg.FileStoragePath, cfg.DefaultFileStoragePath)
5678
assert.Equal(t, cfg.DatabaseDSN, cfg.DefaultDatabaseDSN)
79+
assert.Equal(t, cfg.EnableHTTPS, cfg.DefaultEnableHTTPS)
80+
assert.Equal(t, cfg.CertFile, cfg.CertFile)
81+
assert.Equal(t, cfg.KeyFile, cfg.KeyFile)
82+
}
83+
84+
func TestGetConfig_JSONPriority(t *testing.T) {
85+
// Создаём временный файл с JSON-конфигурацией
86+
tmpFile, err := os.CreateTemp("", "config*.json")
87+
assert.NoError(t, err)
88+
defer os.Remove(tmpFile.Name())
89+
90+
jsonContent := `{
91+
"server_address": "localhost:9999",
92+
"base_url": "http://json",
93+
"file_storage_path": "json_path.txt",
94+
"database_dsn": "json_dsn",
95+
"enable_https": true,
96+
"cert_file": "json_cert.pem",
97+
"key_file": "json_key.pem"
98+
}`
99+
_, err = tmpFile.WriteString(jsonContent)
100+
assert.NoError(t, err)
101+
tmpFile.Close()
102+
103+
// Убедимся, что переменные окружения и флаги не мешают
104+
os.Unsetenv("SERVER_ADDRESS")
105+
os.Unsetenv("BASE_URL")
106+
os.Unsetenv("FILE_STORAGE_PATH")
107+
os.Unsetenv("DATABASE_DSN")
108+
os.Unsetenv("ENABLE_HTTPS")
109+
os.Unsetenv("TLS_CERT")
110+
os.Unsetenv("TLS_KEY")
111+
112+
// Переустанавливаем флаги и добавляем путь к JSON
113+
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
114+
os.Args = []string{"cmd", "-c", tmpFile.Name()}
115+
116+
cfg := GetConfig()
117+
118+
assert.Equal(t, cfg.ServerAddress, "localhost:9999")
119+
assert.Equal(t, cfg.BaseURL, "http://json")
120+
assert.Equal(t, cfg.FileStoragePath, "json_path.txt")
121+
assert.Equal(t, cfg.DatabaseDSN, "json_dsn")
122+
assert.Equal(t, cfg.EnableHTTPS, true)
123+
assert.Equal(t, cfg.CertFile, "json_cert.pem")
124+
assert.Equal(t, cfg.KeyFile, "json_key.pem")
57125
}

0 commit comments

Comments
 (0)