added updated code for core wiring

1. Removed tracing
2. Skipped Registration
This commit is contained in:
MohitKatare-protean
2025-03-25 21:06:34 +05:30
parent 519cca19af
commit ec558558c5
87 changed files with 9279 additions and 711 deletions

View File

@@ -0,0 +1,117 @@
package handler
import (
"context"
"fmt"
"net/http"
"github.com/beckn/beckn-onix/pkg/model"
"github.com/beckn/beckn-onix/pkg/plugin"
"github.com/beckn/beckn-onix/pkg/plugin/definition"
)
// PluginManager defines the methods required for managing plugins in stdHandler.
type PluginManager interface {
Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error)
SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error)
Validator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error)
Router(ctx context.Context, cfg *plugin.Config) (definition.Router, error)
Publisher(ctx context.Context, cfg *plugin.Config) (definition.Publisher, error)
Signer(ctx context.Context, cfg *plugin.Config) (definition.Signer, error)
Step(ctx context.Context, cfg *plugin.Config) (definition.Step, error)
Cache(ctx context.Context, cfg *plugin.Config) (definition.Cache, error)
KeyManager(ctx context.Context, cache definition.Cache, rLookup definition.RegistryLookup, cfg *plugin.Config) (definition.KeyManager, error)
SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error)
}
// Provider represents a function that initializes an HTTP handler using a PluginManager.
type Provider func(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error)
// Type defines different handler types for processing requests.
type Type string
const (
// HandlerTypeStd represents the standard handler type used for general request processing.
HandlerTypeStd Type = "std"
// HandlerTypeRegSub represents the registry subscriber handler type for handling registry subscription requests.
HandlerTypeRegSub Type = "regSub"
// HandlerTypeNPSub represents the network participant subscriber handler type for handling network participant subscription requests.
HandlerTypeNPSub Type = "npSub"
// HandlerTypeLookup represents the lookup handler type used for resolving service details.
HandlerTypeLookup Type = "lookUp"
)
// pluginCfg holds the configuration for various plugins.
type pluginCfg struct {
SchemaValidator *plugin.Config `yaml:"schemaValidator,omitempty"`
SignValidator *plugin.Config `yaml:"signValidator,omitempty"`
Publisher *plugin.Config `yaml:"publisher,omitempty"`
Signer *plugin.Config `yaml:"signer,omitempty"`
Router *plugin.Config `yaml:"router,omitempty"`
Cache *plugin.Config `yaml:"cache,omitempty"`
KeyManager *plugin.Config `yaml:"keyManager,omitempty"`
Middleware []plugin.Config `yaml:"middleware,omitempty"`
Steps []plugin.Config
}
// Config holds the configuration for request processing handlers.
type Config struct {
Plugins pluginCfg `yaml:"plugins"`
Steps []string
Type Type
RegistryURL string `yaml:"registryUrl"`
Role model.Role
SubscriberID string `yaml:"subscriberId"`
Trace map[string]bool
}
// Step represents a named processing step.
type Step string
const (
// StepInitialize represents the initialization phase of the request processing pipeline.
StepInitialize Step = "initialize"
// StepValidate represents the validation phase, where input data is checked for correctness.
StepValidate Step = "validate"
// StepProcess represents the core processing phase of the request.
StepProcess Step = "process"
// StepFinalize represents the finalization phase, where the response is prepared and sent.
StepFinalize Step = "finalize"
)
// validSteps ensures only allowed step values are used.
var validSteps = map[Step]bool{
StepInitialize: true,
StepValidate: true,
StepProcess: true,
StepFinalize: true,
}
// UnmarshalYAML customizes YAML unmarshalling for Step to enforce valid values.
func (s *Step) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stepName string
if err := unmarshal(&stepName); err != nil {
return err
}
step := Step(stepName)
if !validSteps[step] {
return fmt.Errorf("invalid step: %s", stepName)
}
*s = step
return nil
}
// DummyHandler is a basic HTTP handler that returns a fixed response.
func DummyHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Dummy Handler Response"))
}), nil
}

