cloud-game/pkg/network/httpx/server.go
2023-11-26 22:39:46 +03:00

193 lines
4.5 KiB
Go

package httpx
import (
"errors"
"fmt"
"net/http"
"net/url"
"time"
"github.com/giongto35/cloud-game/v3/pkg/logger"
"golang.org/x/crypto/acme/autocert"
)
type Server struct {
http.Server
autoCert *autocert.Manager
opts Options
listener *Listener
redirect *Server
log *logger.Logger
}
type (
Mux struct {
*http.ServeMux
prefix string
}
Handler = http.Handler
HandlerFunc = http.HandlerFunc
ResponseWriter = http.ResponseWriter
Request = http.Request
)
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux(prefix string) *Mux {
return &Mux{ServeMux: http.NewServeMux(), prefix: prefix}
}
func (m *Mux) Prefix(v string) { m.prefix = v }
func (m *Mux) HandleW(pattern string, h func(http.ResponseWriter)) *Mux {
m.ServeMux.HandleFunc(m.prefix+pattern, func(w http.ResponseWriter, _ *http.Request) { h(w) })
return m
}
func (m *Mux) Handle(pattern string, handler Handler) *Mux {
m.ServeMux.Handle(m.prefix+pattern, handler)
return m
}
func (m *Mux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) *Mux {
m.ServeMux.HandleFunc(m.prefix+pattern, handler)
return m
}
func (m *Mux) ServeHTTP(w ResponseWriter, r *Request) { m.ServeMux.ServeHTTP(w, r) }
func NewServer(address string, handler func(*Server) Handler, options ...Option) (*Server, error) {
opts := &Options{
Https: false,
HttpsRedirect: true,
IdleTimeout: 120 * time.Second,
ReadTimeout: 500 * time.Second,
WriteTimeout: 500 * time.Second,
}
opts.override(options...)
if opts.Logger == nil {
opts.Logger = logger.Default()
}
server := &Server{
Server: http.Server{
Addr: address,
IdleTimeout: opts.IdleTimeout,
ReadTimeout: opts.ReadTimeout,
WriteTimeout: opts.WriteTimeout,
},
opts: *opts,
log: opts.Logger,
}
// (╯°□°)╯︵ ┻━┻
server.Handler = handler(server)
if opts.Https && opts.IsAutoHttpsCert() {
server.autoCert = NewTLSConfig(withZonePrefix(opts.HttpsDomain, opts.Zone)).CertManager
server.TLSConfig = server.autoCert.TLSConfig()
}
addr := server.Addr
if server.Addr == "" {
addr = ":http"
if opts.Https {
addr = ":https"
}
opts.Logger.Warn().Msgf("Empty server address has been changed to %v", addr)
}
listener, err := NewListener(addr, server.opts.PortRoll)
if err != nil {
return nil, err
}
server.listener = listener
addr = buildAddress(server.Addr, opts.Zone, *listener)
opts.Logger.Info().Msgf("httpx %v (%v)", addr, server.Addr)
server.Addr = addr
return server, nil
}
func (s *Server) MuxX(prefix string) *Mux { return NewServeMux(prefix) }
func (s *Server) Mux() *Mux { return s.MuxX("") }
func (s *Server) Run() { go s.run() }
func (s *Server) run() {
protocol := s.GetProtocol()
s.log.Debug().Msgf("Starting %s server on %s", protocol, s.Addr)
if s.opts.Https && s.opts.HttpsRedirect {
if rdr, err := s.redirection(); err == nil {
s.redirect = rdr
go s.redirect.Run()
} else {
s.log.Error().Err(err).Msg("couldn't init redirection server")
}
}
var err error
if s.opts.Https {
err = s.ServeTLS(*s.listener, s.opts.HttpsCert, s.opts.HttpsKey)
} else {
err = s.Serve(*s.listener)
}
if errors.Is(err, http.ErrServerClosed) {
s.log.Debug().Msgf("%s server was closed", protocol)
return
}
s.log.Error().Err(err)
}
func (s *Server) Stop() error {
if s.redirect != nil {
_ = s.redirect.Stop()
}
return s.Server.Close()
}
func (s *Server) GetHost() string { return extractHost(s.Addr) }
func (s *Server) GetProtocol() string {
protocol := "http"
if s.opts.Https {
protocol = "https"
}
return protocol
}
func (s *Server) redirection() (*Server, error) {
address := s.Addr
if s.opts.HttpsDomain != "" {
address = s.opts.HttpsDomain
}
addr := buildAddress(address, s.opts.Zone, *s.listener)
s.log.Info().Str("addr", addr).Msg("Start HTTPS redirect server")
srv, err := NewServer(s.opts.HttpsRedirectAddress, func(serv *Server) Handler {
h := NewServeMux("")
h.Handle("/", HandlerFunc(func(w ResponseWriter, r *Request) {
httpsURL := url.URL{Scheme: "https", Host: addr, Path: r.URL.Path, RawQuery: r.URL.RawQuery}
rdr := httpsURL.String()
if s.log.GetLevel() < logger.InfoLevel {
s.log.Debug().
Str("from", fmt.Sprintf("http://%s%s", r.Host, r.URL.String())).
Str("to", rdr).
Msg("Redirect")
}
http.Redirect(w, r, rdr, http.StatusFound)
}))
if serv.autoCert != nil {
return serv.autoCert.HTTPHandler(h)
}
return h
},
WithLogger(s.log),
)
return srv, err
}
func FileServer(dir string) http.Handler { return http.FileServer(http.Dir(dir)) }