diff --git a/config/private.go b/config/controller.go similarity index 63% rename from config/private.go rename to config/controller.go index 40e1bef..771c616 100644 --- a/config/private.go +++ b/config/controller.go @@ -1,10 +1,116 @@ package config import ( + "encoding/json" "fmt" + "os" "strings" ) +// Load builds a representation of the configuration +// The struct definition checks for most format errors +// +// path The path to the configuration +// +// @return The parsed configuration root controller +// @return The error if occured +// +func Load(path string) (*Controller, error) { + + /* (1) Extract data + ---------------------------------------------------------*/ + /* (1) Open file */ + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + /* (2) Init receiver dataset */ + receiver := &Controller{} + + /* (3) Decode json */ + decoder := json.NewDecoder(file) + err = decoder.Decode(receiver) + if err != nil { + return nil, err + } + + /* (4) Format result */ + err = receiver.format("/") + if err != nil { + return nil, err + } + + /* (5) Set default optional fields */ + receiver.setDefaults() + + return receiver, nil + +} + +// Method returns a controller's method if exists +// +// @method The wanted method (case insensitive) +// +// @return<*Method> The requested method +// NIL if not found +// +func (c Controller) Method(method string) *Method { + method = strings.ToUpper(method) + + switch method { + + case "GET": + return c.GET + case "POST": + return c.POST + case "PUT": + return c.PUT + case "DELETE": + return c.DELETE + default: + return nil + + } + +} + +// Browses tries to browse the controller childtree and +// returns the farthest matching child +// +// @path the path to browse +// +// @return The index in 'path' used to find the controller +// @return<*Controller> The farthest match +func (c *Controller) Browse(path []string) (int, *Controller) { + + /* (1) initialise cursors */ + current := c + i := 0 // index in path + + /* (2) Browse while there is uri parts */ + for i < len(path) { + + // 1. Try to get child for this name + child, exists := current.Children[path[i]] + + // 2. Stop if no matching child + if !exists { + break + } + + // 3. Increment cursors + current = child + i++ + + } + + /* (3) Return matches */ + return i, current + +} + // format checks for format errors and missing required fields // it also sets default values to optional fields func (c *Controller) format(controllerName string) error { @@ -35,7 +141,7 @@ func (c *Controller) format(controllerName string) error { /* (3) stop if no parameter */ if method.Ptr.Parameters == nil || len(method.Ptr.Parameters) < 1 { - method.Ptr.Parameters = make(map[string]*MethodParameter, 0) + method.Ptr.Parameters = make(map[string]*Parameter, 0) continue } diff --git a/config/method.go b/config/method.go new file mode 100644 index 0000000..d912156 --- /dev/null +++ b/config/method.go @@ -0,0 +1 @@ +package config diff --git a/config/public.go b/config/public.go deleted file mode 100644 index 9388960..0000000 --- a/config/public.go +++ /dev/null @@ -1,79 +0,0 @@ -package config - -import ( - "encoding/json" - "os" - "strings" -) - -// Load builds a structured representation of the -// configuration file located at @path -// The struct definition checks for most format errors -func Load(path string) (*Controller, error) { - - /* (1) Extract data - ---------------------------------------------------------*/ - /* (1) Open file */ - var configFile, err = os.Open(path) - if err != nil { - return nil, err - } - defer configFile.Close() - - /* (2) Init receiving dataset */ - receiver := &Controller{} - - /* (3) Decode JSON */ - decoder := json.NewDecoder(configFile) - err = decoder.Decode(receiver) - if err != nil { - return nil, err - } - - /* (4) Format result */ - err = receiver.format("/") - if err != nil { - return nil, err - } - - /* (5) Set default optional fields */ - receiver.setDefaults() - - return receiver, nil - -} - -// IsMethodAvailable returns whether a given -// method is available (case insensitive) -func IsMethodAvailable(method string) bool { - for _, m := range AvailableMethods { - if strings.ToUpper(method) == m { - return true - } - } - - return false -} - -// Method returns whether the controller has a given -// method by name (case insensitive) -// NIL is returned if no method is found -func (c Controller) Method(method string) *Method { - method = strings.ToUpper(method) - - switch method { - - case "GET": - return c.GET - case "POST": - return c.POST - case "PUT": - return c.PUT - case "DELETE": - return c.DELETE - default: - return nil - - } - -} diff --git a/config/types.go b/config/types.go index cd94b77..a49da47 100644 --- a/config/types.go +++ b/config/types.go @@ -3,7 +3,7 @@ package config /* (1) Configuration ---------------------------------------------------------*/ -type MethodParameter struct { +type Parameter struct { Description string `json:"des"` Type string `json:"typ"` Rename *string `json:"ren"` @@ -11,10 +11,10 @@ type MethodParameter struct { Default *interface{} `json:"def"` } type Method struct { - Description string `json:"des"` - Permission [][]string `json:"per"` - Parameters map[string]*MethodParameter `json:"par"` - Options map[string]interface{} `json:"opt"` + Description string `json:"des"` + Permission [][]string `json:"per"` + Parameters map[string]*Parameter `json:"par"` + Options map[string]interface{} `json:"opt"` } type Controller struct { @@ -25,5 +25,3 @@ type Controller struct { Children map[string]*Controller `json:"/"` } - -var AvailableMethods = []string{"GET", "POST", "PUT", "DELETE"} diff --git a/implement/response.go b/implement/response.go new file mode 100644 index 0000000..f3acc89 --- /dev/null +++ b/implement/response.go @@ -0,0 +1,34 @@ +package implement + +import ( + "git.xdrm.io/xdrm-brackets/gfw/err" +) + +func NewResponse() *Response { + return &Response{ + data: make(map[string]interface{}), + Err: err.Success, + } +} + +func (i *Response) Set(name string, value interface{}) { + i.m.Lock() + defer i.m.Unlock() + + i.data[name] = value +} + +func (i *Response) Get(name string) interface{} { + i.m.Lock() + value, _ := i.data[name] + i.m.Unlock() + + return value +} + +func (i *Response) Dump() map[string]interface{} { + i.m.Lock() + defer i.m.Unlock() + + return i.data +} diff --git a/implement/types.go b/implement/types.go new file mode 100644 index 0000000..26c6192 --- /dev/null +++ b/implement/types.go @@ -0,0 +1,14 @@ +package implement + +import ( + "git.xdrm.io/xdrm-brackets/gfw/err" + "sync" +) + +type Controller func(map[string]interface{}) *Response + +type Response struct { + data map[string]interface{} + m sync.Mutex + Err err.Error +} diff --git a/request_data.go b/request/dataset.go similarity index 60% rename from request_data.go rename to request/dataset.go index 67ca941..6aaa2d2 100644 --- a/request_data.go +++ b/request/dataset.go @@ -1,4 +1,4 @@ -package gfw +package request import ( "encoding/json" @@ -7,37 +7,37 @@ import ( "log" "net/http" "strings" + "time" ) -// buildRequestDataFromRequest builds a 'requestData' -// from an http request -func buildRequestDataFromRequest(req *http.Request) *requestData { - - i := &requestData{ - Url: make([]*requestParameter, 0), - Get: make(map[string]*requestParameter), - Form: make(map[string]*requestParameter), - Set: make(map[string]*requestParameter), +func NewDataset() *DataSet { + return &DataSet{ + Uri: make([]*Parameter, 0), + Get: make(map[string]*Parameter), + Form: make(map[string]*Parameter), + Set: make(map[string]*Parameter), } +} - // GET (query) data +// Build builds a 'DataSet' from an http request +func (i *DataSet) Build(req *http.Request) { + + /* (1) GET (query) data */ i.fetchGet(req) - // no Form if GET + /* (2) We are done if GET method */ if req.Method == "GET" { - return i + return } - // POST (body) data + /* (3) POST (body) data */ i.fetchForm(req) - return i - } -// bindUrl stores URL data and fills 'Set' +// setUriData stores URL data and fills 'Set' // with creating pointers inside 'Url' -func (i *requestData) fillUrl(data []string) { +func (i *DataSet) SetUri(data []string) { for index, value := range data { @@ -45,25 +45,25 @@ func (i *requestData) fillUrl(data []string) { setindex := fmt.Sprintf("URL#%d", index) // store value in 'Set' - i.Set[setindex] = &requestParameter{ + i.Set[setindex] = &Parameter{ Parsed: false, Value: value, } // create link in 'Url' - i.Url = append(i.Url, i.Set[setindex]) + i.Uri = append(i.Uri, i.Set[setindex]) } } // fetchGet stores data from the QUERY (in url parameters) -func (i *requestData) fetchGet(req *http.Request) { +func (i *DataSet) fetchGet(req *http.Request) { for name, value := range req.URL.Query() { // prevent injections - if isParameterNameInjection(name) { + if nameInjection(name) { log.Printf("get.injection: '%s'\n", name) continue } @@ -72,7 +72,7 @@ func (i *requestData) fetchGet(req *http.Request) { setindex := fmt.Sprintf("GET@%s", name) // store value in 'Set' - i.Set[setindex] = &requestParameter{ + i.Set[setindex] = &Parameter{ Parsed: false, Value: value, } @@ -89,34 +89,44 @@ func (i *requestData) fetchGet(req *http.Request) { // - parse 'form-data' if not supported (not POST requests) // - parse 'x-www-form-urlencoded' // - parse 'application/json' -func (i *requestData) fetchForm(req *http.Request) { +func (i *DataSet) fetchForm(req *http.Request) { + + fmt.Printf("Parsing FORM...") + startn := time.Now().UnixNano() contentType := req.Header.Get("Content-Type") // parse json if strings.HasPrefix(contentType, "application/json") { - i.parseJsonForm(req) + i.parseJson(req) + + fmt.Printf("* %.3f us\n", float64(time.Now().UnixNano()-startn)/1e3) return } // parse urlencoded if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") { - i.parseUrlencodedForm(req) + i.parseUrlencoded(req) + + fmt.Printf("* %.3f us\n", float64(time.Now().UnixNano()-startn)/1e3) return } // parse multipart if strings.HasPrefix(contentType, "multipart/form-data; boundary=") { - i.parseMultipartForm(req) + i.parseMultipart(req) + + fmt.Printf("* %.3f us\n", float64(time.Now().UnixNano()-startn)/1e3) return } // if unknown type store nothing + fmt.Printf("* %.3f us\n", float64(time.Now().UnixNano()-startn)/1e3) } -// parseJsonForm parses JSON from the request body inside 'Form' +// parseJson parses JSON from the request body inside 'Form' // and 'Set' -func (i *requestData) parseJsonForm(req *http.Request) { +func (i *DataSet) parseJson(req *http.Request) { parsed := make(map[string]interface{}, 0) @@ -131,13 +141,13 @@ func (i *requestData) parseJsonForm(req *http.Request) { for name, value := range parsed { // prevent injections - if isParameterNameInjection(name) { + if nameInjection(name) { log.Printf("post.injection: '%s'\n", name) continue } // store value in 'Set' - i.Set[name] = &requestParameter{ + i.Set[name] = &Parameter{ Parsed: true, Value: value, } @@ -149,9 +159,9 @@ func (i *requestData) parseJsonForm(req *http.Request) { } -// parseUrlencodedForm parses urlencoded from the request body inside 'Form' +// parseUrlencoded parses urlencoded from the request body inside 'Form' // and 'Set' -func (i *requestData) parseUrlencodedForm(req *http.Request) { +func (i *DataSet) parseUrlencoded(req *http.Request) { // use http.Request interface req.ParseForm() @@ -159,13 +169,13 @@ func (i *requestData) parseUrlencodedForm(req *http.Request) { for name, value := range req.PostForm { // prevent injections - if isParameterNameInjection(name) { + if nameInjection(name) { log.Printf("post.injection: '%s'\n", name) continue } // store value in 'Set' - i.Set[name] = &requestParameter{ + i.Set[name] = &Parameter{ Parsed: false, Value: value, } @@ -176,9 +186,9 @@ func (i *requestData) parseUrlencodedForm(req *http.Request) { } -// parseMultipartForm parses multi-part from the request body inside 'Form' +// parseMultipart parses multi-part from the request body inside 'Form' // and 'Set' -func (i *requestData) parseMultipartForm(req *http.Request) { +func (i *DataSet) parseMultipart(req *http.Request) { /* (1) Create reader */ mpr := multipart.CreateReader(req) @@ -190,13 +200,13 @@ func (i *requestData) parseMultipartForm(req *http.Request) { for name, component := range mpr.Components { // prevent injections - if isParameterNameInjection(name) { + if nameInjection(name) { log.Printf("post.injection: '%s'\n", name) continue } // store value in 'Set' - i.Set[name] = &requestParameter{ + i.Set[name] = &Parameter{ Parsed: false, File: component.File, Value: component.Data, @@ -210,11 +220,3 @@ func (i *requestData) parseMultipartForm(req *http.Request) { return } - -// isParameterNameInjection returns whether there is -// a parameter name injection: -// - inferred GET parameters -// - inferred URL parameters -func isParameterNameInjection(pName string) bool { - return strings.HasPrefix(pName, "GET@") || strings.HasPrefix(pName, "URL#") -} diff --git a/request/parameter.go b/request/parameter.go new file mode 100644 index 0000000..0d21d83 --- /dev/null +++ b/request/parameter.go @@ -0,0 +1,14 @@ +package request + +// Parse parameter (json-like) if not already done +func (i *Parameter) Parse() { + + /* (1) Stop if already parsed or nil*/ + if i.Parsed || i.Value == nil { + return + } + + /* (2) Try to parse value */ + i.Value = parseParameter(i.Value) + +} diff --git a/request/request.go b/request/request.go new file mode 100644 index 0000000..afbc863 --- /dev/null +++ b/request/request.go @@ -0,0 +1,129 @@ +package request + +import ( + "encoding/json" + "fmt" + "git.xdrm.io/xdrm-brackets/gfw/implement" + "log" + "net/http" + "plugin" + "strings" + "time" +) + +// Build builds an interface request from a http.Request +func Build(req *http.Request) (*Request, error) { + + /* (1) Get useful data */ + uri := normaliseUri(req.URL.Path) + uriparts := strings.Split(uri, "/") + + /* (2) Init request */ + inst := &Request{ + Uri: uriparts, + Path: make([]string, 0, len(uriparts)), + Data: NewDataset(), + } + + /* (3) Build dataset */ + inst.Data.Build(req) + + return inst, nil +} + +// FetchFormData extracts FORM data +// +// - parse 'form-data' if not supported (not POST requests) +// - parse 'x-www-form-urlencoded' +// - parse 'application/json' +func FetchFormData(req *http.Request) map[string]interface{} { + + res := make(map[string]interface{}) + + // Abort if GET request + if req.Method == "GET" { + return res + } + + ct := req.Header.Get("Content-Type") + + if strings.HasPrefix(ct, "application/json") { + + receiver := make(map[string]interface{}, 0) + + // 1. Init JSON reader + decoder := json.NewDecoder(req.Body) + if err := decoder.Decode(&receiver); err != nil { + log.Printf("[parse.json] %s\n", err) + return res + } + + // 2. Return result + return receiver + + } else if strings.HasPrefix(ct, "application/x-www-form-urlencoded") { + + // 1. Parse url encoded data + req.ParseForm() + + // 2. Extract values + for name, value := range req.PostForm { + res[name] = value + } + + } else { // form-data or anything + + startn := time.Now().UnixNano() + // 1. Parse form-data + if err := req.ParseMultipartForm(req.ContentLength + 1); err != nil { + log.Printf("[read.multipart] %s\n", err) + return res + } + + // 2. Extract values + for name, value := range req.PostForm { + res[name] = value + } + fmt.Printf("* %.3f us\n", float64(time.Now().UnixNano()-startn)/1e3) + + } + + return res +} + +// LoadController tries to load a controller from its uri +// checks for its given method ('Get', 'Post', 'Put', or 'Delete') +func (i *Request) LoadController(method string) (implement.Controller, error) { + + /* (1) Build controller path */ + path := fmt.Sprintf("%si.so", i.Path) + + /* (2) Format url */ + tmp := []byte(strings.ToLower(method)) + tmp[0] = tmp[0] - ('a' - 'A') + method = string(tmp) + + fmt.Printf("method is '%s'\n", method) + return nil, nil + + /* (2) Try to load plugin */ + p, err2 := plugin.Open(path) + if err2 != nil { + return nil, err2 + } + + /* (3) Try to extract method */ + m, err2 := p.Lookup(method) + if err2 != nil { + return nil, err2 + } + + /* (4) Check signature */ + callable, validSignature := m.(implement.Controller) + if !validSignature { + return nil, fmt.Errorf("Invalid signature for method %s", method) + } + + return callable, nil + +} diff --git a/request/types.go b/request/types.go new file mode 100644 index 0000000..67866ee --- /dev/null +++ b/request/types.go @@ -0,0 +1,56 @@ +package request + +type Request struct { + // corresponds to the list of uri components + // featuring in the request URI + Uri []string + + // controller path (portion of 'Uri') + Path []string + + // contains all data from URL, GET, and FORM + Data *DataSet +} + +type DataSet struct { + + // ordered values from the URI + // catches all after the controller path + // + // points to Request.Data + Uri []*Parameter + + // uri parameters following the QUERY format + // + // points to Request.Data + Get map[string]*Parameter + + // form data depending on the Content-Type: + // 'application/json' => key-value pair is parsed as json into the map + // 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters + // 'multipart/form-data' => parse form-data format + // + // points to Request.Data + Form map[string]*Parameter + + // contains URL+GET+FORM data with prefixes: + // - FORM: no prefix + // - URL: 'URL#' followed by the index in Uri + // - GET: 'GET@' followed by the key in GET + Set map[string]*Parameter +} + +// Parameter represents an http request parameter +// that can be of type URL, GET, or FORM (multipart, json, urlencoded) +type Parameter struct { + // whether the value has been json-parsed + // for optimisation purpose, parameters are only parsed + // if they are required by the current controller + Parsed bool + + // whether the value is a file + File bool + + // the actual parameter value + Value interface{} +} diff --git a/request/utils.go b/request/utils.go new file mode 100644 index 0000000..8715bdd --- /dev/null +++ b/request/utils.go @@ -0,0 +1,118 @@ +package request + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// normaliseUri removes the trailing '/' to always +// have the same Uri format for later processing +func normaliseUri(uri string) string { + + if len(uri) < 1 { + return uri + } + + if uri[0] == '/' { + uri = uri[1:] + } + + if len(uri) > 1 && uri[len(uri)-1] == '/' { + uri = uri[0 : len(uri)-1] + } + + return uri +} + +// nameInjection returns whether there is +// a parameter name injection: +// - inferred GET parameters +// - inferred URL parameters +func nameInjection(pName string) bool { + return strings.HasPrefix(pName, "GET@") || strings.HasPrefix(pName, "URL#") +} + +// parseParameter parses http GET/POST data +// - []string +// - size = 1 : return json of first element +// - size > 1 : return array of json elements +// - string : return json if valid, else return raw string +func parseParameter(data interface{}) interface{} { + dtype := reflect.TypeOf(data) + dvalue := reflect.ValueOf(data) + + switch dtype.Kind() { + + /* (1) []string -> recursive */ + case reflect.Slice: + + // 1. Return nothing if empty + if dvalue.Len() == 0 { + return nil + } + + // 2. only return first element if alone + if dvalue.Len() == 1 { + + element := dvalue.Index(0) + if element.Kind() != reflect.String { + return nil + } + return parseParameter(element.String()) + + // 3. Return all elements if more than 1 + } else { + + result := make([]interface{}, dvalue.Len()) + + for i, l := 0, dvalue.Len(); i < l; i++ { + element := dvalue.Index(i) + + // ignore non-string + if element.Kind() != reflect.String { + continue + } + + result[i] = parseParameter(element.String()) + } + return result + + } + + /* (2) string -> parse */ + case reflect.String: + + // build json wrapper + wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String()) + + // try to parse as json + var result interface{} + err := json.Unmarshal([]byte(wrapper), &result) + + // return if success + if err == nil { + + mapval, ok := result.(map[string]interface{}) + if !ok { + return dvalue.String() + } + + wrapped, ok := mapval["wrapped"] + if !ok { + return dvalue.String() + } + + return wrapped + } + + // else return as string + return dvalue.String() + + } + + /* (3) NIL if unknown type */ + return dvalue + +} diff --git a/request_builder.go b/request_builder.go deleted file mode 100644 index bff6b5d..0000000 --- a/request_builder.go +++ /dev/null @@ -1,230 +0,0 @@ -package gfw - -import ( - "encoding/json" - "fmt" - "git.xdrm.io/xdrm-brackets/gfw/err" - "log" - "net/http" - "plugin" - "reflect" - "strings" - "time" -) - -// buildRequest builds an interface request -// from a http.Request -func buildRequest(req *http.Request) (*Request, error) { - - /* (1) Get useful data */ - uri := NormaliseUri(req.URL.Path) - uriparts := strings.Split(uri, "/") - - /* (2) Init request */ - inst := &Request{ - Uri: uriparts, - ControllerUri: make([]string, 0, len(uriparts)), - Data: buildRequestDataFromRequest(req), - } - - return inst, nil -} - -// NormaliseUri removes the trailing '/' to always -// have the same Uri format for later processing -func NormaliseUri(uri string) string { - - if len(uri) < 1 { - return uri - } - - if uri[0] == '/' { - uri = uri[1:] - } - - if len(uri) > 1 && uri[len(uri)-1] == '/' { - uri = uri[0 : len(uri)-1] - } - - return uri -} - -// FetchFormData extracts FORM data -// -// - parse 'form-data' if not supported (not POST requests) -// - parse 'x-www-form-urlencoded' -// - parse 'application/json' -func FetchFormData(req *http.Request) map[string]interface{} { - - res := make(map[string]interface{}) - - // Abort if GET request - if req.Method == "GET" { - return res - } - - ct := req.Header.Get("Content-Type") - - if strings.HasPrefix(ct, "application/json") { - - receiver := make(map[string]interface{}, 0) - - // 1. Init JSON reader - decoder := json.NewDecoder(req.Body) - if err := decoder.Decode(&receiver); err != nil { - log.Printf("[parse.json] %s\n", err) - return res - } - - // 2. Return result - return receiver - - } else if strings.HasPrefix(ct, "application/x-www-form-urlencoded") { - - // 1. Parse url encoded data - req.ParseForm() - - // 2. Extract values - for name, value := range req.PostForm { - res[name] = value - } - - } else { // form-data or anything - - startn := time.Now().UnixNano() - // 1. Parse form-data - if err := req.ParseMultipartForm(req.ContentLength + 1); err != nil { - log.Printf("[read.multipart] %s\n", err) - return res - } - - // 2. Extract values - for name, value := range req.PostForm { - res[name] = value - } - fmt.Printf("* %.3f us\n", float64(time.Now().UnixNano()-startn)/1e3) - - } - - return res -} - -// parseHttpData parses http GET/POST data -// - []string -// - size = 1 : return json of first element -// - size > 1 : return array of json elements -// - string : return json if valid, else return raw string -func parseHttpData(data interface{}) interface{} { - dtype := reflect.TypeOf(data) - dvalue := reflect.ValueOf(data) - - switch dtype.Kind() { - - /* (1) []string -> recursive */ - case reflect.Slice: - - // 1. Return nothing if empty - if dvalue.Len() == 0 { - return nil - } - - // 2. only return first element if alone - if dvalue.Len() == 1 { - - element := dvalue.Index(0) - if element.Kind() != reflect.String { - return nil - } - return parseHttpData(element.String()) - - // 3. Return all elements if more than 1 - } else { - - result := make([]interface{}, dvalue.Len()) - - for i, l := 0, dvalue.Len(); i < l; i++ { - element := dvalue.Index(i) - - // ignore non-string - if element.Kind() != reflect.String { - continue - } - - result[i] = parseHttpData(element.String()) - } - return result - - } - - /* (2) string -> parse */ - case reflect.String: - - // build json wrapper - wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String()) - - // try to parse as json - var result interface{} - err := json.Unmarshal([]byte(wrapper), &result) - - // return if success - if err == nil { - - mapval, ok := result.(map[string]interface{}) - if !ok { - return dvalue.String() - } - - wrapped, ok := mapval["wrapped"] - if !ok { - return dvalue.String() - } - - return wrapped - } - - // else return as string - return dvalue.String() - - } - - /* (3) NIL if unknown type */ - return dvalue - -} - -// loadController tries to load a controller from its uri -// checks for its given method ('Get', 'Post', 'Put', or 'Delete') -func (i *Request) loadController(method string) (func(map[string]interface{}) (map[string]interface{}, err.Error), error) { - - /* (1) Build controller path */ - path := fmt.Sprintf("%si.so", i.ControllerUri) - - /* (2) Format url */ - tmp := []byte(strings.ToLower(method)) - tmp[0] = tmp[0] - ('a' - 'A') - method = string(tmp) - - fmt.Printf("method is '%s'\n", method) - return nil, nil - - /* (2) Try to load plugin */ - p, err2 := plugin.Open(path) - if err2 != nil { - return nil, err2 - } - - /* (3) Try to extract method */ - m, err2 := p.Lookup(method) - if err2 != nil { - return nil, err2 - } - - /* (4) Check signature */ - callable, validSignature := m.(func(map[string]interface{}) (map[string]interface{}, err.Error)) - if !validSignature { - return nil, fmt.Errorf("Invalid signature for method %s", method) - } - - return callable, nil - -} diff --git a/router.go b/router.go index 3e03769..174447a 100644 --- a/router.go +++ b/router.go @@ -4,30 +4,30 @@ import ( "fmt" "git.xdrm.io/xdrm-brackets/gfw/config" "git.xdrm.io/xdrm-brackets/gfw/err" + "git.xdrm.io/xdrm-brackets/gfw/request" "log" "net/http" "strings" ) -func (s *Server) route(res http.ResponseWriter, req *http.Request) { +func (s *Server) route(res http.ResponseWriter, httpReq *http.Request) { /* (1) Build request ---------------------------------------------------------*/ /* (1) Try to build request */ - request, err2 := buildRequest(req) + req, err2 := request.Build(httpReq) if err2 != nil { - log.Fatal(req) + log.Fatal(err2) } /* (2) Find a controller ---------------------------------------------------------*/ - controller := s.findController(request) + controller := s.findController(req) /* (3) Check method ---------------------------------------------------------*/ - method := s.getMethod(controller, req.Method) - - if method == nil { + var method *config.Method + if method = controller.Method(httpReq.Method); method == nil { Json, _ := err.UnknownMethod.MarshalJSON() res.Header().Add("Content-Type", "application/json") res.Write(Json) @@ -42,7 +42,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { for name, param := range method.Parameters { /* (1) Extract value */ - p, isset := request.Data.Set[name] + p, isset := req.Data.Set[name] /* (2) Required & missing */ if !isset && !*param.Optional { @@ -53,7 +53,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (3) Optional & missing: set default value */ if !isset { - p = &requestParameter{ + p = &request.Parameter{ Parsed: true, File: param.Type == "FILE", Value: nil, @@ -64,8 +64,8 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { } /* (4) Parse parameter if not file */ - if !p.Parsed && !p.File { - p.Value = parseHttpData(p.Value) + if !p.File { + p.Parse() } /* (4) Fail on unexpected multipart file */ @@ -109,67 +109,43 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (5) Load controller ---------------------------------------------------------*/ - callable, err := request.loadController(req.Method) + callable, err := req.LoadController(httpReq.Method) if err != nil { log.Printf("[err] %s\n", err) return } - fmt.Printf("OK\nplugin: '%si.so'\n", strings.Join(request.ControllerUri, "/")) + fmt.Printf("OK\nplugin: '%si.so'\n", strings.Join(req.Path, "/")) for name, value := range parameters { fmt.Printf(" $%s = %v\n", name, value) } /* (6) Execute and get response ---------------------------------------------------------*/ - out, _ := callable(parameters) - fmt.Printf("-- OUT --\n") - for name, value := range out { - fmt.Printf(" $%s = %v\n", name, value) + resp := callable(parameters) + if resp != nil { + fmt.Printf("-- OUT --\n") + for name, value := range resp.Dump() { + fmt.Printf(" $%s = %v\n", name, value) + } + eJSON, _ := resp.Err.MarshalJSON() + fmt.Printf("-- ERR --\n%s\n", eJSON) } return } -func (s *Server) findController(req *Request) *config.Controller { - /* (1) Init browsing cursors */ - ctl := s.config - uriIndex := 0 +func (s *Server) findController(req *request.Request) *config.Controller { - /* (2) Browse while there is uri parts */ - for uriIndex < len(req.Uri) { - uri := req.Uri[uriIndex] + /* (1) Try to browse by URI */ + pathi, ctl := s.config.Browse(req.Uri) - child, hasKey := ctl.Children[uri] - - // stop if no matchind child - if !hasKey { - break - } - - req.ControllerUri = append(req.ControllerUri, uri) - ctl = child - uriIndex++ - - } + /* (2) Set controller uri */ + req.Path = make([]string, 0, pathi) + req.Path = append(req.Path, req.Uri[:pathi]...) /* (3) Extract & store URI params */ - req.Data.fillUrl(req.Uri[uriIndex:]) + req.Data.SetUri(req.Uri[pathi:]) /* (4) Return controller */ return ctl } - -func (s *Server) getMethod(controller *config.Controller, method string) *config.Method { - - /* (1) Unavailable method */ - if !config.IsMethodAvailable(method) { - return nil - } - - /* (2) Extract method cursor */ - var foundMethod = controller.Method(method) - - /* (3) Return method | nil on error */ - return foundMethod - -} diff --git a/types.go b/types.go index a48aaf2..1c22f9f 100644 --- a/types.go +++ b/types.go @@ -12,58 +12,3 @@ type Server struct { Checker *checker.TypeRegistry // type check err err.Error } - -type Request struct { - // corresponds to the list of uri components - // featuring in the request URI - Uri []string - - // portion of the URI that corresponds to the controllerpath - ControllerUri []string - - // contains all data from URL, GET, and FORM - Data *requestData -} - -type requestData struct { - - // ordered values from the URI - // catches all after the controller path - // - // points to Request.Data - Url []*requestParameter - - // uri parameters following the QUERY format - // - // points to Request.Data - Get map[string]*requestParameter - - // form data depending on the Content-Type: - // 'application/json' => key-value pair is parsed as json into the map - // 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters - // 'multipart/form-data' => parse form-data format - // - // points to Request.Data - Form map[string]*requestParameter - - // contains URL+GET+FORM data with prefixes: - // - FORM: no prefix - // - URL: 'URL#' followed by the index in Uri - // - GET: 'GET@' followed by the key in GET - Set map[string]*requestParameter -} - -// requestParameter represents an http request parameter -// that can be of type URL, GET, or FORM (multipart, json, urlencoded) -type requestParameter struct { - // whether the value has been json-parsed - // for optimisation purpose, parameters are only parsed - // if they are required by the current controller - Parsed bool - - // whether the value is a file - File bool - - // the actual parameter value - Value interface{} -}