vegvisir/pkg/server/server.go

143 lines
4.0 KiB
Go

// ___ ____ ___ ___
// \ \ / / | _ | __| \ \ / / || | __ || || _ |
// \ \/ / |___ | |__ \ \/ / || |___ || ||___|
// \ / | _ | _ | \ / || __ | || ||\\
// \/ |___ |___ | \/ || ____| || || \\
//
// Copyright (c) 2021 Piotr Biernat. https://pbiernat.dev. MIT License
// Repo: https://git.pbiernat.dev/golang/vegvisir
// Package server contains main sever files
package server
import (
"context"
"fmt"
"git.pbiernat.dev/golang/vegvisir/pkg/cache"
"git.pbiernat.dev/golang/vegvisir/pkg/config"
"github.com/valyala/fasthttp"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
const (
// ServerVersion static
ServerVersion = "0.1-dev"
// ServerName static
ServerName = "Vegvisir/" + ServerVersion
)
// Server struct
type Server struct {
config *config.Config
router *Router
respCM *cache.Manager
daemon *fasthttp.Server
}
// NewServer function
func NewServer(cPath string) *Server {
cfg := config.New(cPath)
if err := cfg.Load(); err != nil {
log.Fatalln("Unable to find config file: ", cPath, err)
}
datastore := cache.GetDatastore(cfg.Cache)
return &Server{
config: cfg,
router: NewRouter(cfg, cache.NewMemoryDatastore(), cfg.Cache.RouteTTL),
// ^^ INFO: Routes always use memoryCache cause low size and fast read time (optimalization)
respCM: cache.NewCachedManager(*datastore, "response_", cfg.Cache.ResponseTTL, cache.HTTPRequestHandler),
// ^^ FIXME: add handler detection option by config etc...
}
}
// Run function
func (s *Server) Run() {
//s.memo = cache.New(s.processUrl)
log.Println("Starting server...")
go func() {
serverAddress := s.config.Server.Address + ":" + fmt.Sprint(s.config.Server.Port)
s.daemon = &fasthttp.Server{
Handler: s.mainHandler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 20 * time.Second,
MaxConnsPerIP: 500,
MaxRequestsPerConn: 500,
IdleTimeout: 15 * time.Second, // aka KeepAlive
//CloseOnShutdown: true,
}
if err := s.daemon.ListenAndServe(serverAddress); err != nil {
log.Fatalf("Server panic! Error message: %s", err)
}
}()
// Wait for an interrupt and attempt a graceful shutdown
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
<-interrupt
log.Println("SIGKILL or SIGINT caught, shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.Shutdown(ctx)
log.Println("Server shutdown successfully.")
}
// Shutdown function
func (s *Server) Shutdown(ctx context.Context) { // TODO: wait for all connections to finish
//defer s.respCM.Close()
//defer s.daemon.Shutdown()
log.Println("Shutting down...")
}
func (s *Server) mainHandler(ctx *fasthttp.RequestCtx) {
// http := client.NewHttpClient(ctx)
// move all below logic to concrete handler or sth....
reqURL, sReqURL, sReqMethod := ctx.RequestURI(), string(ctx.RequestURI()), string(ctx.Method())
log.Println("Incoming request:", sReqMethod, sReqURL)
if strings.Contains(sReqURL, "/health") { // quick and tmp fix, hack...
ctx.SetStatusCode(fasthttp.StatusOK)
ctx.SetContentType("application/json")
ctx.SetBodyString("{\"health\":\"OK\"}")
return
}
found, route := s.router.FindByRequestURL(reqURL)
if !found {
// FIXME: return 404 or 5xx error in response? Maybe define it in concrete Backend config?
ctx.SetStatusCode(fasthttp.StatusNotFound)
//ctx.SetConnectionClose()
log.Println("404:", sReqMethod, sReqURL)
return
}
response, err := s.respCM.Fetch(sReqURL, sReqMethod, route)
if err != nil {
// FIXME: Response read error(sending 500 error response)
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
//ctx.SetConnectionClose() // not sure if change abything with connection: keep-alive/close issue ^^
log.Println("Response read error(sending 500 error response)", err)
return
}
ctx.Response.Header.SetBytesV(fasthttp.HeaderContentType, response.Headers.ContentType())
ctx.SetStatusCode(response.Code)
ctx.SetBodyString(response.Body)
//ctx.SetConnectionClose()
}