9seconds 4 лет назад
Родитель
Сommit
3fd5e9eb19

+ 31
- 40
internal/cli/access.go Просмотреть файл

@@ -12,6 +12,10 @@ import (
12 12
 	"strconv"
13 13
 	"strings"
14 14
 	"sync"
15
+
16
+	"github.com/9seconds/mtg/v2/internal/config"
17
+	"github.com/9seconds/mtg/v2/internal/utils"
18
+	"github.com/9seconds/mtg/v2/mtglib"
15 19
 )
16 20
 
17 21
 type accessResponse struct {
@@ -33,8 +37,7 @@ type accessResponseURLs struct {
33 37
 }
34 38
 
35 39
 type Access struct {
36
-	base
37
-
40
+	ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"`                 // nolint: lll
38 41
 	PublicIPv4 net.IP `kong:"help='Public IPv4 address for proxy. By default it is resolved via remote website',name='ipv4',short='i'"`   // nolint: lll
39 42
 	PublicIPv6 net.IP `kong:"help='Public IPv6 address for proxy. By default it is resolved via remote website',name='ipv6',short='I'"`   // nolint: lll
40 43
 	Port       uint   `kong:"help='Port number. Default port is taken from configuration file, bind-to parameter',type:'uint',short='p'"` // nolint: lll
@@ -42,17 +45,19 @@ type Access struct {
42 45
 }
43 46
 
44 47
 func (c *Access) Run(cli *CLI, version string) error {
45
-	if err := c.ReadConfig(version); err != nil {
48
+	conf, err := utils.ReadConfig(c.ConfigPath)
49
+	if err != nil {
46 50
 		return fmt.Errorf("cannot init config: %w", err)
47 51
 	}
48 52
 
49
-	return c.Execute(cli)
50
-}
51
-
52
-func (c *Access) Execute(cli *CLI) error {
53 53
 	resp := &accessResponse{}
54
-	resp.Secret.Base64 = c.Config.Secret.Base64()
55
-	resp.Secret.Hex = c.Config.Secret.Hex()
54
+	resp.Secret.Base64 = conf.Secret.Base64()
55
+	resp.Secret.Hex = conf.Secret.Hex()
56
+
57
+	ntw, err := makeNetwork(conf, version)
58
+	if err != nil {
59
+		return fmt.Errorf("cannot init network: %w", err)
60
+	}
56 61
 
57 62
 	wg := &sync.WaitGroup{}
58 63
 	wg.Add(2) // nolint: gomnd
@@ -60,31 +65,31 @@ func (c *Access) Execute(cli *CLI) error {
60 65
 	go func() {
61 66
 		defer wg.Done()
62 67
 
63
-		ip := cli.Access.PublicIPv4
68
+		ip := c.PublicIPv4
64 69
 		if ip == nil {
65
-			ip = c.getIP("tcp4")
70
+			ip = c.getIP(ntw, "tcp4")
66 71
 		}
67 72
 
68 73
 		if ip != nil {
69 74
 			ip = ip.To4()
70 75
 		}
71 76
 
72
-		resp.IPv4 = c.makeURLs(ip, cli)
77
+		resp.IPv4 = c.makeURLs(conf, ip)
73 78
 	}()
74 79
 
75 80
 	go func() {
76 81
 		defer wg.Done()
77 82
 
78
-		ip := cli.Access.PublicIPv6
83
+		ip := c.PublicIPv6
79 84
 		if ip == nil {
80
-			ip = c.getIP("tcp6")
85
+			ip = c.getIP(ntw, "tcp6")
81 86
 		}
82 87
 
83 88
 		if ip != nil {
84 89
 			ip = ip.To16()
85 90
 		}
86 91
 
87
-		resp.IPv6 = c.makeURLs(ip, cli)
92
+		resp.IPv6 = c.makeURLs(conf, ip)
88 93
 	}()
89 94
 
90 95
 	wg.Wait()
@@ -100,9 +105,9 @@ func (c *Access) Execute(cli *CLI) error {
100 105
 	return nil
101 106
 }
102 107
 
103
-func (c *Access) getIP(protocol string) net.IP {
104
-	client := c.Network.MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error) {
105
-		return c.Network.DialContext(ctx, protocol, address) // nolint: wrapcheck
108
+func (c *Access) getIP(ntw mtglib.Network, protocol string) net.IP {
109
+	client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error) {
110
+		return ntw.DialContext(ctx, protocol, address) // nolint: wrapcheck
106 111
 	})
107 112
 
108 113
 	req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) // nolint: noctx
@@ -134,24 +139,24 @@ func (c *Access) getIP(protocol string) net.IP {
134 139
 	return net.ParseIP(strings.TrimSpace(string(data)))
135 140
 }
136 141
 
137
-func (c *Access) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
142
+func (c *Access) makeURLs(conf *config.Config, ip net.IP) *accessResponseURLs {
138 143
 	if ip == nil {
139 144
 		return nil
140 145
 	}
141 146
 
142
-	portNo := cli.Access.Port
147
+	portNo := c.Port
143 148
 	if portNo == 0 {
144
-		portNo = c.Config.BindTo.PortValue(0)
149
+		portNo = conf.BindTo.Port
145 150
 	}
146 151
 
147 152
 	values := url.Values{}
148 153
 	values.Set("server", ip.String())
149 154
 	values.Set("port", strconv.Itoa(int(portNo)))
150 155
 
151
-	if cli.Access.Hex {
152
-		values.Set("secret", c.Config.Secret.Hex())
156
+	if c.Hex {
157
+		values.Set("secret", conf.Secret.Hex())
153 158
 	} else {
154
-		values.Set("secret", c.Config.Secret.Base64())
159
+		values.Set("secret", conf.Secret.Base64())
155 160
 	}
156 161
 
157 162
 	urlQuery := values.Encode()
@@ -171,22 +176,8 @@ func (c *Access) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
171 176
 			RawQuery: urlQuery,
172 177
 		}).String(),
173 178
 	}
174
-	rv.TgQrCode = c.makeQRCode(rv.TgURL)
175
-	rv.TmeQrCode = c.makeQRCode(rv.TmeURL)
179
+	rv.TgQrCode = utils.MakeQRCodeURL(rv.TgURL)
180
+	rv.TmeQrCode = utils.MakeQRCodeURL(rv.TmeURL)
176 181
 
177 182
 	return rv
178 183
 }
179
-
180
-func (c *Access) makeQRCode(data string) string {
181
-	values := url.Values{}
182
-	values.Set("qzone", "4")
183
-	values.Set("format", "svg")
184
-	values.Set("data", data)
185
-
186
-	return (&url.URL{
187
-		Scheme:   "https",
188
-		Host:     "api.qrserver.com",
189
-		Path:     "v1/create-qr-code",
190
-		RawQuery: values.Encode(),
191
-	}).String()
192
-}

+ 0
- 197
internal/cli/access_test.go Просмотреть файл

@@ -1,197 +0,0 @@
1
-package cli_test
2
-
3
-import (
4
-	"net"
5
-	"net/http"
6
-	"testing"
7
-
8
-	"github.com/9seconds/mtg/v2/internal/config"
9
-	"github.com/9seconds/mtg/v2/internal/testlib"
10
-	"github.com/9seconds/mtg/v2/mtglib"
11
-	"github.com/jarcoal/httpmock"
12
-	"github.com/stretchr/testify/suite"
13
-	"github.com/xeipuuv/gojsonschema"
14
-)
15
-
16
-var accressResponseJSONSchema = func() *gojsonschema.Schema {
17
-	schema, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(`
18
-{
19
-    "type": "object",
20
-    "required": ["secret"],
21
-    "additionalProperties": true,
22
-    "properties": {
23
-        "secret": {
24
-            "type": "object",
25
-            "required": [
26
-                "hex",
27
-                "base64"
28
-            ],
29
-            "additionalProperties": false,
30
-            "properties": {
31
-                "hex": {
32
-                    "type": "string",
33
-                    "minLength": 34
34
-                },
35
-                "base64": {
36
-                    "type": "string",
37
-                    "minLength": 10
38
-                }
39
-            }
40
-        },
41
-        "ipv4": {
42
-            "$ref": "#/definitions/ip"
43
-        },
44
-        "ipv6": {
45
-            "$ref": "#/definitions/ip"
46
-        }
47
-    },
48
-    "definitions": {
49
-        "ip": {
50
-            "type": "object",
51
-            "required": [
52
-                "ip",
53
-                "port",
54
-                "tg_url",
55
-                "tg_qrcode",
56
-                "tme_url",
57
-                "tme_qrcode"
58
-            ],
59
-            "additionalProperties": false,
60
-            "properties": {
61
-                "ip": {
62
-                    "type": "string",
63
-                    "minLength": 1,
64
-                    "anyOf": [
65
-                        {
66
-                            "format": "ipv4"
67
-                        },
68
-                        {
69
-                            "format": "ipv6"
70
-                        }
71
-                    ]
72
-                },
73
-                "port": {
74
-                    "type": "integer",
75
-                    "multipleOf": 1.0,
76
-                    "exclusiveMinimum": 0,
77
-                    "exclusiveMaximum": 65536
78
-                },
79
-                "tg_url": {
80
-                    "type": "string",
81
-                    "minLength": 1,
82
-                    "format": "uri"
83
-                },
84
-                "tg_qrcode": {
85
-                    "type": "string",
86
-                    "minLength": 1,
87
-                    "format": "uri"
88
-                },
89
-                "tme_url": {
90
-                    "type": "string",
91
-                    "minLength": 1,
92
-                    "format": "uri"
93
-                },
94
-                "tme_qrcode": {
95
-                    "type": "string",
96
-                    "minLength": 1,
97
-                    "format": "uri"
98
-                }
99
-            }
100
-        }
101
-    }
102
-}
103
-    `))
104
-	if err != nil {
105
-		panic(err)
106
-	}
107
-
108
-	return schema
109
-}()
110
-
111
-type AccessTestSuite struct {
112
-	CommonTestSuite
113
-}
114
-
115
-func (suite *AccessTestSuite) SetupTest() {
116
-	suite.CommonTestSuite.SetupTest()
117
-
118
-	suite.cli.Access.Config = &config.Config{}
119
-	suite.cli.Access.Config.Secret = mtglib.GenerateSecret("google.com")
120
-	suite.cli.Access.Network = suite.networkMock
121
-
122
-	suite.NoError(
123
-		suite.cli.Access.Config.BindTo.UnmarshalText([]byte("0.0.0.0:80")))
124
-}
125
-
126
-func (suite *AccessTestSuite) TestGenerateNoCalls() {
127
-	suite.cli.Access.PublicIPv4 = net.ParseIP("10.0.0.10")
128
-	suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
129
-
130
-	output := testlib.CaptureStdout(func() {
131
-		suite.NoError(suite.cli.Access.Execute(suite.cli))
132
-	})
133
-
134
-	validated, err := accressResponseJSONSchema.Validate(
135
-		gojsonschema.NewStringLoader(output))
136
-	suite.NoError(err)
137
-	suite.Empty(validated.Errors())
138
-	suite.True(validated.Valid())
139
-
140
-	suite.Contains(output, "10.0.0.10")
141
-	suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
142
-	suite.Contains(output, "ipv4")
143
-	suite.Contains(output, "ipv6")
144
-	suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
145
-	suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
146
-}
147
-
148
-func (suite *AccessTestSuite) TestGenerateIPv4Call() {
149
-	suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
150
-
151
-	httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
152
-		httpmock.NewStringResponder(http.StatusOK, "10.11.12.13"))
153
-
154
-	output := testlib.CaptureStdout(func() {
155
-		suite.NoError(suite.cli.Access.Execute(suite.cli))
156
-	})
157
-
158
-	validated, err := accressResponseJSONSchema.Validate(
159
-		gojsonschema.NewStringLoader(output))
160
-	suite.NoError(err)
161
-	suite.Empty(validated.Errors())
162
-	suite.True(validated.Valid())
163
-
164
-	suite.Contains(output, "10.11.12.13")
165
-	suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
166
-	suite.Contains(output, "ipv4")
167
-	suite.Contains(output, "ipv6")
168
-	suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
169
-	suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
170
-}
171
-
172
-func (suite *AccessTestSuite) TestIPv4CallFail() {
173
-	suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
174
-
175
-	httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
176
-		httpmock.NewStringResponder(http.StatusForbidden, ""))
177
-
178
-	output := testlib.CaptureStdout(func() {
179
-		suite.NoError(suite.cli.Access.Execute(suite.cli))
180
-	})
181
-
182
-	validated, err := accressResponseJSONSchema.Validate(
183
-		gojsonschema.NewStringLoader(output))
184
-	suite.NoError(err)
185
-	suite.Empty(validated.Errors())
186
-	suite.True(validated.Valid())
187
-
188
-	suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
189
-	suite.NotContains(output, "ipv4")
190
-	suite.Contains(output, "ipv6")
191
-	suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
192
-	suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
193
-}
194
-
195
-func TestAccess(t *testing.T) { // nolint: paralleltest
196
-	suite.Run(t, &AccessTestSuite{})
197
-}

