Merge pull request 'Enable CLI driver of async session' (#49) from lash/async-driver into master
Reviewed-on: #49
This commit is contained in:
		
						commit
						b5d33a98f0
					
				
							
								
								
									
										257
									
								
								cmd/africastalking/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								cmd/africastalking/main.go
									
									
									
									
									
										Normal file
									
								
							@ -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.NewATSessionHandler(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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										251
									
								
								cmd/async/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								cmd/async/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,251 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"path"
 | 
			
		||||
	"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/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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
			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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -112,6 +113,7 @@ func getResource(resourceDir string, ctx context.Context) (resource.Resource, er
 | 
			
		||||
	return rfs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	var dbDir string
 | 
			
		||||
	var resourceDir string
 | 
			
		||||
@ -189,8 +191,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)
 | 
			
		||||
	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,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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 v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f
 | 
			
		||||
	github.com/alecthomas/assert/v2 v2.2.2
 | 
			
		||||
	gopkg.in/leonelquinteros/gotext.v1 v1.3.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								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=
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										117
									
								
								internal/handlers/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								internal/handlers/base.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"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/handlers/ussd"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/storage"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 get error", 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 {
 | 
			
		||||
		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)
 | 
			
		||||
	if rqs.Config.EngineDebug {
 | 
			
		||||
		en = en.WithDebug(nil)
 | 
			
		||||
	}
 | 
			
		||||
	rqs.Engine = en
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r && len(rqs.Input) > 0 {
 | 
			
		||||
		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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rqs.Continue = 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								internal/handlers/single.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								internal/handlers/single.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
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.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")
 | 
			
		||||
	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
 | 
			
		||||
	Continue bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
	Reset(rs RequestSession) (RequestSession, error)
 | 
			
		||||
	Shutdown()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								internal/http/at_session_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								internal/http/at_session_handler.go
									
									
									
									
									
										Normal file
									
								
							@ -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.Output(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) Output(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
 | 
			
		||||
}
 | 
			
		||||
@ -1,42 +1,42 @@
 | 
			
		||||
package http
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"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/ussd"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
@ -44,107 +44,79 @@ func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SessionHandler struct {
 | 
			
		||||
	cfgTemplate engine.Config
 | 
			
		||||
	rp RequestParser
 | 
			
		||||
	rs resource.Resource
 | 
			
		||||
	//first resource.EntryFunc
 | 
			
		||||
	hn *ussd.Handlers
 | 
			
		||||
	provider StorageProvider
 | 
			
		||||
	handlers.RequestHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//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 ToSessionHandler(h handlers.RequestHandler) *SessionHandler {
 | 
			
		||||
	return &SessionHandler{
 | 
			
		||||
		cfgTemplate: cfg,
 | 
			
		||||
		rs: rs,
 | 
			
		||||
		//first: first,
 | 
			
		||||
		hn: hn,
 | 
			
		||||
		rp: rp,
 | 
			
		||||
		provider: NewSimpleStorageProvider(stateDb, userdataDb),
 | 
			
		||||
		RequestHandler: h,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
	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 code int
 | 
			
		||||
	var err error
 | 
			
		||||
	var perr error
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
		en = en.WithDebug(nil)
 | 
			
		||||
	rqs := handlers.RequestSession{
 | 
			
		||||
		Ctx: req.Context(),
 | 
			
		||||
		Writer: w,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err = en.Init(ctx)
 | 
			
		||||
	rp := f.GetRequestParser()
 | 
			
		||||
	cfg := f.GetConfig()
 | 
			
		||||
	cfg.SessionId, err = rp.GetSessionId(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		f.writeError(w, 500, "Engine init fail", err)
 | 
			
		||||
		logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
			
		||||
		f.writeError(w, 400, err)
 | 
			
		||||
	}
 | 
			
		||||
	rqs.Config = cfg
 | 
			
		||||
	rqs.Input, err = 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)
 | 
			
		||||
	rqs, err = f.Output(rqs)
 | 
			
		||||
	rqs, perr = f.Reset(rqs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		f.writeError(w, 500, "Write result fail", err)
 | 
			
		||||
		f.writeError(w, 500, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = en.Finish()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		f.writeError(w, 500, "Engine finish fail", err)
 | 
			
		||||
	if perr != nil {
 | 
			
		||||
		f.writeError(w, 500, perr)
 | 
			
		||||
		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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package http
 | 
			
		||||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"git.defalsify.org/vise.git/db"
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user