adapt server to previous api changes
This commit is contained in:
parent
9a5b0dd6e3
commit
d6c22b5ff0
126
http.go
126
http.go
|
@ -3,7 +3,6 @@ package aicra
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/internal/reqdata"
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
|
@ -13,103 +12,96 @@ import (
|
||||||
type httpServer Server
|
type httpServer Server
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler and has to be called on each request
|
// ServeHTTP implements http.Handler and has to be called on each request
|
||||||
func (server httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
defer r.Body.Close()
|
defer req.Body.Close()
|
||||||
|
|
||||||
/* (1) create api.Request from http.Request
|
// 1. find a matching service in the config
|
||||||
---------------------------------------------------------*/
|
service := server.config.Find(req)
|
||||||
request, err := api.NewRequest(r)
|
if service == nil {
|
||||||
|
response := api.NewResponse(api.ErrorUnknownService())
|
||||||
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. build input parameter receiver
|
||||||
|
dataset := reqdata.New(service)
|
||||||
|
|
||||||
|
// 3. extract URI data
|
||||||
|
err := dataset.ExtractURI(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
response := api.NewResponse(api.ErrorMissingParam())
|
||||||
}
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
// 2. find a matching service for this path in the config
|
|
||||||
serviceConf, pathIndex := server.config.Browse(request.URI)
|
|
||||||
if serviceConf == nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. extract the service path from request URI
|
// 4. extract query data
|
||||||
servicePath := strings.Join(request.URI[:pathIndex], "/")
|
err = dataset.ExtractQuery(req)
|
||||||
if !strings.HasPrefix(servicePath, "/") {
|
if err != nil {
|
||||||
servicePath = "/" + servicePath
|
response := api.NewResponse(api.ErrorMissingParam())
|
||||||
}
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
// 4. find method configuration from http method */
|
|
||||||
var methodConf = serviceConf.Method(r.Method)
|
|
||||||
if methodConf == nil {
|
|
||||||
res := api.NewResponse(api.ErrorUnknownMethod())
|
|
||||||
res.ServeHTTP(w, r)
|
|
||||||
logError(res)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. parse data from the request (uri, query, form, json)
|
// 5. extract form/json data
|
||||||
data := reqdata.New(request.URI[pathIndex:], r)
|
err = dataset.ExtractForm(req)
|
||||||
|
if err != nil {
|
||||||
/* (2) check parameters
|
response := api.NewResponse(api.ErrorMissingParam())
|
||||||
---------------------------------------------------------*/
|
response.ServeHTTP(res, req)
|
||||||
parameters, paramError := server.extractParameters(data, methodConf.Parameters)
|
logError(response)
|
||||||
|
|
||||||
// Fail if argument check failed
|
|
||||||
if paramError.Code != api.ErrorSuccess().Code {
|
|
||||||
res := api.NewResponse(paramError)
|
|
||||||
res.ServeHTTP(w, r)
|
|
||||||
logError(res)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Param = parameters
|
// 6. find a matching handler
|
||||||
|
|
||||||
/* (3) search for the handler
|
|
||||||
---------------------------------------------------------*/
|
|
||||||
var foundHandler *api.Handler
|
var foundHandler *api.Handler
|
||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
for _, handler := range server.handlers {
|
for _, handler := range server.handlers {
|
||||||
if handler.GetPath() == servicePath {
|
if handler.GetMethod() == service.Method && handler.GetPath() == service.Pattern {
|
||||||
found = true
|
found = true
|
||||||
if handler.GetMethod() == r.Method {
|
|
||||||
foundHandler = handler
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail if found no handler
|
// 7. fail if found no handler
|
||||||
if foundHandler == nil {
|
if foundHandler == nil {
|
||||||
if found {
|
if found {
|
||||||
res := api.NewResponse()
|
r := api.NewResponse()
|
||||||
res.SetError(api.ErrorUncallableMethod(), servicePath, r.Method)
|
r.SetError(api.ErrorUncallableService(), service.Method, service.Pattern)
|
||||||
res.ServeHTTP(w, r)
|
r.ServeHTTP(res, req)
|
||||||
logError(res)
|
logError(r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res := api.NewResponse()
|
r := api.NewResponse()
|
||||||
res.SetError(api.ErrorUncallableService(), servicePath)
|
r.SetError(api.ErrorUnknownService(), service.Method, service.Pattern)
|
||||||
res.ServeHTTP(w, r)
|
r.ServeHTTP(res, req)
|
||||||
logError(res)
|
logError(r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (4) execute handler and return response
|
// 8. build api.Request from http.Request
|
||||||
---------------------------------------------------------*/
|
apireq, err := api.NewRequest(req)
|
||||||
// 1. feed request with configuration scope
|
if err != nil {
|
||||||
request.Scope = methodConf.Scope
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// 2. execute
|
// 9. feed request with scope & parameters
|
||||||
res := api.NewResponse()
|
apireq.Scope = service.Scope
|
||||||
foundHandler.Handle(*request, res)
|
apireq.Param = dataset.Data
|
||||||
|
|
||||||
// 3. apply headers
|
// 10. execute
|
||||||
for key, values := range res.Headers {
|
response := api.NewResponse()
|
||||||
|
foundHandler.Handle(*apireq, response)
|
||||||
|
|
||||||
|
// 11. apply headers
|
||||||
|
for key, values := range response.Headers {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
w.Header().Add(key, value)
|
res.Header().Add(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. write to response
|
// 12. write to response
|
||||||
res.ServeHTTP(w, r)
|
response.ServeHTTP(res, req)
|
||||||
return
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
15
server.go
15
server.go
|
@ -6,20 +6,18 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
|
"git.xdrm.io/go/aicra/datatype"
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
checker "git.xdrm.io/go/aicra/typecheck"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents an AICRA instance featuring: type checkers, services
|
// Server represents an AICRA instance featuring: type checkers, services
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *config.Service
|
config *config.Server
|
||||||
Checkers *checker.Set
|
|
||||||
handlers []*api.Handler
|
handlers []*api.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a framework instance from a configuration file
|
// New creates a framework instance from a configuration file
|
||||||
func New(configPath string) (*Server, error) {
|
func New(configPath string, dtypes ...datatype.T) (*Server, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
configFile io.ReadCloser
|
configFile io.ReadCloser
|
||||||
|
@ -28,7 +26,6 @@ func New(configPath string) (*Server, error) {
|
||||||
// 1. init instance
|
// 1. init instance
|
||||||
var i = &Server{
|
var i = &Server{
|
||||||
config: nil,
|
config: nil,
|
||||||
Checkers: checker.New(),
|
|
||||||
handlers: make([]*api.Handler, 0),
|
handlers: make([]*api.Handler, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +37,16 @@ func New(configPath string) (*Server, error) {
|
||||||
defer configFile.Close()
|
defer configFile.Close()
|
||||||
|
|
||||||
// 3. load configuration
|
// 3. load configuration
|
||||||
i.config, err = config.Parse(configFile)
|
i.config, err = config.Parse(configFile, dtypes...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. log configuration services
|
// 4. log configuration services
|
||||||
log.Printf("🔧 Reading configuration '%s'\n", configPath)
|
log.Printf("🔧 Reading configuration '%s'\n", configPath)
|
||||||
logService(*i.config, "")
|
for _, service := range i.config.Services {
|
||||||
|
log.Printf(" ->\t%s\t'%s'\n", service.Method, service.Pattern)
|
||||||
|
}
|
||||||
|
|
||||||
return i, nil
|
return i, nil
|
||||||
|
|
||||||
|
|
90
util.go
90
util.go
|
@ -5,101 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
|
||||||
"git.xdrm.io/go/aicra/internal/reqdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// extractParameters extracts parameters for the request and checks
|
|
||||||
// every single one according to configuration options
|
|
||||||
func (s *httpServer) extractParameters(store *reqdata.Store, methodParam map[string]*config.Parameter) (map[string]interface{}, api.Error) {
|
|
||||||
|
|
||||||
// init vars
|
|
||||||
parameters := make(map[string]interface{})
|
|
||||||
|
|
||||||
// for each param of the config
|
|
||||||
for name, param := range methodParam {
|
|
||||||
|
|
||||||
// 1. extract value
|
|
||||||
p, isset := store.Set[name]
|
|
||||||
|
|
||||||
// 2. fail if required & missing
|
|
||||||
if !isset && !param.Optional {
|
|
||||||
apiErr := api.ErrorMissingParam()
|
|
||||||
apiErr.SetArguments(name)
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. optional & missing: set default value
|
|
||||||
if !isset {
|
|
||||||
p = &reqdata.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 {
|
|
||||||
apiErr := api.ErrorInvalidParam()
|
|
||||||
apiErr.SetArguments(param.Rename, "FILE")
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. ignore type check if file
|
|
||||||
if gotFile {
|
|
||||||
parameters[param.Rename] = p.Value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. check type
|
|
||||||
if s.Checkers.Run(param.Type, p.Value) != nil {
|
|
||||||
apiErr := api.ErrorInvalidParam()
|
|
||||||
apiErr.SetArguments(param.Rename, param.Type, p.Value)
|
|
||||||
return nil, apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters[param.Rename] = p.Value
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return parameters, api.ErrorSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
var handledMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
var handledMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
||||||
|
|
||||||
// Prints an error as HTTP response
|
// Prints an error as HTTP response
|
||||||
func logError(res *api.Response) {
|
func logError(res *api.Response) {
|
||||||
log.Printf("[http.fail] %v\n", res)
|
log.Printf("[http.fail] %v\n", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logService logs a service details
|
|
||||||
func logService(s config.Service, path string) {
|
|
||||||
for _, method := range handledMethods {
|
|
||||||
if m := s.Method(method); m != nil {
|
|
||||||
if path == "" {
|
|
||||||
log.Printf(" ->\t%s\t'/'\n", method)
|
|
||||||
} else {
|
|
||||||
log.Printf(" ->\t%s\t'%s'\n", method, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Children != nil {
|
|
||||||
for subPath, child := range s.Children {
|
|
||||||
logService(*child, path+"/"+subPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue