github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/platform/server.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package platform
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"net"
    24  	"net/http"
    25  	"os"
    26  	"os/signal"
    27  	"syscall"
    28  	"time"
    29  
    30  	"github.com/dchest/uniuri"
    31  	"github.com/oklog/run"
    32  	"go.uber.org/zap"
    33  
    34  	"github.com/zntrio/harp/v2/pkg/sdk/log"
    35  	"github.com/zntrio/harp/v2/pkg/sdk/platform/diagnostic"
    36  	"github.com/zntrio/harp/v2/pkg/sdk/platform/reloader"
    37  )
    38  
    39  // -----------------------------------------------------------------------------
    40  
    41  // Server represents platform server.
    42  type Server struct {
    43  	Debug           bool
    44  	Name            string
    45  	Version         string
    46  	Revision        string
    47  	Instrumentation InstrumentationConfig
    48  	Network         string
    49  	Address         string
    50  	Builder         func(ln net.Listener, group *run.Group)
    51  }
    52  
    53  // Serve starts the server listening process.
    54  func Serve(ctx context.Context, srv *Server) error {
    55  	// Generate an instance identifier
    56  	appID := uniuri.NewLen(64)
    57  
    58  	// Prepare logger
    59  	log.Setup(ctx, &log.Options{
    60  		Debug:    srv.Debug,
    61  		AppName:  srv.Name,
    62  		AppID:    appID,
    63  		Version:  srv.Version,
    64  		Revision: srv.Revision,
    65  		LogLevel: srv.Instrumentation.Logs.Level,
    66  	})
    67  
    68  	// Preparing instrumentation
    69  	instrumentationRouter := instrumentServer(ctx, srv)
    70  
    71  	// Configure graceful restart
    72  	upg := reloader.Create(ctx)
    73  
    74  	var group run.Group
    75  
    76  	// Instrumentation server
    77  	{
    78  		ln, err := upg.Listen(srv.Instrumentation.Network, srv.Instrumentation.Listen)
    79  		if err != nil {
    80  			return fmt.Errorf("platform: unable to start instrumentation server: %w", err)
    81  		}
    82  
    83  		server := &http.Server{
    84  			Handler: instrumentationRouter,
    85  			// Set timeouts to avoid Slowloris attacks.
    86  			ReadHeaderTimeout: time.Second * 20,
    87  			WriteTimeout:      time.Second * 60,
    88  			ReadTimeout:       time.Second * 60,
    89  			IdleTimeout:       time.Second * 120,
    90  		}
    91  
    92  		group.Add(
    93  			func() error {
    94  				log.For(ctx).Info("Starting instrumentation server", zap.String("address", ln.Addr().String()))
    95  				return server.Serve(ln)
    96  			},
    97  			func(e error) {
    98  				log.For(ctx).Info("Shutting instrumentation server down")
    99  
   100  				ctxShutdown, cancel := context.WithTimeout(ctx, 60*time.Second)
   101  				defer cancel()
   102  
   103  				log.CheckErrCtx(ctx, "Error raised while shutting down the server", server.Shutdown(ctxShutdown))
   104  				log.SafeClose(server, "Unable to close instrumentation server")
   105  			},
   106  		)
   107  	}
   108  
   109  	// Initialiaze network listener
   110  	ln, err := upg.Listen(srv.Network, srv.Address)
   111  	if err != nil {
   112  		return fmt.Errorf("unable to start server listener: %w", err)
   113  	}
   114  
   115  	// Initialize the component
   116  	srv.Builder(ln, &group)
   117  
   118  	// Setup signal handler
   119  	{
   120  		var (
   121  			cancelInterrupt = make(chan struct{})
   122  			ch              = make(chan os.Signal, 2)
   123  		)
   124  		defer close(ch)
   125  
   126  		group.Add(
   127  			func() error {
   128  				signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
   129  
   130  				select {
   131  				case <-ch:
   132  					log.For(ctx).Info("Captured signal")
   133  				case <-cancelInterrupt:
   134  				}
   135  
   136  				return nil
   137  			},
   138  			func(e error) {
   139  				close(cancelInterrupt)
   140  				signal.Stop(ch)
   141  			},
   142  		)
   143  	}
   144  
   145  	// Register graceful restart handler
   146  	upg.SetupGracefulRestart(ctx, group)
   147  
   148  	// Run goroutine group
   149  	return group.Run()
   150  }
   151  
   152  func instrumentServer(ctx context.Context, srv *Server) *http.ServeMux {
   153  	instrumentationRouter := http.NewServeMux()
   154  
   155  	// Register common features
   156  	if srv.Instrumentation.Diagnostic.Enabled {
   157  		cancelFunc, err := diagnostic.Register(ctx, &srv.Instrumentation.Diagnostic.Config, instrumentationRouter)
   158  		if err != nil {
   159  			log.For(ctx).Fatal("Unable to register diagnostic instrumentation", zap.Error(err))
   160  		}
   161  		defer cancelFunc()
   162  	}
   163  
   164  	return instrumentationRouter
   165  }