From dde9f552a6bb0525811b2685a8e4e0947cf8e2a6 Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 12 Sep 2024 03:30:23 +0100 Subject: [PATCH 01/13] Isolate http specific parts to minimal --- cmd/http/main.go | 1 + internal/handlers/single.go | 47 +++++++ internal/http/server.go | 182 ++++++++++++++++---------- internal/{http => storage}/storage.go | 2 +- 4 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 internal/handlers/single.go rename internal/{http => storage}/storage.go (98%) diff --git a/cmd/http/main.go b/cmd/http/main.go index 7b085a8..819abca 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -112,6 +112,7 @@ func getResource(resourceDir string, ctx context.Context) (resource.Resource, er return rfs, nil } + func main() { var dbDir string var resourceDir string diff --git a/internal/handlers/single.go b/internal/handlers/single.go new file mode 100644 index 0000000..7b6c9db --- /dev/null +++ b/internal/handlers/single.go @@ -0,0 +1,47 @@ +package handlers + +import ( + "context" + "errors" + "io" + + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/persist" + + "git.grassecon.net/urdt/ussd/internal/storage" +) + +var ( + ErrInvalidRequest = errors.New("invalid request for context") + ErrSessionMissing = errors.New("missing session") + ErrInvalidInput = errors.New("invalid input") + ErrStorage = errors.New("storage retrieval fail") + ErrEngineType = errors.New("incompatible engine") + ErrEngineInit = errors.New("engine init fail") + ErrEngineExec = errors.New("engine exec fail") +) + +type RequestSession struct { + Ctx context.Context + Config engine.Config + Engine engine.Engine + Input []byte + Storage storage.Storage + Writer io.Writer +} + +type engineMaker func(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine + +type RequestParser interface { + GetSessionId(rq any) (string, error) + GetInput(rq any) ([]byte, error) +} + +type RequestHandler interface { + GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine + Process(rs RequestSession) (RequestSession, error) + Output(rs RequestSession) (RequestSession, error) + Reset(rs RequestSession) (RequestSession, error) + ShutDown() +} diff --git a/internal/http/server.go b/internal/http/server.go index 7d1d8fe..8425302 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -1,9 +1,9 @@ package http import ( - "fmt" "io/ioutil" "net/http" + "strconv" "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" @@ -11,32 +11,38 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" + "git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + "git.grassecon.net/urdt/ussd/internal/storage" ) var ( logg = logging.NewVanilla().WithDomain("httpserver") ) -type RequestParser interface { - GetSessionId(rq *http.Request) (string, error) - GetInput(rq *http.Request) ([]byte, error) -} - type DefaultRequestParser struct { } -func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) { - v := rq.Header.Get("X-Vise-Session") + +func(rp *DefaultRequestParser) GetSessionId(rq any) (string, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return "", handlers.ErrInvalidRequest + } + v := rqv.Header.Get("X-Vise-Session") if v == "" { - return "", fmt.Errorf("no session found") + return "", handlers.ErrSessionMissing } return v, nil } -func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) { - defer rq.Body.Close() - v, err := ioutil.ReadAll(rq.Body) +func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return nil, handlers.ErrInvalidRequest + } + defer rqv.Body.Close() + v, err := ioutil.ReadAll(rqv.Body) if err != nil { return nil, err } @@ -45,33 +51,30 @@ func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) { type SessionHandler struct { cfgTemplate engine.Config - rp RequestParser + rp handlers.RequestParser rs resource.Resource - //first resource.EntryFunc hn *ussd.Handlers - provider StorageProvider + provider storage.StorageProvider } -//func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, first resource.EntryFunc) *SessionHandler { -func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *SessionHandler { +func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp handlers.RequestParser, hn *ussd.Handlers) *SessionHandler { return &SessionHandler{ cfgTemplate: cfg, rs: rs, - //first: first, hn: hn, rp: rp, - provider: NewSimpleStorageProvider(stateDb, userdataDb), + provider: storage.NewSimpleStorageProvider(stateDb, userdataDb), } } -func(f *SessionHandler) writeError(w http.ResponseWriter, code int, msg string, err error) { - w.Header().Set("X-Vise", msg + ": " + err.Error()) - w.Header().Set("Content-Length", "0") +func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { + s := err.Error() + w.Header().Set("Content-Length", strconv.Itoa(len(s))) w.WriteHeader(code) _, err = w.Write([]byte{}) if err != nil { + logg.Errorf("error writing error!!", "err", err, "olderr", s) w.WriteHeader(500) - w.Header().Set("X-Vise", err.Error()) } return } @@ -83,68 +86,115 @@ func(f* SessionHandler) Shutdown() { } } -func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { +func(f *SessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { + en := engine.NewEngine(cfg, rs) + en = en.WithPersister(pr) + return en +} + +func(f *SessionHandler) Process(rqs handlers.RequestSession) (handlers.RequestSession, error) { var r bool - sessionId, err := f.rp.GetSessionId(req) - if err != nil { - f.writeError(w, 400, "Session missing", err) - return - } - input, err := f.rp.GetInput(req) - if err != nil { - f.writeError(w, 400, "Input read fail", err) - return - } - ctx := req.Context() - cfg := f.cfgTemplate - cfg.SessionId = sessionId + var err error + var ok bool + + logg.InfoCtxf(rqs.Ctx, "new request", rqs) - logg.InfoCtxf(ctx, "new request", "session", cfg.SessionId, "input", input) - - storage, err := f.provider.Get(cfg.SessionId) + rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) if err != nil { - f.writeError(w, 500, "Storage retrieval fail", err) - return + logg.ErrorCtxf(rqs.Ctx, "", "storage error", "err", err) + return rqs, handlers.ErrStorage + } + + f.hn = f.hn.WithPersister(rqs.Storage.Persister) + eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) + en, ok := eni.(*engine.DefaultEngine) + if !ok { + return rqs, handlers.ErrEngineType } - f.hn = f.hn.WithPersister(storage.Persister) - defer f.provider.Put(cfg.SessionId, storage) - en := getEngine(cfg, f.rs, storage.Persister) en = en.WithFirst(f.hn.Init) - if cfg.EngineDebug { + if rqs.Config.EngineDebug { en = en.WithDebug(nil) } + rqs.Engine = en - r, err = en.Init(ctx) + r, err = rqs.Engine.Init(rqs.Ctx) if err != nil { - f.writeError(w, 500, "Engine init fail", err) + return rqs, err + } + + if r && len(rqs.Input) > 0 { + r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input) + } + if err != nil { + return rqs, err + } + + _ = r + return rqs, nil +} + +func(f *SessionHandler) Output(rqs handlers.RequestSession) error { + var err error + _, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) + return err +} + +func(f *SessionHandler) Reset(rqs handlers.RequestSession) error { + defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) + return rqs.Engine.Finish() +} + +func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + var code int + var err error + + rqs := handlers.RequestSession{ + Ctx: req.Context(), + Writer: w, + } + + cfg := f.cfgTemplate + cfg.SessionId, err = f.rp.GetSessionId(req) + if err != nil { + logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) + f.writeError(w, 400, err) + } + rqs.Config = cfg + rqs.Input, err = f.rp.GetInput(req) + if err != nil { + logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) + f.writeError(w, 400, err) return } - if r && len(input) > 0 { - r, err = en.Exec(ctx, input) + + rqs, err = f.Process(rqs) + switch err { + case handlers.ErrStorage: + code = 500 + case handlers.ErrEngineInit: + code = 500 + case handlers.ErrEngineExec: + code = 500 + default: + code = 200 } - if err != nil { - f.writeError(w, 500, "Engine exec fail", err) + + if code != 200 { + f.writeError(w, 500, err) return } w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain") - _, err = en.WriteResult(ctx, w) + err = f.Output(rqs) if err != nil { - f.writeError(w, 500, "Write result fail", err) - return - } - err = en.Finish() - if err != nil { - f.writeError(w, 500, "Engine finish fail", err) + f.writeError(w, 500, err) return } - _ = r -} - -func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine { - en := engine.NewEngine(cfg, rs) - en = en.WithPersister(pr) - return en + err = f.Reset(rqs) + if err != nil { + f.writeError(w, 500, err) + return + } } diff --git a/internal/http/storage.go b/internal/storage/storage.go similarity index 98% rename from internal/http/storage.go rename to internal/storage/storage.go index 9b0cf44..d009fd0 100644 --- a/internal/http/storage.go +++ b/internal/storage/storage.go @@ -1,4 +1,4 @@ -package http +package storage import ( "git.defalsify.org/vise.git/db" -- 2.45.2 From d49f866ca4770d18a75f9d02556b6d15d65ed91d Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 12 Sep 2024 04:07:55 +0100 Subject: [PATCH 02/13] Factor out methods common to http and async cli --- cmd/async/main.go | 236 ++++++++++++++++++++++++++++++++++++ internal/handlers/base.go | 102 ++++++++++++++++ internal/handlers/single.go | 7 ++ internal/http/server.go | 96 ++------------- 4 files changed, 354 insertions(+), 87 deletions(-) create mode 100644 cmd/async/main.go create mode 100644 internal/handlers/base.go diff --git a/cmd/async/main.go b/cmd/async/main.go new file mode 100644 index 0000000..cd3a926 --- /dev/null +++ b/cmd/async/main.go @@ -0,0 +1,236 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path" + + "git.defalsify.org/vise.git/asm" + "git.defalsify.org/vise.git/db" + fsdb "git.defalsify.org/vise.git/db/fs" + gdbmdb "git.defalsify.org/vise.git/db/gdbm" + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/logging" + + "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + "git.grassecon.net/urdt/ussd/internal/handlers" +) + +var ( + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") +) + +type asyncRequestParser struct { + sessionId string + input []byte +} + +func(p *asyncRequestParser) GetSessionId(r any) (string, error) { + return p.sessionId, nil +} + +func(p *asyncRequestParser) GetInput(r any) ([]byte, error) { + return p.input, nil +} + +func getFlags(fp string, debug bool) (*asm.FlagParser, error) { + flagParser := asm.NewFlagParser().WithDebug() + _, err := flagParser.Load(fp) + if err != nil { + return nil, err + } + return flagParser, nil +} + +func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) { + + ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore) + if err != nil { + return nil, err + } + rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) + rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) + rs.AddLocalFunc("save_pin", ussdHandlers.SavePin) + rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) + rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) + rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus) + rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize) + rs.AddLocalFunc("quit", ussdHandlers.Quit) + rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) + rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) + rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) + rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) + rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) + rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) + rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient) + rs.AddLocalFunc("get_sender", ussdHandlers.GetSender) + rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount) + rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin) + rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname) + rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname) + rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender) + rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) + rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) + rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) + rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance) + rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) + rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) + rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) + rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) + rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) + rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit) + rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) + + return ussdHandlers, nil +} + +func ensureDbDir(dbDir string) error { + err := os.MkdirAll(dbDir, 0700) + if err != nil { + return fmt.Errorf("state dir create exited with error: %v\n", err) + } + return nil +} + +func getStateStore(dbDir string, ctx context.Context) (db.Db, error) { + store := gdbmdb.NewGdbmDb() + storeFile := path.Join(dbDir, "state.gdbm") + store.Connect(ctx, storeFile) + return store, nil +} + +func getUserdataDb(dbDir string, ctx context.Context) db.Db { + store := gdbmdb.NewGdbmDb() + storeFile := path.Join(dbDir, "userdata.gdbm") + store.Connect(ctx, storeFile) + + return store +} + +func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) { + store := fsdb.NewFsDb() + err := store.Connect(ctx, resourceDir) + if err != nil { + return nil, err + } + rfs := resource.NewDbResource(store) + return rfs, nil +} + + +func main() { + var sessionId string + var dbDir string + var resourceDir string + var size uint + var engineDebug bool + var stateDebug bool + var host string + var port uint + flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") + flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output") + flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output") + flag.UintVar(&size, "s", 160, "max size of output") + flag.StringVar(&host, "h", "127.0.0.1", "http host") + flag.UintVar(&port, "p", 7123, "http port") + flag.Parse() + + logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId) + + ctx := context.Background() + pfp := path.Join(scriptDir, "pp.csv") + flagParser, err := getFlags(pfp, true) + + if err != nil { + os.Exit(1) + } + + cfg := engine.Config{ + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(16), + } + if stateDebug { + cfg.StateDebug = true + } + if engineDebug { + cfg.EngineDebug = true + } + + rs, err := getResource(resourceDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + err = ensureDbDir(dbDir) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + userdataStore := getUserdataDb(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + defer userdataStore.Close() + + dbResource, ok := rs.(*resource.DbResource) + if !ok { + os.Exit(1) + } + + hl, err := getHandler(flagParser, dbResource, userdataStore) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + stateStore, err := getStateStore(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + defer stateStore.Close() + + rp := &asyncRequestParser{ + sessionId: sessionId, + } + sh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) + cfg.SessionId = sessionId + rqs := handlers.RequestSession{ + Ctx: ctx, + Writer: os.Stdout, + Config: cfg, + } + for true { + rqs, err = sh.Process(rqs) + if err != nil { + fmt.Errorf("error in process: %v", err) + os.Exit(1) + } + rqs, err = sh.Output(rqs) + if err != nil { + fmt.Errorf("error in output: %v", err) + os.Exit(1) + } + rqs, err = sh.Reset(rqs) + if err != nil { + fmt.Errorf("error in reset: %v", err) + os.Exit(1) + } + fmt.Println("") + _, err = fmt.Scanln(&rqs.Input) + if err != nil { + fmt.Errorf("error in input: %v", err) + os.Exit(1) + } + } +} diff --git a/internal/handlers/base.go b/internal/handlers/base.go new file mode 100644 index 0000000..fba62c9 --- /dev/null +++ b/internal/handlers/base.go @@ -0,0 +1,102 @@ +package handlers + +import ( + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/persist" + "git.defalsify.org/vise.git/db" + + "git.grassecon.net/urdt/ussd/internal/storage" + "git.grassecon.net/urdt/ussd/internal/handlers/ussd" +) + +type BaseSessionHandler struct { + cfgTemplate engine.Config + rp RequestParser + rs resource.Resource + hn *ussd.Handlers + provider storage.StorageProvider +} + +func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *BaseSessionHandler { + return &BaseSessionHandler{ + cfgTemplate: cfg, + rs: rs, + hn: hn, + rp: rp, + provider: storage.NewSimpleStorageProvider(stateDb, userdataDb), + } +} + +func(f* BaseSessionHandler) Shutdown() { + err := f.provider.Close() + if err != nil { + logg.Errorf("handler shutdown error", "err", err) + } +} + +func(f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { + en := engine.NewEngine(cfg, rs) + en = en.WithPersister(pr) + return en +} + +func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) { + var r bool + var err error + var ok bool + + logg.InfoCtxf(rqs.Ctx, "new request", rqs) + + rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) + if err != nil { + logg.ErrorCtxf(rqs.Ctx, "", "storage error", "err", err) + return rqs, ErrStorage + } + + f.hn = f.hn.WithPersister(rqs.Storage.Persister) + eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) + en, ok := eni.(*engine.DefaultEngine) + if !ok { + return rqs, ErrEngineType + } + en = en.WithFirst(f.hn.Init) + if rqs.Config.EngineDebug { + en = en.WithDebug(nil) + } + rqs.Engine = en + + r, err = rqs.Engine.Init(rqs.Ctx) + if err != nil { + return rqs, err + } + + if r && len(rqs.Input) > 0 { + r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input) + } + if err != nil { + return rqs, err + } + + _ = r + return rqs, nil +} + +func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) { + var err error + _, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) + return rqs, err +} + +func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) { + defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) + return rqs, rqs.Engine.Finish() +} + +func(f *BaseSessionHandler) GetConfig() engine.Config { + return f.cfgTemplate +} + +func(f *BaseSessionHandler) GetRequestParser() RequestParser { + return f.rp +} diff --git a/internal/handlers/single.go b/internal/handlers/single.go index 7b6c9db..40b0594 100644 --- a/internal/handlers/single.go +++ b/internal/handlers/single.go @@ -8,10 +8,15 @@ import ( "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/persist" + "git.defalsify.org/vise.git/logging" "git.grassecon.net/urdt/ussd/internal/storage" ) +var ( + logg = logging.NewVanilla().WithDomain("handlers") +) + var ( ErrInvalidRequest = errors.New("invalid request for context") ErrSessionMissing = errors.New("missing session") @@ -39,6 +44,8 @@ type RequestParser interface { } type RequestHandler interface { + GetConfig() engine.Config + GetRequestParser() RequestParser GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine Process(rs RequestSession) (RequestSession, error) Output(rs RequestSession) (RequestSession, error) diff --git a/internal/http/server.go b/internal/http/server.go index 8425302..af5413a 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -5,15 +5,9 @@ import ( "net/http" "strconv" - "git.defalsify.org/vise.git/db" - "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/logging" - "git.defalsify.org/vise.git/persist" - "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/internal/handlers" - "git.grassecon.net/urdt/ussd/internal/handlers/ussd" - "git.grassecon.net/urdt/ussd/internal/storage" ) var ( @@ -50,20 +44,12 @@ func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { } type SessionHandler struct { - cfgTemplate engine.Config - rp handlers.RequestParser - rs resource.Resource - hn *ussd.Handlers - provider storage.StorageProvider + handlers.RequestHandler } -func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp handlers.RequestParser, hn *ussd.Handlers) *SessionHandler { +func ToSessionHandler(h handlers.RequestHandler) *SessionHandler { return &SessionHandler{ - cfgTemplate: cfg, - rs: rs, - hn: hn, - rp: rp, - provider: storage.NewSimpleStorageProvider(stateDb, userdataDb), + RequestHandler: h, } } @@ -79,71 +65,6 @@ func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { return } -func(f* SessionHandler) Shutdown() { - err := f.provider.Close() - if err != nil { - logg.Errorf("handler shutdown error", "err", err) - } -} - -func(f *SessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { - en := engine.NewEngine(cfg, rs) - en = en.WithPersister(pr) - return en -} - -func(f *SessionHandler) Process(rqs handlers.RequestSession) (handlers.RequestSession, error) { - var r bool - var err error - var ok bool - - logg.InfoCtxf(rqs.Ctx, "new request", rqs) - - rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) - if err != nil { - logg.ErrorCtxf(rqs.Ctx, "", "storage error", "err", err) - return rqs, handlers.ErrStorage - } - - f.hn = f.hn.WithPersister(rqs.Storage.Persister) - eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) - en, ok := eni.(*engine.DefaultEngine) - if !ok { - return rqs, handlers.ErrEngineType - } - en = en.WithFirst(f.hn.Init) - if rqs.Config.EngineDebug { - en = en.WithDebug(nil) - } - rqs.Engine = en - - r, err = rqs.Engine.Init(rqs.Ctx) - if err != nil { - return rqs, err - } - - if r && len(rqs.Input) > 0 { - r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input) - } - if err != nil { - return rqs, err - } - - _ = r - return rqs, nil -} - -func(f *SessionHandler) Output(rqs handlers.RequestSession) error { - var err error - _, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) - return err -} - -func(f *SessionHandler) Reset(rqs handlers.RequestSession) error { - defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) - return rqs.Engine.Finish() -} - func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var code int var err error @@ -153,14 +74,15 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { Writer: w, } - cfg := f.cfgTemplate - cfg.SessionId, err = f.rp.GetSessionId(req) + rp := f.GetRequestParser() + cfg := f.GetConfig() + cfg.SessionId, err = rp.GetSessionId(req) if err != nil { logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) f.writeError(w, 400, err) } rqs.Config = cfg - rqs.Input, err = f.rp.GetInput(req) + rqs.Input, err = rp.GetInput(req) if err != nil { logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) f.writeError(w, 400, err) @@ -186,13 +108,13 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain") - err = f.Output(rqs) + rqs, err = f.Output(rqs) if err != nil { f.writeError(w, 500, err) return } - err = f.Reset(rqs) + rqs, err = f.Reset(rqs) if err != nil { f.writeError(w, 500, err) return -- 2.45.2 From b31d3b907a901f7a1ad17df4c8589652853b792d Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 12 Sep 2024 04:13:57 +0100 Subject: [PATCH 03/13] Add shutdown to async, rehabilitate http cmd --- cmd/async/main.go | 15 +++++++++++++++ cmd/http/main.go | 4 +++- internal/handlers/single.go | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cmd/async/main.go b/cmd/async/main.go index cd3a926..b40f29f 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -5,7 +5,9 @@ import ( "flag" "fmt" "os" + "os/signal" "path" + "syscall" "git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/db" @@ -210,6 +212,19 @@ func main() { Writer: os.Stdout, Config: cfg, } + + cint := make(chan os.Signal) + cterm := make(chan os.Signal) + signal.Notify(cint, os.Interrupt, syscall.SIGINT) + signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) + go func() { + select { + case _ = <-cint: + case _ = <-cterm: + } + sh.Shutdown() + }() + for true { rqs, err = sh.Process(rqs) if err != nil { diff --git a/cmd/http/main.go b/cmd/http/main.go index 819abca..2006a47 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -20,6 +20,7 @@ import ( "git.defalsify.org/vise.git/logging" "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + "git.grassecon.net/urdt/ussd/internal/handlers" httpserver "git.grassecon.net/urdt/ussd/internal/http" ) @@ -191,7 +192,8 @@ func main() { rp := &httpserver.DefaultRequestParser{} //sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init) - sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) + bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) + sh := httpserver.ToSessionHandler(bsh) s := &http.Server{ Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Handler: sh, diff --git a/internal/handlers/single.go b/internal/handlers/single.go index 40b0594..3910be8 100644 --- a/internal/handlers/single.go +++ b/internal/handlers/single.go @@ -38,6 +38,7 @@ type RequestSession struct { type engineMaker func(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine +// TODO: seems like can remove this. type RequestParser interface { GetSessionId(rq any) (string, error) GetInput(rq any) ([]byte, error) @@ -50,5 +51,5 @@ type RequestHandler interface { Process(rs RequestSession) (RequestSession, error) Output(rs RequestSession) (RequestSession, error) Reset(rs RequestSession) (RequestSession, error) - ShutDown() + Shutdown() } -- 2.45.2 From 9c751aff30e77ed959982d6ffb39bff96f1170c3 Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 12 Sep 2024 04:19:13 +0100 Subject: [PATCH 04/13] Update go-vise --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 71730c4..a05d09c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.22.6 require ( - git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7 + git.defalsify.org/vise.git 0d23e0dbb57fe63b6626527fddc86649cfc20f8f github.com/alecthomas/assert/v2 v2.2.2 gopkg.in/leonelquinteros/gotext.v1 v1.3.1 ) -- 2.45.2 From ffeb28e851af4c4eddca76b6d93ba4da6389e38e Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 12 Sep 2024 15:56:34 +0300 Subject: [PATCH 05/13] Use latest go-vise package --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a05d09c..e317ed4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.22.6 require ( - git.defalsify.org/vise.git 0d23e0dbb57fe63b6626527fddc86649cfc20f8f + git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f github.com/alecthomas/assert/v2 v2.2.2 gopkg.in/leonelquinteros/gotext.v1 v1.3.1 ) -- 2.45.2 From 5e4e6a21a054a43cc3c4f667a233bba16b55c36a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 12 Sep 2024 15:56:57 +0300 Subject: [PATCH 06/13] Added africastalking binary --- cmd/africastalking/main.go | 257 +++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 cmd/africastalking/main.go diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go new file mode 100644 index 0000000..c5016dc --- /dev/null +++ b/cmd/africastalking/main.go @@ -0,0 +1,257 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "path" + "strconv" + "strings" + "syscall" + + "git.defalsify.org/vise.git/asm" + "git.defalsify.org/vise.git/db" + fsdb "git.defalsify.org/vise.git/db/fs" + gdbmdb "git.defalsify.org/vise.git/db/gdbm" + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/logging" + "git.defalsify.org/vise.git/resource" + + "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + httpserver "git.grassecon.net/urdt/ussd/internal/http" +) + +var ( + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") +) + +type atRequestParser struct {} + +func(arp *atRequestParser) GetSessionId(rq any) (string, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return "", handlers.ErrInvalidRequest + } + if err := rqv.ParseForm(); err != nil { + return "", fmt.Errorf("failed to parse form data: %v", err) + } + + phoneNumber := rqv.FormValue("phoneNumber") + if phoneNumber == "" { + return "", fmt.Errorf("no phone number found") + } + + return phoneNumber, nil +} + +func(arp *atRequestParser) GetInput(rq any) ([]byte, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return nil, handlers.ErrInvalidRequest + } + if err := rqv.ParseForm(); err != nil { + return nil, fmt.Errorf("failed to parse form data: %v", err) + } + + text := rqv.FormValue("text") + + parts := strings.Split(text, "*") + if len(parts) == 0 { + return nil, fmt.Errorf("no input found") + } + + return []byte(parts[len(parts)-1]), nil +} + + +func getFlags(fp string, debug bool) (*asm.FlagParser, error) { + flagParser := asm.NewFlagParser().WithDebug() + _, err := flagParser.Load(fp) + if err != nil { + return nil, err + } + return flagParser, nil +} + +func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) { + + ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore) + if err != nil { + return nil, err + } + rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) + rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) + rs.AddLocalFunc("save_pin", ussdHandlers.SavePin) + rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) + rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) + rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus) + rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize) + rs.AddLocalFunc("quit", ussdHandlers.Quit) + rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) + rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) + rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) + rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) + rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) + rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) + rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient) + rs.AddLocalFunc("get_sender", ussdHandlers.GetSender) + rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount) + rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin) + rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname) + rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname) + rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender) + rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) + rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) + rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) + rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance) + rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) + rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) + rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) + rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) + rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) + rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit) + rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) + + return ussdHandlers, nil +} + +func ensureDbDir(dbDir string) error { + err := os.MkdirAll(dbDir, 0700) + if err != nil { + return fmt.Errorf("state dir create exited with error: %v\n", err) + } + return nil +} + +func getStateStore(dbDir string, ctx context.Context) (db.Db, error) { + store := gdbmdb.NewGdbmDb() + storeFile := path.Join(dbDir, "state.gdbm") + store.Connect(ctx, storeFile) + return store, nil +} + +func getUserdataDb(dbDir string, ctx context.Context) db.Db { + store := gdbmdb.NewGdbmDb() + storeFile := path.Join(dbDir, "userdata.gdbm") + store.Connect(ctx, storeFile) + + return store +} + +func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) { + store := fsdb.NewFsDb() + err := store.Connect(ctx, resourceDir) + if err != nil { + return nil, err + } + rfs := resource.NewDbResource(store) + return rfs, nil +} + + +func main() { + var dbDir string + var resourceDir string + var size uint + var engineDebug bool + var stateDebug bool + var host string + var port uint + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") + flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output") + flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output") + flag.UintVar(&size, "s", 160, "max size of output") + flag.StringVar(&host, "h", "127.0.0.1", "http host") + flag.UintVar(&port, "p", 7123, "http port") + flag.Parse() + + logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) + + ctx := context.Background() + pfp := path.Join(scriptDir, "pp.csv") + flagParser, err := getFlags(pfp, true) + + if err != nil { + os.Exit(1) + } + + cfg := engine.Config{ + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(16), + } + if stateDebug { + cfg.StateDebug = true + } + if engineDebug { + cfg.EngineDebug = true + } + + rs, err := getResource(resourceDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + err = ensureDbDir(dbDir) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + userdataStore := getUserdataDb(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + defer userdataStore.Close() + + dbResource, ok := rs.(*resource.DbResource) + if !ok { + os.Exit(1) + } + + hl, err := getHandler(flagParser, dbResource, userdataStore) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + stateStore, err := getStateStore(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + defer stateStore.Close() + + rp := &atRequestParser{} + bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) + sh := httpserver.ToSessionHandler(bsh) + s := &http.Server{ + Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), + Handler: sh, + } + s.RegisterOnShutdown(sh.Shutdown) + + cint := make(chan os.Signal) + cterm := make(chan os.Signal) + signal.Notify(cint, os.Interrupt, syscall.SIGINT) + signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) + go func() { + select { + case _ = <-cint: + case _ = <-cterm: + } + s.Shutdown(ctx) + }() + err = s.ListenAndServe() + if err != nil { + logg.Infof("Server closed with error", "err", err) + } +} -- 2.45.2 From 9b4a4eeaf44a134118ff5d3658dd5a3cb24f3c3e Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 12 Sep 2024 16:46:11 +0100 Subject: [PATCH 07/13] Temporary solution for make sure storage object gets put back in all cases of execution --- cmd/http/main.go | 1 - go.mod | 2 +- go.sum | 4 ++-- internal/handlers/base.go | 17 ++++++++++++++++- internal/handlers/single.go | 2 +- internal/http/server.go | 8 ++++---- internal/storage/storage.go | 12 ++++++------ 7 files changed, 30 insertions(+), 16 deletions(-) diff --git a/cmd/http/main.go b/cmd/http/main.go index 2006a47..1e132b4 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -191,7 +191,6 @@ func main() { defer stateStore.Close() rp := &httpserver.DefaultRequestParser{} - //sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init) bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) sh := httpserver.ToSessionHandler(bsh) s := &http.Server{ diff --git a/go.mod b/go.mod index a05d09c..e317ed4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.22.6 require ( - git.defalsify.org/vise.git 0d23e0dbb57fe63b6626527fddc86649cfc20f8f + git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f github.com/alecthomas/assert/v2 v2.2.2 gopkg.in/leonelquinteros/gotext.v1 v1.3.1 ) diff --git a/go.sum b/go.sum index b40a422..1c00969 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7 h1:embPZDx0Sgpq6jp9vcZ1GVI0eum3PsPCmAfxAa/1KLI= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f h1:CuJvG3NyMoRtHUim4aZdrfjjJBg2AId7z0yp7Q97bRM= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= diff --git a/internal/handlers/base.go b/internal/handlers/base.go index fba62c9..9f5c849 100644 --- a/internal/handlers/base.go +++ b/internal/handlers/base.go @@ -50,7 +50,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) if err != nil { - logg.ErrorCtxf(rqs.Ctx, "", "storage error", "err", err) + logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err) return rqs, ErrStorage } @@ -58,6 +58,11 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) en, ok := eni.(*engine.DefaultEngine) if !ok { + perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage) + rqs.Storage = nil + if perr != nil { + logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr) + } return rqs, ErrEngineType } en = en.WithFirst(f.hn.Init) @@ -68,6 +73,11 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) r, err = rqs.Engine.Init(rqs.Ctx) if err != nil { + perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage) + rqs.Storage = nil + if perr != nil { + logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr) + } return rqs, err } @@ -75,6 +85,11 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input) } if err != nil { + perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage) + rqs.Storage = nil + if perr != nil { + logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr) + } return rqs, err } diff --git a/internal/handlers/single.go b/internal/handlers/single.go index 3910be8..f786d41 100644 --- a/internal/handlers/single.go +++ b/internal/handlers/single.go @@ -32,7 +32,7 @@ type RequestSession struct { Config engine.Config Engine engine.Engine Input []byte - Storage storage.Storage + Storage *storage.Storage Writer io.Writer } diff --git a/internal/http/server.go b/internal/http/server.go index af5413a..3ea0159 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -68,6 +68,7 @@ func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var code int var err error + var perr error rqs := handlers.RequestSession{ Ctx: req.Context(), @@ -109,14 +110,13 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain") rqs, err = f.Output(rqs) + rqs, perr = f.Reset(rqs) if err != nil { f.writeError(w, 500, err) return } - - rqs, err = f.Reset(rqs) - if err != nil { - f.writeError(w, 500, err) + if perr != nil { + f.writeError(w, 500, perr) return } } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index d009fd0..53f4392 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -11,31 +11,31 @@ type Storage struct { } type StorageProvider interface { - Get(sessionId string) (Storage, error) - Put(sessionId string, storage Storage) error + Get(sessionId string) (*Storage, error) + Put(sessionId string, storage *Storage) error Close() error } type SimpleStorageProvider struct { - Storage + *Storage } func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider { pe := persist.NewPersister(stateStore) pe = pe.WithFlush() return &SimpleStorageProvider{ - Storage: Storage{ + Storage: &Storage{ Persister: pe, UserdataDb: userdataStore, }, } } -func (p *SimpleStorageProvider) Get(sessionId string) (Storage, error) { +func (p *SimpleStorageProvider) Get(sessionId string) (*Storage, error) { return p.Storage, nil } -func (p *SimpleStorageProvider) Put(sessionId string, storage Storage) error { +func (p *SimpleStorageProvider) Put(sessionId string, storage *Storage) error { return nil } -- 2.45.2 From 762f90adf669a55350d975333f1f722546d69e74 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 12 Sep 2024 23:52:35 +0300 Subject: [PATCH 08/13] Added Continue bool to track whether the execution should be terminated --- internal/handlers/base.go | 2 +- internal/handlers/single.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/handlers/base.go b/internal/handlers/base.go index fba62c9..483aa02 100644 --- a/internal/handlers/base.go +++ b/internal/handlers/base.go @@ -78,7 +78,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) return rqs, err } - _ = r + rqs.Continue = r return rqs, nil } diff --git a/internal/handlers/single.go b/internal/handlers/single.go index 3910be8..69a4d1e 100644 --- a/internal/handlers/single.go +++ b/internal/handlers/single.go @@ -34,6 +34,7 @@ type RequestSession struct { Input []byte Storage storage.Storage Writer io.Writer + Continue bool } type engineMaker func(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine -- 2.45.2 From 512460fdeb506be3a373ee574f0bef068f87418f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Sep 2024 16:02:04 +0300 Subject: [PATCH 09/13] Added Custom AtOutput to append CON or END to output --- internal/handlers/base.go | 31 ++++++++++++++++++++++++++----- internal/handlers/single.go | 1 + 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/internal/handlers/base.go b/internal/handlers/base.go index 483aa02..054654b 100644 --- a/internal/handlers/base.go +++ b/internal/handlers/base.go @@ -1,13 +1,15 @@ package handlers import ( - "git.defalsify.org/vise.git/engine" - "git.defalsify.org/vise.git/resource" - "git.defalsify.org/vise.git/persist" - "git.defalsify.org/vise.git/db" + "io" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/persist" + "git.defalsify.org/vise.git/resource" - "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + "git.grassecon.net/urdt/ussd/internal/storage" ) type BaseSessionHandler struct { @@ -88,6 +90,25 @@ func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) return rqs, err } +func (f *BaseSessionHandler) AtOutput(rqs RequestSession) (RequestSession, error) { + var err error + var prefix string + + if rqs.Continue { + prefix = "CON " + } else { + prefix = "END " + } + + _, err = io.WriteString(rqs.Writer, prefix) + if err != nil { + return rqs, err + } + + _, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) + return rqs, err +} + func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) { defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) return rqs, rqs.Engine.Finish() diff --git a/internal/handlers/single.go b/internal/handlers/single.go index 69a4d1e..a900c14 100644 --- a/internal/handlers/single.go +++ b/internal/handlers/single.go @@ -51,6 +51,7 @@ type RequestHandler interface { GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine Process(rs RequestSession) (RequestSession, error) Output(rs RequestSession) (RequestSession, error) + AtOutput(rs RequestSession) (RequestSession, error) Reset(rs RequestSession) (RequestSession, error) Shutdown() } -- 2.45.2 From b53658e0389f5d612dfe30ddb5b7fdeaeb66987d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Sep 2024 16:03:31 +0300 Subject: [PATCH 10/13] have AtOutput as an option --- cmd/africastalking/main.go | 2 +- internal/http/server.go | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index c5016dc..be34b7f 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -232,7 +232,7 @@ func main() { rp := &atRequestParser{} bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) - sh := httpserver.ToSessionHandler(bsh) + sh := httpserver.ToSessionHandler(bsh, httpserver.WithAtOutput()) s := &http.Server{ Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Handler: sh, diff --git a/internal/http/server.go b/internal/http/server.go index af5413a..964d23b 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -43,14 +43,30 @@ func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { return v, nil } -type SessionHandler struct { - handlers.RequestHandler + + +type SessionHandlerOption func(*SessionHandler) + +func WithAtOutput() SessionHandlerOption { + return func(sh *SessionHandler) { + sh.useAtOutput = true + } } -func ToSessionHandler(h handlers.RequestHandler) *SessionHandler { - return &SessionHandler{ +type SessionHandler struct { + handlers.RequestHandler + useAtOutput bool +} + +func ToSessionHandler(h handlers.RequestHandler, opts ...SessionHandlerOption) *SessionHandler { + sh := &SessionHandler{ RequestHandler: h, + useAtOutput: false, } + for _, opt := range opts { + opt(sh) + } + return sh } func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { @@ -108,7 +124,11 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain") - rqs, err = f.Output(rqs) + if f.useAtOutput { + rqs, err = f.AtOutput(rqs) + } else { + rqs, err = f.Output(rqs) + } if err != nil { f.writeError(w, 500, err) return -- 2.45.2 From 3cb0b099e443b104e2b618bac752e12c64a79f11 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Sat, 14 Sep 2024 15:55:45 +0300 Subject: [PATCH 11/13] use the new AtSessionHandler --- cmd/africastalking/main.go | 2 +- internal/http/at_session_handler.go | 93 +++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 internal/http/at_session_handler.go diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index be34b7f..bc834d4 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -232,7 +232,7 @@ func main() { rp := &atRequestParser{} bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) - sh := httpserver.ToSessionHandler(bsh, httpserver.WithAtOutput()) + sh := httpserver.NewATSessionHandler(bsh) s := &http.Server{ Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Handler: sh, diff --git a/internal/http/at_session_handler.go b/internal/http/at_session_handler.go new file mode 100644 index 0000000..a8682cb --- /dev/null +++ b/internal/http/at_session_handler.go @@ -0,0 +1,93 @@ +package http + +import ( + "io" + "net/http" + + "git.grassecon.net/urdt/ussd/internal/handlers" +) + +type ATSessionHandler struct { + *SessionHandler +} + +func NewATSessionHandler(h handlers.RequestHandler) *ATSessionHandler { + return &ATSessionHandler{ + SessionHandler: ToSessionHandler(h), + } +} + +func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + var code int + var err error + + rqs := handlers.RequestSession{ + Ctx: req.Context(), + Writer: w, + } + + rp := ash.GetRequestParser() + cfg := ash.GetConfig() + cfg.SessionId, err = rp.GetSessionId(req) + if err != nil { + logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) + ash.writeError(w, 400, err) + } + rqs.Config = cfg + rqs.Input, err = rp.GetInput(req) + if err != nil { + logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) + ash.writeError(w, 400, err) + return + } + + rqs, err = ash.Process(rqs) + switch err { + case handlers.ErrStorage: + code = 500 + case handlers.ErrEngineInit: + code = 500 + case handlers.ErrEngineExec: + code = 500 + default: + code = 200 + } + + if code != 200 { + ash.writeError(w, 500, err) + return + } + + w.WriteHeader(200) + w.Header().Set("Content-Type", "text/plain") + rqs, err = ash.ATOutput(rqs) + if err != nil { + ash.writeError(w, 500, err) + return + } + + rqs, err = ash.Reset(rqs) + if err != nil { + ash.writeError(w, 500, err) + return + } +} + +func (ash *ATSessionHandler) ATOutput(rqs handlers.RequestSession) (handlers.RequestSession, error) { + var err error + var prefix string + + if rqs.Continue { + prefix = "CON " + } else { + prefix = "END " + } + + _, err = io.WriteString(rqs.Writer, prefix) + if err != nil { + return rqs, err + } + + _, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) + return rqs, err +} \ No newline at end of file -- 2.45.2 From 1e9c9cf6ad5e9fa5642c784f41184f7583eb3dde Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Sat, 14 Sep 2024 15:57:16 +0300 Subject: [PATCH 12/13] removed AT specific code --- internal/handlers/base.go | 21 --------------------- internal/handlers/single.go | 1 - internal/http/server.go | 34 +++++++--------------------------- 3 files changed, 7 insertions(+), 49 deletions(-) diff --git a/internal/handlers/base.go b/internal/handlers/base.go index 054654b..53df2c0 100644 --- a/internal/handlers/base.go +++ b/internal/handlers/base.go @@ -1,8 +1,6 @@ package handlers import ( - "io" - "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/persist" @@ -90,25 +88,6 @@ func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) return rqs, err } -func (f *BaseSessionHandler) AtOutput(rqs RequestSession) (RequestSession, error) { - var err error - var prefix string - - if rqs.Continue { - prefix = "CON " - } else { - prefix = "END " - } - - _, err = io.WriteString(rqs.Writer, prefix) - if err != nil { - return rqs, err - } - - _, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) - return rqs, err -} - func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) { defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) return rqs, rqs.Engine.Finish() diff --git a/internal/handlers/single.go b/internal/handlers/single.go index a900c14..69a4d1e 100644 --- a/internal/handlers/single.go +++ b/internal/handlers/single.go @@ -51,7 +51,6 @@ type RequestHandler interface { GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine Process(rs RequestSession) (RequestSession, error) Output(rs RequestSession) (RequestSession, error) - AtOutput(rs RequestSession) (RequestSession, error) Reset(rs RequestSession) (RequestSession, error) Shutdown() } diff --git a/internal/http/server.go b/internal/http/server.go index 964d23b..3ea0159 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -43,30 +43,14 @@ func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { return v, nil } - - -type SessionHandlerOption func(*SessionHandler) - -func WithAtOutput() SessionHandlerOption { - return func(sh *SessionHandler) { - sh.useAtOutput = true - } -} - type SessionHandler struct { handlers.RequestHandler - useAtOutput bool } -func ToSessionHandler(h handlers.RequestHandler, opts ...SessionHandlerOption) *SessionHandler { - sh := &SessionHandler{ +func ToSessionHandler(h handlers.RequestHandler) *SessionHandler { + return &SessionHandler{ RequestHandler: h, - useAtOutput: false, } - for _, opt := range opts { - opt(sh) - } - return sh } func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { @@ -84,6 +68,7 @@ func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var code int var err error + var perr error rqs := handlers.RequestSession{ Ctx: req.Context(), @@ -124,19 +109,14 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain") - if f.useAtOutput { - rqs, err = f.AtOutput(rqs) - } else { - rqs, err = f.Output(rqs) - } + rqs, err = f.Output(rqs) + rqs, perr = f.Reset(rqs) if err != nil { f.writeError(w, 500, err) return } - - rqs, err = f.Reset(rqs) - if err != nil { - f.writeError(w, 500, err) + if perr != nil { + f.writeError(w, 500, perr) return } } -- 2.45.2 From 6fe2e7287f157c67b9365dc5f9211b96d337eb91 Mon Sep 17 00:00:00 2001 From: Alfred Kamanda Date: Sat, 14 Sep 2024 16:01:18 +0200 Subject: [PATCH 13/13] Update the func name to Output Signed-off-by: Alfred Kamanda --- internal/http/at_session_handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/http/at_session_handler.go b/internal/http/at_session_handler.go index a8682cb..4a0cafa 100644 --- a/internal/http/at_session_handler.go +++ b/internal/http/at_session_handler.go @@ -60,7 +60,7 @@ func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain") - rqs, err = ash.ATOutput(rqs) + rqs, err = ash.Output(rqs) if err != nil { ash.writeError(w, 500, err) return @@ -73,7 +73,7 @@ func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) } } -func (ash *ATSessionHandler) ATOutput(rqs handlers.RequestSession) (handlers.RequestSession, error) { +func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.RequestSession, error) { var err error var prefix string -- 2.45.2