+ 0
- 81
internal/cli/base.go Просмотреть файл

@@ -1,81 +0,0 @@
1
-package cli
2
-
3
-import (
4
-	"fmt"
5
-	"net"
6
-	"net/url"
7
-	"os"
8
-
9
-	"github.com/9seconds/mtg/v2/internal/config"
10
-	"github.com/9seconds/mtg/v2/mtglib"
11
-	"github.com/9seconds/mtg/v2/network"
12
-)
13
-
14
-type base struct {
15
-	ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll
16
-
17
-	Network mtglib.Network `kong:"-"`
18
-	Config  *config.Config `kong:"-"`
19
-}
20
-
21
-func (b *base) ReadConfig(version string) error {
22
-	content, err := os.ReadFile(b.ConfigPath)
23
-	if err != nil {
24
-		return fmt.Errorf("cannot read config file: %w", err)
25
-	}
26
-
27
-	conf, err := config.Parse(content)
28
-	if err != nil {
29
-		return fmt.Errorf("cannot parse config: %w", err)
30
-	}
31
-
32
-	ntw, err := b.makeNetwork(conf, version)
33
-	if err != nil {
34
-		return fmt.Errorf("cannot build a network: %w", err)
35
-	}
36
-
37
-	b.Config = conf
38
-	b.Network = ntw
39
-
40
-	return nil
41
-}
42
-
43
-func (b *base) makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
44
-	tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout)
45
-	httpTimeout := conf.Network.Timeout.HTTP.Value(network.DefaultHTTPTimeout)
46
-	dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()
47
-	bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize)
48
-	userAgent := "mtg/" + version
49
-
50
-	baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
51
-	if err != nil {
52
-		return nil, fmt.Errorf("cannot build a default dialer: %w", err)
53
-	}
54
-
55
-	proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
56
-
57
-	for _, v := range conf.Network.Proxies {
58
-		if value := v.Value(nil); value != nil {
59
-			proxyURLs = append(proxyURLs, v.Value(nil))
60
-		}
61
-	}
62
-
63
-	switch len(proxyURLs) {
64
-	case 0:
65
-		return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
66
-	case 1:
67
-		socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
68
-		if err != nil {
69
-			return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
70
-		}
71
-
72
-		return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
73
-	}
74
-
75
-	socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
76
-	if err != nil {
77
-		return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
78
-	}
79
-
80
-	return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
81
-}

