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
|
package gfw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"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"
|
"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 {
|
func (s *Server) Launch(port uint16) error {
|
||||||
|
|
||||||
/* (1) Bind router */
|
/* (1) Bind router */
|
||||||
http.HandleFunc("/", s.route)
|
http.HandleFunc("/", s.routeRequest)
|
||||||
|
|
||||||
/* (2) Bind listener */
|
/* (2) Bind listener */
|
||||||
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
|
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