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)) }