Skip to content
Snippets Groups Projects
server.go 7.26 KiB
Newer Older
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

Androlo's avatar
Androlo committed
package server

import (
Androlo's avatar
Androlo committed
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/hyperledger/burrow/logging"
	"github.com/tommy351/gin-cors"
	"gopkg.in/tylerb/graceful.v1"
Androlo's avatar
Androlo committed
)

var (
	killTime = 100 * time.Millisecond
)

type HttpService interface {
	Process(*http.Request, http.ResponseWriter)
}

// A server serves a number of different http calls.
type Server interface {
	Start(*ServerConfig, *gin.Engine)
Androlo's avatar
Androlo committed
	Running() bool
	Shutdown(ctx context.Context) error
Androlo's avatar
Androlo committed
}

// The ServeProcess wraps all the Servers. Starting it will
// add all the server handlers to the router and start listening
// for incoming requests. There is also startup and shutdown events
// that can be listened to, on top of any events that the servers
// may have (the default websocket server has events for monitoring
// sessions. Startup event listeners should be added before calling
// 'Start()'. Stop event listeners can be added up to the point where
// the server is stopped and the event is fired.
type ServeProcess struct {
Androlo's avatar
Androlo committed
	servers          []Server
	stopChan         chan struct{}
	startListenChans []chan struct{}
	stopListenChans  []chan struct{}
	srv              *graceful.Server
Silas Davis's avatar
Silas Davis committed
	logger           *logging.Logger
Androlo's avatar
Androlo committed
}

// Initializes all the servers and starts listening for connections.
func (serveProcess *ServeProcess) Start() error {
	gin.SetMode(gin.ReleaseMode)
	router := gin.New()
Androlo's avatar
Androlo committed

Androlo's avatar
Androlo committed
	ch := NewCORSMiddleware(config.CORS)
	router.Use(gin.Recovery(), logHandler(serveProcess.logger), contentTypeMW, ch)
Androlo's avatar
Androlo committed

	address := config.Bind.Address
	port := config.Bind.Port
Androlo's avatar
Androlo committed

	if port == 0 {
		return fmt.Errorf("0 is not a valid port.")
	}

	listenAddress := address + ":" + fmt.Sprintf("%d", port)
	srv := &graceful.Server{
		Server: &http.Server{
			Handler: router,
		},
	}

	// Start the servers/handlers.
	for _, s := range serveProcess.servers {
Androlo's avatar
Androlo committed
		s.Start(config, router)
	}

	var lst net.Listener
	l, lErr := net.Listen("tcp", listenAddress)
	if lErr != nil {
		return lErr
	}

	// For secure connections.
Androlo's avatar
Androlo committed
	if config.TLS.TLS {
Androlo's avatar
Androlo committed
		addr := srv.Addr
		if addr == "" {
			addr = ":https"
		}

		tConfig := &tls.Config{}
		if tConfig.NextProtos == nil {
			tConfig.NextProtos = []string{"http/1.1"}
		}

		var tErr error
		tConfig.Certificates = make([]tls.Certificate, 1)
Androlo's avatar
Androlo committed
		tConfig.Certificates[0], tErr = tls.LoadX509KeyPair(config.TLS.CertPath, config.TLS.KeyPath)
Androlo's avatar
Androlo committed
		if tErr != nil {
			return tErr
		}

		lst = tls.NewListener(l, tConfig)
	} else {
		lst = l
	}
Silas Davis's avatar
Silas Davis committed
	serveProcess.logger.InfoMsg("Server started.",
		"address", serveProcess.config.Bind.Address,
		"port", serveProcess.config.Bind.Port)
	for _, c := range serveProcess.startListenChans {
Androlo's avatar
Androlo committed
		c <- struct{}{}
	}
Androlo's avatar
Androlo committed
	// Start the serve routine.
Androlo's avatar
Androlo committed
	go func() {
		serveProcess.srv.Serve(lst)
		for _, s := range serveProcess.servers {
			s.Shutdown(context.Background())
Androlo's avatar
Androlo committed
		}
	}()
Androlo's avatar
Androlo committed
	// Listen to the process stop event, it will call 'Stop'
Androlo's avatar
Androlo committed
	// on the graceful Server. This happens when someone
Androlo's avatar
Androlo committed
	// calls 'Stop' on the process.
Androlo's avatar
Androlo committed
	go func() {
Silas Davis's avatar
Silas Davis committed
		serveProcess.logger.InfoMsg("Close signal sent to server.")
Androlo's avatar
Androlo committed
	}()
Androlo's avatar
Androlo committed
	// Listen to the servers stop event. It is triggered when
	// the server has been fully shut down.
Androlo's avatar
Androlo committed
	go func() {
Silas Davis's avatar
Silas Davis committed
		serveProcess.logger.InfoMsg("Server stop event fired. Good bye.")
		for _, c := range serveProcess.stopListenChans {
Androlo's avatar
Androlo committed
			c <- struct{}{}
		}
	}()
	return nil
}