+ 0
- 33
internal/cli/base_internal_test.go Просмотреть файл

@@ -1,33 +0,0 @@
1
-package cli
2
-
3
-import (
4
-	"path/filepath"
5
-	"testing"
6
-
7
-	"github.com/stretchr/testify/suite"
8
-)
9
-
10
-type BaseTestSuite struct {
11
-	suite.Suite
12
-
13
-	b base
14
-}
15
-
16
-func (suite *BaseTestSuite) SetupTest() {
17
-	suite.b = base{}
18
-}
19
-
20
-func (suite *BaseTestSuite) TestReadConfigNok() {
21
-	suite.b.ConfigPath = filepath.Join("testdata", "unknown")
22
-	suite.Error(suite.b.ReadConfig("dev"))
23
-}
24
-
25
-func (suite *BaseTestSuite) TestReadConfig() {
26
-	suite.b.ConfigPath = filepath.Join("testdata", "minimal.toml")
27
-	suite.NoError(suite.b.ReadConfig("dev"))
28
-}
29
-
30
-func TestBase(t *testing.T) {
31
-	t.Parallel()
32
-	suite.Run(t, &BaseTestSuite{})
33
-}

+ 0
- 51
internal/cli/generate_secret_test.go Просмотреть файл

@@ -1,51 +0,0 @@
1
-package cli_test
2
-
3
-import (
4
-	"strings"
5
-	"testing"
6
-
7
-	"github.com/9seconds/mtg/v2/internal/testlib"
8
-	"github.com/9seconds/mtg/v2/mtglib"
9
-	"github.com/stretchr/testify/suite"
10
-)
11
-
12
-type GenerateSecretTestSuite struct {
13
-	CommonTestSuite
14
-}
15
-
16
-func (suite *GenerateSecretTestSuite) SetupTest() {
17
-	suite.CommonTestSuite.SetupTest()
18
-
19
-	suite.cli.GenerateSecret.HostName = "google.com"
20
-}
21
-
22
-func (suite *GenerateSecretTestSuite) TestDefault() {
23
-	output := testlib.CaptureStdout(func() {
24
-		suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
25
-	})
26
-	suite.True(strings.HasPrefix(output, "7"))
27
-
28
-	secret, err := mtglib.ParseSecret(output)
29
-	suite.NoError(err)
30
-	suite.True(secret.Valid())
31
-	suite.Equal("google.com", secret.Host)
32
-}
33
-
34
-func (suite *GenerateSecretTestSuite) TestHex() {
35
-	suite.cli.GenerateSecret.Hex = true
36
-
37
-	output := testlib.CaptureStdout(func() {
38
-		suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
39
-	})
40
-	suite.True(strings.HasPrefix(output, "ee"))
41
-
42
-	secret, err := mtglib.ParseSecret(output)
43
-	suite.NoError(err)
44
-	suite.True(secret.Valid())
45
-	suite.Equal("google.com", secret.Host)
46
-}
47
-
48
-func TestGenerateSecret(t *testing.T) {
49
-	t.Parallel()
50
-	suite.Run(t, &GenerateSecretTestSuite{})
51
-}

