Skip to content
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

refactor: add service package and start splitting HTTP handling #1427

2 changes: 1 addition & 1 deletion api/api_interface_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type CacheControl interface {
FlushCaches(ctx context.Context)
}

func RegisterOpenAPIEndpoints(router chi.Router, impl StrictServerInterface) {
func registerOpenAPIEndpoints(router chi.Router, impl StrictServerInterface) {
middleware := []StrictMiddlewareFunc{ctxWithHTTPRequestMiddleware}

HandlerFromMuxWithBaseURL(NewStrictHandler(impl, middleware), router, "/api")
Expand Down
2 changes: 1 addition & 1 deletion api/api_interface_impl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ var _ = Describe("API implementation tests", func() {
Describe("RegisterOpenAPIEndpoints", func() {
It("adds routes", func() {
rtr := chi.NewRouter()
RegisterOpenAPIEndpoints(rtr, sut)
registerOpenAPIEndpoints(rtr, sut)

Expect(rtr.Routes()).ShouldNot(BeEmpty())
})
Expand Down
27 changes: 27 additions & 0 deletions api/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package api

import (
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/service"
"github.com/0xERR0R/blocky/util"
)

// Service implements service.HTTPService.
type Service struct {
service.SimpleHTTP
}

func NewService(cfg config.APIService, server StrictServerInterface) *Service {
endpoints := util.ConcatSlices(
service.EndpointsFromAddrs(service.HTTPProtocol, cfg.Addrs.HTTP),
service.EndpointsFromAddrs(service.HTTPSProtocol, cfg.Addrs.HTTPS),
)

s := &Service{
SimpleHTTP: service.NewSimpleHTTP("API", endpoints),
}

registerOpenAPIEndpoints(s.Router(), server)

return s
}
43 changes: 43 additions & 0 deletions api/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package api

import (
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/service"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("API Service", func() {
var (
cfg config.APIService
sut *Service
err error
)

BeforeEach(func() {
cfg, err = config.WithDefaults[config.APIService]()
Expect(err).Should(Succeed())

cfg.Addrs.HTTP = config.ListenConfig{":80"}
cfg.Addrs.HTTPS = config.ListenConfig{":443"}
})

JustBeforeEach(func() {
sut = NewService(cfg, nil)
})

Describe("NewService", func() {
When("enabled", func() {
It("uses the configured addresses", func() {
Expect(sut.ExposeOn()).Should(ContainElements(
Equal(service.Endpoint{Protocol: "http", AddrConf: ":80"}),
Equal(service.Endpoint{Protocol: "https", AddrConf: ":443"}),
))
})

It("sets up routes", func() {
Expect(sut.Router().Routes()).ShouldNot(BeEmpty())
})
})
})
})
5 changes: 5 additions & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/evt"
"github.com/0xERR0R/blocky/log"
"github.com/0xERR0R/blocky/metrics"
"github.com/0xERR0R/blocky/server"
"github.com/0xERR0R/blocky/util"

Expand Down Expand Up @@ -49,6 +50,10 @@
ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()

if cfg.Prometheus.Enable {
metrics.StartCollection()

Check warning on line 54 in cmd/serve.go

View check run for this annotation

Codecov / codecov/patch

cmd/serve.go#L54

Added line #L54 was not covered by tests
}

srv, err := server.NewServer(ctx, cfg)
if err != nil {
return fmt.Errorf("can't start server: %w", err)
Expand Down
31 changes: 31 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
Redis Redis `yaml:"redis"`
Log log.Config `yaml:"log"`
Ports Ports `yaml:"ports"`
Services Services `yaml:"-"` // not user exposed yet
MinTLSServeVer TLSVersion `yaml:"minTlsServeVersion" default:"1.2"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
Expand Down Expand Up @@ -263,6 +264,19 @@
} `yaml:",inline"`
}

// Services holds network service related configuration.
//
// The actual config layout is not decided yet.
// See https://github.com/0xERR0R/blocky/issues/1206
//
// The `yaml` struct tags are just for manual testing,
// and require replacing `yaml:"-"` in Config to work.
type Services struct {
API APIService `yaml:"control-api"`
DoH DoHService `yaml:"dns-over-https"`
Metrics MetricsService `yaml:"metrics"`
}

type Ports struct {
DNS ListenConfig `yaml:"dns" default:"53"`
HTTP ListenConfig `yaml:"http"`
Expand Down Expand Up @@ -601,6 +615,23 @@
cfg.Upstreams.validate(logger)
}

// CopyPortsToServices sets Services values to match Ports.
//
// This should be replaced with a migration once everything from Ports is supported in Services.
// Done this way for now to avoid creating temporary generic services and updating all Ports related code at once.
func (cfg *Config) CopyPortsToServices() {
httpAddrs := AllHTTPAddrs{
HTTPAddrs: HTTPAddrs{HTTP: cfg.Ports.HTTP},
HTTPSAddrs: HTTPSAddrs{HTTPS: cfg.Ports.HTTPS},

Check warning on line 625 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L622-L625

Added lines #L622 - L625 were not covered by tests
}

cfg.Services = Services{
API: APIService{Addrs: httpAddrs},
DoH: DoHService{Addrs: httpAddrs},
Metrics: MetricsService{Addrs: httpAddrs},

Check warning on line 631 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L628-L631

Added lines #L628 - L631 were not covered by tests
}
}

// ConvertPort converts string representation into a valid port (0 - 65535)
func ConvertPort(in string) (uint16, error) {
const (
Expand Down
25 changes: 25 additions & 0 deletions config/http_services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package config

type (
APIService HTTPService
DoHService HTTPService
MetricsService HTTPService
)

// HTTPService can be used by any service that uses HTTP(S).
type HTTPService struct {
Addrs AllHTTPAddrs `yaml:"addrs"`
}

type AllHTTPAddrs struct {
HTTPAddrs `yaml:",inline"`
HTTPSAddrs `yaml:",inline"`
}

type HTTPAddrs struct {
HTTP ListenConfig `yaml:"http"`
}

type HTTPSAddrs struct {
HTTPS ListenConfig `yaml:"https"`
}
60 changes: 60 additions & 0 deletions helpertest/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package helpertest

import (
"crypto/tls"
"crypto/x509"
"sync"

"github.com/0xERR0R/blocky/util"
. "github.com/onsi/gomega"
)

const tlsTestServerName = "test.blocky.invalid"

type tlsData struct {
ServerCfg *tls.Config
ClientCfg *tls.Config
}

// Lazy init
//
//nolint:gochecknoglobals
var (
initTLSData sync.Once
tlsDataStorage tlsData
)

func getTLSData() tlsData {
initTLSData.Do(func() {
cert, err := util.TLSGenerateSelfSignedCert([]string{tlsTestServerName})
Expect(err).Should(Succeed())

tlsDataStorage.ServerCfg = &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}

certPool := x509.NewCertPool()
certPool.AddCert(cert.Leaf)

tlsDataStorage.ClientCfg = &tls.Config{
RootCAs: certPool,
ServerName: tlsTestServerName,
MinVersion: tls.VersionTLS13,
}
})

return tlsDataStorage
}

// TLSTestServerConfig returns a TLS Config for use by test servers.
func TLSTestServerConfig() *tls.Config {
return getTLSData().ServerCfg.Clone()
}

// TLSTestServerConfig returns a TLS Config for use by test clients.
//
// This is required to connect to a test TLS server, otherwise TLS verification fails.
func TLSTestClientConfig() *tls.Config {
return getTLSData().ClientCfg.Clone()
}
17 changes: 5 additions & 12 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package metrics

import (
"github.com/0xERR0R/blocky/config"

"github.com/go-chi/chi/v5"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

//nolint:gochecknoglobals
Expand All @@ -17,12 +13,9 @@
_ = reg.Register(c)
}

// Start starts prometheus endpoint
func Start(router *chi.Mux, cfg config.Metrics) {
if cfg.Enable {
_ = reg.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
_ = reg.Register(collectors.NewGoCollector())
router.Handle(cfg.Path, promhttp.InstrumentMetricHandler(reg,
promhttp.HandlerFor(reg, promhttp.HandlerOpts{})))
}
func StartCollection() {
_ = reg.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
_ = reg.Register(collectors.NewGoCollector())

Check warning on line 18 in metrics/metrics.go

View check run for this annotation

Codecov / codecov/patch

metrics/metrics.go#L16-L18

Added lines #L16 - L18 were not covered by tests

registerEventListeners()

Check warning on line 20 in metrics/metrics.go

View check run for this annotation

Codecov / codecov/patch

metrics/metrics.go#L20

Added line #L20 was not covered by tests
}
4 changes: 2 additions & 2 deletions metrics/metrics_event_publisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"github.com/prometheus/client_golang/prometheus"
)

// RegisterEventListeners registers all metric handlers by the event bus
func RegisterEventListeners() {
// registerEventListeners registers all metric handlers on the event bus
func registerEventListeners() {

Check warning on line 15 in metrics/metrics_event_publisher.go

View check run for this annotation

Codecov / codecov/patch

metrics/metrics_event_publisher.go#L15

Added line #L15 was not covered by tests
registerBlockingEventListeners()
registerCachingEventListeners()
registerApplicationEventListeners()
Expand Down
36 changes: 36 additions & 0 deletions metrics/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package metrics

import (
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/service"
"github.com/0xERR0R/blocky/util"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Service implements service.HTTPService.
type Service struct {
service.SimpleHTTP
}

func NewService(cfg config.MetricsService, metricsCfg config.Metrics) *Service {
endpoints := util.ConcatSlices(
service.EndpointsFromAddrs(service.HTTPProtocol, cfg.Addrs.HTTP),
service.EndpointsFromAddrs(service.HTTPSProtocol, cfg.Addrs.HTTPS),
)

if !metricsCfg.Enable || len(endpoints) == 0 {
// Avoid setting up collectors and listeners
return new(Service)
}

s := &Service{
SimpleHTTP: service.NewSimpleHTTP("Metrics", endpoints),
}

s.Router().Handle(
metricsCfg.Path,
promhttp.InstrumentMetricHandler(reg, promhttp.HandlerFor(reg, promhttp.HandlerOpts{})),
)

return s
}
Loading