// Stop will release the port, process any remaining requests
// up until the timeout duration is passed, at which point it
// will abort them and shut down.
func (serveProcess *ServeProcess) Shutdown(ctx context.Context) error {
	var err error
	for _, s := range serveProcess.servers {
		serr := s.Shutdown(ctx)
		if serr != nil && err == nil {
			err = serr
		}
Androlo's avatar
Androlo committed
	}

	lChan := serveProcess.StopEventChannel()
	serveProcess.stopChan <- struct{}{}
Androlo's avatar
Androlo committed
	select {
	case <-lChan:
		return err
	case <-ctx.Done():
		return ctx.Err()
Androlo's avatar
Androlo committed
	}
}

// Get a start-event channel from the server. The start event
// is fired after the Start() function is called, and after
// the server has started listening for incoming connections.
// An error here .
func (serveProcess *ServeProcess) StartEventChannel() <-chan struct{} {
Androlo's avatar
Androlo committed
	lChan := make(chan struct{}, 1)
	serveProcess.startListenChans = append(serveProcess.startListenChans, lChan)
Androlo's avatar
Androlo committed
	return lChan
}

// Get a stop-event channel from the server. The event happens
// after the Stop() function has been called, and after the
// timeout has passed. When the timeout has passed it will wait
Androlo's avatar
Androlo committed
// for confirmation from the http.Server, which normally takes
// a very short time (milliseconds).
func (serveProcess *ServeProcess) StopEventChannel() <-chan struct{} {
Androlo's avatar
Androlo committed
	lChan := make(chan struct{}, 1)
	serveProcess.stopListenChans = append(serveProcess.stopListenChans, lChan)
Androlo's avatar
Androlo committed
	return lChan
}

// Creates a new serve process.
Silas Davis's avatar
Silas Davis committed
func NewServeProcess(config *ServerConfig, logger *logging.Logger,
	servers ...Server) (*ServeProcess, error) {
Androlo's avatar
Androlo committed
	if config == nil {
		return nil, fmt.Errorf("Nil passed as server configuration")
Androlo's avatar
Androlo committed
	} else {
		scfg = *config
Androlo's avatar
Androlo committed
	}
	stopChan := make(chan struct{}, 1)
	startListeners := make([]chan struct{}, 0)
	stopListeners := make([]chan struct{}, 0)
	sp := &ServeProcess{
		config:           &scfg,
		servers:          servers,
		stopChan:         stopChan,
		startListenChans: startListeners,
		stopListenChans:  stopListeners,
		srv:              nil,
Silas Davis's avatar
Silas Davis committed
		logger:           logger.WithScope("ServeProcess"),
Androlo's avatar
Androlo committed
}

Androlo's avatar
Androlo committed
// Used to enable log15 logging instead of the default Gin logging.
Silas Davis's avatar
Silas Davis committed
func logHandler(logger *logging.Logger) gin.HandlerFunc {
	logger = logger.WithScope("ginLogHandler")
Androlo's avatar
Androlo committed

Androlo's avatar
Androlo committed

Androlo's avatar
Androlo committed

		clientIP := c.ClientIP()
		method := c.Request.Method
		statusCode := c.Writer.Status()
		comment := c.Errors.String()
Androlo's avatar
Androlo committed

Silas Davis's avatar
Silas Davis committed
		logger.Info.Log("client_ip", clientIP,
			"status_code", statusCode,
			"method", method,
			"path", path,
			"error", comment)
	}
Androlo's avatar
Androlo committed
}
Androlo's avatar
Androlo committed

func NewCORSMiddleware(options CORS) gin.HandlerFunc {
Androlo's avatar
Androlo committed
	o := cors.Options{
		AllowCredentials: options.AllowCredentials,
		AllowHeaders:     options.AllowHeaders,
		AllowMethods:     options.AllowMethods,
		AllowOrigins:     options.AllowOrigins,
		ExposeHeaders:    options.ExposeHeaders,
		MaxAge:           time.Duration(options.MaxAge),
	}
	return cors.Middleware(o)
}

// Just a catch-all for POST requests right now. Only allow default charset (utf8).
func contentTypeMW(c *gin.Context) {
	if c.Request.Method == "POST" && c.ContentType() != "application/json" {
		c.AbortWithError(415, fmt.Errorf("Media type not supported: "+c.ContentType()))
	} else {
		c.Next()
	}
}