implement driver for type checkers | move controller/middleware/checker definition inside the 'driver' package (all uses are exported from this place) | now driver.Driver returns 'Controller', 'Middleware', and 'Checker' in the interface | a lot a junk

This commit is contained in:
Adrien Marquès 2018-10-01 19:27:38 +02:00
parent 37fe30ebc7
commit d406338777
14 changed files with 380 additions and 438 deletions

View File

@ -1,14 +1,7 @@
package driver package driver
import ( import (
"encoding/json"
"fmt"
e "git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response"
"net/http"
"os/exec"
"path/filepath" "path/filepath"
"strings"
) )
// Generic tells the aicra instance to use the generic driver to load controller/middleware executables // Generic tells the aicra instance to use the generic driver to load controller/middleware executables
@ -40,125 +33,19 @@ func (d Generic) Build(_root, _folder, _path string) string {
// Compiled returns whether the driver has to be build // Compiled returns whether the driver has to be build
func (d Generic) Compiled() bool { return false } func (d Generic) Compiled() bool { return false }
// RunController implements the Driver interface // LoadController implements the Driver interface
func (d *Generic) RunController(_path []string, _method string) (func(response.Arguments) response.Response, e.Error) { func (d *Generic) LoadController(_path string) (Controller, error) {
return GenericController(_path), nil
/* (1) Build controller path */
path := strings.Join(_path, "-")
if len(path) == 0 {
path = fmt.Sprintf("./controller/ROOT")
} else {
path = fmt.Sprintf("./controller/%s", path)
}
/* (2) Format method */
method := strings.ToUpper(_method)
return func(d response.Arguments) response.Response {
res := response.New()
/* (1) Prepare stdin data */
d["_HTTP_METHOD_"] = method
stdin, err := json.Marshal(d)
if err != nil {
res.Err = e.UncallableController
return *res
}
/* (2) Try to load command with <stdin> -> stdout */
cmd := exec.Command(path, string(stdin))
stdout, err := cmd.Output()
if err != nil {
res.Err = e.UncallableController
return *res
}
/* (3) Get output json */
var outputI interface{}
err = json.Unmarshal(stdout, &outputI)
if err != nil {
res.Err = e.UncallableController
return *res
}
output, ok := outputI.(map[string]interface{})
if !ok {
res.Err = e.UncallableController
return *res
}
res.Err = e.Success
// extract error (success by default or on error)
if outErr, ok := output["error"]; ok {
errCode, ok := outErr.(float64)
if ok {
res.Err = e.Error{Code: int(errCode), Reason: "unknown reason", Arguments: nil}
}
delete(output, "error")
}
/* (4) fill response */
for k, v := range output {
res.Set(k, v)
}
return *res
}, e.Success
} }
// LoadMiddleware returns a new middleware function; it must be a // LoadMiddleware returns a new middleware; it must be a
// valid and existing folder/filename file // valid and existing folder/filename file
func (d *Generic) LoadMiddleware(_path string) (func(http.Request, *[]string), error) { func (d *Generic) LoadMiddleware(_path string) (Middleware, error) {
return GenericMiddleware(_path), nil
/* (1) Check plugin name */ }
if len(_path) < 1 {
return nil, fmt.Errorf("Middleware name must not be empty") // LoadChecker returns a new middleware; it must be a
} // valid and existing folder/filename file
func (d *Generic) LoadChecker(_path string) (Checker, error) {
/* (2) Create method + error */ return GenericChecker(_path), nil
return func(_req http.Request, _scope *[]string) {
/* (1) Prepare stdin data */
stdin, err := json.Marshal(_scope)
if err != nil {
return
}
/* (2) Try to load command with <stdin> -> stdout */
cmd := exec.Command(_path, string(stdin))
stdout, err := cmd.Output()
if err != nil {
return
}
/* (3) Get output json */
var outputI interface{}
err = json.Unmarshal(stdout, &outputI)
if err != nil {
return
}
/* (4) Get as []string */
scope, ok := outputI.([]interface{})
if !ok {
return
}
/* (5) Try to add each value to the scope */
for _, v := range scope {
stringScope, ok := v.(string)
if !ok {
continue
}
*_scope = append(*_scope, stringScope)
}
}, nil
} }

166
driver/generic.mockup.go Normal file
View File

@ -0,0 +1,166 @@
package driver
import (
"encoding/json"
e "git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response"
"net/http"
"os/exec"
"strings"
)
// GenericController is the mockup for returning a controller with as a string the path
type GenericController string
func (path GenericController) Get(d response.Arguments) response.Response {
res := response.New()
/* (1) Prepare stdin data */
stdin, err := json.Marshal(d)
if err != nil {
res.Err = e.UncallableController
return *res
}
/* (2) Try to load command with <stdin> -> stdout */
cmd := exec.Command(string(path), string(stdin))
stdout, err := cmd.Output()
if err != nil {
res.Err = e.UncallableController
return *res
}
/* (3) Get output json */
var outputI interface{}
err = json.Unmarshal(stdout, &outputI)
if err != nil {
res.Err = e.UncallableController
return *res
}
output, ok := outputI.(map[string]interface{})
if !ok {
res.Err = e.UncallableController
return *res
}
res.Err = e.Success
// extract error (success by default or on error)
if outErr, ok := output["error"]; ok {
errCode, ok := outErr.(float64)
if ok {
res.Err = e.Error{Code: int(errCode), Reason: "unknown reason", Arguments: nil}
}
delete(output, "error")
}
/* (4) fill response */
for k, v := range output {
res.Set(k, v)
}
return *res
}
func (path GenericController) Post(d response.Arguments) response.Response {
return path.Get(d)
}
func (path GenericController) Put(d response.Arguments) response.Response {
return path.Get(d)
}
func (path GenericController) Delete(d response.Arguments) response.Response {
return path.Get(d)
}
// GenericMiddleware is the mockup for returning a middleware as a string (its path)
type GenericMiddleware string
func (path GenericMiddleware) Inspect(_req http.Request, _scope *[]string) {
/* (1) Prepare stdin data */
stdin, err := json.Marshal(_scope)
if err != nil {
return
}
/* (2) Try to load command with <stdin> -> stdout */
cmd := exec.Command(string(path), string(stdin))
stdout, err := cmd.Output()
if err != nil {
return
}
/* (3) Get output json */
var outputI interface{}
err = json.Unmarshal(stdout, &outputI)
if err != nil {
return
}
/* (4) Get as []string */
scope, ok := outputI.([]interface{})
if !ok {
return
}
/* (5) Try to add each value to the scope */
for _, v := range scope {
stringScope, ok := v.(string)
if !ok {
continue
}
*_scope = append(*_scope, stringScope)
}
}
// GenericChecker is the mockup for returning a checker as a string (its path)
type GenericChecker string
func (path GenericChecker) Match(_type string) bool {
/* (1) Try to load command with <stdin> -> stdout */
cmd := exec.Command(string(path), _type)
stdout, err := cmd.Output()
if err != nil {
return false
}
/* (2) Parse output */
output := strings.ToLower(strings.Trim(string(stdout), " \t\r\n"))
return output == "true" || output == "1"
}
func (path GenericChecker) Check(_value interface{}) bool {
/* (1) Prepare stdin data */
indata := make(map[string]interface{})
indata["value"] = _value
stdin, err := json.Marshal(indata)
if err != nil {
return false
}
/* (2) Try to load command with <stdin> -> stdout */
cmd := exec.Command(string(path), string(stdin))
stdout, err := cmd.Output()
if err != nil {
return false
}
/* (2) Parse output */
output := strings.ToLower(strings.Trim(string(stdout), " \t\r\n"))
return output == "true" || output == "1"
}

View File

@ -2,12 +2,8 @@ package driver
import ( import (
"fmt" "fmt"
"git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response"
"net/http"
"path/filepath" "path/filepath"
"plugin" "plugin"
"strings"
) )
// Plugin tells the aicra instance to use the plugin driver to load controller/middleware executables // Plugin tells the aicra instance to use the plugin driver to load controller/middleware executables
@ -34,104 +30,88 @@ func (d Plugin) Path(_root, _folder, _src string) string {
// Source returns the source path from the universal path // Source returns the source path from the universal path
func (d Plugin) Source(_root, _folder, _path string) string { func (d Plugin) Source(_root, _folder, _path string) string {
return filepath.Join(_root, _folder, _path, "main.go") return filepath.Join(_root, _folder, _path, "main.go")
} }
// Build returns the build path from the universal path // Build returns the build path from the universal path
func (d Plugin) Build(_root, _folder, _path string) string { func (d Plugin) Build(_root, _folder, _path string) string {
if _path == "" {
return fmt.Sprintf("%s", filepath.Join(_root, ".build", _folder, "ROOT.so"))
}
return fmt.Sprintf("%s.so", filepath.Join(_root, ".build", _folder, _path)) return fmt.Sprintf("%s.so", filepath.Join(_root, ".build", _folder, _path))
} }
// Compiled returns whether the driver has to be build // Compiled returns whether the driver has to be build
func (d Plugin) Compiled() bool { return true } func (d Plugin) Compiled() bool { return true }
// RunController implements the Driver interface // LoadController returns a new Controller
func (d *Plugin) RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) { func (d *Plugin) LoadController(_path string) (Controller, error) {
/* (1) Build controller path */ /* 1. Try to load plugin */
path := strings.Join(_path, "-")
if len(path) == 0 {
path = fmt.Sprintf(".build/controller/ROOT.so")
} else {
path = fmt.Sprintf(".build/controller/%s.so", path)
}
/* (2) Format url */
method := strings.ToLower(_method)
/* (2) Try to load plugin */
p, err2 := plugin.Open(path)
if err2 != nil {
return nil, err.UncallableController
}
/* (3) Try to extract exported field */
m, err2 := p.Lookup("Export")
if err2 != nil {
return nil, err.UncallableController
}
exported, ok := m.(func() Controller)
if !ok {
return nil, err.UncallableController
}
/* (4) Controller */
ctl := exported()
/* (4) Check signature */
switch method {
case "get":
return ctl.Get, err.Success
case "post":
return ctl.Post, err.Success
case "put":
return ctl.Put, err.Success
case "delete":
return ctl.Delete, err.Success
}
fmt.Printf("method: %s\n", method)
return nil, err.UncallableMethod
}
// LoadMiddleware returns a new middleware function; it must be a
// valid and existing folder/filename file with the .so extension
func (d *Plugin) LoadMiddleware(_path string) (func(http.Request, *[]string), error) {
// ignore non .so files
if !strings.HasSuffix(_path, ".so") {
return nil, fmt.Errorf("Invalid name")
}
/* (1) Check plugin name */
if len(_path) < 1 {
return nil, fmt.Errorf("Middleware name must not be empty")
}
/* (2) Check plugin extension */
if !strings.HasSuffix(_path, ".so") {
_path = fmt.Sprintf("%s.so", _path)
}
/* (3) Try to load the plugin */
p, err := plugin.Open(_path) p, err := plugin.Open(_path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
/* (4) Extract exported fields */ /* 2. Try to extract exported field */
mw, err := p.Lookup("Export") m, err := p.Lookup("Export")
if err != nil { if err != nil {
return nil, fmt.Errorf("Missing method 'Inspect()'; %s", err) return nil, err
} }
exported, ok := mw.(func() Middleware) exporter, ok := m.(func() Controller)
if !ok { if !ok {
return nil, fmt.Errorf("Inspect() is malformed") return nil, err
} }
/* (5) Return Inspect method */ /* 3. Controller */
return exported().Inspect, nil return exporter(), nil
}
// LoadMiddleware returns a new Middleware
func (d *Plugin) LoadMiddleware(_path string) (Middleware, error) {
/* 1. Try to load plugin */
p, err := plugin.Open(_path)
if err != nil {
return nil, err
}
/* 2. Try to extract exported field */
m, err := p.Lookup("Export")
if err != nil {
return nil, err
}
exporter, ok := m.(func() Middleware)
if !ok {
return nil, err
}
return exporter(), nil
}
// LoadChecker returns a new Checker
func (d *Plugin) LoadChecker(_path string) (Checker, error) {
/* 1. Try to load plugin */
p, err := plugin.Open(_path)
if err != nil {
return nil, err
}
/* 2. Try to extract exported field */
m, err := p.Lookup("Export")
if err != nil {
return nil, err
}
exporter, ok := m.(func() Checker)
if !ok {
return nil, err
}
return exporter(), nil
} }

View File

@ -1,7 +1,6 @@
package driver package driver
import ( import (
"git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response" "git.xdrm.io/go/aicra/response"
"net/http" "net/http"
) )
@ -14,8 +13,15 @@ type Driver interface {
Build(string, string, string) string Build(string, string, string) string
Compiled() bool Compiled() bool
RunController(_path []string, _method string) (func(response.Arguments) response.Response, err.Error) LoadController(_path string) (Controller, error)
LoadMiddleware(_path string) (func(http.Request, *[]string), error) LoadMiddleware(_path string) (Middleware, error)
LoadChecker(_path string) (Checker, error)
}
// Checker is the interface that type checkers implementation must follow
type Checker interface {
Match(string) bool
Check(interface{}) bool
} }
// Controller is the interface that controller implementation must follow // Controller is the interface that controller implementation must follow

View File

@ -2,104 +2,20 @@ package checker
import ( import (
"errors" "errors"
"fmt" "git.xdrm.io/go/aicra/driver"
"io/ioutil"
"log"
"plugin"
"strings"
) )
var ErrNoMatchingType = errors.New("no matching type") var ErrNoMatchingType = errors.New("no matching type")
var ErrDoesNotMatch = errors.New("does not match") var ErrDoesNotMatch = errors.New("does not match")
var ErrEmptyTypeName = errors.New("type name must not be empty")
// CreateRegistry creates an empty type registry // CreateRegistry creates an empty type registry
func CreateRegistry(_folder string) *Registry { func CreateRegistry() Registry {
return make(Registry)
/* (1) Create registry */
reg := &Registry{
Types: make([]Type, 0),
}
/* (2) List types */
files, err := ioutil.ReadDir(_folder)
if err != nil {
log.Fatal(err)
}
/* (3) Else try to load each given default */
for _, file := range files {
// ignore non .so files
if !strings.HasSuffix(file.Name(), ".so") {
continue
}
err := reg.add(file.Name())
if err != nil {
log.Printf("cannot load plugin '%s'", file.Name())
}
}
return reg
} }
// add adds a type to the registry; it must be a // Add adds a new checker for a path
// valid and existing plugin name with or without the .so extension func (reg Registry) Add(_path string, _element driver.Checker) {
// it must be located in the relative directory ./types reg[_path] = _element
func (reg *Registry) add(pluginName string) error {
/* (1) Check plugin name */
if len(pluginName) < 1 {
return ErrEmptyTypeName
}
/* (2) Check if valid plugin name */
if strings.ContainsAny(pluginName, "/") {
return fmt.Errorf("'%s' can only be a name, not a path", pluginName)
}
/* (3) Check plugin extension */
if !strings.HasSuffix(pluginName, ".so") {
pluginName = fmt.Sprintf("%s.so", pluginName)
}
/* (4) Try to load the plugin */
p, err := plugin.Open(fmt.Sprintf(".build/type/%s", pluginName))
if err != nil {
return err
}
/* (5) Export wanted properties */
matcher, err := p.Lookup("Match")
if err != nil {
return fmt.Errorf("Missing method 'Match()'; %s", err)
}
checker, err := p.Lookup("Check")
if err != nil {
return fmt.Errorf("Missing method 'Check()'; %s", err)
}
/* (6) Cast Match+Check */
matcherCast, ok := matcher.(func(string) bool)
if !ok {
return fmt.Errorf("Match() is malformed")
}
checkerCast, ok := checker.(func(interface{}) bool)
if !ok {
return fmt.Errorf("Check() is malformed")
}
/* (7) Add type to registry */
reg.Types = append(reg.Types, Type{
Match: matcherCast,
Check: checkerCast,
})
return nil
} }
// Run finds a type checker from the registry matching the type @typeName // Run finds a type checker from the registry matching the type @typeName
@ -107,28 +23,23 @@ func (reg *Registry) add(pluginName string) error {
// the @typeName name, error is returned by default. // the @typeName name, error is returned by default.
func (reg Registry) Run(typeName string, value interface{}) error { func (reg Registry) Run(typeName string, value interface{}) error {
var T *Type
/* (1) Iterate to find matching type (take first) */ /* (1) Iterate to find matching type (take first) */
for _, t := range reg.Types { for _, t := range reg {
// stop if found // stop if found
if t.Match(typeName) { if t.Match(typeName) {
T = &t
break // check value
if t.Check(value) {
return nil
}
return ErrDoesNotMatch
} }
} }
/* (2) Abort if no matching type */ return ErrNoMatchingType
if T == nil {
return ErrNoMatchingType
}
/* (3) Check */
if !T.Check(value) {
return ErrDoesNotMatch
}
return nil
} }

View File

@ -1,5 +1,9 @@
package checker package checker
import (
"git.xdrm.io/go/aicra/driver"
)
// Matcher returns whether a type 'name' matches a type // Matcher returns whether a type 'name' matches a type
type Matcher func(name string) bool type Matcher func(name string) bool
@ -8,15 +12,6 @@ type Matcher func(name string) bool
// to provide indulgent type check if needed // to provide indulgent type check if needed
type Checker func(value interface{}) bool type Checker func(value interface{}) bool
// Type contains all necessary methods
// for a type provided by user/developer
type Type struct {
Match func(string) bool
Check func(interface{}) bool
}
// Registry represents a registry containing all available // Registry represents a registry containing all available
// Type-s to be used by the framework according to the configuration // Type-s to be used by the framework according to the configuration
type Registry struct { type Registry map[string]driver.Checker
Types []Type // registered Type-s
}

View File

@ -7,19 +7,15 @@ import (
"path/filepath" "path/filepath"
) )
// format inits the map if not set
func (b *builder) format() {
if b.Map == nil {
b.Map = make(map[string]string)
}
}
// InferFromFolder fills the 'Map' by browsing recursively the // InferFromFolder fills the 'Map' by browsing recursively the
// 'Folder' field // 'Folder' field
func (b *builder) InferFromFolder(_root string, _driver driver.Driver) { func (b *builder) InferFromFolder(_root string, _driver driver.Driver) {
// init map
if b.Map == nil {
b.Map = make(map[string]string)
}
// 1. ignore if no Folder // 1. ignore if no Folder
if len(b.Folder) < 1 { if len(b.Folder) < 1 {
return return
@ -46,10 +42,10 @@ func (b *builder) InferFromFolder(_root string, _driver driver.Driver) {
// format name // format name
name := upath name := upath
if name == "ROOT" { if name == "/" {
name = "" name = ""
} }
name = fmt.Sprintf("/%s", name) name = fmt.Sprintf("%s", name)
// add to map // add to map
b.Map[name] = upath b.Map[name] = upath

View File

@ -2,22 +2,20 @@ package config
// Default contains the default values when ommited in json // Default contains the default values when ommited in json
var Default = Schema{ var Default = Schema{
Root: ".",
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 80, Port: 80,
DriverName: "", DriverName: "",
Types: &builder{ Types: &builder{
Default: true, Default: true,
Folder: "", Folder: "type",
Map: nil,
}, },
Controllers: &builder{ Controllers: &builder{
Default: false, Default: false,
Folder: "", Folder: "controller",
Map: nil,
}, },
Middlewares: &builder{ Middlewares: &builder{
Default: false, Default: false,
Folder: "", Folder: "middleware",
Map: nil,
}, },
} }

View File

@ -41,12 +41,7 @@ func Parse(_path string) (*Schema, error) {
return nil, errors.New("invalid driver; choose from 'generic', 'plugin'") return nil, errors.New("invalid driver; choose from 'generic', 'plugin'")
} }
/* 5. Fail if type map is set */ /* 5. Fail on absolute folders */
if receiver.Types.Map != nil {
return nil, errors.New("types must not feature the 'map'")
}
/* 6. Fail on absolute folders */
if len(receiver.Types.Folder) > 0 && filepath.IsAbs(receiver.Types.Folder) { if len(receiver.Types.Folder) > 0 && filepath.IsAbs(receiver.Types.Folder) {
return nil, errors.New("types folder must be relative to root") return nil, errors.New("types folder must be relative to root")
} }
@ -83,7 +78,7 @@ func (m *Schema) setDefaults() {
m.Port = Default.Port m.Port = Default.Port
} }
// 4. Empty builders // 4. Use default builders if not set
if m.Types == nil { if m.Types == nil {
m.Types = Default.Types m.Types = Default.Types
} }
@ -94,10 +89,16 @@ func (m *Schema) setDefaults() {
m.Middlewares = Default.Middlewares m.Middlewares = Default.Middlewares
} }
// 5. Init map if not set // 5. Use default folders if not set
m.Types.format() if m.Types.Folder == "" {
m.Controllers.format() m.Types.Folder = Default.Types.Folder
m.Middlewares.format() }
if m.Controllers.Folder == "" {
m.Controllers.Folder = Default.Controllers.Folder
}
if m.Middlewares.Folder == "" {
m.Middlewares.Folder = Default.Middlewares.Folder
}
// 6. Infer Maps from Folders // 6. Infer Maps from Folders
m.Types.InferFromFolder(m.Root, m.Driver) m.Types.InferFromFolder(m.Root, m.Driver)

View File

@ -12,13 +12,13 @@ type builder struct {
Folder string `json:"folder,ommitempty"` Folder string `json:"folder,ommitempty"`
// Map defines the association path=>file // Map defines the association path=>file
Map map[string]string `json:"map,ommitempty"` Map map[string]string
} }
// Schema represents an AICRA configuration (not the API, the server, drivers, etc) // Schema represents an AICRA configuration (not the API, the server, drivers, etc)
type Schema struct { type Schema struct {
// Root is root of the project structure // Root is root of the project structure default is "." (current directory)
Root string `json:"root"` Root string `json:"root,ommitempty"`
// Host is the hostname to listen to (default is 0.0.0.0) // Host is the hostname to listen to (default is 0.0.0.0)
Host string `json:"host,ommitempty"` Host string `json:"host,ommitempty"`
@ -26,28 +26,24 @@ type Schema struct {
Port uint16 `json:"port,ommitempty"` Port uint16 `json:"port,ommitempty"`
// DriverName is the driver used to load the controllers and middlewares // DriverName is the driver used to load the controllers and middlewares
// (default is 'plugin')
DriverName string `json:"driver"` DriverName string `json:"driver"`
Driver driver.Driver Driver driver.Driver
// Types defines : // Types defines :
// - the type folder // - the type folder
// - each type by 'name => path'
// - whether to load the built-in types // - whether to load the built-in types
// //
// types are ommited if not set (no default) // types are ommited if not set (no default)
Types *builder `json:"types,ommitempty"` Types *builder `json:"types,ommitempty"`
// Controllers defines : // Controllers defines :
// - the controller folder (as a single string) // - the controller folder
// - each controller by 'name => path' (as a map)
// //
// (default is .build/controller) // (default is .build/controller)
Controllers *builder `json:"controllers,ommitempty"` Controllers *builder `json:"controllers,ommitempty"`
// Middlewares defines : // Middlewares defines :
// - the middleware folder (as a single string) // - the middleware folder
// - each middleware by 'name => path' (as a map)
// //
// (default is .build/middleware) // (default is .build/middleware)
Middlewares *builder `json:"middlewares,ommitempty"` Middlewares *builder `json:"middlewares,ommitempty"`

View File

@ -1,9 +1,6 @@
package request package request
import ( import (
"git.xdrm.io/go/aicra/driver"
"git.xdrm.io/go/aicra/err"
"git.xdrm.io/go/aicra/response"
"net/http" "net/http"
"strings" "strings"
) )
@ -27,11 +24,3 @@ func FromHTTP(req *http.Request) (*Request, error) {
return inst, nil return inst, nil
} }
// RunController tries to load a controller from its uri
// checks for its given method ('Get', 'Post', 'Put', or 'Delete')
func (i *Request) RunController(_method string, _driver driver.Driver) (func(response.Arguments) response.Response, err.Error) {
return _driver.RunController(i.Path, _method)
}

View File

@ -2,38 +2,17 @@ package middleware
import ( import (
"git.xdrm.io/go/aicra/driver" "git.xdrm.io/go/aicra/driver"
"io/ioutil"
"log"
"net/http" "net/http"
"path"
) )
// CreateRegistry creates an empty middleware registry // CreateRegistry creates an empty registry
func CreateRegistry(_driver driver.Driver, _folder string) *Registry { func CreateRegistry() Registry {
return make(Registry)
}
/* (1) Create registry */ // Add adds a new middleware for a path
reg := &Registry{ func (reg Registry) Add(_path string, _element driver.Middleware) {
Middlewares: make([]*Wrapper, 0), reg[_path] = _element
}
/* (2) List middleware files */
files, err := ioutil.ReadDir(_folder)
if err != nil {
log.Fatal(err)
}
/* (3) Else try to load each given default */
for _, file := range files {
mwFunc, err := _driver.LoadMiddleware(path.Join(_folder, file.Name()))
if err != nil {
log.Printf("cannot load middleware '%s' | %s", file.Name(), err)
}
reg.Middlewares = append(reg.Middlewares, &Wrapper{Inspect: mwFunc})
}
return reg
} }
// Run executes all middlewares (default browse order) // Run executes all middlewares (default browse order)
@ -43,8 +22,8 @@ func (reg Registry) Run(req http.Request) []string {
scope := make([]string, 0) scope := make([]string, 0)
/* (2) Execute each middleware */ /* (2) Execute each middleware */
for _, m := range reg.Middlewares { for _, mw := range reg {
m.Inspect(req, &scope) mw.Inspect(req, &scope)
} }
return scope return scope

View File

@ -1,7 +1,7 @@
package middleware package middleware
import ( import (
"net/http" "git.xdrm.io/go/aicra/driver"
) )
// Scope represents a list of scope processed by middlewares // Scope represents a list of scope processed by middlewares
@ -12,13 +12,7 @@ import (
// purposes, the type is always used as its definition ([]string) // purposes, the type is always used as its definition ([]string)
type Scope []string type Scope []string
// Wrapper is a struct that stores middleware Inspect() method
type Wrapper struct {
Inspect func(http.Request, *[]string)
}
// Registry represents a registry containing all registered // Registry represents a registry containing all registered
// middlewares to be processed before routing any request // middlewares to be processed before routing any request
type Registry struct { // The map is <name> => <middleware>
Middlewares []*Wrapper type Registry map[string]driver.Middleware
}

View File

@ -7,8 +7,10 @@ import (
"git.xdrm.io/go/aicra/internal/config" "git.xdrm.io/go/aicra/internal/config"
apirequest "git.xdrm.io/go/aicra/internal/request" apirequest "git.xdrm.io/go/aicra/internal/request"
"git.xdrm.io/go/aicra/middleware" "git.xdrm.io/go/aicra/middleware"
"git.xdrm.io/go/aicra/response"
"log" "log"
"net/http" "net/http"
"strings"
) )
// Server represents an AICRA instance featuring: // Server represents an AICRA instance featuring:
@ -16,9 +18,9 @@ import (
// * its middlewares // * its middlewares
// * its controllers (api config) // * its controllers (api config)
type Server struct { type Server struct {
controller *api.Controller // controllers controller *api.Controller // controllers
checker *checker.Registry // type checker registry checker checker.Registry // type checker registry
middleware *middleware.Registry // middlewares middleware middleware.Registry // middlewares
schema *config.Schema schema *config.Schema
} }
@ -34,32 +36,47 @@ func New(_path string) (*Server, error) {
return nil, err return nil, err
} }
/* 2. Default driver : Plugin */ /* 2. Init instance */
_folders := make([]string, 0, 2)
_folders = append(_folders, ".build/type")
if schema.Driver.Name() == "plugin" { // plugin
_folders = append(_folders, ".build/middleware")
} else { // generic
_folders = append(_folders, "middleware")
}
/* (1) Init instance */
var i = &Server{ var i = &Server{
controller: nil, controller: nil,
schema: schema, schema: schema,
} }
/* (2) Load configuration */ /* 3. Load configuration */
i.controller, err = api.Parse(_path) i.controller, err = api.Parse(_path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
/* (3) Default type registry */ /* 4. Load type registry */
i.checker = checker.CreateRegistry(_folders[0]) i.checker = checker.CreateRegistry()
/* (4) Default middleware registry */ // add default types if set
i.middleware = middleware.CreateRegistry(schema.Driver, _folders[1])
// add custom types
for name, path := range schema.Types.Map {
fullpath := schema.Driver.Build(schema.Root, schema.Types.Folder, path)
mwFunc, err := schema.Driver.LoadChecker(fullpath)
if err != nil {
log.Printf("cannot load type checker '%s' | %s", name, err)
}
i.checker.Add(path, mwFunc)
}
/* 5. Load middleware registry */
i.middleware = middleware.CreateRegistry()
for name, path := range schema.Middlewares.Map {
fullpath := schema.Driver.Build(schema.Root, schema.Middlewares.Folder, path)
mwFunc, err := schema.Driver.LoadMiddleware(fullpath)
if err != nil {
log.Printf("cannot load middleware '%s' | %s", name, err)
}
i.middleware.Add(path, mwFunc)
}
return i, nil return i, nil
@ -109,25 +126,52 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
/* (5) Load controller /* (5) Load controller
---------------------------------------------------------*/ ---------------------------------------------------------*/
controllerImplementation, callErr := apiRequest.RunController(req.Method, s.schema.Driver) // get paths
if callErr.Code != e.Success.Code { ctlBuildPath := strings.Join(apiRequest.Path, "/")
httpError(res, callErr) ctlBuildPath = s.schema.Driver.Build(s.schema.Root, s.schema.Controllers.Folder, ctlBuildPath)
log.Printf("[err] %s\n", err)
// get controller
ctlObject, err := s.schema.Driver.LoadController(ctlBuildPath)
httpMethod := strings.ToUpper(req.Method)
if err != nil {
httpErr := e.UncallableController
httpErr.BindArgument(err)
httpError(res, httpErr)
log.Printf("err( %s )\n", err)
return
}
var ctlMethod func(response.Arguments) response.Response
// select method
switch httpMethod {
case "GET":
ctlMethod = ctlObject.Get
case "POST":
ctlMethod = ctlObject.Post
case "PUT":
ctlMethod = ctlObject.Put
case "DELETE":
ctlMethod = ctlObject.Delete
default:
httpError(res, e.UnknownMethod)
return return
} }
/* (6) Execute and get response /* (6) Execute and get response
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Give Authorization header into controller */ /* (1) Give HTTP METHOD */
parameters["_HTTP_METHOD_"] = httpMethod
/* (2) Give Authorization header into controller */
parameters["_AUTHORIZATION_"] = req.Header.Get("Authorization") parameters["_AUTHORIZATION_"] = req.Header.Get("Authorization")
/* (2) Give Scope into controller */ /* (3) Give Scope into controller */
parameters["_SCOPE_"] = scope parameters["_SCOPE_"] = scope
/* (3) Execute */ /* (4) Execute */
response := controllerImplementation(parameters) response := ctlMethod(parameters)
/* (4) Extract http headers */ /* (5) Extract http headers */
for k, v := range response.Dump() { for k, v := range response.Dump() {
if k == "_REDIRECT_" { if k == "_REDIRECT_" {
if newLocation, ok := v.(string); ok { if newLocation, ok := v.(string); ok {