diff --git a/cmd/main.go b/cmd/main.go index c2878a9..964bf5b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,6 +4,8 @@ import ( "context" "flag" "fmt" + "io" + "net/http" "os" "path" @@ -23,6 +25,65 @@ var ( scriptDir = path.Join("services", "registration") ) + +type LocalHandler struct { + sessionId string +} + +func NewLocalHandler() *LocalHandler { + return &LocalHandler{ + sessionId: "", + } +} + +type RequestParser interface { + GetSessionId(*http.Request) (string, error) + GetInput(*http.Request) ([]byte, 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") + } + return v, nil +} + +func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) { + defer rq.Body.Close() + v, err := io.ReadAll(rq.Body) + if err != nil { + return nil, err + } + return v, nil +} + +type DefaultSessionHandler struct { + cfgTemplate engine.Config + rp RequestParser + rh *LocalHandler + dbDir string + resourceDir string +} + +func NewDefaultSessionHandler(dbDir string, resourceDir string, rp RequestParser, outputSize uint32, flagCount uint32) *DefaultSessionHandler { + rh := NewLocalHandler() + return &DefaultSessionHandler{ + cfgTemplate: engine.Config{ + OutputSize: outputSize, + Root: "root", + FlagCount: flagCount, + }, + rh: rh, + rp: rp, + dbDir: dbDir, + resourceDir: resourceDir, + } +} + func getParser(fp string, debug bool) (*asm.FlagParser, error) { flagParser := asm.NewFlagParser().WithDebug() _, err := flagParser.Load(fp) @@ -74,29 +135,29 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.P return ussdHandlers, nil } -func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) { - err := os.MkdirAll(dbDir, 0700) +func(f *DefaultSessionHandler) getPersister(ctx context.Context) (*persist.Persister, error) { + err := os.MkdirAll(f.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") + storeFile := path.Join(f.dbDir, "state.gdbm") store.Connect(ctx, storeFile) pr := persist.NewPersister(store) return pr, nil } -func getUserdataDb(dbDir string, ctx context.Context) db.Db { +func(f *DefaultSessionHandler) getUserdataDb(ctx context.Context) db.Db { store := gdbmdb.NewGdbmDb() - storeFile := path.Join(dbDir, "userdata.gdbm") + storeFile := path.Join(f.dbDir, "userdata.gdbm") store.Connect(ctx, storeFile) return store } -func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) { +func(f *DefaultSessionHandler) getResource(ctx context.Context) (resource.Resource, error) { store := fsdb.NewFsDb() - err := store.Connect(ctx, resourceDir) + err := store.Connect(ctx, f.resourceDir) if err != nil { return nil, err } @@ -104,87 +165,144 @@ 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 { +func(f *DefaultSessionHandler) getEngine(rs resource.Resource, pr *persist.Persister, sessionId string) *engine.DefaultEngine { + cfg := f.cfgTemplate + cfg.SessionId = sessionId en := engine.NewEngine(cfg, rs) en = en.WithPersister(pr) return en } +func(f *DefaultSessionHandler) 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 *DefaultSessionHandler) 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() + ctx = context.WithValue(ctx, "SessionId", sessionId) + + pfp := path.Join(scriptDir, "pp.csv") + flagParser, err := getParser(pfp, true) + if err != nil { + f.writeError(w, 500, "flagParser failed with error:", err) + return + } + + rs, err := f.getResource(ctx) + if err != nil { + f.writeError(w, 500, "getResource failed with error:", err) + return + } + + pr, err := f.getPersister(ctx) + if err != nil { + f.writeError(w, 500, "getPersister failed with error:", err) + return + } + + store := f.getUserdataDb(ctx) + + dbResource, ok := rs.(*resource.DbResource) + if !ok { + f.writeError(w, 500, "getHandler exited with error:", err) + return + } + + hl, err := getHandler(flagParser, dbResource, pr, store) + if err != nil { + f.writeError(w, 500, "getHandler exited with error:", err) + return + } + + en := f.getEngine(rs, pr, sessionId) + en = en.WithFirst(hl.Init) + + if len(input) == 0 { + r, err = en.Init(ctx) + } else { + r, err = en.Exec(ctx, input) + } + if err != nil { + f.writeError(w, 500, "Engine exec fail", err) + return + } + + // _, err = en.Init(ctx) + // if err != nil { + // f.writeError(w, 500, "Engine exec fail", err) + // return + // } + + // err = engine.Loop(ctx, en, os.Stdin, os.Stdout) + // if err != nil { + // f.writeError(w, 500, "Loop 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 + } + + _ = r +} + func main() { + var host string + var port string var dbDir string var resourceDir string var size uint + var flagCount uint var sessionId string var debug bool + flag.StringVar(&host, "h", "127.0.0.1", "http host") + flag.StringVar(&port, "p", "7123", "http port") 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.UintVar(&flagCount, "f", 16, "flag count") flag.Parse() - logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) + logg.Infof("starting server", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "flagCount", flagCount) - ctx := context.Background() - ctx = context.WithValue(ctx, "SessionId", sessionId) - pfp := path.Join(scriptDir, "pp.csv") - flagParser, err := getParser(pfp, true) + rp := &DefaultRequestParser{} + h := NewDefaultSessionHandler(dbDir, resourceDir, rp, uint32(size), uint32(flagCount)) + s := &http.Server{ + Addr: fmt.Sprintf("%s:%s", host, port), + Handler: h, + } + err := s.ListenAndServe() 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, pr, store) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - en := getEngine(cfg, rs, pr) - en = en.WithFirst(hl.Init) - if debug { - en = en.WithDebug(nil) - } - - _, err = en.Init(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err) - os.Exit(1) - } - - err = engine.Loop(ctx, en, os.Stdin, os.Stdout) - if err != nil { - fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) + fmt.Fprintf(os.Stderr, "Server error: %s", err) os.Exit(1) } }