View File

@@ -0,0 +1,264 @@
package handler
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/http/httputil"
"net/url"
"github.com/beckn/beckn-onix/core/module/client"
"github.com/beckn/beckn-onix/pkg/log"
"github.com/beckn/beckn-onix/pkg/model"
"github.com/beckn/beckn-onix/pkg/plugin"
"github.com/beckn/beckn-onix/pkg/plugin/definition"
"github.com/beckn/beckn-onix/pkg/response"
)
// stdHandler orchestrates the execution of defined processing steps.
type stdHandler struct {
signer definition.Signer
steps []definition.Step
signValidator definition.Verifier
cache definition.Cache
km definition.KeyManager
schemaValidator definition.SchemaValidator
router definition.Router
publisher definition.Publisher
SubscriberID string
role model.Role
}
// NewStdHandler initializes a new processor with plugins and steps.
func NewStdHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) {
h := &stdHandler{
steps: []definition.Step{},
SubscriberID: cfg.SubscriberID,
role: cfg.Role,
}
// Initialize plugins
if err := h.initPlugins(ctx, mgr, &cfg.Plugins, cfg.RegistryURL); err != nil {
return nil, fmt.Errorf("failed to initialize plugins: %w", err)
}
// Initialize steps
if err := h.initSteps(ctx, mgr, cfg); err != nil {
return nil, fmt.Errorf("failed to initialize steps: %w", err)
}
return h, nil
}
// ServeHTTP processes an incoming HTTP request and executes defined processing steps.
func (h *stdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, err := h.stepCtx(r, w.Header())
if err != nil {
log.Errorf(r.Context(), err, "stepCtx(r):%v", err)
response.SendNack(r.Context(), w, err)
return
}
log.Request(r.Context(), r, ctx.Body)
// Execute processing steps
for _, step := range h.steps {
if err := step.Run(ctx); err != nil {
log.Errorf(ctx, err, "%T.run(%v):%v", step, ctx, err)
response.SendNack(ctx, w, err)
return
}
}
// Restore request body before forwarding or publishing
r.Body = io.NopCloser(bytes.NewReader(ctx.Body))
if ctx.Route == nil {
response.SendAck(w)
return
}
// Handle routing based on the defined route type
route(ctx, r, w, h.publisher)
}
// stepCtx creates a new StepContext for processing an HTTP request.
func (h *stdHandler) stepCtx(r *http.Request, rh http.Header) (*model.StepContext, error) {
var bodyBuffer bytes.Buffer
if _, err := io.Copy(&bodyBuffer, r.Body); err != nil {
return nil, model.NewBadReqErr(err)
}
r.Body.Close()
subID := h.subID(r.Context())
if len(subID) == 0 {
return nil, model.NewBadReqErr(fmt.Errorf("subscriberID not set"))
}
return &model.StepContext{
Context: r.Context(),
Request: r,
Body: bodyBuffer.Bytes(),
Role: h.role,
SubID: subID,
RespHeader: rh,
}, nil
}
// subID retrieves the subscriber ID from the request context.
func (h *stdHandler) subID(ctx context.Context) string {
rSubID, ok := ctx.Value("subscriber_id").(string)
if ok {
return rSubID
}
return h.SubscriberID
}
// route handles request forwarding or message publishing based on the routing type.
func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb definition.Publisher) {
log.Debugf(ctx, "Routing to ctx.Route to %#v", ctx.Route)
switch ctx.Route.Type {
case "url":
log.Infof(ctx.Context, "Forwarding request to URL: %s", ctx.Route.URL)
proxy(r, w, ctx.Route.URL)
return
case "publisher":
if pb == nil {
err := fmt.Errorf("publisher plugin not configured")
log.Errorf(ctx.Context, err, "Invalid configuration:%v", err)
response.SendNack(ctx, w, err)
return
}
log.Infof(ctx.Context, "Publishing message to: %s", ctx.Route.Publisher)
if err := pb.Publish(ctx, ctx.Body); err != nil {
log.Errorf(ctx.Context, err, "Failed to publish message")
http.Error(w, "Error publishing message", http.StatusInternalServerError)
response.SendNack(ctx, w, err)
return
}
default:
err := fmt.Errorf("unknown route type: %s", ctx.Route.Type)
log.Errorf(ctx.Context, err, "Invalid configuration:%v", err)
response.SendNack(ctx, w, err)
return
}
response.SendAck(w)
}
// proxy forwards the request to a target URL using a reverse proxy.
func proxy(r *http.Request, w http.ResponseWriter, target *url.URL) {
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
r.URL.Path = target.Path
r.Header.Set("X-Forwarded-Host", r.Host)
proxy := httputil.NewSingleHostReverseProxy(target)
log.Infof(r.Context(), "Proxying request to: %s", target)
proxy.ServeHTTP(w, r)
}
// loadPlugin is a generic function to load and validate plugins.
func loadPlugin[T any](ctx context.Context, name string, cfg *plugin.Config, mgrFunc func(context.Context, *plugin.Config) (T, error)) (T, error) {
var zero T
if cfg == nil {
log.Debugf(ctx, "Skipping %s plugin: not configured", name)
return zero, nil
}
plugin, err := mgrFunc(ctx, cfg)
if err != nil {
return zero, fmt.Errorf("failed to load %s plugin (%s): %w", name, cfg.ID, err)
}
log.Debugf(ctx, "Loaded %s plugin: %s", name, cfg.ID)
return plugin, nil
}
// loadKeyManager loads the KeyManager plugin using the provided PluginManager, cache, and registry URL.
func loadKeyManager(ctx context.Context, mgr PluginManager, cache definition.Cache, cfg *plugin.Config, regURL string) (definition.KeyManager, error) {
if cfg == nil {
log.Debug(ctx, "Skipping KeyManager plugin: not configured")
return nil, nil
}
if cache == nil {
return nil, fmt.Errorf("failed to load KeyManager plugin (%s): Cache plugin not configured", cfg.ID)
}
rClient := client.NewRegisteryClient(&client.Config{RegisteryURL: regURL})
km, err := mgr.KeyManager(ctx, cache, rClient, cfg)
if err != nil {
return nil, fmt.Errorf("failed to load cache plugin (%s): %w", cfg.ID, err)
}
log.Debugf(ctx, "Loaded Keymanager plugin: %s", cfg.ID)
return km, nil
}
// initPlugins initializes required plugins for the processor.
func (h *stdHandler) initPlugins(ctx context.Context, mgr PluginManager, cfg *pluginCfg, regURL string) error {
var err error
if h.cache, err = loadPlugin(ctx, "Cache", cfg.Cache, mgr.Cache); err != nil {
return err
}
if h.km, err = loadKeyManager(ctx, mgr, h.cache, cfg.KeyManager, regURL); err != nil {
return err
}
if h.signValidator, err = loadPlugin(ctx, "SignValidator", cfg.SignValidator, mgr.SignValidator); err != nil {
return err
}
if h.schemaValidator, err = loadPlugin(ctx, "SchemaValidator", cfg.SchemaValidator, mgr.SchemaValidator); err != nil {
return err
}
if h.router, err = loadPlugin(ctx, "Router", cfg.Router, mgr.Router); err != nil {
return err
}
if h.publisher, err = loadPlugin(ctx, "Publisher", cfg.Publisher, mgr.Publisher); err != nil {
return err
}
if h.signer, err = loadPlugin(ctx, "Signer", cfg.Signer, mgr.Signer); err != nil {
return err
}
log.Debugf(ctx, "All required plugins successfully loaded for stdHandler")
return nil
}
// initSteps initializes and validates processing steps for the processor.
func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Config) error {
steps := make(map[string]definition.Step)
// Load plugin-based steps
for _, c := range cfg.Plugins.Steps {
step, err := mgr.Step(ctx, &c)
if err != nil {
return fmt.Errorf("failed to initialize plugin step %s: %w", c.ID, err)
}
steps[c.ID] = step
}
// Register processing steps
for _, step := range cfg.Steps {
var s definition.Step
var err error
switch step {
case "sign":
s, err = newSignStep(h.signer, h.km)
case "validateSign":
s, err = newValidateSignStep(h.signValidator, h.km)
case "validateSchema":
s, err = newValidateSchemaStep(h.schemaValidator)
case "addRoute":
s, err = newRouteStep(h.router)
case "broadcast":
s = &broadcastStep{}
default:
if customStep, exists := steps[step]; exists {
s = customStep
} else {
return fmt.Errorf("unrecognized step: %s", step)
}
}
if err != nil {
return err
}
h.steps = append(h.steps, s)
}
log.Infof(ctx, "Processor steps initialized: %v", cfg.Steps)
return nil
}

