This commit is contained in:
Eugeny Leonov 2022-07-03 19:37:48 +05:00
parent ccaeca3216
commit 258fd92243
6 changed files with 241 additions and 45 deletions

View File

@ -8,4 +8,7 @@ const (
ErrHeadersAlreadySent errs.ErrCode = "ErrHeadersAlreadySent"
ErrNoHandler errs.ErrCode = "ErrNoHandler"
ErrRouteNotFound errs.ErrCode = "ErrRouteNotFound"
ErrPanic errs.ErrCode = "ErrPanic"
ErrJSONError errs.ErrCode = "ErrJSONError"
ErrConfigError errs.ErrCode = "ErrConfigError"
)

16
go.mod
View File

@ -3,13 +3,18 @@ module git.leolab.info/lib/httpsrv
go 1.18
require (
git.leolab.info/lib/errs v0.1.3
git.leolab.info/lib/errs v0.1.4-0.20220629142621-07c17c7f1fd6
git.leolab.info/lib/file v0.1.4
git.leolab.info/lib/logger v0.1.4
git.leolab.info/lib/sess v0.1.2
git.leolab.info/lib/sess v0.1.3-0.20220629143050-d7327da7b17a
gorm.io/datatypes v1.0.6
gorm.io/driver/mysql v1.3.4
gorm.io/driver/postgres v1.3.7
gorm.io/driver/sqlite v1.3.2
gorm.io/gorm v1.23.5
)
require (
git.leolab.info/lib/file v0.1.4 // indirect
git.leolab.info/lib/replacer v1.0.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
@ -27,9 +32,4 @@ require (
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
gorm.io/datatypes v1.0.6 // indirect
gorm.io/driver/mysql v1.3.4 // indirect
gorm.io/driver/postgres v1.3.7 // indirect
gorm.io/driver/sqlite v1.3.2 // indirect
gorm.io/gorm v1.23.5 // indirect
)

22
go.sum
View File

@ -1,21 +1,21 @@
git.leolab.info/lib/errs v0.1.3-0.20220620092627-3c0add8c6bf0 h1:Cnx2R4xots6Lj+1bFN+W95r0C8nnb8h3cm7XXaApAdE=
git.leolab.info/lib/errs v0.1.3-0.20220620092627-3c0add8c6bf0/go.mod h1:ZRDe0VxOtua1Da5RMolHxar4o5FQszm3yRVICE7nO2Y=
git.leolab.info/lib/errs v0.1.3 h1:Xl+SG+1v70SM+rzvX0cH0phkcQ9YRvvlEDWfXKaYTZk=
git.leolab.info/lib/errs v0.1.3/go.mod h1:ZRDe0VxOtua1Da5RMolHxar4o5FQszm3yRVICE7nO2Y=
git.leolab.info/lib/file v0.1.3 h1:RMs3nGkvqIQ6WRJQ61WCRefbUsN8iTrdxR9EviU+njE=
git.leolab.info/lib/file v0.1.3/go.mod h1:bYuqZ1iYODbblArnWfo2FUeSs8V26XB9DfrqBiblguw=
git.leolab.info/lib/errs v0.1.4-0.20220622102805-362678ebf196 h1:bvesIM/0+07qYfP0GAHeE8+dByBbmRW768nXLQa0Yzw=
git.leolab.info/lib/errs v0.1.4-0.20220622102805-362678ebf196/go.mod h1:ZRDe0VxOtua1Da5RMolHxar4o5FQszm3yRVICE7nO2Y=
git.leolab.info/lib/errs v0.1.4-0.20220629085318-4c913950dd07 h1:ZOgNEfPqOSIt+H3oGTKmRqunQEscag1jsVRYxsJQK6k=
git.leolab.info/lib/errs v0.1.4-0.20220629085318-4c913950dd07/go.mod h1:ZRDe0VxOtua1Da5RMolHxar4o5FQszm3yRVICE7nO2Y=
git.leolab.info/lib/errs v0.1.4-0.20220629094124-65d5855c4b57 h1:+Xz5V+bgqaz9ZqbOj/9gNbGixSAmjPtZqTCrW0HB0DU=
git.leolab.info/lib/errs v0.1.4-0.20220629094124-65d5855c4b57/go.mod h1:ZRDe0VxOtua1Da5RMolHxar4o5FQszm3yRVICE7nO2Y=
git.leolab.info/lib/errs v0.1.4-0.20220629142621-07c17c7f1fd6 h1:M4/JTm9I3ntihd3L0JEPzn8jmT26KiN9a4+vY+DKsQE=
git.leolab.info/lib/errs v0.1.4-0.20220629142621-07c17c7f1fd6/go.mod h1:ZRDe0VxOtua1Da5RMolHxar4o5FQszm3yRVICE7nO2Y=
git.leolab.info/lib/file v0.1.4 h1:pbz9Z+2ir7Y8fcVRq/w1kd1q9w0gtUb18ds4HLvPktU=
git.leolab.info/lib/file v0.1.4/go.mod h1:DDTNOvdqbgGJsac0YIOr3qQ4AMK5rK349lBLCDX3I9U=
git.leolab.info/lib/logger v0.1.4-0.20220620083644-8fc3c6c28d21 h1:f9CXBXMtEmYWJ5Vg2CRy+O1hlAOnY2gJYSjva1ScSOw=
git.leolab.info/lib/logger v0.1.4-0.20220620083644-8fc3c6c28d21/go.mod h1:PUwkv1pVHzadjc+XpmPjOeyLqtxl39NEzA2ZCUA2Jvs=
git.leolab.info/lib/logger v0.1.4 h1:UutvWYy4pi71hGn3lgVE55HOwS1h6yQj78h/8G+hx08=
git.leolab.info/lib/logger v0.1.4/go.mod h1:f01A2OrmNb9GTNru2HJMBLBVfM2eO/aIBAWVigT66Tk=
git.leolab.info/lib/replacer v1.0.0 h1:fpffzb4ku/8jW2v88Z8DJN+tRC2K6x2Im/725EzxYtI=
git.leolab.info/lib/replacer v1.0.0/go.mod h1:lhOdX1HTgZm+yda+85+A9xDJchxnTtES29vQL/HpCIs=
git.leolab.info/lib/sess v0.1.1-0.20220620095111-8e13e94ded27 h1:Nfxhil9FlrVwXtWB5SVE2Zy/n/uMZFZSqLRS6kN2+FM=
git.leolab.info/lib/sess v0.1.1-0.20220620095111-8e13e94ded27/go.mod h1:PffLlFnvu8gBhPj3XHmu1O3AXHh8HSFRsWF8bqAPh8I=
git.leolab.info/lib/sess v0.1.2 h1:45lWb+YUwd0ilJKKUc5iPjyjo+UhpRb5ZU48UwBomq4=
git.leolab.info/lib/sess v0.1.2/go.mod h1:a7QwiuUjxwNR3HEv7Lxj/CZnC+uFCQErpWg/BcnAb+A=
git.leolab.info/lib/sess v0.1.3-0.20220629143050-d7327da7b17a h1:Gfbc5c00Nz0bgj8zDmjXhsEh8/bBk/nixxj+ULpWFRA=
git.leolab.info/lib/sess v0.1.3-0.20220629143050-d7327da7b17a/go.mod h1:Z+my+uw/gmjIQLSrxLgErtQRcD50AKDCbZGv70+QIgg=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
@ -229,8 +229,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -9,16 +9,32 @@ import (
"strings"
"sync"
"git.leolab.info/lib/file"
"git.leolab.info/lib/logger"
"git.leolab.info/lib/sess"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"git.leolab.info/lib/errs"
)
//Конфигурация
// 0.1.1
type Config struct {
DSN string
Sess sess.Config
}
//Основная модель
// 0.1.1
type Server struct {
Dbg bool
log *logger.Logger
hande HandleFunc
Dbg bool
log *logger.Logger
ds *gorm.DB
hande Handler
routes *route
lst net.Listener
@ -31,8 +47,10 @@ type Server struct {
proc chan interface{}
}
type Handler func(sr *Rec) *Rec
type Handler func(*Rec) *Rec
//Создание экземпляра сервера
// 0.1.0
func New(lst net.Listener, log *logger.Logger) (srv *Server, err *errs.Err) {
if lst == nil {
return nil, errs.RaiseError(errs.ErrError, "net.Listener in NIL")
@ -54,73 +72,109 @@ func New(lst net.Listener, log *logger.Logger) (srv *Server, err *errs.Err) {
return srv, nil
}
func (s *Server) Init(cfg *sess.Config) (err *errs.Err) {
//Инициализация сервера
// 0.1.1
func (s *Server) Init(cfg *Config) (err *errs.Err) {
s.log.Debug("Initializing...")
s.ses, err = sess.NewStore(cfg)
s.ses, err = sess.NewStore(&cfg.Sess)
if err != nil {
return errs.RaiseError(err.Code, err.Msg)
//return errs.UpError(err)
}
s.ds, err = GetDS(cfg.DSN)
if err != nil {
return errs.UpError(err)
}
s.log.Debug("Initialized")
return nil
}
//Add static route for path
// 0.1.0
func (s *Server) Static(path string, fs fs.FS) {
s.log.Debug("Add static route: " + path)
s.mux.Handle(path, http.FileServer(http.FS(fs)))
}
//Add route
func (s *Server) AddRoute(path string, fn HandleFunc) {
// 0.1.0
func (s *Server) AddRoute(path string, fn Handler) {
s.log.Debug("Add route: " + path)
s.routes.add(path, fn)
}
//Default handler, called when no one other hired
func (s *Server) DefaultHandle(fn HandleFunc) {
// 0.1.0
func (s *Server) DefaultHandle(fn Handler) {
s.hande = fn
}
//Запуск сервера
// 0.1.0
func (s *Server) Start() {
s.log.Debug("Starting...")
s.log.Info("Starting...")
s.mux.HandleFunc("/", s.reqHandler)
s.wg.Add(1)
go s.exec()
}
//Останов сервера
// 0.1.0
func (s *Server) Stop() {
s.log.Debug("Stopping...")
s.log.Info("Stopping...")
close(s.proc)
s.wg.Wait()
s.log.Debug("Stopped")
s.log.Info("Stopped")
}
//Выполнение сервера
// 0.1.0
func (s *Server) exec() {
defer s.wg.Done()
s.srv.Handler = s.mux
go func() {
if e := s.srv.Serve(s.lst); e != nil {
errs.RaiseError(ErrServerError, e.Error())
err := errs.RaiseError(ErrServerError, e.Error())
s.log.Error(err.Error(), map[string]interface{}{"Error": err})
}
}()
s.log.Debug("Started")
s.log.Info("Started")
<-s.proc
s.log.Debug("Finishing...")
s.log.Info("Finishing...")
}
//Основной обработчик
// 0.1.1
func (s *Server) reqHandler(w http.ResponseWriter, r *http.Request) {
rl := rlStart(s)
/*
defer func(rl *ReqRec) {
if r := recover(); r != nil {
er := errs.RaiseError(ErrPanic, "Panic while proceed '"+rl.ReqUrl+"' request")
s.log.Error(er.Error(), map[string]interface{}{"Panic": r})
rl.Finish(er)
} else {
rl.Finish(nil)
}
}(rl)
*/
if r.URL.Path[len(r.URL.Path)-1] == '/' {
r.URL.Path = r.URL.Path + "index"
}
rl.ReqUrl = r.URL.Path
fa := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/")
if len(fa) == 0 {
fa = append(fa, "index")
}
rl.Addr = r.RemoteAddr
rl.XAddr = r.Header.Get("X-Forwarded-For")
rl.UserAgent = r.Header.Get("User-Agent")
b, e := ioutil.ReadAll(r.Body)
if e != nil {
errs.RaiseError(ErrReadBodyError, e.Error()+" for: "+r.URL.Path)
er := errs.RaiseError(ErrReadBodyError, e.Error()+" for: "+r.URL.Path)
s.log.Error(er.Error())
b = make([]byte, 0)
}
sr := &Rec{
@ -139,21 +193,23 @@ func (s *Server) reqHandler(w http.ResponseWriter, r *http.Request) {
},
Data: make(map[string]interface{}),
//Sess: s._ses.Get(w, r),
_content: make([]byte, 0),
_sent: false,
}
var err *errs.Err
sr.Sess, err = s.ses.Get(w, r)
if err != nil {
errs.RaiseError(err.Code, "Session ERROR: "+err.Error())
er := errs.RaiseError(err.Code, "Session ERROR: "+err.Error(), err)
s.log.Error("Session ERROR: " + err.Error())
rsp := Error(sr, "Session error: "+err.Error())
rsp := Error(sr, er.Error())
rsp.Sent()
rl.Finish(er)
return
}
rl.SSID = sr.Sess.SSID
s.log.Debug("Request: " + sr.Req.Lnk)
//s.log.Debug("Request: " + sr.Req.Lnk)
if e := json.Unmarshal(b, &sr.Req.Data); e != nil {
//No JSON data in body, or wrong
@ -162,19 +218,62 @@ func (s *Server) reqHandler(w http.ResponseWriter, r *http.Request) {
} else {
s.log.Debug("JSON Data for "+sr.Req.Lnk, sr.Req.Data)
}
rl.ReqData = sr.Req.Data
q := r.URL.Query()
for k, v := range q {
sr.Req.Vars[k] = strings.Join(v, ",")
}
for k, v := range sr.Req.Vars {
rl.ReqVars[k] = v
}
rsp := s.routes.Do(sr)
if rsp == nil {
errs.RaiseError(ErrServerError, "NIL response")
er := errs.RaiseError(ErrServerError, "NIL response")
s.log.Error("NIL response: " + sr.Req.Lnk)
w.WriteHeader(500)
w.Write([]byte(errs.RaiseError(ErrServerError, "NIL response").Error()))
w.Write([]byte(er.Error()))
rl.Finish(er)
return
}
if !rsp._sent {
rsp.Sent()
}
rl.Finish(nil)
}
func GetDS(dsn string) (ds *gorm.DB, err *errs.Err) {
if dsn == "" {
return nil, nil
}
var e error
cfg := &gorm.Config{}
dsa := strings.Split(dsn, ">")
if len(dsa) < 2 {
return nil, errs.RaiseError(ErrConfigError, "Invalid DSN: "+dsn)
}
switch dsa[0] {
case "sqlite":
ds, e = gorm.Open(sqlite.Open(file.RealPath(dsa[1])), cfg)
if e != nil {
return nil, errs.RaiseError(errs.ErrDSError, e.Error())
}
case "mysql":
ds, e = gorm.Open(mysql.Open(dsa[1]), cfg)
if e != nil {
return nil, errs.RaiseError(errs.ErrDSError, e.Error())
}
case "postgres":
ds, e = gorm.Open(postgres.New(postgres.Config{
DSN: dsa[1],
}), cfg)
if e != nil {
return nil, errs.RaiseError(errs.ErrDSError, e.Error())
}
default:
return nil, errs.RaiseError(errs.ErrDSError, "Incorrect DSN type: "+dsa[0])
}
if e := ds.AutoMigrate(&ReqRec{}); e != nil {
return nil, errs.RaiseError(errs.ErrDSError, e.Error())
}
return ds, nil
}

86
reqlog.go Normal file
View File

@ -0,0 +1,86 @@
package httpsrv
import (
"encoding/json"
"time"
"git.leolab.info/lib/errs"
"git.leolab.info/lib/file"
"gorm.io/datatypes"
)
//Структура записи лога запросов
// 0.1.1
type ReqRec struct {
SSID string
Addr string
XAddr string
UserAgent string
ReqUrl string
ReqVars datatypes.JSONMap //map[string]string
ReqData datatypes.JSONMap //map[string]interface{}
TimeStart time.Time
TimeFinish time.Time
Duration time.Duration
Err datatypes.JSONMap
s *Server
}
//Преобразовать в JSON
// 0.1.1
func (rl *ReqRec) MarshalJSON() ([]byte, error) {
jm := map[string]interface{}{
"SSID": rl.SSID,
"Addr": rl.Addr,
"XAddr": rl.XAddr,
"UserAgent": rl.UserAgent,
"ReqUrl": rl.ReqUrl,
"ReqVars": rl.ReqVars,
"ReqData": rl.ReqData,
"TimeStart": rl.TimeStart,
"TimeFinish": rl.TimeFinish,
"Duration": rl.Duration,
"Err": rl.Err,
}
jb, e := json.MarshalIndent(jm, "", "\t")
if e != nil {
errs.RaiseError(ErrJSONError, e.Error())
return nil, e
}
return jb, nil
}
//Преобразовать в строку
// 0.1.1
func (rl *ReqRec) toString() string {
return rl.TimeStart.Format("02-01-06 03:04:05.000") + " [" + rl.Duration.String() + "] " + rl.SSID + " " + rl.ReqUrl
}
//Начать запись лога
// 0.1.1
func rlStart(srv *Server) *ReqRec {
return &ReqRec{
TimeStart: time.Now(),
s: srv,
ReqVars: map[string]interface{}{},
ReqData: map[string]interface{}{},
}
}
//Завершить запись лога
// 0.1.1
func (rl *ReqRec) Finish(err *errs.Err) {
if err != nil {
rl.Err = err.Map()
} else {
rl.Err = nil
}
rl.TimeFinish = time.Now()
rl.Duration = rl.TimeFinish.Sub(rl.TimeStart)
file.Append(file.GetBinDir()+file.GetBinName()+".reqlog", []byte(rl.toString()+"\n"))
if rl.s.ds != nil {
if e := rl.s.ds.Model(&ReqRec{}).Create(rl).Error; e != nil {
errs.RaiseError(errs.ErrDSError, e.Error())
}
}
}

View File

@ -6,15 +6,17 @@ import (
"git.leolab.info/lib/errs"
)
type HandleFunc func(sr *Rec) *Rec
//Структура маршрута
// 0.1.0
type route struct {
value string
next map[string]*route
handler HandleFunc
handler Handler
s *Server
}
//Создание маршрута
// 0.1.0
func newRoute(srv *Server) *route {
r := &route{
s: srv,
@ -24,7 +26,9 @@ func newRoute(srv *Server) *route {
return r
}
func (r *route) add(path string, h HandleFunc) {
//Добавление маршрута
// 0.1.0
func (r *route) add(path string, h Handler) {
if path[len(path)-1] == '/' {
path = path + "index"
}
@ -32,7 +36,9 @@ func (r *route) add(path string, h HandleFunc) {
r.append(pa, h)
}
func (r *route) append(pa []string, h HandleFunc) {
//Добавление маршрута
// 0.1.0
func (r *route) append(pa []string, h Handler) {
f := pa[0]
v := ""
pa = pa[1:]
@ -55,6 +61,8 @@ func (r *route) append(pa []string, h HandleFunc) {
}
}
//Выполнение запроса
// 0.1.0
func (r *route) Do(sr *Rec) *Rec {
f := sr.Next()
if f != "" {