diff --git a/http.go b/http.go new file mode 100644 index 0000000..dc6ad88 --- /dev/null +++ b/http.go @@ -0,0 +1,112 @@ +package aicra + +import ( + "log" + "net/http" + "strings" + + "git.xdrm.io/go/aicra/api" + "git.xdrm.io/go/aicra/internal/reqdata" +) + +// httpServer wraps the aicra server to allow handling http requests +type httpServer Server + +// ServeHTTP implements http.Handler and has to be called on each request +func (s httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + // 1. build API request from HTTP request + request, err := api.NewRequest(r) + if err != nil { + log.Fatal(err) + } + + // 2. find a matching service for this path in the config + serviceDef, pathIndex := s.services.Browse(request.URI) + if serviceDef == nil { + return + } + servicePath := strings.Join(request.URI[:pathIndex], "/") + if !strings.HasPrefix(servicePath, "/") { + servicePath = "/" + servicePath + } + + // 3. check if matching methodDef exists in config */ + var methodDef = serviceDef.Method(r.Method) + if methodDef == nil { + response := api.NewResponse(api.ErrorUnknownMethod()) + response.ServeHTTP(w, r) + logError(response) + return + } + + // 4. parse every input data from the request + store := reqdata.New(request.URI[pathIndex:], r) + + /* (4) Check parameters + ---------------------------------------------------------*/ + parameters, paramError := s.extractParameters(store, methodDef.Parameters) + + // Fail if argument check failed + if paramError.Code != api.ErrorSuccess().Code { + response := api.NewResponse(paramError) + response.ServeHTTP(w, r) + logError(response) + return + } + + request.Param = parameters + + /* (5) Search a matching handler + ---------------------------------------------------------*/ + var serviceHandler *api.Handler + var serviceFound bool + + for _, handler := range s.handlers { + if handler.GetPath() == servicePath { + serviceFound = true + if handler.GetMethod() == r.Method { + serviceHandler = handler + } + } + } + + // fail if found no handler + if serviceHandler == nil { + if serviceFound { + response := api.NewResponse() + response.SetError(api.ErrorUncallableMethod(), servicePath, r.Method) + response.ServeHTTP(w, r) + logError(response) + return + } + + response := api.NewResponse() + response.SetError(api.ErrorUncallableService(), servicePath) + response.ServeHTTP(w, r) + logError(response) + return + } + + /* (6) Execute handler and return response + ---------------------------------------------------------*/ + // 1. feed request with configuration scope + request.Scope = methodDef.Scope + + // 1. execute + response := api.NewResponse() + serviceHandler.Handle(*request, response) + + // 2. apply headers + for key, values := range response.Headers { + for _, value := range values { + w.Header().Add(key, value) + } + } + + // 3. write to response + response.ServeHTTP(w, r) + return + +} diff --git a/server.go b/server.go index cd6cfff..a94c55a 100644 --- a/server.go +++ b/server.go @@ -3,14 +3,11 @@ package aicra import ( "io" "log" - "net/http" "os" - "strings" "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/internal/config" - "git.xdrm.io/go/aicra/internal/reqdata" checker "git.xdrm.io/go/aicra/typecheck" ) @@ -48,109 +45,14 @@ func New(configPath string) (*Server, error) { return nil, err } + // 4. log configuration services + log.Printf("=== Aicra configuration ===\n") + logService(*i.services, "") + return i, nil } -// ServeHTTP implements http.Handler and has to be called on each request -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - // 1. build API request from HTTP request - request, err := api.NewRequest(r) - if err != nil { - log.Fatal(err) - } - - // 2. find a matching service for this path in the config - serviceDef, pathIndex := s.services.Browse(request.URI) - if serviceDef == nil { - return - } - servicePath := strings.Join(request.URI[:pathIndex], "/") - if !strings.HasPrefix(servicePath, "/") { - servicePath = "/" + servicePath - } - - // 3. check if matching methodDef exists in config */ - var methodDef = serviceDef.Method(r.Method) - if methodDef == nil { - response := api.NewResponse(api.ErrorUnknownMethod()) - response.ServeHTTP(w, r) - logError(response) - return - } - - // 4. parse every input data from the request - store := reqdata.New(request.URI[pathIndex:], r) - - /* (4) Check parameters - ---------------------------------------------------------*/ - parameters, paramError := s.extractParameters(store, methodDef.Parameters) - - // Fail if argument check failed - if paramError.Code != api.ErrorSuccess().Code { - response := api.NewResponse(paramError) - response.ServeHTTP(w, r) - logError(response) - return - } - - request.Param = parameters - - /* (5) Search a matching handler - ---------------------------------------------------------*/ - var serviceHandler *api.Handler - var serviceFound bool - - for _, handler := range s.handlers { - if handler.GetPath() == servicePath { - serviceFound = true - if handler.GetMethod() == r.Method { - serviceHandler = handler - } - } - } - - // fail if found no handler - if serviceHandler == nil { - if serviceFound { - response := api.NewResponse() - response.SetError(api.ErrorUncallableMethod(), servicePath, r.Method) - response.ServeHTTP(w, r) - logError(response) - return - } - - response := api.NewResponse() - response.SetError(api.ErrorUncallableService(), servicePath) - response.ServeHTTP(w, r) - logError(response) - return - } - - /* (6) Execute handler and return response - ---------------------------------------------------------*/ - // 1. feed request with configuration scope - request.Scope = methodDef.Scope - - // 1. execute - response := api.NewResponse() - serviceHandler.Handle(*request, response) - - // 2. apply headers - for key, values := range response.Headers { - for _, value := range values { - w.Header().Add(key, value) - } - } - - // 3. write to response - response.ServeHTTP(w, r) - return - -} - // HandleFunc sets a new handler for an HTTP method to a path func (s *Server) HandleFunc(httpMethod, path string, handlerFunc api.HandlerFunc) { handler := api.NewHandler(httpMethod, path, handlerFunc) @@ -161,3 +63,16 @@ func (s *Server) HandleFunc(httpMethod, path string, handlerFunc api.HandlerFunc func (s *Server) Handle(handler *api.Handler) { s.handlers = append(s.handlers, handler) } + +// HTTP converts the server to a http server +func (s Server) HTTP() httpServer { + + // 1. log available handlers + log.Printf("=== Mapped handlers ===\n") + for i := 0; i < len(s.handlers); i++ { + log.Printf("* [rest] %s\t'%s'\n", s.handlers[i].GetMethod(), s.handlers[i].GetPath()) + } + + // 2. cast to http server + return httpServer(s) +} diff --git a/util.go b/util.go index 9bc7964..2672f76 100644 --- a/util.go +++ b/util.go @@ -2,6 +2,7 @@ package aicra import ( "log" + "net/http" "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/internal/config" @@ -10,7 +11,7 @@ import ( // extractParameters extracts parameters for the request and checks // every single one according to configuration options -func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string]*config.Parameter) (map[string]interface{}, api.Error) { +func (s *httpServer) extractParameters(store *reqdata.Store, methodParam map[string]*config.Parameter) (map[string]interface{}, api.Error) { // init vars parameters := make(map[string]interface{}) @@ -77,7 +78,28 @@ func (s *Server) extractParameters(store *reqdata.Store, methodParam map[string] return parameters, api.ErrorSuccess() } +var handledMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} + // Prints an error as HTTP response func logError(res *api.Response) { 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("* [rest] %s\t'/'\n", method) + } else { + log.Printf("* [rest] %s\t'%s'\n", method, path) + } + } + } + + if s.Children != nil { + for subPath, child := range s.Children { + logService(*child, path+"/"+subPath) + } + } +}