181
core/module/handler/step.go Normal file
View File

@@ -0,0 +1,181 @@
package handler
import (
"context"
"fmt"
"strings"
"time"
"github.com/beckn/beckn-onix/pkg/log"
"github.com/beckn/beckn-onix/pkg/model"
"github.com/beckn/beckn-onix/pkg/plugin/definition"
)
// signStep represents the signing step in the processing pipeline.
type signStep struct {
signer definition.Signer
km definition.KeyManager
}
// newSignStep initializes and returns a new signing step.
func newSignStep(signer definition.Signer, km definition.KeyManager) (definition.Step, error) {
if signer == nil {
return nil, fmt.Errorf("invalid config: Signer plugin not configured")
}
if km == nil {
return nil, fmt.Errorf("invalid config: KeyManager plugin not configured")
}
return &signStep{signer: signer, km: km}, nil
}
// Run executes the signing step.
func (s *signStep) Run(ctx *model.StepContext) error {
keyID, key, err := s.km.SigningPrivateKey(ctx, ctx.SubID)
if err != nil {
return fmt.Errorf("failed to get signing key: %w", err)
}
createdAt := time.Now().Unix()
validTill := time.Now().Add(5 * time.Minute).Unix()
sign, err := s.signer.Sign(ctx, ctx.Body, key, createdAt, validTill)
if err != nil {
return fmt.Errorf("failed to sign request: %w", err)
}
authHeader := fmt.Sprintf("Signature keyId=\"%s|%s|ed25519\",algorithm=\"ed25519\",created=\"%d\",expires=\"%d\",headers=\"(created) (expires) digest\",signature=\"%s\"", ctx.SubID, keyID, createdAt, validTill, sign)
header := model.AuthHeaderSubscriber
if ctx.Role == model.RoleGateway {
header = model.AuthHeaderGateway
}
ctx.Request.Header.Set(header, authHeader)
return nil
}
// validateSignStep represents the signature validation step.
type validateSignStep struct {
validator definition.Verifier
km definition.KeyManager
}
// newValidateSignStep initializes and returns a new validate sign step.
func newValidateSignStep(signValidator definition.Verifier, km definition.KeyManager) (definition.Step, error) {
if signValidator == nil {
return nil, fmt.Errorf("invalid config: SignValidator plugin not configured")
}
if km == nil {
return nil, fmt.Errorf("invalid config: KeyManager plugin not configured")
}
return &validateSignStep{validator: signValidator, km: km}, nil
}
// Run executes the validation step.
func (s *validateSignStep) Run(ctx *model.StepContext) error {
unauthHeader := fmt.Sprintf("Signature realm=\"%s\",headers=\"(created) (expires) digest\"", ctx.SubID)
headerValue := ctx.Request.Header.Get(model.AuthHeaderGateway)
if len(headerValue) != 0 {
if err := s.validate(ctx, headerValue); err != nil {
ctx.RespHeader.Set(model.UnaAuthorizedHeaderGateway, unauthHeader)
return model.NewSignValidationErrf("failed to validate %s: %w", model.AuthHeaderGateway, err)
}
}
headerValue = ctx.Request.Header.Get(model.AuthHeaderSubscriber)
if len(headerValue) == 0 {
ctx.RespHeader.Set(model.UnaAuthorizedHeaderSubscriber, unauthHeader)
return model.NewSignValidationErrf("%s missing", model.UnaAuthorizedHeaderSubscriber)
}
if err := s.validate(ctx, headerValue); err != nil {
ctx.RespHeader.Set(model.UnaAuthorizedHeaderSubscriber, unauthHeader)
return model.NewSignValidationErrf("failed to validate %s: %w", model.AuthHeaderSubscriber, err)
}
return nil
}
// validate checks the validity of the provided signature header.
func (s *validateSignStep) validate(ctx *model.StepContext, value string) error {
headerParts := strings.Split(value, "|")
ids := strings.Split(headerParts[0], "\"")
if len(ids) < 2 || len(headerParts) < 3 {
return fmt.Errorf("malformed sign header")
}
subID := ids[1]
keyID := headerParts[1]
key, err := s.km.SigningPublicKey(ctx, subID, keyID)
if err != nil {
return fmt.Errorf("failed to get validation key: %w", err)
}
if _, err := s.validator.Verify(ctx, ctx.Body, []byte(value), key); err != nil {
return fmt.Errorf("sign validation failed: %w", err)
}
return nil
}
// validateSchemaStep represents the schema validation step.
type validateSchemaStep struct {
validator definition.SchemaValidator
}
// newValidateSchemaStep creates and returns the validateSchema step after validation
func newValidateSchemaStep(schemaValidator definition.SchemaValidator) (definition.Step, error) {
if schemaValidator == nil {
return nil, fmt.Errorf("invalid config: SchemaValidator plugin not configured")
}
log.Debug(context.Background(), "adding schema validator")
return &validateSchemaStep{validator: schemaValidator}, nil
}
// Run executes the schema validation step.
func (s *validateSchemaStep) Run(ctx *model.StepContext) error {
if err := s.validator.Validate(ctx, ctx.Request.URL, ctx.Body); err != nil {
return fmt.Errorf("schema validation failed: %w", err)
}
return nil
}
// addRouteStep represents the route determination step.
type addRouteStep struct {
router definition.Router
}
// newRouteStep initializes and returns a new routing step.
func newRouteStep(router definition.Router) (definition.Step, error) {
if router == nil {
return nil, fmt.Errorf("invalid config: Router plugin not configured")
}
return &addRouteStep{router: router}, nil
}
// Run executes the routing step.
func (s *addRouteStep) Run(ctx *model.StepContext) error {
route, err := s.router.Route(ctx, ctx.Request.URL, ctx.Body)
if err != nil {
return fmt.Errorf("failed to determine route: %w", err)
}
log.Debugf(ctx, "Routing to %#v", route)
ctx.Route = route
log.Debugf(ctx, "ctx.Route to %#v", ctx.Route)
return nil
}
// broadcastStep is a stub for broadcasting.
type broadcastStep struct{}
// Run is a placeholder for future implementation.
func (b *broadcastStep) Run(ctx *model.StepContext) error {
// TODO: Implement broadcast logic if needed
return nil
}
// subscribeStep is a stub for subscription handling.
type subscribeStep struct{}
// Run is a placeholder for future implementation.
func (s *subscribeStep) Run(ctx *model.StepContext) error {
// TODO: Implement subscription logic if needed
return nil
}
// tracingStep wraps a Step with OpenTelemetry tracing
type tracingStep struct {
step definition.Step
name string
}