initial import of sources

This commit is contained in:
Олег Бородин
2024-06-18 10:15:22 +02:00
commit ada2a49a64
42 changed files with 12444 additions and 0 deletions

3
internal/config/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*~
path.go
user.go

143
internal/config/config.go Normal file
View File

@@ -0,0 +1,143 @@
package config
import (
"flag"
"fmt"
"os"
"path/filepath"
"hamlogger/pkg/aux509"
"github.com/go-yaml/yaml"
)
const (
defaultHostname = "localhost"
defaultCertFile = "hamlogger.cert"
defaultKeyFile = "hamlogger.key"
applicationName = "hamlogger"
defaultPort int = 20120
)
var (
defaultBuild = "NODATE"
)
type ServiceConfig struct {
PortNum int `json:"port" yaml:"port"`
}
type Config struct {
Service ServiceConfig `json:"service" yaml:"service"`
Hostname string `json:"hostname" yaml:"hostname"`
Debug bool `json:"debug" yaml:"debug"`
Build string `json:"build" yaml:"build"`
LogPath string `json:"logfile" yaml:"logfile"`
RunPath string `json:"runfile" yaml:"runfile"`
DataPath string `json:"datadir" yaml:"datadir"`
Daemon bool `json:"daemon" yaml:"daemon"`
CertPath string `json:"certPath" yaml:"certPath"`
KeyPath string `json:"keyPath" yaml:"keyPath"`
X509Cert string `json:"x509Cert" yaml:"x509Cert"`
X509Key string `json:"x509Key" yaml:"x509Key"`
}
func NewConfig() *Config {
conf := &Config{
Service: ServiceConfig{
PortNum: defaultPort,
},
Hostname: defaultHostname,
Build: defaultBuild,
DataPath: datadirPath,
Daemon: false,
Debug: false,
}
conf.LogPath = filepath.Join(logdirPath, fmt.Sprintf("%s.log", applicationName))
conf.RunPath = filepath.Join(rundirPath, fmt.Sprintf("%s.pid", applicationName))
return conf
}
func (conf *Config) ReadFile() error {
var err error
configPath := filepath.Join(confdirPath, fmt.Sprintf("%sd.yaml", applicationName))
confBytes, err := os.ReadFile(configPath)
if err != nil {
return err
}
err = yaml.Unmarshal(confBytes, conf)
if err != nil {
return err
}
return err
}
func (conf *Config) ReadEnv() error {
var err error
if conf.CertPath != "" && conf.KeyPath != "" {
if !filepath.IsAbs(conf.CertPath) {
conf.CertPath = filepath.Join(confdirPath, conf.CertPath)
}
certBytes, err := os.ReadFile(conf.CertPath)
if err != nil {
return err
}
if !filepath.IsAbs(conf.KeyPath) {
conf.KeyPath = filepath.Join(confdirPath, conf.KeyPath)
}
keyBytes, err := os.ReadFile(conf.KeyPath)
if err != nil {
return err
}
conf.X509Cert = string(certBytes)
conf.X509Key = string(keyBytes)
}
return err
}
func (conf *Config) ReadOpts() error {
var err error
exeName := filepath.Base(os.Args[0])
flag.IntVar(&conf.Service.PortNum, "port", conf.Service.PortNum, "listen port")
flag.BoolVar(&conf.Daemon, "daemon", conf.Daemon, "run as daemon")
flag.BoolVar(&conf.Debug, "debug", conf.Debug, "on debug mode")
help := func() {
fmt.Println("")
fmt.Printf("Usage: %s [option]\n", exeName)
fmt.Println("")
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println("")
}
flag.Usage = help
flag.Parse()
return err
}
func (conf *Config) Postproc() error {
var err error
if conf.X509Cert == "" || conf.X509Key == "" {
certBytes, keyBytes, err := aux509.CreateX509SelfSignedCert(conf.Hostname)
if err != nil {
return err
}
conf.X509Cert = string(certBytes)
conf.X509Key = string(keyBytes)
}
return err
}
func (conf *Config) Yaml() (string, error) {
var err error
var res string
yamlBytes, err := yaml.Marshal(conf)
if err != nil {
return res, err
}
res = string(yamlBytes)
return res, err
}

View File

@@ -0,0 +1,10 @@
package config
const (
confdirPath = "@srv_confdir@"
rundirPath = "@srv_rundir@"
logdirPath = "@srv_logdir@"
datadirPath = "@srv_datadir@"
)

View File

