storj/pkg/httpserver/server.go
Andrew Harding 416fa80e85
Link Sharing Service (#2431)
Link sharing service. See `docs/design/link-sharing-service.md` for the design and `cmd/linksharing/README.md` for operational instructions.
2019-07-18 06:26:09 -06:00

133 lines
3.3 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package httpserver
import (
"context"
"crypto/tls"
"net"
"net/http"
"time"
"github.com/zeebo/errs"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"storj.io/storj/internal/errs2"
)
const (
// DefaultShutdownTimeout is the default ShutdownTimeout (see Config)
DefaultShutdownTimeout = time.Second * 10
)
// Config holds the HTTP server configuration
type Config struct {
// Name is the name of the server. It is only used for logging. It can
// be empty.
Name string
// Address is the address to bind the server to. It must be set.
Address string
// Handler is the HTTP handler to be served. It must be set.
Handler http.Handler
// TLSConfig is the TLS configuration for the server. It is optional.
TLSConfig *tls.Config
// ShutdownTimeout controls how long to wait for requests to finish before
// returning from Run() after the context is canceled. It defaults to
// 10 seconds if unset. If set to a negative value, the server will be
// closed immediately.
ShutdownTimeout time.Duration
}
// Server is the HTTP server
type Server struct {
log *zap.Logger
name string
listener net.Listener
server *http.Server
shutdownTimeout time.Duration
}
// New creates a new URL Service Server.
func New(log *zap.Logger, config Config) (*Server, error) {
switch {
case config.Address == "":
return nil, errs.New("server address is required")
case config.Handler == nil:
return nil, errs.New("server handler is required")
}
listener, err := net.Listen("tcp", config.Address)
if err != nil {
return nil, errs.New("unable to listen on %s: %v", config.Address, err)
}
server := &http.Server{
Handler: config.Handler,
TLSConfig: config.TLSConfig,
ErrorLog: zap.NewStdLog(log),
}
if config.ShutdownTimeout == 0 {
config.ShutdownTimeout = DefaultShutdownTimeout
}
if config.Name != "" {
log = log.With(zap.String("server", config.Name))
}
return &Server{
log: log,
name: config.Name,
listener: listener,
server: server,
shutdownTimeout: config.ShutdownTimeout,
}, nil
}
// Run runs the server until it's either closed or it errors.
func (server *Server) Run(ctx context.Context) (err error) {
ctx, cancel := context.WithCancel(ctx)
var group errgroup.Group
group.Go(func() error {
<-ctx.Done()
server.log.Info("Server shutting down")
return shutdownWithTimeout(server.server, server.shutdownTimeout)
})
group.Go(func() (err error) {
defer cancel()
server.log.With(zap.String("addr", server.Addr())).Sugar().Info("Server started")
if server.server.TLSConfig == nil {
err = server.server.Serve(server.listener)
} else {
err = server.server.ServeTLS(server.listener, "", "")
}
if err == http.ErrServerClosed {
return nil
}
server.log.With(zap.Error(err)).Error("Server closed unexpectedly")
return err
})
return group.Wait()
}
// Addr returns the public address.
func (server *Server) Addr() string {
return server.listener.Addr().String()
}
func shutdownWithTimeout(server *http.Server, timeout time.Duration) error {
if timeout < 0 {
return server.Close()
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return errs2.IgnoreCanceled(server.Shutdown(ctx))
}