From dd2468a4d7b4e1c511da3b47950c7430cc132f7f Mon Sep 17 00:00:00 2001 From: lash Date: Fri, 6 Sep 2024 00:40:57 +0100 Subject: [PATCH 1/8] Http server harness Add storage retrieval solution for http handler Successfully executed account regisration using http Set upstream go-vise dependency version in go.mod Adapt menuhandler to upstream --- cmd/http/main.go | 199 ++++++++++++++++++++++++++ cmd/main.go | 10 +- go.mod | 12 +- go.sum | 4 +- internal/handlers/ussd/menuhandler.go | 26 ++-- internal/http/server.go | 130 +++++++++++++++++ internal/http/storage.go | 43 ++++++ 7 files changed, 401 insertions(+), 23 deletions(-) create mode 100644 cmd/http/main.go create mode 100644 internal/http/server.go create mode 100644 internal/http/storage.go diff --git a/cmd/http/main.go b/cmd/http/main.go new file mode 100644 index 0000000..d253c12 --- /dev/null +++ b/cmd/http/main.go @@ -0,0 +1,199 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net/http" + "os" + "path" + "strconv" + + "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/persist" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/logging" + + "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") +) + +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 getStateStore(dbDir string, ctx context.Context) (db.Db, error) { + err := os.MkdirAll(dbDir, 0700) + if err != nil { + return nil, fmt.Errorf("state dir create exited with error: %v\n", err) + } + 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 getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine { + en := engine.NewEngine(cfg, rs) + en = en.WithPersister(pr) + return en +} + +func main() { + var dbDir string + var resourceDir string + var size uint + var sessionId string + 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) + + ctx := context.Background() + ctx = context.WithValue(ctx, "SessionId",sessionId) + pfp := path.Join(scriptDir, "pp.csv") + flagParser, err := getFlags(pfp, true) + + if err != nil { + os.Exit(1) + } + + cfg := engine.Config{ + Root: "root", + SessionId: sessionId, + 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) + } + + userdataStore := getUserdataDb(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + 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) + } + + sh := httpserver.NewSessionHandler(cfg, rs, userdataStore, stateStore, hl.Init) + s := &http.Server{ + Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), + Handler: sh, + } + + err = s.ListenAndServe() + if err != nil { + fmt.Fprintf(os.Stderr, "Server error: %s", err) + os.Exit(1) + } +} diff --git a/cmd/main.go b/cmd/main.go index fc734d4..9547dc4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,10 +34,11 @@ func getParser(fp string, debug bool) (*asm.FlagParser, error) { func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) { - ussdHandlers, err := ussd.NewHandlers(appFlags, pe, userdataStore) + ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore) if err != nil { return nil, err } + ussdHandlers = ussdHandlers.WithPersister(pe) rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) rs.AddLocalFunc("save_pin", ussdHandlers.SavePin) @@ -145,7 +146,7 @@ func main() { os.Exit(1) } - pr, err := getPersister(dbDir, ctx) + pe, err := getPersister(dbDir, ctx) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) @@ -159,16 +160,17 @@ func main() { dbResource, ok := rs.(*resource.DbResource) if !ok { + fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } - hl, err := getHandler(flagParser, dbResource, pr, store) + hl, err := getHandler(flagParser, dbResource, pe, store) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } - en := getEngine(cfg, rs, pr) + en := getEngine(cfg, rs, pe) en = en.WithFirst(hl.Init) if debug { en = en.WithDebug(nil) diff --git a/go.mod b/go.mod index e2aff05..dc20ac5 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,12 @@ module git.grassecon.net/urdt/ussd go 1.22.6 +require ( + git.defalsify.org/vise.git v0.1.0-rc.2.0.20240907200911-15fe28c9d5b0 + github.com/alecthomas/assert/v2 v2.2.2 + gopkg.in/leonelquinteros/gotext.v1 v1.3.1 +) + require ( github.com/alecthomas/participle/v2 v2.0.0 // indirect github.com/alecthomas/repr v0.2.0 // indirect @@ -17,9 +23,3 @@ require ( github.com/x448/float16 v0.8.4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -require ( - git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89 - 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 2624d07..d065871 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89 h1:YyQODhMwSM5YD9yKHM5jCF0HC0RQtE3MkVXcTnOhXJo= -git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +git.defalsify.org/vise.git v0.1.0-rc.2.0.20240907200911-15fe28c9d5b0 h1:B9kE2XXjrYmHNIgRV6fR1WLWE8+z8OvDhJSc96lbGPQ= +git.defalsify.org/vise.git v0.1.0-rc.2.0.20240907200911-15fe28c9d5b0/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/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 3b441c2..3eef63d 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -66,21 +66,17 @@ type Handlers struct { accountService server.AccountServiceInterface } -func NewHandlers(parser *asm.FlagParser, pe *persist.Persister, userdataStore db.Db) (*Handlers, error) { - userDb := utils.UserDataStore{ - Db: userdataStore, - } - if pe == nil { - return nil, fmt.Errorf("cannot create handler with nil persister") - } +func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") } + userDb := &utils.UserDataStore{ + Db: userdataStore, + } h := &Handlers{ - pe: pe, - userdataStore: &userDb, - flagManager: parser, - accountService: &server.AccountService{}, + userdataStore: userDb, + flagManager: appFlags, + accountService: &server.AccountService{}, } return h, nil } @@ -94,6 +90,14 @@ func isValidPIN(pin string) bool { return match } +func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { + if h.pe != nil { + panic("persister already set") + } + h.pe = pe + return h +} + func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { var r resource.Result diff --git a/internal/http/server.go b/internal/http/server.go new file mode 100644 index 0000000..aa53448 --- /dev/null +++ b/internal/http/server.go @@ -0,0 +1,130 @@ +package http + +import ( + "fmt" + "io/ioutil" + "net/http" + + "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" +) + +var ( + logg = logging.NewVanilla().WithDomain("httpserver") +) + +type RequestParser struct { +} + +func(rp *RequestParser) GetSessionId(rq *http.Request) (string, error) { + v := rq.Header.Get("X-Vise-Session") + if v == "" { + return "", fmt.Errorf("no session found") + } + return v, nil +} + +func(rp *RequestParser) GetInput(rq *http.Request) ([]byte, error) { + defer rq.Body.Close() + v, err := ioutil.ReadAll(rq.Body) + if err != nil { + return nil, err + } + return v, nil +} + +type SessionHandler struct { + cfgTemplate engine.Config + rp RequestParser + rs resource.Resource + first resource.EntryFunc + provider StorageProvider +} + +func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, first resource.EntryFunc) *SessionHandler { + return &SessionHandler{ + cfgTemplate: cfg, + rs: rs, + first: first, + rp: RequestParser{}, + provider: 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") + w.WriteHeader(code) + _, err = w.Write([]byte{}) + if err != nil { + w.WriteHeader(500) + w.Header().Set("X-Vise", err.Error()) + } + return +} + +func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + 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 + + logg.InfoCtxf(ctx, "new request", "session", cfg.SessionId, "input", input) + + storage, err := f.provider.Get(cfg.SessionId) + if err != nil { + f.writeError(w, 500, "Storage retrieval fail", err) + return + } + en := getEngine(cfg, f.rs, storage.Persister) + en = en.WithFirst(f.first) + if cfg.EngineDebug { + en = en.WithDebug(nil) + } + + r, err = en.Init(ctx) + if err != nil { + f.writeError(w, 500, "Engine init fail", err) + return + } + if r && len(input) > 0 { + r, err = en.Exec(ctx, input) + } + if err != nil { + f.writeError(w, 500, "Engine exec fail", err) + return + } + + w.WriteHeader(200) + w.Header().Set("Content-Type", "text/plain") + _, err = en.WriteResult(ctx, w) + 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) + 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 +} diff --git a/internal/http/storage.go b/internal/http/storage.go new file mode 100644 index 0000000..012c56c --- /dev/null +++ b/internal/http/storage.go @@ -0,0 +1,43 @@ +package http + +import ( + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/persist" +) + +type Storage struct { + Persister *persist.Persister + UserdataDb db.Db +} + +type StorageProvider interface { + Get(sessionId string) (Storage, error) + Put(sessionId string, storage Storage) error + Close() error +} + +type SimpleStorageProvider struct { + Storage +} + +func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider { + pe := persist.NewPersister(stateStore) + return &SimpleStorageProvider{ + Storage: Storage{ + Persister: pe, + UserdataDb: userdataStore, + }, + } +} + +func (p *SimpleStorageProvider) Get(sessionId string) (Storage, error) { + return p.Storage, nil +} + +func (p *SimpleStorageProvider) Put(sessionId string, storage Storage) error { + return nil +} + +func (p *SimpleStorageProvider) Close() error { + return nil +} -- 2.45.2 From 8e3ff27bb87d63e81f51feb3baee99f21804e567 Mon Sep 17 00:00:00 2001 From: lash Date: Tue, 10 Sep 2024 20:44:10 +0100 Subject: [PATCH 2/8] Ensure db close on http signal shutdown, correct stores to provider --- cmd/http/main.go | 39 ++++++++++++++++++++++++++++++--------- cmd/main.go | 12 ++++++++++-- internal/http/server.go | 8 ++++++++ internal/http/storage.go | 2 +- internal/utils/db.go | 1 - 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/cmd/http/main.go b/cmd/http/main.go index d253c12..1edc1fe 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -6,8 +6,10 @@ import ( "fmt" "net/http" "os" + "os/signal" "path" "strconv" + "syscall" "git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/db" @@ -78,11 +80,15 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore return ussdHandlers, nil } -func getStateStore(dbDir string, ctx context.Context) (db.Db, error) { +func ensureDbDir(dbDir string) error { err := os.MkdirAll(dbDir, 0700) if err != nil { - return nil, fmt.Errorf("state dir create exited with error: %v\n", err) + 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) @@ -117,12 +123,10 @@ func main() { var dbDir string var resourceDir string var size uint - var sessionId string 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") @@ -135,7 +139,6 @@ func main() { logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) ctx := context.Background() - ctx = context.WithValue(ctx, "SessionId",sessionId) pfp := path.Join(scriptDir, "pp.csv") flagParser, err := getFlags(pfp, true) @@ -145,7 +148,6 @@ func main() { cfg := engine.Config{ Root: "root", - SessionId: sessionId, OutputSize: uint32(size), FlagCount: uint32(16), } @@ -162,11 +164,18 @@ func main() { 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 { @@ -184,16 +193,28 @@ func main() { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } + defer stateStore.Close() - sh := httpserver.NewSessionHandler(cfg, rs, userdataStore, stateStore, hl.Init) + sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, hl.Init) 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 { - fmt.Fprintf(os.Stderr, "Server error: %s", err) - os.Exit(1) + logg.Infof("Server closed with error", "err", err) } } diff --git a/cmd/main.go b/cmd/main.go index 9547dc4..9222c13 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -75,10 +75,18 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.P return ussdHandlers, nil } -func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) { +func ensureDbDir(dbDir string) error { err := os.MkdirAll(dbDir, 0700) if err != nil { - return nil, fmt.Errorf("state dir create exited with error: %v\n", err) + return fmt.Errorf("state dir create exited with error: %v\n", err) + } + return nil +} + +func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) { + err := ensureDbDir(dbDir) + if err != nil { + return nil, err } store := gdbmdb.NewGdbmDb() storeFile := path.Join(dbDir, "state.gdbm") diff --git a/internal/http/server.go b/internal/http/server.go index aa53448..4ca7f73 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -66,6 +66,13 @@ func(f *SessionHandler) writeError(w http.ResponseWriter, code int, msg string, return } +func(f* SessionHandler) Shutdown() { + err := f.provider.Close() + if err != nil { + logg.Errorf("handler shutdown error", "err", err) + } +} + func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var r bool sessionId, err := f.rp.GetSessionId(req) @@ -89,6 +96,7 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { f.writeError(w, 500, "Storage retrieval fail", err) return } + defer f.provider.Put(cfg.SessionId, storage) en := getEngine(cfg, f.rs, storage.Persister) en = en.WithFirst(f.first) if cfg.EngineDebug { diff --git a/internal/http/storage.go b/internal/http/storage.go index 012c56c..f8243cc 100644 --- a/internal/http/storage.go +++ b/internal/http/storage.go @@ -39,5 +39,5 @@ func (p *SimpleStorageProvider) Put(sessionId string, storage Storage) error { } func (p *SimpleStorageProvider) Close() error { - return nil + return p.Storage.UserdataDb.Close() } diff --git a/internal/utils/db.go b/internal/utils/db.go index 5b128f6..94ce250 100644 --- a/internal/utils/db.go +++ b/internal/utils/db.go @@ -39,7 +39,6 @@ func PackKey(typ DataTyp, data []byte) []byte { } func ReadEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp) ([]byte, error) { - store.SetPrefix(db.DATATYPE_USERDATA) store.SetSession(sessionId) k := PackKey(typ, []byte(sessionId)) -- 2.45.2 From 681f293d3c01e59e66ea381ada3644b06507a772 Mon Sep 17 00:00:00 2001 From: lash Date: Tue, 10 Sep 2024 23:09:10 +0100 Subject: [PATCH 3/8] Externalize requestparser, flush persister on http request end --- cmd/http/main.go | 3 ++- go.mod | 2 +- go.sum | 4 ++-- internal/http/server.go | 16 +++++++++++----- internal/http/storage.go | 1 + 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/http/main.go b/cmd/http/main.go index 1edc1fe..071cf00 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -195,7 +195,8 @@ func main() { } defer stateStore.Close() - sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, hl.Init) + rp := &httpserver.DefaultRequestParser{} + sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init) s := &http.Server{ Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Handler: sh, diff --git a/go.mod b/go.mod index dc20ac5..d11f113 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.2.0.20240907200911-15fe28c9d5b0 + git.defalsify.org/vise.git v0.1.0-rc.3.0.20240910220239-03876d1a78bd 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 d065871..0d09e94 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.1.0-rc.2.0.20240907200911-15fe28c9d5b0 h1:B9kE2XXjrYmHNIgRV6fR1WLWE8+z8OvDhJSc96lbGPQ= -git.defalsify.org/vise.git v0.1.0-rc.2.0.20240907200911-15fe28c9d5b0/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240910220239-03876d1a78bd h1:pEYKEwz8qHiLfhQC+yYPv3jun7+WAa8xlGtaJpOErQ0= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240910220239-03876d1a78bd/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/http/server.go b/internal/http/server.go index 4ca7f73..eef41c9 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -16,10 +16,15 @@ var ( logg = logging.NewVanilla().WithDomain("httpserver") ) -type RequestParser struct { +type RequestParser interface { + GetSessionId(rq *http.Request) (string, error) + GetInput(rq *http.Request) ([]byte, error) } -func(rp *RequestParser) GetSessionId(rq *http.Request) (string, error) { +type DefaultRequestParser struct { +} + +func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) { v := rq.Header.Get("X-Vise-Session") if v == "" { return "", fmt.Errorf("no session found") @@ -27,7 +32,7 @@ func(rp *RequestParser) GetSessionId(rq *http.Request) (string, error) { return v, nil } -func(rp *RequestParser) GetInput(rq *http.Request) ([]byte, error) { +func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) { defer rq.Body.Close() v, err := ioutil.ReadAll(rq.Body) if err != nil { @@ -44,12 +49,12 @@ type SessionHandler struct { provider StorageProvider } -func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, first resource.EntryFunc) *SessionHandler { +func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, first resource.EntryFunc) *SessionHandler { return &SessionHandler{ cfgTemplate: cfg, rs: rs, first: first, - rp: RequestParser{}, + rp: rp, provider: NewSimpleStorageProvider(stateDb, userdataDb), } } @@ -128,6 +133,7 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { f.writeError(w, 500, "Engine finish fail", err) return } + _ = r } diff --git a/internal/http/storage.go b/internal/http/storage.go index f8243cc..9b0cf44 100644 --- a/internal/http/storage.go +++ b/internal/http/storage.go @@ -22,6 +22,7 @@ type SimpleStorageProvider struct { func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider { pe := persist.NewPersister(stateStore) + pe = pe.WithFlush() return &SimpleStorageProvider{ Storage: Storage{ Persister: pe, -- 2.45.2 From 7438531900e5d2a516978095a14a98a2c573efc6 Mon Sep 17 00:00:00 2001 From: lash Date: Wed, 11 Sep 2024 04:10:05 +0100 Subject: [PATCH 4/8] Update govise to include removed dead asm code --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d11f113..8c5944e 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.20240910220239-03876d1a78bd + git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911030253-cab74c82992d 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 0d09e94..778b3e9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240910220239-03876d1a78bd h1:pEYKEwz8qHiLfhQC+yYPv3jun7+WAa8xlGtaJpOErQ0= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240910220239-03876d1a78bd/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911030253-cab74c82992d h1:Xv2WQpggqCRmx2Nm1+S0uAKsDcNHo7+TURAEewwASf4= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911030253-cab74c82992d/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= -- 2.45.2 From 44015b1c761f2a8bd88da16fc2eab21c41854d71 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Sep 2024 15:40:49 +0300 Subject: [PATCH 5/8] Parse the request body to get the PhoneNumber and Input text --- internal/http/server.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/http/server.go b/internal/http/server.go index eef41c9..040c035 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -2,8 +2,8 @@ package http import ( "fmt" - "io/ioutil" "net/http" + "strings" "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" @@ -25,20 +25,31 @@ type DefaultRequestParser struct { } func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) { - v := rq.Header.Get("X-Vise-Session") - if v == "" { - return "", fmt.Errorf("no session found") + if err := rq.ParseForm(); err != nil { + return "", fmt.Errorf("failed to parse form data: %v", err) } - return v, nil + + phoneNumber := rq.FormValue("phoneNumber") + if phoneNumber == "" { + return "", fmt.Errorf("no phone number found") + } + + return phoneNumber, nil } func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) { - defer rq.Body.Close() - v, err := ioutil.ReadAll(rq.Body) - if err != nil { - return nil, err + if err := rq.ParseForm(); err != nil { + return nil, fmt.Errorf("failed to parse form data: %v", err) } - return v, nil + + text := rq.FormValue("text") + + parts := strings.Split(text, "*") + if len(parts) == 0 { + return nil, fmt.Errorf("no input found") + } + + return []byte(parts[len(parts)-1]), nil } type SessionHandler struct { -- 2.45.2 From 660fcaa0b6668262ccb8926003b6a840265d280c Mon Sep 17 00:00:00 2001 From: lash Date: Wed, 11 Sep 2024 17:23:42 +0100 Subject: [PATCH 6/8] Import go-vise fixing missing exit on state reset --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8c5944e..71730c4 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.20240911030253-cab74c82992d + git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7 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 778b3e9..b40a422 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911030253-cab74c82992d h1:Xv2WQpggqCRmx2Nm1+S0uAKsDcNHo7+TURAEewwASf4= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911030253-cab74c82992d/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +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= 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= -- 2.45.2 From 836e5fe8ee144ba7faed1f5aadd3805a2e06c599 Mon Sep 17 00:00:00 2001 From: lash Date: Wed, 11 Sep 2024 17:32:55 +0100 Subject: [PATCH 7/8] Revert africas talking changes in http --- internal/http/server.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/internal/http/server.go b/internal/http/server.go index 040c035..eef41c9 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -2,8 +2,8 @@ package http import ( "fmt" + "io/ioutil" "net/http" - "strings" "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" @@ -25,31 +25,20 @@ type DefaultRequestParser struct { } func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) { - if err := rq.ParseForm(); err != nil { - return "", fmt.Errorf("failed to parse form data: %v", err) + v := rq.Header.Get("X-Vise-Session") + if v == "" { + return "", fmt.Errorf("no session found") } - - phoneNumber := rq.FormValue("phoneNumber") - if phoneNumber == "" { - return "", fmt.Errorf("no phone number found") - } - - return phoneNumber, nil + return v, nil } func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) { - if err := rq.ParseForm(); err != nil { - return nil, fmt.Errorf("failed to parse form data: %v", err) + defer rq.Body.Close() + v, err := ioutil.ReadAll(rq.Body) + if err != nil { + return nil, err } - - text := rq.FormValue("text") - - parts := strings.Split(text, "*") - if len(parts) == 0 { - return nil, fmt.Errorf("no input found") - } - - return []byte(parts[len(parts)-1]), nil + return v, nil } type SessionHandler struct { -- 2.45.2 From 514e043e3821be502eb993bc0c9312849234565c Mon Sep 17 00:00:00 2001 From: lash Date: Wed, 11 Sep 2024 17:53:12 +0100 Subject: [PATCH 8/8] Fix symptom of handler missing persister --- cmd/http/main.go | 10 ++-------- internal/http/server.go | 14 ++++++++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/http/main.go b/cmd/http/main.go index 071cf00..7b085a8 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -16,7 +16,6 @@ import ( 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/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/logging" @@ -113,12 +112,6 @@ func getResource(resourceDir string, ctx context.Context) (resource.Resource, er return rfs, nil } -func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine { - en := engine.NewEngine(cfg, rs) - en = en.WithPersister(pr) - return en -} - func main() { var dbDir string var resourceDir string @@ -196,7 +189,8 @@ func main() { defer stateStore.Close() rp := &httpserver.DefaultRequestParser{} - sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init) + //sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init) + sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) 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 eef41c9..7d1d8fe 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -10,6 +10,8 @@ import ( "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/ussd" ) var ( @@ -45,15 +47,18 @@ type SessionHandler struct { cfgTemplate engine.Config rp RequestParser rs resource.Resource - first resource.EntryFunc + //first resource.EntryFunc + hn *ussd.Handlers provider 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, first resource.EntryFunc) *SessionHandler { +func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *SessionHandler { return &SessionHandler{ cfgTemplate: cfg, rs: rs, - first: first, + //first: first, + hn: hn, rp: rp, provider: NewSimpleStorageProvider(stateDb, userdataDb), } @@ -101,9 +106,10 @@ func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { f.writeError(w, 500, "Storage retrieval fail", err) return } + 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.first) + en = en.WithFirst(f.hn.Init) if cfg.EngineDebug { en = en.WithDebug(nil) } -- 2.45.2