mirror of
https://github.com/labstack/echo.git
synced 2024-12-22 20:06:21 +02:00
214 lines
6.2 KiB
Go
214 lines
6.2 KiB
Go
|
package echo
|
||
|
|
||
|
import (
|
||
|
stdContext "context"
|
||
|
"crypto/tls"
|
||
|
"fmt"
|
||
|
"io/fs"
|
||
|
"log"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
banner = "Echo (v%s). High performance, minimalist Go web framework https://echo.labstack.com"
|
||
|
)
|
||
|
|
||
|
// StartConfig is for creating configured http.Server instance to start serve http(s) requests with given Echo instance
|
||
|
type StartConfig struct {
|
||
|
// Address for the server to listen on (if not using custom listener)
|
||
|
Address string
|
||
|
|
||
|
// ListenerNetwork allows setting listener network (see net.Listen for allowed values)
|
||
|
// Optional: defaults to "tcp"
|
||
|
ListenerNetwork string
|
||
|
|
||
|
// CertFilesystem is file system used to load certificates and keys (if certs/keys are given as paths)
|
||
|
CertFilesystem fs.FS
|
||
|
|
||
|
// DisableHTTP2 disables supports for HTTP2 in TLS server
|
||
|
DisableHTTP2 bool
|
||
|
|
||
|
// HideBanner does not log Echo banner on server startup
|
||
|
HideBanner bool
|
||
|
|
||
|
// HidePort does not log port on server startup
|
||
|
HidePort bool
|
||
|
|
||
|
// GracefulContext is context that completion signals graceful shutdown start
|
||
|
GracefulContext stdContext.Context
|
||
|
|
||
|
// GracefulTimeout is period which server allows listeners to finish serving ongoing requests. If this time is exceeded process is exited
|
||
|
// Defaults to 10 seconds
|
||
|
GracefulTimeout time.Duration
|
||
|
|
||
|
// OnShutdownError allows customization of what happens when (graceful) server Shutdown method returns an error.
|
||
|
// Defaults to calling e.logger.Error(err)
|
||
|
OnShutdownError func(err error)
|
||
|
|
||
|
// TLSConfigFunc allows modifying TLS configuration before listener is created with it.
|
||
|
TLSConfigFunc func(tlsConfig *tls.Config)
|
||
|
|
||
|
// ListenerAddrFunc allows getting listener address before server starts serving requests on listener. Useful when
|
||
|
// address is set as random (`:0`) port.
|
||
|
ListenerAddrFunc func(addr net.Addr)
|
||
|
|
||
|
// BeforeServeFunc allows customizing/accessing server before server starts serving requests on listener.
|
||
|
BeforeServeFunc func(s *http.Server) error
|
||
|
}
|
||
|
|
||
|
// Start starts a HTTP server.
|
||
|
func (sc StartConfig) Start(e *Echo) error {
|
||
|
logger := e.Logger
|
||
|
server := http.Server{
|
||
|
Handler: e,
|
||
|
// NB: all http.Server errors will be logged through Logger.Write calls. We could create writer that wraps
|
||
|
// logger and calls Logger.Error internally when http.Server logs error - atm we will use this naive way.
|
||
|
ErrorLog: log.New(logger, "", 0),
|
||
|
}
|
||
|
|
||
|
var tlsConfig *tls.Config = nil
|
||
|
if sc.TLSConfigFunc != nil {
|
||
|
tlsConfig = &tls.Config{}
|
||
|
configureTLS(&sc, tlsConfig)
|
||
|
sc.TLSConfigFunc(tlsConfig)
|
||
|
}
|
||
|
|
||
|
listener, err := createListener(&sc, tlsConfig)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return serve(&sc, &server, listener, logger)
|
||
|
}
|
||
|
|
||
|
// StartTLS starts a HTTPS server.
|
||
|
// If `certFile` or `keyFile` is `string` the values are treated as file paths.
|
||
|
// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
|
||
|
func (sc StartConfig) StartTLS(e *Echo, certFile, keyFile interface{}) error {
|
||
|
logger := e.Logger
|
||
|
s := http.Server{
|
||
|
Handler: e,
|
||
|
// NB: all http.Server errors will be logged through Logger.Write calls. We could create writer that wraps
|
||
|
// logger and calls Logger.Error internally when http.Server logs error - atm we will use this naive way.
|
||
|
ErrorLog: log.New(logger, "", 0),
|
||
|
}
|
||
|
|
||
|
certFs := sc.CertFilesystem
|
||
|
if certFs == nil {
|
||
|
certFs = os.DirFS(".")
|
||
|
}
|
||
|
cert, err := filepathOrContent(certFile, certFs)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
key, err := filepathOrContent(keyFile, certFs)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
cer, err := tls.X509KeyPair(cert, key)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}}
|
||
|
configureTLS(&sc, tlsConfig)
|
||
|
if sc.TLSConfigFunc != nil {
|
||
|
sc.TLSConfigFunc(tlsConfig)
|
||
|
}
|
||
|
|
||
|
listener, err := createListener(&sc, tlsConfig)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return serve(&sc, &s, listener, logger)
|
||
|
}
|
||
|
|
||
|
func serve(sc *StartConfig, server *http.Server, listener net.Listener, logger Logger) error {
|
||
|
if sc.BeforeServeFunc != nil {
|
||
|
if err := sc.BeforeServeFunc(server); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
startupGreetings(sc, logger, listener)
|
||
|
|
||
|
if sc.GracefulContext != nil {
|
||
|
ctx, cancel := stdContext.WithCancel(sc.GracefulContext)
|
||
|
defer cancel() // make sure this graceful coroutine will end when serve returns by some other means
|
||
|
go gracefulShutdown(ctx, sc, server, logger)
|
||
|
}
|
||
|
return server.Serve(listener)
|
||
|
}
|
||
|
|
||
|
func configureTLS(sc *StartConfig, tlsConfig *tls.Config) {
|
||
|
if !sc.DisableHTTP2 {
|
||
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func createListener(sc *StartConfig, tlsConfig *tls.Config) (net.Listener, error) {
|
||
|
listenerNetwork := sc.ListenerNetwork
|
||
|
if listenerNetwork == "" {
|
||
|
listenerNetwork = "tcp"
|
||
|
}
|
||
|
|
||
|
var listener net.Listener
|
||
|
var err error
|
||
|
if tlsConfig != nil {
|
||
|
listener, err = tls.Listen(listenerNetwork, sc.Address, tlsConfig)
|
||
|
} else {
|
||
|
listener, err = net.Listen(listenerNetwork, sc.Address)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if sc.ListenerAddrFunc != nil {
|
||
|
sc.ListenerAddrFunc(listener.Addr())
|
||
|
}
|
||
|
return listener, nil
|
||
|
}
|
||
|
|
||
|
func startupGreetings(sc *StartConfig, logger Logger, listener net.Listener) {
|
||
|
if !sc.HideBanner {
|
||
|
bannerText := fmt.Sprintf(banner, Version)
|
||
|
logger.Write([]byte(bannerText))
|
||
|
}
|
||
|
|
||
|
if !sc.HidePort {
|
||
|
logger.Write([]byte(fmt.Sprintf("http(s) server started on %s", listener.Addr())))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func filepathOrContent(fileOrContent interface{}, certFilesystem fs.FS) (content []byte, err error) {
|
||
|
switch v := fileOrContent.(type) {
|
||
|
case string:
|
||
|
return fs.ReadFile(certFilesystem, v)
|
||
|
case []byte:
|
||
|
return v, nil
|
||
|
default:
|
||
|
return nil, ErrInvalidCertOrKeyType
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func gracefulShutdown(gracefulCtx stdContext.Context, sc *StartConfig, server *http.Server, logger Logger) {
|
||
|
<-gracefulCtx.Done() // wait until shutdown context is closed.
|
||
|
// note: is server if closed by other means this method is still run but is good as no-op
|
||
|
|
||
|
timeout := sc.GracefulTimeout
|
||
|
if timeout == 0 {
|
||
|
timeout = 10 * time.Second
|
||
|
}
|
||
|
shutdownCtx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
|
||
|
defer cancel()
|
||
|
|
||
|
if err := server.Shutdown(shutdownCtx); err != nil {
|
||
|
// we end up here when listeners are not shut down within given timeout
|
||
|
if sc.OnShutdownError != nil {
|
||
|
sc.OnShutdownError(err)
|
||
|
return
|
||
|
}
|
||
|
logger.Error(fmt.Errorf("failed to shut down server within given timeout: %w", err))
|
||
|
}
|
||
|
}
|