From e6754a1e9236b1effeac3260077c2fda59ecc1c4 Mon Sep 17 00:00:00 2001 From: lash Date: Fri, 6 Sep 2024 00:40:57 +0100 Subject: [PATCH] WIP http server harness --- cmd/http/main.go | 191 ++++++++++++++++++++++++++ cmd/main.go | 12 +- http/server.go | 121 ++++++++++++++++ internal/handlers/ussd/menuhandler.go | 14 +- 4 files changed, 323 insertions(+), 15 deletions(-) create mode 100644 cmd/http/main.go create mode 100644 http/server.go diff --git a/cmd/http/main.go b/cmd/http/main.go new file mode 100644 index 0000000..5c0db03 --- /dev/null +++ b/cmd/http/main.go @@ -0,0 +1,191 @@ +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/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 getPersister(dbDir string, ctx context.Context) (*persist.Persister, 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) + pr := persist.NewPersister(store) + return pr, 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 debug 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(&debug, "d", 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), + } + + rs, err := getResource(resourceDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } +// +// pr, err := getPersister(dbDir, ctx) +// if err != nil { +// fmt.Fprintf(os.Stderr, err.Error()) +// os.Exit(1) +// } + + store := 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, store) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + sh := httpserver.NewSessionHandler(cfg, rs, store, hl.Init) + s := &http.Server{ + Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(port)), + Handler: sh, + } + + err = engine.Loop(ctx, en, os.Stdin, os.Stdout) + if err != nil { + fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) + os.Exit(1) + } +} diff --git a/cmd/main.go b/cmd/main.go index a7f7174..613eb68 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -104,11 +104,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 @@ -164,11 +159,8 @@ func main() { os.Exit(1) } - hl, err := getHandler(flagParser, dbResource, pr, store) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } + hl := getHandler(flagParser, dbResource, store) + hl = hl.WithPersister(pe) en := getEngine(cfg, rs, pr) en = en.WithFirst(hl.Init) diff --git a/http/server.go b/http/server.go new file mode 100644 index 0000000..4cc4388 --- /dev/null +++ b/http/server.go @@ -0,0 +1,121 @@ +package http + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/persist" + "git.defalsify.org/vise.git/resource" +) + +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 +} + +func NewSessionHandler(cfg engine.Config, rs resource.Resource, userdataDb db.Db, first resource.EntryFunc) *SessionHandler { + return &SessionHandler{ + cfgTemplate: cfg, + rs: rs, + first: first, + rp: RequestParser{}, + } +} + +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 + + en := getEngine(cfg, f.rs, f.pr) + en = en.WithFirst(f.first) + if cfg.EngineDebug { + en = en.WithDebug(nil) + } + + if len(input) == 0 { + r, err = en.Init(ctx) + } else { + r, err = en.Exec(ctx, input) + } + + _, err = en.Init(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err) + os.Exit(1) + } + + 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/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 8b22753..2a62ce1 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -84,15 +84,11 @@ type Handlers struct { accountService server.AccountServiceInterface } -func NewHandlers(appFlags *asm.FlagParser, pe *persist.Persister, userdataStore db.Db) (*Handlers, error) { - 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") } h := &Handlers{ - pe: pe, userdataStore: userdataStore, flagManager: appFlags, accountFileHandler: utils.NewAccountFileHandler(userdataStore), @@ -110,6 +106,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