mirror of https://github.com/0xERR0R/blocky.git
124 lines
3.0 KiB
Go
124 lines
3.0 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/0xERR0R/blocky/util"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
const (
|
|
HTTPProtocol = "http"
|
|
HTTPSProtocol = "https"
|
|
)
|
|
|
|
// HTTPService is a Service using a HTTP router.
|
|
type HTTPService interface {
|
|
Service
|
|
Merger
|
|
|
|
// Router returns the service's router.
|
|
Router() chi.Router
|
|
}
|
|
|
|
// HTTPInfo can be embedded in structs to help implement HTTPService.
|
|
type HTTPInfo struct {
|
|
Info
|
|
|
|
mux *chi.Mux
|
|
}
|
|
|
|
func NewHTTPInfo(name string, endpoints []Endpoint) HTTPInfo {
|
|
return HTTPInfo{
|
|
Info: NewInfo(name, endpoints),
|
|
|
|
mux: chi.NewMux(),
|
|
}
|
|
}
|
|
|
|
func (i *HTTPInfo) Router() chi.Router { return i.mux }
|
|
|
|
var _ HTTPService = (*SimpleHTTP)(nil)
|
|
|
|
// SimpleHTTP implements HTTPService usinig the default HTTP merger.
|
|
type SimpleHTTP struct{ HTTPInfo }
|
|
|
|
func NewSimpleHTTP(name string, endpoints []Endpoint) SimpleHTTP {
|
|
return SimpleHTTP{HTTPInfo: NewHTTPInfo(name, endpoints)}
|
|
}
|
|
|
|
func (s *SimpleHTTP) Merge(other Service) (Merger, error) {
|
|
return MergeHTTP(s, other)
|
|
}
|
|
|
|
// MergeHTTP merges two compatible HTTPServices.
|
|
//
|
|
// The second parameter is of type `Service` to make it easy to call
|
|
// from a `Merger.Merge` implementation.
|
|
func MergeHTTP(a HTTPService, b Service) (Merger, error) {
|
|
return newHTTPMerger(a).Merge(b)
|
|
}
|
|
|
|
var _ HTTPService = (*httpMerger)(nil)
|
|
|
|
// httpMerger can merge HTTPServices by combining their routes.
|
|
type httpMerger struct {
|
|
inner []HTTPService
|
|
router chi.Router
|
|
endpoints endpointSet
|
|
}
|
|
|
|
func newHTTPMerger(first HTTPService) *httpMerger {
|
|
return &httpMerger{
|
|
inner: []HTTPService{first},
|
|
router: first.Router(),
|
|
}
|
|
}
|
|
|
|
func (m *httpMerger) String() string { return svcString(m) }
|
|
|
|
func (m *httpMerger) ServiceName() string {
|
|
names := util.ForEach(m.inner, func(svc HTTPService) string {
|
|
return svc.ServiceName()
|
|
})
|
|
|
|
return strings.Join(names, " & ")
|
|
}
|
|
|
|
func (m *httpMerger) ExposeOn() []Endpoint { return m.endpoints.ToSlice() }
|
|
func (m *httpMerger) Router() chi.Router { return m.router }
|
|
|
|
func (m *httpMerger) Merge(other Service) (Merger, error) {
|
|
httpSvc, ok := other.(HTTPService)
|
|
if !ok {
|
|
return nil, errors.New("not an HTTPService")
|
|
}
|
|
|
|
type middleware = func(http.Handler) http.Handler
|
|
|
|
// Can't do `.Mount("/", ...)` otherwise we can only merge at most once since / will already be defined
|
|
_ = chi.Walk(httpSvc.Router(), func(method, route string, handler http.Handler, middlewares ...middleware) error {
|
|
m.router.With(middlewares...).Method(method, route, handler)
|
|
|
|
// Expose /example/ as /example too
|
|
// Workaround for chi.Walk missing the second form https://github.com/go-chi/chi/issues/830
|
|
// This means we expose the route without the slash even if it wasn't oringinally registered as such!
|
|
// The main point of this is for DoH (/dns-query).
|
|
if strings.HasSuffix(route, "/") {
|
|
route := strings.TrimSuffix(route, "/")
|
|
m.router.With(middlewares...).Method(method, route, handler)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
m.inner = append(m.inner, httpSvc)
|
|
|
|
// Don't expose any service more than it expects
|
|
m.endpoints.IntersectSlice(other.ExposeOn())
|
|
|
|
return m, nil
|
|
}
|