Files
golib/httpserver/pool.go
Nicolas JUHEL f015ad5283 PKG Logger :
- fix issue #109
- fix issue #106
- fix bug with color linux / win
- fix bug with color between std, file and syslog
- fix using default logger instead of setup logger
- fix logrus logger level
- add debug message to test logger (test-httpserver)

PKG httpserver :
- apply logger's changes
- optimize httpserver shutdown process

PKG httpcli :
- add new params for log error / log check

PKG ldap :
- add new params for log error / log check
2021-06-17 14:10:30 +02:00

473 lines
8.7 KiB
Go

/*
* MIT License
*
* Copyright (c) 2020 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*/
package httpserver
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"regexp"
"strings"
"syscall"
liberr "github.com/nabbar/golib/errors"
liblog "github.com/nabbar/golib/logger"
libsem "github.com/nabbar/golib/semaphore"
libsts "github.com/nabbar/golib/status"
)
type FieldType uint8
const (
HandlerDefault = "default"
FieldName FieldType = iota
FieldBind
FieldExpose
)
type MapUpdPoolServer func(srv Server) Server
type MapRunPoolServer func(srv Server)
type pool []Server
type PoolServer interface {
Add(srv ...Server) (PoolServer, liberr.Error)
Get(bindAddress string) Server
Del(bindAddress string) PoolServer
Has(bindAddress string) bool
Len() int
SetLogger(log liblog.FuncLog)
MapRun(f MapRunPoolServer)
MapUpd(f MapUpdPoolServer)
List(fieldFilter, fieldReturn FieldType, pattern, regex string) []string
Filter(field FieldType, pattern, regex string) PoolServer
IsRunning(asLeast bool) bool
WaitNotify(ctx context.Context, cancel context.CancelFunc)
Listen(handler http.Handler) liberr.Error
ListenMultiHandler(handler map[string]http.Handler) liberr.Error
Restart()
Shutdown()
StatusInfo(bindAddress string) (name string, release string, hash string)
StatusHealth(bindAddress string) error
StatusRoute(prefix string, fctMessage libsts.FctMessage, sts libsts.RouteStatus)
}
func NewPool(srv ...Server) PoolServer {
p, _ := make(pool, 0).Add(srv...)
return p
}
func (p pool) MapRun(f MapRunPoolServer) {
if p == nil {
return
}
for _, s := range p {
f(s)
}
}
func (p pool) MapUpd(f MapUpdPoolServer) {
if p == nil {
return
}
for i, s := range p {
p[i] = f(s)
}
}
func (p pool) Add(srv ...Server) (PoolServer, liberr.Error) {
var r = make(pool, 0)
if p != nil {
r = p
}
for _, s := range srv {
if !r.Has(s.GetBindable()) {
r = append(r, s)
continue
}
for _, x := range r {
if x.GetBindable() != s.GetBindable() {
continue
} else if !x.Merge(s) {
r = r.Del(s.GetBindable()).(pool)
r = append(r, s)
break
}
}
}
return r, nil
}
func (p pool) Get(bindAddress string) Server {
if !p.Has(bindAddress) {
return nil
}
for _, s := range p {
if s.GetBindable() == bindAddress {
return s
}
}
return nil
}
func (p pool) Del(bindAddress string) PoolServer {
if !p.Has(bindAddress) {
return p
}
var r = make(pool, 0)
for _, s := range p {
if s.GetBindable() != bindAddress {
r = append(r, s)
}
if s.IsRunning() {
s.Shutdown()
}
}
return r
}
func (p pool) Has(bindAddress string) bool {
if p.Len() < 1 {
return false
}
for _, s := range p {
if s.GetBindable() == bindAddress {
return true
}
}
return false
}
func (p pool) Len() int {
if p == nil {
return 0
}
return len(p)
}
func (p pool) SetLogger(log liblog.FuncLog) {
p.MapUpd(func(srv Server) Server {
srv.SetLogger(log)
return srv
})
}
func (p pool) List(fieldFilter, fieldReturn FieldType, pattern, regex string) []string {
var (
r = make([]string, 0)
f string
)
if p.Len() < 1 {
return r
}
pattern = strings.ToLower(pattern)
p.MapRun(func(srv Server) {
switch fieldFilter {
case FieldBind:
f = srv.GetBindable()
case FieldExpose:
f = srv.GetExpose()
case FieldName:
f = srv.GetName()
default:
f = srv.GetName()
}
f = strings.ToLower(f)
if pattern != "" && strings.Contains(f, pattern) {
switch fieldReturn {
case FieldBind:
r = append(r, srv.GetBindable())
case FieldExpose:
r = append(r, srv.GetExpose())
case FieldName:
r = append(r, srv.GetName())
default:
r = append(r, srv.GetName())
}
return
}
if regex == "" {
return
}
if ok, err := regexp.MatchString(regex, srv.GetName()); err == nil && ok {
switch fieldReturn {
case FieldBind:
r = append(r, srv.GetBindable())
case FieldExpose:
r = append(r, srv.GetExpose())
case FieldName:
r = append(r, srv.GetName())
default:
r = append(r, srv.GetName())
}
}
})
return r
}
func (p pool) Filter(field FieldType, pattern, regex string) PoolServer {
if p.Len() < 1 {
return nil
}
var (
r = make(pool, 0)
f string
)
pattern = strings.ToLower(pattern)
p.MapRun(func(srv Server) {
switch field {
case FieldBind:
f = srv.GetBindable()
case FieldExpose:
f = srv.GetExpose()
case FieldName:
f = srv.GetName()
default:
f = srv.GetName()
}
f = strings.ToLower(f)
if pattern != "" && strings.Contains(f, pattern) {
r = append(r, srv)
return
}
if regex == "" {
return
}
if ok, err := regexp.MatchString(regex, srv.GetName()); err == nil && ok {
r = append(r, srv)
}
})
return r
}
func (p pool) IsRunning(atLeast bool) bool {
if p.Len() < 1 {
return false
}
var r = false
for _, s := range p {
if s.IsRunning() {
r = true
continue
}
if !atLeast {
return false
}
}
return r
}
func (p pool) WaitNotify(ctx context.Context, cancel context.CancelFunc) {
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT)
signal.Notify(quit, syscall.SIGTERM)
signal.Notify(quit, syscall.SIGQUIT)
select {
case <-quit:
p.Shutdown()
if cancel != nil {
cancel()
}
case <-ctx.Done():
p.Shutdown()
if cancel != nil {
cancel()
}
}
}
func (p pool) Listen(handler http.Handler) liberr.Error {
return p.ListenMultiHandler(map[string]http.Handler{HandlerDefault: handler})
}
func (p pool) ListenMultiHandler(handler map[string]http.Handler) liberr.Error {
if p.Len() < 1 {
return nil
}
var e liberr.Error
e = ErrorPoolListen.Error(nil)
liblog.InfoLevel.Log("Calling listen for All Servers")
p.MapRun(func(srv Server) {
if len(handler) < 1 {
e.AddParentError(srv.Listen(nil))
} else {
for k := range handler {
if len(handler) == 1 {
e.AddParentError(srv.Listen(handler[k]))
break
} else if strings.ToLower(k) == srv.GetHandlerKey() {
e.AddParentError(srv.Listen(handler[k]))
break
}
}
}
})
liblog.InfoLevel.Log("End of Calling listen for All Servers")
if !e.HasParent() {
e = nil
}
return e
}
func (p pool) runMapCommand(f func(sem libsem.Sem, srv Server)) {
if p.Len() < 1 {
return
}
var (
s libsem.Sem
x context.Context
c context.CancelFunc
)
x, c = context.WithTimeout(context.Background(), timeoutShutdown)
defer func() {
c()
s.DeferMain()
}()
s = libsem.NewSemaphoreWithContext(x, 0)
p.MapRun(func(srv Server) {
_ = s.NewWorker()
go func(sem libsem.Sem, srv Server) {
f(sem, srv)
}(s, srv)
})
_ = s.WaitAll()
}
func (p pool) runMapRestart(sem libsem.Sem, srv Server) {
defer func() {
if sem != nil {
sem.DeferWorker()
}
}()
if srv != nil {
srv.Restart()
}
}
func (p pool) runMapShutdown(sem libsem.Sem, srv Server) {
defer func() {
if sem != nil {
sem.DeferWorker()
}
}()
if srv != nil {
srv.Shutdown()
}
}
func (p pool) Restart() {
p.runMapCommand(p.runMapRestart)
}
func (p pool) Shutdown() {
p.runMapCommand(p.runMapShutdown)
}
func (p pool) StatusInfo(bindAddress string) (name string, release string, hash string) {
if s := p.Get(bindAddress); s != nil {
return s.StatusInfo()
}
return fmt.Sprintf("missing server '%s'", bindAddress), "", ""
}
func (p pool) StatusHealth(bindAddress string) error {
if s := p.Get(bindAddress); s != nil {
return s.StatusHealth()
}
//nolint #goerr113
return fmt.Errorf("missing server '%s'", bindAddress)
}
func (p pool) StatusRoute(keyPrefix string, fctMessage libsts.FctMessage, sts libsts.RouteStatus) {
p.MapRun(func(srv Server) {
bind := srv.GetBindable()
sts.ComponentNew(fmt.Sprintf("%s-%s", keyPrefix, bind), srv.StatusComponent(fctMessage))
})
}