+ 0
- 37
internal/cli/init_test.go Просмотреть файл

@@ -1,37 +0,0 @@
1
-package cli_test
2
-
3
-import (
4
-	"net/http"
5
-
6
-	"github.com/9seconds/mtg/v2/internal/cli"
7
-	"github.com/9seconds/mtg/v2/internal/testlib"
8
-	"github.com/jarcoal/httpmock"
9
-	"github.com/stretchr/testify/mock"
10
-	"github.com/stretchr/testify/suite"
11
-)
12
-
13
-type CommonTestSuite struct {
14
-	suite.Suite
15
-
16
-	cli         *cli.CLI
17
-	networkMock *testlib.MtglibNetworkMock
18
-	httpClient  *http.Client
19
-}
20
-
21
-func (suite *CommonTestSuite) SetupTest() {
22
-	suite.networkMock = &testlib.MtglibNetworkMock{}
23
-	suite.httpClient = &http.Client{}
24
-	suite.cli = &cli.CLI{}
25
-
26
-	httpmock.ActivateNonDefault(suite.httpClient)
27
-
28
-	suite.networkMock.
29
-		On("MakeHTTPClient", mock.Anything).
30
-		Maybe().
31
-		Return(suite.httpClient)
32
-}
33
-
34
-func (suite *CommonTestSuite) TearDownTest() {
35
-	suite.networkMock.AssertExpectations(suite.T())
36
-	httpmock.DeactivateAndReset()
37
-}

+ 4
- 151
internal/cli/proxy.go Просмотреть файл

@@ -2,166 +2,19 @@ package cli
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"net"
6
-	"os"
7 5
 
8
-	"github.com/9seconds/mtg/v2/antireplay"
9
-	"github.com/9seconds/mtg/v2/events"
10 6
 	"github.com/9seconds/mtg/v2/internal/utils"
11
-	"github.com/9seconds/mtg/v2/ipblocklist"
12
-	"github.com/9seconds/mtg/v2/logger"
13
-	"github.com/9seconds/mtg/v2/mtglib"
14
-	"github.com/9seconds/mtg/v2/stats"
15
-	"github.com/rs/zerolog"
16 7
 )
17 8
 
18 9
 type Proxy struct {
19
-	base
10
+	ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll
20 11
 }
21 12
 
22 13
 func (c *Proxy) Run(cli *CLI, version string) error {
23
-	if err := c.ReadConfig(version); err != nil {
24
-		return fmt.Errorf("cannot init config: %w", err)
25
-	}
26
-
27
-	return c.Execute()
28
-}
29
-
30
-func (c *Proxy) Execute() error {
31
-	zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
32
-	zerolog.TimestampFieldName = "timestamp"
33
-	zerolog.LevelFieldName = "level"
34
-
35
-	if c.Config.Debug {
36
-		zerolog.SetGlobalLevel(zerolog.DebugLevel)
37
-	} else {
38
-		zerolog.SetGlobalLevel(zerolog.WarnLevel)
39
-	}
40
-
41
-	ctx := utils.RootContext()
42
-	opts := mtglib.ProxyOpts{
43
-		Logger:          logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
44
-		Network:         c.Network,
45
-		AntiReplayCache: antireplay.NewNoop(),
46
-		IPBlocklist:     ipblocklist.NewNoop(),
47
-		EventStream:     events.NewNoopStream(),
48
-
49
-		Secret:             c.Config.Secret,
50
-		BufferSize:         c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
51
-		DomainFrontingPort: c.Config.DomainFrontingPort.Value(mtglib.DefaultDomainFrontingPort),
52
-		IdleTimeout:        c.Config.Network.Timeout.Idle.Value(mtglib.DefaultIdleTimeout),
53
-		PreferIP:           c.Config.PreferIP.Value(mtglib.DefaultPreferIP),
54
-	}
55
-
56
-	opts.Logger.BindStr("configuration", c.Config.String()).Debug("configuration")
57
-
58
-	c.setupAntiReplayCache(&opts)
59
-
60
-	if err := c.setupIPBlocklist(&opts); err != nil {
61
-		return fmt.Errorf("cannot setup ipblocklist: %w", err)
62
-	}
63
-
64
-	if err := c.setupEventStream(&opts); err != nil {
65
-		return fmt.Errorf("cannot setup event stream: %w", err)
66
-	}
67
-
68
-	proxy, err := mtglib.NewProxy(opts)
69
-	if err != nil {
70
-		return fmt.Errorf("cannot create a proxy: %w", err)
71
-	}
72
-
73
-	listener, err := net.Listen("tcp", c.Config.BindTo.String())
74
-	if err != nil {
75
-		return fmt.Errorf("cannot start proxy: %w", err)
76
-	}
77
-
78
-	go proxy.Serve(listener) // nolint: errcheck
79
-
80
-	<-ctx.Done()
81
-	listener.Close()
82
-	proxy.Shutdown()
83
-
84
-	return nil
85
-}
86
-
87
-func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
88
-	if !c.Config.Defense.AntiReplay.Enabled {
89
-		return
90
-	}
91
-
92
-	opts.AntiReplayCache = antireplay.NewStableBloomFilter(
93
-		c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultStableBloomFilterMaxSize),
94
-		c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultStableBloomFilterErrorRate),
95
-	)
96
-}
97
-
98
-func (c *Proxy) setupIPBlocklist(opts *mtglib.ProxyOpts) error {
99
-	if !c.Config.Defense.Blocklist.Enabled {
100
-		return nil
101
-	}
102
-
103
-	remoteURLs := []string{}
104
-	localFiles := []string{}
105
-
106
-	for _, v := range c.Config.Defense.Blocklist.URLs {
107
-		if v.IsRemote() {
108
-			remoteURLs = append(remoteURLs, v.String())
109
-		} else {
110
-			localFiles = append(localFiles, v.String())
111
-		}
112
-	}
113
-
114
-	firehol, err := ipblocklist.NewFirehol(opts.Logger.Named("ipblockist"),
115
-		c.Network,
116
-		c.Config.Defense.Blocklist.DownloadConcurrency,
117
-		remoteURLs,
118
-		localFiles)
14
+	conf, err := utils.ReadConfig(c.ConfigPath)
119 15
 	if err != nil {
120
-		return err // nolint: wrapcheck
121
-	}
122
-
123
-	go firehol.Run(c.Config.Defense.Blocklist.UpdateEach.Value(ipblocklist.DefaultFireholUpdateEach))
124
-
125
-	opts.IPBlocklist = firehol
126
-
127
-	return nil
128
-}
129
-
130
-func (c *Proxy) setupEventStream(opts *mtglib.ProxyOpts) error {
131
-	factories := make([]events.ObserverFactory, 0, 2)
132
-
133
-	if c.Config.Stats.StatsD.Enabled {
134
-		statsdFactory, err := stats.NewStatsd(
135
-			c.Config.Stats.StatsD.Address.String(),
136
-			opts.Logger.Named("statsd"),
137
-			c.Config.Stats.StatsD.MetricPrefix.Value(stats.DefaultStatsdMetricPrefix),
138
-			c.Config.Stats.StatsD.TagFormat.Value(stats.DefaultStatsdTagFormat))
139
-		if err != nil {
140
-			return fmt.Errorf("cannot build statsd observer: %w", err)
141
-		}
142
-
143
-		factories = append(factories, statsdFactory.Make)
144
-	}
145
-
146
-	if c.Config.Stats.Prometheus.Enabled {
147
-		prometheus := stats.NewPrometheus(
148
-			c.Config.Stats.Prometheus.MetricPrefix.Value(stats.DefaultMetricPrefix),
149
-			c.Config.Stats.Prometheus.HTTPPath.Value("/"),
150
-		)
151
-
152
-		listener, err := net.Listen("tcp", c.Config.Stats.Prometheus.BindTo.String())
153
-		if err != nil {
154
-			return fmt.Errorf("cannot start a listener for prometheus: %w", err)
155
-		}
156
-
157
-		go prometheus.Serve(listener) // nolint: errcheck
158
-
159
-		factories = append(factories, prometheus.Make)
160
-	}
161
-
162
-	if len(factories) > 0 {
163
-		opts.EventStream = events.NewEventStream(factories)
16
+		return fmt.Errorf("cannot init config: %w", err)
164 17
 	}
165 18
 
166
-	return nil
19
+    return runProxy(conf, version)
167 20
 }

+ 208
- 0
internal/cli/run_proxy.go Просмотреть файл

@@ -0,0 +1,208 @@
1
+package cli
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"net/url"
7
+	"os"
8
+
9
+	"github.com/9seconds/mtg/v2/antireplay"
10
+	"github.com/9seconds/mtg/v2/events"
11
+	"github.com/9seconds/mtg/v2/internal/config"
12
+	"github.com/9seconds/mtg/v2/internal/utils"
13
+	"github.com/9seconds/mtg/v2/ipblocklist"
14
+	"github.com/9seconds/mtg/v2/logger"
15
+	"github.com/9seconds/mtg/v2/mtglib"
16
+	"github.com/9seconds/mtg/v2/network"
17
+	"github.com/9seconds/mtg/v2/stats"
18
+	"github.com/rs/zerolog"
19
+)
20
+
21
+func makeLogger(conf *config.Config) mtglib.Logger {
22
+	zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
23
+	zerolog.TimestampFieldName = "timestamp"
24
+	zerolog.LevelFieldName = "level"
25
+
26
+	if conf.Debug.Get(false) {
27
+		zerolog.SetGlobalLevel(zerolog.DebugLevel)
28
+	} else {
29
+		zerolog.SetGlobalLevel(zerolog.WarnLevel)
30
+	}
31
+
32
+	baseLogger := zerolog.New(os.Stdout).With().Timestamp().Logger()
33
+
34
+	return logger.NewZeroLogger(baseLogger)
35
+}
36
+
37
+func makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
38
+	tcpTimeout := conf.Network.Timeout.TCP.Get(network.DefaultTimeout)
39
+	httpTimeout := conf.Network.Timeout.HTTP.Get(network.DefaultHTTPTimeout)
40
+	dohIP := conf.Network.DOHIP.Get(net.ParseIP(network.DefaultDOHHostname)).String()
41
+	bufferSize := conf.TCPBuffer.Get(network.DefaultBufferSize)
42
+	userAgent := "mtg/" + version
43
+
44
+	baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
45
+	if err != nil {
46
+		return nil, fmt.Errorf("cannot build a default dialer: %w", err)
47
+	}
48
+
49
+	if len(conf.Network.Proxies) == 0 {
50
+		return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
51
+	}
52
+
53
+	proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
54
+	for _, v := range conf.Network.Proxies {
55
+		if value := v.Get(nil); value != nil {
56
+			proxyURLs = append(proxyURLs, value)
57
+		}
58
+	}
59
+
60
+	if len(proxyURLs) == 1 {
61
+		socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
62
+		if err != nil {
63
+			return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
64
+		}
65
+
66
+		return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
67
+	}
68
+
69
+	socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
70
+	if err != nil {
71
+		return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
72
+	}
73
+
74
+	return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
75
+}
76
+
77
+func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
78
+	if !conf.Defense.AntiReplay.Enabled.Get(false) {
79
+		return antireplay.NewNoop()
80
+	}
81
+
82
+	return antireplay.NewStableBloomFilter(
83
+		conf.Defense.AntiReplay.MaxSize.Get(antireplay.DefaultStableBloomFilterMaxSize),
84
+		conf.Defense.AntiReplay.ErrorRate.Get(antireplay.DefaultStableBloomFilterErrorRate),
85
+	)
86
+}
87
+
88
+func makeIPBlocklist(conf *config.Config, logger mtglib.Logger, ntw mtglib.Network) (mtglib.IPBlocklist, error) {
89
+	if !conf.Defense.Blocklist.Enabled.Get(false) {
90
+		return ipblocklist.NewNoop(), nil
91
+	}
92
+
93
+	remoteURLs := []string{}
94
+	localFiles := []string{}
95
+
96
+	for _, v := range conf.Defense.Blocklist.URLs {
97
+		if v.IsRemote() {
98
+			remoteURLs = append(remoteURLs, v.String())
99
+		} else {
100
+			localFiles = append(localFiles, v.String())
101
+		}
102
+	}
103
+
104
+	firehol, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
105
+		ntw,
106
+		conf.Defense.Blocklist.DownloadConcurrency.Get(1),
107
+		remoteURLs,
108
+		localFiles)
109
+	if err != nil {
110
+		return nil, fmt.Errorf("incorrect parameters for firehol: %w", err)
111
+	}
112
+
113
+	return firehol, nil
114
+}
115
+
116
+func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) {
117
+	factories := make([]events.ObserverFactory, 0, 2)
118
+
119
+	if conf.Stats.StatsD.Enabled.Get(false) {
120
+		statsdFactory, err := stats.NewStatsd(
121
+			conf.Stats.StatsD.Address.Get(""),
122
+			logger.Named("statsd"),
123
+			conf.Stats.StatsD.MetricPrefix.Get(stats.DefaultStatsdMetricPrefix),
124
+			conf.Stats.StatsD.TagFormat.Get(stats.DefaultStatsdTagFormat))
125
+		if err != nil {
126
+			return nil, fmt.Errorf("cannot build statsd observer: %w", err)
127
+		}
128
+
129
+		factories = append(factories, statsdFactory.Make)
130
+	}
131
+
132
+	if conf.Stats.Prometheus.Enabled.Get(false) {
133
+		prometheus := stats.NewPrometheus(
134
+			conf.Stats.Prometheus.MetricPrefix.Get(stats.DefaultMetricPrefix),
135
+			conf.Stats.Prometheus.HTTPPath.Get("/"),
136
+		)
137
+
138
+		listener, err := net.Listen("tcp", conf.Stats.Prometheus.BindTo.Get(""))
139
+		if err != nil {
140
+			return nil, fmt.Errorf("cannot start a listener for prometheus: %w", err)
141
+		}
142
+
143
+		go prometheus.Serve(listener) // nolint: errcheck
144
+
145
+		factories = append(factories, prometheus.Make)
146
+	}
147
+
148
+	if len(factories) > 0 {
149
+		return events.NewEventStream(factories), nil
150
+	}
151
+
152
+	return events.NewNoopStream(), nil
153
+}
154
+
155
+func runProxy(conf *config.Config, version string) error {
156
+	logger := makeLogger(conf)
157
+
158
+	logger.BindStr("configuration", conf.String()).Debug("configuration")
159
+
160
+	ntw, err := makeNetwork(conf, version)
161
+	if err != nil {
162
+		return fmt.Errorf("cannot build network: %w", err)
163
+	}
164
+
165
+	blocklist, err := makeIPBlocklist(conf, logger, ntw)
166
+	if err != nil {
167
+		return fmt.Errorf("cannot build ip blocklist: %w", err)
168
+	}
169
+
170
+	eventStream, err := makeEventStream(conf, logger)
171
+	if err != nil {
172
+		return fmt.Errorf("cannot build event stream: %w", err)
173
+	}
174
+
175
+	opts := mtglib.ProxyOpts{
176
+		Logger:          logger,
177
+		Network:         ntw,
178
+		AntiReplayCache: makeAntiReplayCache(conf),
179
+		IPBlocklist:     blocklist,
180
+		EventStream:     eventStream,
181
+
182
+		Secret:             conf.Secret,
183
+		BufferSize:         conf.TCPBuffer.Get(mtglib.DefaultBufferSize),
184
+		DomainFrontingPort: conf.DomainFrontingPort.Get(mtglib.DefaultDomainFrontingPort),
185
+		IdleTimeout:        conf.Network.Timeout.Idle.Get(mtglib.DefaultIdleTimeout),
186
+		PreferIP:           conf.PreferIP.Get(mtglib.DefaultPreferIP),
187
+	}
188
+
189
+	proxy, err := mtglib.NewProxy(opts)
190
+	if err != nil {
191
+		return fmt.Errorf("cannot create a proxy: %w", err)
192
+	}
193
+
194
+	listener, err := net.Listen("tcp", conf.BindTo.Get(""))
195
+	if err != nil {
196
+		return fmt.Errorf("cannot start proxy: %w", err)
197
+	}
198
+
199
+	ctx := utils.RootContext()
200
+
201
+	go proxy.Serve(listener) // nolint: errcheck
202
+
203
+	<-ctx.Done()
204
+	listener.Close()
205
+	proxy.Shutdown()
206
+
207
+	return nil
208
+}

