github.com/pawelgaczynski/gain@v0.4.0-alpha.0.20230821120126-41f1e60a18da/examples/cli/main.go (about)

     1  //nolint:gochecknoinits,forbidigo,goerr113,forcetypeassert,wrapcheck
     2  package main
     3  
     4  import (
     5  	"fmt"
     6  	"log"
     7  	"net/http"
     8  	_ "net/http/pprof" //nolint:gosec
     9  	"os"
    10  	"os/signal"
    11  	"runtime"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/pawelgaczynski/gain"
    16  	gainNet "github.com/pawelgaczynski/gain/pkg/net"
    17  	"github.com/rs/zerolog"
    18  	"github.com/urfave/cli/v2"
    19  )
    20  
    21  const (
    22  	port = 9000
    23  )
    24  
    25  var (
    26  	goMaxProcs             = runtime.NumCPU() * 2
    27  	pprofReadHeaderTimeout = 3 * time.Second
    28  )
    29  
    30  var architectures = []string{
    31  	"reactor", "sharding",
    32  }
    33  
    34  var allowedNetworks = []string{
    35  	gainNet.TCP, gainNet.UDP,
    36  }
    37  
    38  var handlers = []string{
    39  	"http", "echo",
    40  }
    41  
    42  type cmdConfig struct {
    43  	gain.Config
    44  	port    int
    45  	handler string
    46  	network string
    47  }
    48  
    49  var contains = func(s []string, str string) bool {
    50  	for _, v := range s {
    51  		if v == str {
    52  			return true
    53  		}
    54  	}
    55  
    56  	return false
    57  }
    58  
    59  var config = &cmdConfig{Config: gain.NewConfig()}
    60  
    61  var flags = []cli.Flag{
    62  	&cli.StringFlag{
    63  		Name:  "architecture",
    64  		Value: "reactor",
    65  		Usage: "server architecture",
    66  		Action: func(ctx *cli.Context, v string) error {
    67  			if !contains(architectures, v) {
    68  				return fmt.Errorf("possible values for architectures: %v", architectures)
    69  			}
    70  			switch v {
    71  			case "reactor":
    72  				config.Architecture = gain.Reactor
    73  			case "sharding":
    74  				config.Architecture = gain.SocketSharding
    75  			}
    76  
    77  			return nil
    78  		},
    79  	},
    80  	&cli.IntFlag{
    81  		Name:        "workers",
    82  		Value:       runtime.NumCPU(),
    83  		Usage:       "number of workers",
    84  		Destination: &config.Workers,
    85  	},
    86  	&cli.IntFlag{
    87  		Name:        "port",
    88  		Value:       port,
    89  		Usage:       "listen port",
    90  		Destination: &config.port,
    91  	},
    92  	&cli.BoolFlag{
    93  		Name:        "prettyLogger",
    94  		Value:       false,
    95  		Usage:       "print prettier logs. Warning: it can slow down server",
    96  		Destination: &config.PrettyLogger,
    97  	},
    98  	&cli.BoolFlag{
    99  		Name:        "asyncHandler",
   100  		Value:       false,
   101  		Usage:       "use async handler for writes",
   102  		Destination: &config.AsyncHandler,
   103  	},
   104  	&cli.BoolFlag{
   105  		Name:        "goroutinePool",
   106  		Value:       false,
   107  		Usage:       "use goroutine pool for async handler",
   108  		Destination: &config.GoroutinePool,
   109  	},
   110  	&cli.BoolFlag{
   111  		Name:        "cpuAffinity",
   112  		Value:       false,
   113  		Usage:       "set CPU Affinity",
   114  		Destination: &config.CPUAffinity,
   115  	},
   116  	&cli.BoolFlag{
   117  		Name:        "cbpf",
   118  		Value:       false,
   119  		Usage:       "use CBPF for bette packet locality",
   120  		Destination: &config.CBPFilter,
   121  	},
   122  	&cli.BoolFlag{
   123  		Name:        "processPriority",
   124  		Value:       false,
   125  		Usage:       "set high process priority. Note: requires root privileges",
   126  		Destination: &config.ProcessPriority,
   127  	},
   128  	&cli.StringFlag{
   129  		Name:  "loggerLevel",
   130  		Value: "error",
   131  		Usage: "logger level",
   132  		Action: func(ctx *cli.Context, v string) error {
   133  			var err error
   134  			config.LoggerLevel, err = zerolog.ParseLevel(v)
   135  
   136  			return err
   137  		},
   138  	},
   139  	&cli.StringFlag{
   140  		Name:        "handler",
   141  		Value:       "echo",
   142  		Usage:       "handler type. Allowed values are 'http' and 'echo'",
   143  		Destination: &config.handler,
   144  		Action: func(ctx *cli.Context, v string) error {
   145  			if !contains(handlers, v) {
   146  				return fmt.Errorf("possible values for handler: %v", handlers)
   147  			}
   148  
   149  			return nil
   150  		},
   151  	},
   152  	&cli.StringFlag{
   153  		Name:        "network",
   154  		Value:       gainNet.TCP,
   155  		Usage:       "network type",
   156  		Destination: &config.network,
   157  		Action: func(ctx *cli.Context, v string) error {
   158  			if !contains(allowedNetworks, v) {
   159  				return fmt.Errorf("possible values for networks: %v", allowedNetworks)
   160  			}
   161  
   162  			return nil
   163  		},
   164  	},
   165  }
   166  
   167  func getHTTPLastLine() []byte {
   168  	return []byte("\r\nContent-Length: 13\r\n\r\nHello, World!")
   169  }
   170  
   171  var (
   172  	httpFirstLine = []byte("HTTP/1.1 200 OK\r\nServer: gain\r\nContent-Type: text/plain\r\nDate: ")
   173  	httpLastLine  = getHTTPLastLine()
   174  )
   175  
   176  var now atomic.Value
   177  
   178  func ticktock() {
   179  	now.Store(nowTimeFormat())
   180  
   181  	for range time.Tick(time.Second) {
   182  		now.Store(nowTimeFormat())
   183  	}
   184  }
   185  
   186  func NowTimeFormat() string {
   187  	return now.Load().(string)
   188  }
   189  
   190  func nowTimeFormat() string {
   191  	return time.Now().Format("Mon, 02 Jan 2006 15:04:05 GMT")
   192  }
   193  
   194  type EchoHandler struct {
   195  	gain.DefaultEventHandler
   196  }
   197  
   198  func (h EchoHandler) OnRead(c gain.Conn, n int) {
   199  	buf, _ := c.Next(n)
   200  	_, _ = c.Write(buf)
   201  }
   202  
   203  type HTTPHandler struct {
   204  	gain.DefaultEventHandler
   205  }
   206  
   207  func (h HTTPHandler) OnRead(conn gain.Conn, n int) {
   208  	_, err := conn.Discard(n)
   209  	if err != nil {
   210  		log.Panic(err)
   211  	}
   212  
   213  	_, err = conn.Write(httpFirstLine)
   214  	if err != nil {
   215  		log.Panic(err)
   216  	}
   217  
   218  	_, err = conn.Write([]byte(NowTimeFormat()))
   219  	if err != nil {
   220  		log.Panic(err)
   221  	}
   222  
   223  	_, err = conn.Write(httpLastLine)
   224  	if err != nil {
   225  		log.Panic(err)
   226  	}
   227  }
   228  
   229  func main() {
   230  	runtime.GOMAXPROCS(goMaxProcs)
   231  
   232  	go func() {
   233  		server := &http.Server{
   234  			Addr:              "0.0.0.0:6061",
   235  			ReadHeaderTimeout: pprofReadHeaderTimeout,
   236  		}
   237  		log.Println(server.ListenAndServe())
   238  	}()
   239  
   240  	var server gain.Server
   241  	app := &cli.App{
   242  		EnableBashCompletion: true,
   243  		Flags:                flags,
   244  		Name:                 "cli",
   245  		Usage:                "Gain TCP/UDP server",
   246  		Action: func(*cli.Context) error {
   247  			fmt.Printf("Configuration: %+v \n", config)
   248  			fmt.Printf("Starting Gain %s Server...\n", config.network)
   249  			if config.handler == "echo" {
   250  				server = gain.NewServer(EchoHandler{}, config.Config)
   251  			} else if config.handler == "http" {
   252  				server = gain.NewServer(HTTPHandler{}, config.Config)
   253  			}
   254  
   255  			return server.Start(fmt.Sprintf("%s://0.0.0.0:%d", config.network, port))
   256  		},
   257  	}
   258  
   259  	c := make(chan os.Signal, 1)
   260  	signal.Notify(c, os.Interrupt)
   261  
   262  	go func() {
   263  		<-c
   264  		fmt.Println("Server closing...")
   265  		server.Shutdown()
   266  	}()
   267  
   268  	if err := app.Run(os.Args); err != nil {
   269  		log.Panic(err)
   270  	}
   271  }
   272  
   273  func init() {
   274  	now.Store(nowTimeFormat())
   275  
   276  	go ticktock()
   277  }