@@ -0,0 +1,48 @@
package database
import (
"path/filepath"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
const schema = `
--- DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL
);
CREATE INDEX IF NOT EXISTS users_index
ON users(name);
`
type Database struct {
datapath string
db *sqlx.DB
}
func NewDatabase(datapath string) *Database {
return &Database{
datapath: datapath,
}
}
func (db *Database) InitDatabase() error {
var err error
dbPath := filepath.Join(db.datapath, "hamlogger.db")
db.db, err = sqlx.Open("sqlite3", dbPath)
if err != nil {
return err
}
err = db.db.Ping()
if err != nil {
return err
}
_, err = db.db.Exec(schema)
if err != nil {
return err
}
return err
}

View File

@@ -0,0 +1,17 @@
package descriptor
type Radiolink struct {
ID string
Caller string
Responder string
Timestamp string
Modulation string
OriginID string
}
type Origin struct {
ID string
Name string
Cert string
Key string
}

View File

@@ -0,0 +1,27 @@
package handler
import (
"hamlogger/internal/database"
"hamlogger/pkg/logger"
)
type HandlerConfig struct {
Datadir string
Database *database.Database
}
type Handler struct {
log *logger.Logger
db *database.Database
datadir string
}
func NewHandler(conf *HandlerConfig) (*Handler, error) {
var err error
hand := &Handler{
datadir: conf.Datadir,
db: conf.Database,
log: logger.NewLogger("handler"),
}
return hand, err
}

15
internal/handler/hello.go Normal file
View File

@@ -0,0 +1,15 @@
package handler
import (
"hamlogger/internal/router"
)
type HelloParams struct{}
type HelloResult struct{}
func (hand *Handler) Hello(ctx *router.Context) {
var err error
res := &HelloResult{}
hand.SendResult(ctx, res, err)
}

View File

@@ -0,0 +1,11 @@
package handler
import (
"hamlogger/internal/router"
"net/http"
)
func (hand *Handler) NotFound(ctx *router.Context) {
ctx.SetStatus(http.StatusNotFound)
ctx.SendText("Route not found")
}

View File

@@ -0,0 +1,30 @@
package handler
import (
"fmt"
"hamlogger/internal/router"
)
type Response struct {
Error bool `json:"error"`
Message string `json:"message,omitempty"`
Result any `json:"result,omitempty"`
}
func (hand *Handler) SendResult(ctx *router.Context, res any, err error) {
var resp *Response
if err != nil {
resp = &Response{
Error: true,
Message: fmt.Sprintf("%v", err),
Result: res,
}
} else {
resp = &Response{
Error: false,
Result: res,
}
}
ctx.SendJSON(resp)
}

View File

@@ -0,0 +1,107 @@
package router
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func bindObj(obj interface{}, kvmap map[string]string, sTag string) error {
var err error
vElem := reflect.ValueOf(obj).Elem()
sElem := reflect.TypeOf(obj).Elem()
for i := 0; i < vElem.NumField(); i++ {
vField := vElem.Field(i)
tag := sElem.Field(i).Tag.Get(sTag)
if tag != "" {
sVal, exists := kvmap[tag]
if exists {
switch vField.Kind() {
case reflect.String:
vField.SetString(sVal)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
iVal, err := strconv.ParseInt(sVal, 10, 64)
if err != nil {
return err
}
vField.SetInt(iVal)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
iVal, err := strconv.ParseUint(sVal, 10, 64)
if err != nil {
return err
}
vField.SetUint(iVal)
case reflect.Bool:
bVal, err := strconv.ParseBool(sVal)
if err != nil {
return err
}
vField.SetBool(bVal)
case reflect.Float32, reflect.Float64:
fVal, err := strconv.ParseFloat(sVal, 64)
if err != nil {
return err
}
vField.SetFloat(fVal)
}
}
}
}
return err
}
const (
defaultItemPattern = `(?P<%s>[a-zA-Z0-9_][\-\.a-zA-Z0-9_=:~]+)`
itemRegexepPattern = `(?P<%s>%s)`
)
func pathComp(path string) string {
convFunc := func(item []byte) []byte {
itemData := strings.Split(string(item), ":")
var convItem string
if len(itemData) > 1 {
convItem = fmt.Sprintf(itemRegexepPattern, itemData[0], itemData[1])
} else {
convItem = fmt.Sprintf(defaultItemPattern, string(item))
}
return []byte(convItem)
}
return string(replaceTemplates([]byte(path), convFunc))
}
func replaceTemplates(path []byte, convFunc func([]byte) []byte) []byte {
convPath := make([]byte, 0)
tmpBuf := make([]byte, 0)
var lsPos int
lbFound := false
var rsPos int
for i := 0; i < len(path); i++ {
switch {
case path[i] == '{':
lsPos = i + 1
if lbFound {
convPath = append(convPath, tmpBuf...)
}
lbFound = true
tmpBuf = make([]byte, 0)
tmpBuf = append(tmpBuf, path[i])
case path[i] == '}':
rsPos = i
if lbFound {
subst := convFunc(path[lsPos:rsPos])
convPath = append(convPath, subst...)
lbFound = false
} else {
convPath = append(convPath, path[i])
}
default:
if !lbFound {
convPath = append(convPath, path[i])
} else {
tmpBuf = append(tmpBuf, path[i])
}
}
}
return convPath
}

View File

@@ -0,0 +1,80 @@
package router
import (
"encoding/json"
"fmt"
"net/http"
)
type Context struct {
Req *http.Request
Writer http.ResponseWriter
PathMap map[string]string
StatusCode int
}
func (ctx *Context) SendJSON(payload any) {
ctx.Writer.Header().Set("Content-Type", "application/json")
json.NewEncoder(ctx.Writer).Encode(payload)
}
func (ctx *Context) SendText(payload string) {
ctx.Writer.Header().Set("Content-Type", "text/plain")
ctx.Writer.Write([]byte(payload))
}
func (ctx *Context) SetStatus(httpStatus int) {
ctx.StatusCode = httpStatus
ctx.Writer.WriteHeader(httpStatus)
}
func (ctx *Context) SetHeader(key, value string) {
ctx.Writer.Header().Set(key, value)
}
func (ctx *Context) SendError(httpStatus int) {
ctx.StatusCode = httpStatus
ctx.Writer.WriteHeader(httpStatus)
ctx.Writer.Header().Set("Content-Type", "text/plain")
message := fmt.Sprintf("%d %s", httpStatus, http.StatusText(httpStatus))
ctx.Writer.Write([]byte(message))
}
func (ctx *Context) GetHeader(key string) string {
return ctx.Req.Header.Get(key)
}
func (ctx *Context) GetMethod() string {
return ctx.Req.Method
}
func (ctx *Context) GetQueryMap() map[string][]string {
res := make(map[string][]string)
for key, val := range ctx.Req.URL.Query() {
res[key] = val
}
return res
}
func (ctx *Context) GetQueryVar(key string) string {
return ctx.Req.URL.Query().Get(key)
}
func (ctx *Context) GetPathVar(key string) string {
return ctx.PathMap[key]
}
func (ctx *Context) BindPath(obj any) error {
return bindObj(obj, ctx.PathMap, "uri")
}
func (ctx *Context) BindQuery(obj any) error {
qMap := make(map[string]string)
for key, val := range ctx.Req.URL.Query() {
if len(val) == 1 {
qMap[key] = val[0]
}
}
return bindObj(obj, qMap, "param")
}

35
internal/router/corsmw.go Normal file
View File

@@ -0,0 +1,35 @@
package router
func NewCorsMiddleware() func(Handler) Handler {
mw := func(next Handler) Handler {
return newCorsHandler(next)
}
return mw
}
type corsHandler struct {
next Handler
}
func newCorsHandler(next Handler) *corsHandler {
return &corsHandler{
next: next,
}
}
func (hand corsHandler) ServeHTTP(ctx *Context) {
origin := ctx.Req.Header.Get("Origin")
if origin != "" {
ctx.SetHeader("Access-Control-Allow-Origin", origin)
ctx.SetHeader("Access-Control-Allow-Methods",
"POST, GET, OPTIONS, PUT, DELETE, UPDATE, PATCH")
ctx.SetHeader("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
ctx.SetHeader("Access-Control-Max-Age", "86400")
ctx.SetHeader("Access-Control-Allow-Credentials", "true")
}
if ctx.Req.Method == "OPTIONS" {
return
}
hand.next.ServeHTTP(ctx)
}

View File

@@ -0,0 +1,49 @@
package router
import (
"fmt"
//"time"
//"sigs.k8s.io/yaml"
)
func SampleMiddleware(next Handler) Handler {
return HandlerFunc(func(ctx *Context) {
next.ServeHTTP(ctx)
//fmt.Printf("%s %s %s %s %s", time.Now().Format(time.RFC3339), ctx.Req.RemoteAddr,
// ctx.Req.Method, ctx.Req.URL.String(), ctx.Req.Proto)
fmt.Printf("%s %s %s %s", ctx.Req.RemoteAddr, ctx.Req.Method, ctx.Req.URL.String(),
ctx.Req.Proto)
})
}
func NewLoggingMiddleware(print func(string, ...any)) func(Handler) Handler {
mw := func(next Handler) Handler {
return newLoggingHandler(next, print)
}
return mw
}
type loggingHandler struct {
next Handler
print func(string, ...any)
}
func newLoggingHandler(next Handler, print func(string, ...any)) *loggingHandler {
return &loggingHandler{
next: next,
print: print,
}
}
func (hand loggingHandler) ServeHTTP(ctx *Context) {
hand.next.ServeHTTP(ctx)
//data, _ := yaml.Marshal(ctx.Req.URL)
//hand.print("%s %s %s %s %s %++v", time.Now().Format(time.RFC3339), ctx.Req.RemoteAddr,
// ctx.Req.Method, ctx.Req.URL.String(), ctx.Req.Proto, ctx.StatusCode)
hand.print("%s %s %s %s %d", ctx.Req.RemoteAddr, ctx.Req.Method, ctx.Req.URL.String(),
ctx.Req.Proto, ctx.StatusCode)
}

View File

@@ -0,0 +1,40 @@
package router
import (
"net/http"
"runtime/debug"
"time"
)
func NewRecoveryMiddleware(print func(string, ...any)) func(Handler) Handler {
mw := func(next Handler) Handler {
return newRecoveryHandler(next, print)
}
return mw
}
type recoveryHandler struct {
next Handler
print func(string, ...any)
}
func newRecoveryHandler(next Handler, print func(string, ...any)) *recoveryHandler {
return &recoveryHandler{
next: next,
print: print,
}
}
func (hand recoveryHandler) ServeHTTP(ctx *Context) {
exitFunc := func() {
err := recover()
if err != nil {
ctx.Writer.WriteHeader(http.StatusInternalServerError)
stack := string(debug.Stack())
timestamp := time.Now().Format(time.RFC3339)
hand.print("%s %v ; %s\n", timestamp, err, stack)
}
}
defer exitFunc()
hand.next.ServeHTTP(ctx)
}

157
internal/router/router.go Normal file
View File

@@ -0,0 +1,157 @@
package router
import (
"fmt"
"net/http"
"regexp"
)
type HandlerFunc func(ctx *Context)
func (handlerFunc HandlerFunc) ServeHTTP(ctx *Context) {
handlerFunc(ctx)
}
type Handler interface {
ServeHTTP(ctx *Context)
}
type Router struct {
routingHandler *RoutingHandler
headHandler Handler
}
func NewRouter() *Router {
rh := NewRoutingHandler()
return &Router{
headHandler: rh,
routingHandler: rh,
}
}
func (rt *Router) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
ctx := &Context{
Writer: writer,
Req: req,
PathMap: make(map[string]string),
StatusCode: http.StatusOK,
}
rt.headHandler.ServeHTTP(ctx)
}
func (rout *Router) Use(mw func(next Handler) Handler) {
rout.headHandler = mw(rout.headHandler)
}
func (rout *Router) AddRoute(method, path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute(method, path, handlerFunc)
}
func (rout *Router) Get(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("GET", path, handlerFunc)
}
func (rout *Router) Head(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("HEAD", path, handlerFunc)
}
func (rout *Router) Post(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("POST", path, handlerFunc)
}
func (rout *Router) Put(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("PUT", path, handlerFunc)
}
func (rout *Router) Patch(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("PATCH", path, handlerFunc)
}
func (rout *Router) Delete(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("DELETE", path, handlerFunc)
}
func (rout *Router) Option(path string, handlerFunc HandlerFunc) {
rout.routingHandler.AddRoute("OPTION", path, handlerFunc)
}
func (rout *Router) NotFound(handlerFunc HandlerFunc) {
rout.routingHandler.notFound = handlerFunc
}
func (rout *Router) ListRoutes() []*Route {
return rout.routingHandler.Routes
}
type RoutingHandler struct {
Routes []*Route
notFound Handler
}
func NewRoutingHandler() *RoutingHandler {
notFound := HandlerFunc(func(ctx *Context) {
http.NotFound(ctx.Writer, ctx.Req)
})
return &RoutingHandler{
notFound: notFound,
}
}
const globalRoutePattern = `^%s$`
func (hand *RoutingHandler) AddRoute(method, path string, handlerFunc HandlerFunc) error {
var err error
path = pathComp(path)
path = fmt.Sprintf(globalRoutePattern, path)
re, err := regexp.Compile(path)
if err != nil {
return err
}
route := &Route{
Method: method,
Path: path,
Regexp: re,
Handler: handlerFunc,
}
hand.Routes = append(hand.Routes, route)
return err
}
func (hand *RoutingHandler) ServeHTTP(ctx *Context) {
handler := hand.notFound
for _, route := range hand.Routes {
match := route.Match(ctx.Req)
if !match {
continue
}
pathMatch := route.Regexp.FindStringSubmatch(ctx.Req.URL.Path)
subNames := route.Regexp.SubexpNames()
for i, val := range pathMatch {
key := subNames[i]
if key != "" {
ctx.PathMap[key] = val
}
}
handler = route.Handler
break
}
handler.ServeHTTP(ctx)
}
type Route struct {
Method string
Path string
Regexp *regexp.Regexp
Handler HandlerFunc
}
func (route *Route) Match(req *http.Request) bool {
if req.Method != route.Method {
return false
}
match := route.Regexp.MatchString(req.URL.Path)
if !match {
return false
}
return true
}

237
internal/server/server.go Normal file
View File

@@ -0,0 +1,237 @@
package server
import (
"os"
"os/signal"
"os/user"
"path/filepath"
"strconv"
"syscall"
"hamlogger/internal/config"
"hamlogger/internal/database"
"hamlogger/internal/handler"
"hamlogger/internal/service"
"hamlogger/pkg/logger"
)
type Server struct {
conf *config.Config
svc *service.Service
hand *handler.Handler
log *logger.Logger
db *database.Database
}
func NewServer() (*Server, error) {
var err error
srv := &Server{}
srv.log = logger.NewLogger("server")
return srv, err
}
func (srv *Server) Service() *service.Service {
return srv.svc
}
func (srv *Server) Configure() error {
var err error
srv.conf = config.NewConfig()
err = srv.conf.ReadFile()
if err != nil {
return err
}
err = srv.conf.ReadEnv()
if err != nil {
return err
}
err = srv.conf.ReadOpts()
if err != nil {
return err
}
err = srv.conf.Postproc()
if err != nil {
return err
}
return err
}
func (srv *Server) Build() error {
var err error
srv.log.Infof("Build server")
confData, err := srv.conf.Yaml()
if err != nil {
return err
}
srv.log.Infof("Current configuration is:\n%s\n", confData)
// Database
srv.db = database.NewDatabase(srv.conf.DataPath)
err = srv.db.InitDatabase()
if err != nil {
return err
}
// Create X509 certs
handlerConfig := &handler.HandlerConfig{
Datadir: srv.conf.DataPath,
Database: srv.db,
}
srv.hand, err = handler.NewHandler(handlerConfig)
if err != nil {
return err
}
// Create service
serviceConfig := &service.ServiceConfig{
PortNum: srv.conf.Service.PortNum,
Hostname: srv.conf.Hostname,
Handler: srv.hand,
X509Cert: []byte(srv.conf.X509Cert),
X509Key: []byte(srv.conf.X509Key),
}
srv.svc, err = service.NewService(serviceConfig)
if err != nil {
return err
}
err = srv.svc.Build()
if err != nil {
return err
}
if err != nil {
return err
}
return err
}
func (srv *Server) Run() error {
var err error
currUser, err := user.Current()
if err != nil {
return err
}
srv.log.Infof("Running server as user %s", currUser.Username)
sigs := make(chan os.Signal, 1)
svcDone := make(chan error, 1)
// Run service
startService := func(svc *service.Service, done chan error) {
err = svc.Run()
if err != nil {
srv.log.Errorf("Service error: %v", err)
done <- err
}
}
go startService(srv.svc, svcDone)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
var signal os.Signal
select {
case signal = <-sigs:
srv.log.Infof("Services stopped by signal: %v", signal)
srv.svc.Stop()
case err = <-svcDone:
srv.log.Infof("Service stopped by service error: %v", err)
srv.svc.Stop()
}
return err
}
func (srv *Server) PseudoFork() error {
const successExit int = 0
var keyEnv string = "IMX0LTSELMRF8K"
var err error
_, isChild := os.LookupEnv(keyEnv)
switch {
case !isChild:
os.Setenv(keyEnv, "TRUE")
procAttr := syscall.ProcAttr{}
cwd, err := os.Getwd()
if err != nil {
return err
}
var sysFiles = make([]uintptr, 3)
sysFiles[0] = uintptr(syscall.Stdin)
sysFiles[1] = uintptr(syscall.Stdout)
sysFiles[2] = uintptr(syscall.Stderr)
procAttr.Files = sysFiles
procAttr.Env = os.Environ()
procAttr.Dir = cwd
_, err = syscall.ForkExec(os.Args[0], os.Args, &procAttr)
if err != nil {
return err
}
os.Exit(successExit)
case isChild:
_, err = syscall.Setsid()
if err != nil {
return err
}
}
os.Unsetenv(keyEnv)
return err
}
func (srv *Server) Daemonize() error {
var err error
if srv.conf.Daemon {
// Restart process process
err = srv.PseudoFork()
if err != nil {
return err
}
// Redirect stdin
nullFile, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
return err
}
err = syscall.Dup2(int(nullFile.Fd()), int(os.Stdin.Fd()))
if err != nil {
return err
}
// Redirect stderr and stout
logdir := filepath.Dir(srv.conf.LogPath)
err = os.MkdirAll(logdir, 0750)
if err != nil {
return err
}
logFile, err := os.OpenFile(srv.conf.LogPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640)
if err != nil {
return err
}
err = syscall.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))
if err != nil {
return err
}
err = syscall.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))
if err != nil {
return err
}
// Write process ID
rundir := filepath.Dir(srv.conf.RunPath)
err = os.MkdirAll(rundir, 0750)
if err != nil {
return err
}
pidFile, err := os.OpenFile(srv.conf.RunPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer pidFile.Close()
currPid := os.Getpid()
_, err = pidFile.WriteString(strconv.Itoa(currPid))
if err != nil {
return err
}
}
return err
}