+ 0
- 66
internal/cli/utils.go Просмотреть файл

@@ -1,66 +0,0 @@
1
-package cli
2
-
3
-import (
4
-	"fmt"
5
-	"net"
6
-	"net/url"
7
-	"os"
8
-
9
-	"github.com/9seconds/mtg/v2/internal/config2"
10
-	"github.com/9seconds/mtg/v2/mtglib"
11
-	"github.com/9seconds/mtg/v2/network"
12
-)
13
-
14
-func readTOMLConfig(path string) (*config2.Config, error) {
15
-	content, err := os.ReadFile(path)
16
-	if err != nil {
17
-		return nil, fmt.Errorf("cannot read config file: %w", err)
18
-	}
19
-
20
-	conf, err := config2.Parse(content)
21
-	if err != nil {
22
-		return nil, fmt.Errorf("cannot parse config: %w", err)
23
-	}
24
-
25
-	return conf, nil
26
-}
27
-
28
-func makeNetwork(conf *config2.Config, version string) (mtglib.Network, error) {
29
-	tcpTimeout := conf.Network.Timeout.TCP.Get(network.DefaultTimeout)
30
-	httpTimeout := conf.Network.Timeout.HTTP.Get(network.DefaultHTTPTimeout)
31
-	dohIP := conf.Network.DOHIP.Get(net.ParseIP(network.DefaultDOHHostname)).String()
32
-	bufferSize := conf.TCPBuffer.Get(network.DefaultBufferSize)
33
-	userAgent := "mtg/" + version
34
-
35
-	baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
36
-	if err != nil {
37
-		return nil, fmt.Errorf("cannot build a default dialer: %w", err)
38
-	}
39
-
40
-	if len(conf.Network.Proxies) == 0 {
41
-		return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
42
-	}
43
-
44
-	proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
45
-	for _, v := range conf.Network.Proxies {
46
-		if value := v.Get(nil); value != nil {
47
-			proxyURLs = append(proxyURLs, value)
48
-		}
49
-	}
50
-
51
-	if len(proxyURLs) == 1 {
52
-		socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
53
-		if err != nil {
54
-			return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
55
-		}
56
-
57
-		return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
58
-	}
59
-
60
-	socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
61
-	if err != nil {
62
-		return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
63
-	}
64
-
65
-	return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
66
-}

