big update

This commit is contained in:
Adrien Marquès 2018-06-01 10:51:51 +02:00
parent f0727cb9ca
commit ca05b7ff29
14 changed files with 555 additions and 471 deletions

View File

@ -1,10 +1,116 @@
package config package config
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"strings" "strings"
) )
// Load builds a representation of the configuration
// The struct definition checks for most format errors
//
// path<string> The path to the configuration
//
// @return<controller> The parsed configuration root controller
// @return<err> 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<string> 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<int> 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 // format checks for format errors and missing required fields
// it also sets default values to optional fields // it also sets default values to optional fields
func (c *Controller) format(controllerName string) error { func (c *Controller) format(controllerName string) error {
@ -35,7 +141,7 @@ func (c *Controller) format(controllerName string) error {
/* (3) stop if no parameter */ /* (3) stop if no parameter */
if method.Ptr.Parameters == nil || len(method.Ptr.Parameters) < 1 { 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 continue
} }

1
config/method.go Normal file
View File

@ -0,0 +1 @@
package config

View File

@ -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
}
}

View File

@ -3,7 +3,7 @@ package config
/* (1) Configuration /* (1) Configuration
---------------------------------------------------------*/ ---------------------------------------------------------*/
type MethodParameter struct { type Parameter struct {
Description string `json:"des"` Description string `json:"des"`
Type string `json:"typ"` Type string `json:"typ"`
Rename *string `json:"ren"` Rename *string `json:"ren"`
@ -13,7 +13,7 @@ type MethodParameter struct {
type Method struct { type Method struct {
Description string `json:"des"` Description string `json:"des"`
Permission [][]string `json:"per"` Permission [][]string `json:"per"`
Parameters map[string]*MethodParameter `json:"par"` Parameters map[string]*Parameter `json:"par"`
Options map[string]interface{} `json:"opt"` Options map[string]interface{} `json:"opt"`
} }
@ -25,5 +25,3 @@ type Controller struct {
Children map[string]*Controller `json:"/"` Children map[string]*Controller `json:"/"`
} }
var AvailableMethods = []string{"GET", "POST", "PUT", "DELETE"}

34
implement/response.go Normal file
View File

@ -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
}

14
implement/types.go Normal file
View File

@ -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
}

View File

@ -1,4 +1,4 @@
package gfw package request
import ( import (
"encoding/json" "encoding/json"
@ -7,37 +7,37 @@ import (
"log" "log"
"net/http" "net/http"
"strings" "strings"
"time"
) )
// buildRequestDataFromRequest builds a 'requestData' func NewDataset() *DataSet {
// from an http request return &DataSet{
func buildRequestDataFromRequest(req *http.Request) *requestData { Uri: make([]*Parameter, 0),
Get: make(map[string]*Parameter),
i := &requestData{ Form: make(map[string]*Parameter),
Url: make([]*requestParameter, 0), Set: make(map[string]*Parameter),
Get: make(map[string]*requestParameter), }
Form: make(map[string]*requestParameter),
Set: make(map[string]*requestParameter),
} }
// 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) i.fetchGet(req)
// no Form if GET /* (2) We are done if GET method */
if req.Method == "GET" { if req.Method == "GET" {
return i return
} }
// POST (body) data /* (3) POST (body) data */
i.fetchForm(req) i.fetchForm(req)
return i
} }
// bindUrl stores URL data and fills 'Set' // setUriData stores URL data and fills 'Set'
// with creating pointers inside 'Url' // with creating pointers inside 'Url'
func (i *requestData) fillUrl(data []string) { func (i *DataSet) SetUri(data []string) {
for index, value := range data { for index, value := range data {
@ -45,25 +45,25 @@ func (i *requestData) fillUrl(data []string) {
setindex := fmt.Sprintf("URL#%d", index) setindex := fmt.Sprintf("URL#%d", index)
// store value in 'Set' // store value in 'Set'
i.Set[setindex] = &requestParameter{ i.Set[setindex] = &Parameter{
Parsed: false, Parsed: false,
Value: value, Value: value,
} }
// create link in 'Url' // 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) // 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() { for name, value := range req.URL.Query() {
// prevent injections // prevent injections
if isParameterNameInjection(name) { if nameInjection(name) {
log.Printf("get.injection: '%s'\n", name) log.Printf("get.injection: '%s'\n", name)
continue continue
} }
@ -72,7 +72,7 @@ func (i *requestData) fetchGet(req *http.Request) {
setindex := fmt.Sprintf("GET@%s", name) setindex := fmt.Sprintf("GET@%s", name)
// store value in 'Set' // store value in 'Set'
i.Set[setindex] = &requestParameter{ i.Set[setindex] = &Parameter{
Parsed: false, Parsed: false,
Value: value, Value: value,
} }
@ -89,34 +89,44 @@ func (i *requestData) fetchGet(req *http.Request) {
// - parse 'form-data' if not supported (not POST requests) // - parse 'form-data' if not supported (not POST requests)
// - parse 'x-www-form-urlencoded' // - parse 'x-www-form-urlencoded'
// - parse 'application/json' // - 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") contentType := req.Header.Get("Content-Type")
// parse json // parse json
if strings.HasPrefix(contentType, "application/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 return
} }
// parse urlencoded // parse urlencoded
if strings.HasPrefix(contentType, "application/x-www-form-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 return
} }
// parse multipart // parse multipart
if strings.HasPrefix(contentType, "multipart/form-data; boundary=") { 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 return
} }
// if unknown type store nothing // 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' // and 'Set'
func (i *requestData) parseJsonForm(req *http.Request) { func (i *DataSet) parseJson(req *http.Request) {
parsed := make(map[string]interface{}, 0) parsed := make(map[string]interface{}, 0)
@ -131,13 +141,13 @@ func (i *requestData) parseJsonForm(req *http.Request) {
for name, value := range parsed { for name, value := range parsed {
// prevent injections // prevent injections
if isParameterNameInjection(name) { if nameInjection(name) {
log.Printf("post.injection: '%s'\n", name) log.Printf("post.injection: '%s'\n", name)
continue continue
} }
// store value in 'Set' // store value in 'Set'
i.Set[name] = &requestParameter{ i.Set[name] = &Parameter{
Parsed: true, Parsed: true,
Value: value, 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' // and 'Set'
func (i *requestData) parseUrlencodedForm(req *http.Request) { func (i *DataSet) parseUrlencoded(req *http.Request) {
// use http.Request interface // use http.Request interface
req.ParseForm() req.ParseForm()
@ -159,13 +169,13 @@ func (i *requestData) parseUrlencodedForm(req *http.Request) {
for name, value := range req.PostForm { for name, value := range req.PostForm {
// prevent injections // prevent injections
if isParameterNameInjection(name) { if nameInjection(name) {
log.Printf("post.injection: '%s'\n", name) log.Printf("post.injection: '%s'\n", name)
continue continue
} }
// store value in 'Set' // store value in 'Set'
i.Set[name] = &requestParameter{ i.Set[name] = &Parameter{
Parsed: false, Parsed: false,
Value: value, 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' // and 'Set'
func (i *requestData) parseMultipartForm(req *http.Request) { func (i *DataSet) parseMultipart(req *http.Request) {
/* (1) Create reader */ /* (1) Create reader */
mpr := multipart.CreateReader(req) mpr := multipart.CreateReader(req)
@ -190,13 +200,13 @@ func (i *requestData) parseMultipartForm(req *http.Request) {
for name, component := range mpr.Components { for name, component := range mpr.Components {
// prevent injections // prevent injections
if isParameterNameInjection(name) { if nameInjection(name) {
log.Printf("post.injection: '%s'\n", name) log.Printf("post.injection: '%s'\n", name)
continue continue
} }
// store value in 'Set' // store value in 'Set'
i.Set[name] = &requestParameter{ i.Set[name] = &Parameter{
Parsed: false, Parsed: false,
File: component.File, File: component.File,
Value: component.Data, Value: component.Data,
@ -210,11 +220,3 @@ func (i *requestData) parseMultipartForm(req *http.Request) {
return 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#")
}

14
request/parameter.go Normal file
View File

@ -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)
}

129
request/request.go Normal file
View File

@ -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
}

56
request/types.go Normal file
View File

@ -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{}
}

118
request/utils.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -4,30 +4,30 @@ import (
"fmt" "fmt"
"git.xdrm.io/xdrm-brackets/gfw/config" "git.xdrm.io/xdrm-brackets/gfw/config"
"git.xdrm.io/xdrm-brackets/gfw/err" "git.xdrm.io/xdrm-brackets/gfw/err"
"git.xdrm.io/xdrm-brackets/gfw/request"
"log" "log"
"net/http" "net/http"
"strings" "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) Build request
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Try to build request */ /* (1) Try to build request */
request, err2 := buildRequest(req) req, err2 := request.Build(httpReq)
if err2 != nil { if err2 != nil {
log.Fatal(req) log.Fatal(err2)
} }
/* (2) Find a controller /* (2) Find a controller
---------------------------------------------------------*/ ---------------------------------------------------------*/
controller := s.findController(request) controller := s.findController(req)
/* (3) Check method /* (3) Check method
---------------------------------------------------------*/ ---------------------------------------------------------*/
method := s.getMethod(controller, req.Method) var method *config.Method
if method = controller.Method(httpReq.Method); method == nil {
if method == nil {
Json, _ := err.UnknownMethod.MarshalJSON() Json, _ := err.UnknownMethod.MarshalJSON()
res.Header().Add("Content-Type", "application/json") res.Header().Add("Content-Type", "application/json")
res.Write(Json) res.Write(Json)
@ -42,7 +42,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
for name, param := range method.Parameters { for name, param := range method.Parameters {
/* (1) Extract value */ /* (1) Extract value */
p, isset := request.Data.Set[name] p, isset := req.Data.Set[name]
/* (2) Required & missing */ /* (2) Required & missing */
if !isset && !*param.Optional { if !isset && !*param.Optional {
@ -53,7 +53,7 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
/* (3) Optional & missing: set default value */ /* (3) Optional & missing: set default value */
if !isset { if !isset {
p = &requestParameter{ p = &request.Parameter{
Parsed: true, Parsed: true,
File: param.Type == "FILE", File: param.Type == "FILE",
Value: nil, Value: nil,
@ -64,8 +64,8 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
} }
/* (4) Parse parameter if not file */ /* (4) Parse parameter if not file */
if !p.Parsed && !p.File { if !p.File {
p.Value = parseHttpData(p.Value) p.Parse()
} }
/* (4) Fail on unexpected multipart file */ /* (4) Fail on unexpected multipart file */
@ -109,67 +109,43 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) {
/* (5) Load controller /* (5) Load controller
---------------------------------------------------------*/ ---------------------------------------------------------*/
callable, err := request.loadController(req.Method) callable, err := req.LoadController(httpReq.Method)
if err != nil { if err != nil {
log.Printf("[err] %s\n", err) log.Printf("[err] %s\n", err)
return 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 { for name, value := range parameters {
fmt.Printf(" $%s = %v\n", name, value) fmt.Printf(" $%s = %v\n", name, value)
} }
/* (6) Execute and get response /* (6) Execute and get response
---------------------------------------------------------*/ ---------------------------------------------------------*/
out, _ := callable(parameters) resp := callable(parameters)
if resp != nil {
fmt.Printf("-- OUT --\n") fmt.Printf("-- OUT --\n")
for name, value := range out { for name, value := range resp.Dump() {
fmt.Printf(" $%s = %v\n", name, value) fmt.Printf(" $%s = %v\n", name, value)
} }
eJSON, _ := resp.Err.MarshalJSON()
fmt.Printf("-- ERR --\n%s\n", eJSON)
}
return return
} }
func (s *Server) findController(req *Request) *config.Controller { func (s *Server) findController(req *request.Request) *config.Controller {
/* (1) Init browsing cursors */
ctl := s.config
uriIndex := 0
/* (2) Browse while there is uri parts */ /* (1) Try to browse by URI */
for uriIndex < len(req.Uri) { pathi, ctl := s.config.Browse(req.Uri)
uri := req.Uri[uriIndex]
child, hasKey := ctl.Children[uri] /* (2) Set controller uri */
req.Path = make([]string, 0, pathi)
// stop if no matchind child req.Path = append(req.Path, req.Uri[:pathi]...)
if !hasKey {
break
}
req.ControllerUri = append(req.ControllerUri, uri)
ctl = child
uriIndex++
}
/* (3) Extract & store URI params */ /* (3) Extract & store URI params */
req.Data.fillUrl(req.Uri[uriIndex:]) req.Data.SetUri(req.Uri[pathi:])
/* (4) Return controller */ /* (4) Return controller */
return ctl 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
}

View File

@ -12,58 +12,3 @@ type Server struct {
Checker *checker.TypeRegistry // type check Checker *checker.TypeRegistry // type check
err err.Error 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{}
}