diff --git a/loader.go b/loader.go deleted file mode 100644 index bb75add..0000000 --- a/loader.go +++ /dev/null @@ -1,40 +0,0 @@ -package gfw - -import ( - "git.xdrm.io/go/aicra/checker" - "git.xdrm.io/go/aicra/config" -) - -// Init initilises a new framework instance -// -// - path is the configuration path -// -// - if typeChecker is nil, defaults will be used (all *.so files -// inside ./types local directory) -// -func Init(path string, typeChecker *checker.TypeRegistry) (*Server, error) { - - /* (1) Init instance */ - inst := &Server{ - config: nil, - Params: make(map[string]interface{}), - } - - /* (2) Load configuration */ - config, err := config.Load(path) - if err != nil { - return nil, err - } - inst.config = config - - /* (3) Store registry if not nil */ - if typeChecker != nil { - inst.Checker = typeChecker - return inst, nil - } - - /* (4) Default registry creation */ - inst.Checker = checker.CreateRegistry(true) - - return inst, nil -} diff --git a/router.go b/router.go deleted file mode 100644 index 760fb02..0000000 --- a/router.go +++ /dev/null @@ -1,176 +0,0 @@ -package gfw - -import ( - "encoding/json" - "git.xdrm.io/go/aicra/config" - "git.xdrm.io/go/aicra/err" - "git.xdrm.io/go/aicra/implement" - "git.xdrm.io/go/aicra/request" - "log" - "net/http" -) - -func (s *Server) route(res http.ResponseWriter, httpReq *http.Request) { - - /* (1) Build request - ---------------------------------------------------------*/ - /* (1) Try to build request */ - req, err2 := request.Build(httpReq) - if err2 != nil { - log.Fatal(err2) - } - - /* (2) Find a controller - ---------------------------------------------------------*/ - controller := s.findController(req) - - /* (3) Check method - ---------------------------------------------------------*/ - 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) - log.Printf("[err] %s\n", err.UnknownMethod.Reason) - return - } - - /* (4) Check parameters - ---------------------------------------------------------*/ - var paramError err.Error = err.Success - parameters := make(map[string]interface{}) - for name, param := range method.Parameters { - - /* (1) Extract value */ - p, isset := req.Data.Set[name] - - /* (2) Required & missing */ - if !isset && !param.Optional { - paramError = err.MissingParam - paramError.BindArgument(name) - break - } - - /* (3) Optional & missing: set default value */ - if !isset { - p = &request.Parameter{ - Parsed: true, - File: param.Type == "FILE", - Value: nil, - } - if param.Default != nil { - p.Value = *param.Default - } - - // we are done - parameters[param.Rename] = p.Value - continue - } - - /* (4) Parse parameter if not file */ - if !p.File { - p.Parse() - } - - /* (5) Fail on unexpected multipart file */ - waitFile, gotFile := param.Type == "FILE", p.File - if gotFile && !waitFile || !gotFile && waitFile { - paramError = err.InvalidParam - paramError.BindArgument(param.Rename) - paramError.BindArgument("FILE") - break - } - - /* (6) Do not check if file */ - if gotFile { - parameters[param.Rename] = p.Value - continue - } - - /* (7) Check type */ - if s.Checker.Run(param.Type, p.Value) != nil { - - paramError = err.InvalidParam - paramError.BindArgument(param.Rename) - paramError.BindArgument(param.Type) - paramError.BindArgument(p.Value) - break - - } - - parameters[param.Rename] = p.Value - - } - - // Fail if argument check failed - if paramError.Code != err.Success.Code { - Json, _ := paramError.MarshalJSON() - res.Header().Add("Content-Type", "application/json") - res.Write(Json) - log.Printf("[err] %s\n", paramError.Reason) - return - } - - /* (5) Load controller - ---------------------------------------------------------*/ - callable, err := req.LoadController(httpReq.Method) - if err != nil { - log.Printf("[err] %s\n", err) - return - } - - /* (6) Execute and get response - ---------------------------------------------------------*/ - /* (1) Add optional Authorization header */ - authHeader := httpReq.Header.Get("Authorization") - if len(authHeader) > 0 { - parameters["_AUTHORIZATION_"] = authHeader - } - - /* (2) Execute */ - responseBarebone := implement.NewResponse() - response := callable(parameters, responseBarebone) - - /* (3) Extract http headers */ - for k, v := range response.Dump() { - if k == "_REDIRECT_" { - redir, ok := v.(string) - if !ok { - continue - } - res.Header().Add("Location", redir) - res.WriteHeader(308) // permanent redirect - return - } - } - - /* (4) Build JSON response */ - formattedResponse := response.Dump() - formattedResponse["error"] = response.Err.Code - formattedResponse["reason"] = response.Err.Reason - if response.Err.Arguments != nil && len(response.Err.Arguments) > 0 { - formattedResponse["args"] = response.Err.Arguments - } - jsonResponse, _ := json.Marshal(formattedResponse) - - res.Header().Add("Content-Type", "application/json") - res.Write(jsonResponse) - return -} - -func (s *Server) findController(req *request.Request) *config.Controller { - - /* (1) Try to browse by URI */ - pathi, ctl := s.config.Browse(req.Uri) - - /* (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.SetUri(req.Uri[pathi:]) - - /* (4) Return controller */ - return ctl - -} diff --git a/server.go b/server.go index 7949ca5..aca37b7 100644 --- a/server.go +++ b/server.go @@ -1,17 +1,189 @@ package gfw import ( + "encoding/json" "fmt" + "git.xdrm.io/go/aicra/checker" + "git.xdrm.io/go/aicra/config" + "git.xdrm.io/go/aicra/err" + "git.xdrm.io/go/aicra/implement" + "git.xdrm.io/go/aicra/request" + "log" "net/http" ) -// Launch listens and binds the server to the given port +// Init initilises a new framework instance +// +// - path is the configuration path +// +// - if typeChecker is nil, defaults will be used (all *.so files +// inside ./types local directory) +func Init(path string, typeChecker *checker.TypeRegistry) (*Server, error) { + + /* (1) Init instance */ + inst := &Server{ + config: nil, + Params: make(map[string]interface{}), + } + + /* (2) Load configuration */ + config, err := config.Load(path) + if err != nil { + return nil, err + } + inst.config = config + + /* (3) Store registry if not nil */ + if typeChecker != nil { + inst.Checker = typeChecker + return inst, nil + } + + /* (4) Default registry creation */ + inst.Checker = checker.CreateRegistry(true) + + return inst, nil +} + +// Listens and binds the server to the given port func (s *Server) Launch(port uint16) error { /* (1) Bind router */ - http.HandleFunc("/", s.route) + http.HandleFunc("/", s.routeRequest) /* (2) Bind listener */ return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) } + +// Router called for each request +func (s *Server) routeRequest(res http.ResponseWriter, httpReq *http.Request) { + + /* (1) Build request */ + req, err2 := request.Build(httpReq) + if err2 != nil { + log.Fatal(err2) + } + + /* (2) Middleware: authentication */ + // TODO: Auth + + /* (3) Find a matching controller */ + controller := s.findController(req) + + /* (4) Check if matching method exists */ + var method = controller.Method(httpReq.Method) + + if method == nil { + httpError(res, err.UnknownMethod) + return + } + + /* (4) Check parameters + ---------------------------------------------------------*/ + var paramError err.Error = err.Success + parameters := make(map[string]interface{}) + for name, param := range method.Parameters { + + /* (1) Extract value */ + p, isset := req.Data.Set[name] + + /* (2) Required & missing */ + if !isset && !param.Optional { + paramError = err.MissingParam + paramError.BindArgument(name) + break + } + + /* (3) Optional & missing: set default value */ + if !isset { + p = &request.Parameter{ + Parsed: true, + File: param.Type == "FILE", + Value: nil, + } + if param.Default != nil { + p.Value = *param.Default + } + + // we are done + parameters[param.Rename] = p.Value + continue + } + + /* (4) Parse parameter if not file */ + if !p.File { + p.Parse() + } + + /* (5) Fail on unexpected multipart file */ + waitFile, gotFile := param.Type == "FILE", p.File + if gotFile && !waitFile || !gotFile && waitFile { + paramError = err.InvalidParam + paramError.BindArgument(param.Rename) + paramError.BindArgument("FILE") + break + } + + /* (6) Do not check if file */ + if gotFile { + parameters[param.Rename] = p.Value + continue + } + + /* (7) Check type */ + if s.Checker.Run(param.Type, p.Value) != nil { + + paramError = err.InvalidParam + paramError.BindArgument(param.Rename) + paramError.BindArgument(param.Type) + paramError.BindArgument(p.Value) + break + + } + + parameters[param.Rename] = p.Value + + } + + // Fail if argument check failed + if paramError.Code != err.Success.Code { + httpError(res, paramError) + return + } + + /* (5) Load controller + ---------------------------------------------------------*/ + callable, err := req.LoadController(httpReq.Method) + if err != nil { + log.Printf("[err] %s\n", err) + return + } + + /* (6) Execute and get response + ---------------------------------------------------------*/ + /* (1) Show Authorization header into controller */ + authHeader := httpReq.Header.Get("Authorization") + if len(authHeader) > 0 { + parameters["_AUTHORIZATION_"] = authHeader + } + + /* (2) Execute */ + responseBarebone := implement.NewResponse() + response := callable(parameters, responseBarebone) + + /* (3) Extract http headers */ + for k, v := range response.Dump() { + if k == "_REDIRECT_" { + if newLocation, ok := v.(string); ok { + httpRedirect(res, newLocation) + } + continue + } + } + + /* (4) Build JSON response */ + httpPrint(res, response) + return + +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..b8c9f5c --- /dev/null +++ b/util.go @@ -0,0 +1,62 @@ +package gfw + +import ( + "encoding/json" + "git.xdrm.io/go/aicra/config" + "git.xdrm.io/go/aicra/err" + "git.xdrm.io/go/aicra/implement" + "git.xdrm.io/go/aicra/request" + "log" + "net/http" +) + +func (s *Server) findController(req *request.Request) *config.Controller { + + /* (1) Try to browse by URI */ + pathi, ctl := s.config.Browse(req.Uri) + + /* (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.SetUri(req.Uri[pathi:]) + + /* (4) Return controller */ + return ctl + +} + +// Redirects to another location (http protocol) +func httpRedirect(r http.ResponseWriter, loc string) { + r.Header().Add("Location", loc) + r.WriteHeader(308) // permanent redirect +} + +// Prints an HTTP response +func httpPrint(r http.ResponseWriter, res implement.Response) { + // get response data + formattedResponse := res.Dump() + + // add error fields + formattedResponse["error"] = res.Err.Code + formattedResponse["reason"] = res.Err.Reason + + // add arguments if any + if res.Err.Arguments != nil && len(res.Err.Arguments) > 0 { + formattedResponse["args"] = res.Err.Arguments + } + + // write this json + jsonResponse, _ := json.Marshal(formattedResponse) + r.Header().Add("Content-Type", "application/json") + r.Write(jsonResponse) +} + +// Prints an error as HTTP response +func httpError(r http.ResponseWriter, e err.Error) { + Json, _ := e.MarshalJSON() + r.Header().Add("Content-Type", "application/json") + r.Write(Json) + log.Printf("[http.fail] %s\n", e.Reason) +}