+ 7
- 10
internal/config/type_bool.go Просмотреть файл

@@ -3,7 +3,6 @@ package config
3 3
 import (
4 4
 	"fmt"
5 5
 	"strconv"
6
-	"strings"
7 6
 )
8 7
 
9 8
 type TypeBool struct {
@@ -11,15 +10,13 @@ type TypeBool struct {
11 10
 }
12 11
 
13 12
 func (t *TypeBool) Set(data string) error {
14
-	switch strings.ToLower(data) {
15
-	case "1", "y", "yes", "enabled", "true":
16
-		t.Value = true
17
-	case "0", "n", "no", "disabled", "false":
18
-		t.Value = false
19
-	default:
20
-		return fmt.Errorf("incorrect bool value %s", data)
13
+	parsed, err := strconv.ParseBool(data)
14
+	if err != nil {
15
+		return fmt.Errorf("incorrect bool value: %s", data)
21 16
 	}
22 17
 
18
+	t.Value = parsed
19
+
23 20
 	return nil
24 21
 }
25 22
 
@@ -27,11 +24,11 @@ func (t TypeBool) Get(defaultValue bool) bool {
27 24
 	return t.Value || defaultValue
28 25
 }
29 26
 
30
-func (t *TypeBool) UnmarshalText(data []byte) error {
27
+func (t *TypeBool) UnmarshalJSON(data []byte) error {
31 28
 	return t.Set(string(data))
32 29
 }
33 30
 
34
-func (t TypeBool) MarshalText() ([]byte, error) {
31
+func (t TypeBool) MarshalJSON() ([]byte, error) {
35 32
 	return []byte(t.String()), nil
36 33
 }
37 34
 

+ 23
- 29
internal/config/type_bool_test.go Просмотреть файл

@@ -20,53 +20,41 @@ type TypeBoolTestSuite struct {
20 20
 }
21 21
 
22 22
 func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
23
-	testData := []string{
23
+	testData := []interface{}{
24 24
 		"",
25 25
 		"np",
26 26
 		"нет",
27
+		int(10),
28
+		[]int{},
27 29
 	}
28 30
 
29 31
 	for _, v := range testData {
30
-		data, err := json.Marshal(map[string]string{
32
+		data, err := json.Marshal(map[string]interface{}{
31 33
 			"value": v,
32 34
 		})
33 35
 		suite.NoError(err)
34 36
 
35
-		suite.T().Run(v, func(t *testing.T) {
37
+		suite.T().Run(fmt.Sprintf("%v", v), func(t *testing.T) {
36 38
 			assert.Error(t, json.Unmarshal(data, &typeBoolTestStruct{}))
37 39
 		})
38 40
 	}
39 41
 }
40 42
 
41 43
 func (suite *TypeBoolTestSuite) TestUnmarshalOk() {
42
-	testData := map[string]bool{
43
-		"0":        false,
44
-		"N":        false,
45
-		"nO":       false,
46
-		"no":       false,
47
-		"dISAbLEd": false,
48
-		"False":    false,
49
-		"false":    false,
50
-
51
-		"1":       true,
52
-		"y":       true,
53
-		"Yes":     true,
54
-		"yes":     true,
55
-		"enABLED": true,
56
-		"True":    true,
57
-		"TRUE":    true,
58
-		"true":    true,
44
+	testData := []bool{
45
+		true,
46
+		false,
59 47
 	}
60 48
 
61
-	for k, v := range testData {
49
+	for _, v := range testData {
62 50
 		value := v
63 51
 
64
-		data, err := json.Marshal(map[string]string{
65
-			"value": k,
52
+		data, err := json.Marshal(map[string]bool{
53
+			"value": v,
66 54
 		})
67 55
 		suite.NoError(err)
68 56
 
69
-		suite.T().Run(k, func(t *testing.T) {
57
+		suite.T().Run(strconv.FormatBool(v), func(t *testing.T) {
70 58
 			testStruct := &typeBoolTestStruct{}
71 59
 			assert.NoError(t, json.Unmarshal(data, testStruct))
72 60
 
@@ -81,18 +69,24 @@ func (suite *TypeBoolTestSuite) TestUnmarshalOk() {
81 69
 
82 70
 func (suite *TypeBoolTestSuite) TestMarshalOk() {
83 71
 	for _, v := range []bool{true, false} {
84
-		name := strconv.FormatBool(v)
72
+		value := v
85 73
 
86
-		suite.T().Run(name, func(t *testing.T) {
74
+		suite.T().Run(strconv.FormatBool(v), func(t *testing.T) {
87 75
 			testStruct := typeBoolTestStruct{
88 76
 				Value: config.TypeBool{
89
-					Value: v,
77
+					Value: value,
90 78
 				},
91 79
 			}
92 80
 
93
-			data, err := json.Marshal(testStruct)
81
+			encodedJSON, err := json.Marshal(testStruct)
94 82
 			assert.NoError(t, err)
95
-			assert.JSONEq(t, fmt.Sprintf(`{"value": "%s"}`, name), string(data))
83
+
84
+			expectedJSON, err := json.Marshal(map[string]bool{
85
+				"value": value,
86
+			})
87
+			assert.NoError(t, err)
88
+
89
+			assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
96 90
 		})
97 91
 	}
98 92
 }

+ 2
- 2
internal/config/type_error_rate.go Просмотреть файл

@@ -34,11 +34,11 @@ func (t TypeErrorRate) Get(defaultValue float64) float64 {
34 34
 	return t.Value
35 35
 }
36 36
 
37
-func (t *TypeErrorRate) UnmarshalText(data []byte) error {
37
+func (t *TypeErrorRate) UnmarshalJSON(data []byte) error {
38 38
 	return t.Set(string(data))
39 39
 }
40 40
 
41
-func (t TypeErrorRate) MarshalText() ([]byte, error) {
41
+func (t TypeErrorRate) MarshalJSON() ([]byte, error) {
42 42
 	return []byte(t.String()), nil
43 43
 }
44 44
 

+ 8
- 21
internal/config/type_error_rate_test.go Просмотреть файл

@@ -45,27 +45,14 @@ func (suite *TypeErrorRateTestSuite) TestUnmarshalFail() {
45 45
 }
46 46
 
47 47
 func (suite *TypeErrorRateTestSuite) TestUnmarshalOk() {
48
-	testData := map[string]float64{
49
-		"1":   1.0,
50
-		"1.0": 1.0,
51
-		"0.5": 0.5,
52
-		".5":  0.5,
53
-	}
54
-
55
-	for k, v := range testData {
56
-		value := v
57
-
58
-		data, err := json.Marshal(map[string]string{
59
-			"value": k,
60
-		})
61
-		suite.NoError(err)
48
+	data, err := json.Marshal(map[string]float64{
49
+		"value": 1.0,
50
+	})
51
+	suite.NoError(err)
62 52
 
63
-		suite.T().Run(k, func(t *testing.T) {
64
-			testStruct := &typeErrorRateTestStruct{}
65
-			assert.NoError(t, json.Unmarshal(data, testStruct))
66
-			assert.InEpsilon(t, value, testStruct.Value.Value, 1e-10)
67
-		})
68
-	}
53
+	testStruct := &typeErrorRateTestStruct{}
54
+	suite.NoError(json.Unmarshal(data, testStruct))
55
+	suite.InEpsilon(1.0, testStruct.Value.Value, 1e-10)
69 56
 }
70 57
 
71 58
 func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
@@ -77,7 +64,7 @@ func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
77 64
 
78 65
 	encodedJson, err := json.Marshal(testStruct)
79 66
 	suite.NoError(err)
80
-	suite.JSONEq(`{"value": "1.01"}`, string(encodedJson))
67
+	suite.JSONEq(`{"value": 1.01}`, string(encodedJson))
81 68
 }
82 69
 
83 70
 func (suite *TypeErrorRateTestSuite) TestGet() {

+ 4
- 0
internal/config/type_hostport.go Просмотреть файл

@@ -8,6 +8,8 @@ import (
8 8
 
9 9
 type TypeHostPort struct {
10 10
 	Value string
11
+	Host  string
12
+	Port  uint
11 13
 }
12 14
 
13 15
 func (t *TypeHostPort) Set(value string) error {
@@ -34,6 +36,8 @@ func (t *TypeHostPort) Set(value string) error {
34 36
 	}
35 37
 
36 38
 	t.Value = net.JoinHostPort(host, port)
39
+	t.Port = uint(portValue)
40
+	t.Host = host
37 41
 
38 42
 	return nil
39 43
 }

+ 19
- 0
internal/utils/make_qr_code_url.go Просмотреть файл

@@ -0,0 +1,19 @@
1
+package utils
2
+
3
+import "net/url"
4
+
5
+func MakeQRCodeURL(data string) string {
6
+	values := url.Values{}
7
+	values.Set("qzone", "4")
8
+	values.Set("format", "svg")
9
+	values.Set("data", data)
10
+
11
+	rv := url.URL{
12
+		Scheme:   "https",
13
+		Host:     "api.qrserver.com",
14
+		Path:     "v1/create-qr-code",
15
+		RawQuery: values.Encode(),
16
+	}
17
+
18
+	return rv.String()
19
+}

+ 26
- 0
internal/utils/read_config.go Просмотреть файл

@@ -0,0 +1,26 @@
1
+package utils
2
+
3
+import (
4
+	"fmt"
5
+	"os"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config"
8
+)
9
+
10
+func ReadConfig(path string) (*config.Config, error) {
11
+	content, err := os.ReadFile(path)
12
+	if err != nil {
13
+		return nil, fmt.Errorf("cannot read config file: %w", err)
14
+	}
15
+
16
+	conf, err := config.Parse(content)
17
+	if err != nil {
18
+		return nil, fmt.Errorf("cannot parse config: %w", err)
19
+	}
20
+
21
+	if err := conf.Validate(); err != nil {
22
+		return nil, fmt.Errorf("invalid config: %w", err)
23
+	}
24
+
25
+	return conf, nil
26
+}

+ 1
- 1
mtglib/proxy_opts.go Просмотреть файл

@@ -62,7 +62,7 @@ type ProxyOpts struct {
62 62
 	// specifies a hostname only.
63 63
 	//
64 64
 	// This is an optional setting.
65
-	DomainFrontingPort uint
65
+	DomainFrontingPort uint16
66 66
 
67 67
 	// IdleTimeout is a timeout for relay when we have to break a
68 68
 	// stream.

Загрузка…
Отмена
Сохранить