119
internal/service/service.go Normal file
View File

@@ -0,0 +1,119 @@
package service
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"time"
"hamlogger/internal/handler"
"hamlogger/internal/router"
"hamlogger/pkg/logger"
)
type ServiceConfig struct {
Handler *handler.Handler
PortNum int
Hostname string
X509Cert []byte
X509Key []byte
}
type Service struct {
hand *handler.Handler
hsrv *http.Server
log *logger.Logger
rout *router.Router
listen net.Listener
portnum int
hostname string
x509cert []byte
x509key []byte
}
func NewService(conf *ServiceConfig) (*Service, error) {
var err error
svc := &Service{
hand: conf.Handler,
portnum: conf.PortNum,
hostname: conf.Hostname,
x509cert: conf.X509Cert,
x509key: conf.X509Key,
}
svc.log = logger.NewLogger("service")
return svc, err
}
func (svc *Service) Build() error {
var err error
svc.log.Debugf("Building service")
svc.rout = router.NewRouter()
svc.rout.Use(router.NewRecoveryMiddleware(svc.log.Errorf))
svc.rout.Use(router.NewLoggingMiddleware(svc.log.Infof))
svc.rout.Use(router.NewCorsMiddleware())
svc.rout.Get(`/api/v1/service/hello`, svc.hand.Hello)
svc.rout.NotFound(svc.hand.NotFound)
routes := svc.rout.ListRoutes()
if err != nil {
return err
}
for _, route := range routes {
svc.log.Infof("Registed route: %s %s", route.Method, route.Path)
}
const useTLS = true
if useTLS {
tlsCert, err := tls.X509KeyPair(svc.x509cert, svc.x509key)
if err != nil {
return err
}
tlsConfig := tls.Config{
Certificates: []tls.Certificate{tlsCert},
ClientAuth: tls.NoClientCert,
InsecureSkipVerify: true,
}
listenAddress := fmt.Sprintf(":%d", svc.portnum)
svc.listen, err = tls.Listen("tcp", listenAddress, &tlsConfig)
if err != nil {
return err
}
} else {
listenAddress := fmt.Sprintf(":%d", svc.portnum)
svc.listen, err = net.Listen("tcp", listenAddress)
if err != nil {
return err
}
}
svc.hsrv = &http.Server{
Handler: svc.rout,
}
return err
}
func (svc *Service) Run() error {
var err error
err = svc.hsrv.Serve(svc.listen)
if err != nil {
return err
}
return err
}
func (svc *Service) Stop() {
svc.log.Infof("Stopping service")
if svc.hsrv != nil {
downWaiting := 5 * time.Second
ctx, _ := context.WithTimeout(context.Background(), downWaiting)
svc.hsrv.Shutdown(ctx)
}
}