big update
This commit is contained in:
parent
f0727cb9ca
commit
ca05b7ff29
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
package config
|
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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"`
|
||||||
|
@ -11,10 +11,10 @@ type MethodParameter struct {
|
||||||
Default *interface{} `json:"def"`
|
Default *interface{} `json:"def"`
|
||||||
}
|
}
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
|
@ -25,5 +25,3 @@ type Controller struct {
|
||||||
|
|
||||||
Children map[string]*Controller `json:"/"`
|
Children map[string]*Controller `json:"/"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var AvailableMethods = []string{"GET", "POST", "PUT", "DELETE"}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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#")
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -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{}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
80
router.go
80
router.go
|
@ -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)
|
||||||
fmt.Printf("-- OUT --\n")
|
if resp != nil {
|
||||||
for name, value := range out {
|
fmt.Printf("-- OUT --\n")
|
||||||
fmt.Printf(" $%s = %v\n", name, value)
|
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
|
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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
55
types.go
55
types.go
|
@ -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{}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue