refactor 'router' + 'loader' into 'server'
This commit is contained in:
parent
2222661174
commit
f0719e606c
40
loader.go
40
loader.go
|
@ -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
|
||||
}
|
176
router.go
176
router.go
|
@ -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
|
||||
|
||||
}
|
176
server.go
176
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
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue