move aicra builder and server into their own files
This commit is contained in:
parent
09362aad83
commit
c5cdba8007
|
@ -0,0 +1,99 @@
|
||||||
|
package aicra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.xdrm.io/go/aicra/datatype"
|
||||||
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
|
"git.xdrm.io/go/aicra/internal/dynfunc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder for an aicra server
|
||||||
|
type Builder struct {
|
||||||
|
conf *config.Server
|
||||||
|
handlers []*apiHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents an server handler
|
||||||
|
type apiHandler struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
dyn *dynfunc.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddType adds an available datatype to the api definition
|
||||||
|
func (b *Builder) AddType(t datatype.T) {
|
||||||
|
if b.conf == nil {
|
||||||
|
b.conf = &config.Server{}
|
||||||
|
}
|
||||||
|
if b.conf.Services != nil {
|
||||||
|
panic(ErrLateType)
|
||||||
|
}
|
||||||
|
b.conf.Types = append(b.conf.Types, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the builder with its api definition
|
||||||
|
// panics if already setup
|
||||||
|
func (b *Builder) Setup(r io.Reader) error {
|
||||||
|
if b.conf == nil {
|
||||||
|
b.conf = &config.Server{}
|
||||||
|
}
|
||||||
|
if b.conf.Services != nil {
|
||||||
|
panic(ErrAlreadySetup)
|
||||||
|
}
|
||||||
|
return b.conf.Parse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind a dynamic handler to a REST service
|
||||||
|
func (b *Builder) Bind(method, path string, fn interface{}) error {
|
||||||
|
if b.conf.Services == nil {
|
||||||
|
return ErrNotSetup
|
||||||
|
}
|
||||||
|
|
||||||
|
// find associated service
|
||||||
|
var service *config.Service
|
||||||
|
for _, s := range b.conf.Services {
|
||||||
|
if method == s.Method && path == s.Pattern {
|
||||||
|
service = s
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if service == nil {
|
||||||
|
return fmt.Errorf("%s '%s': %w", method, path, ErrUnknownService)
|
||||||
|
}
|
||||||
|
|
||||||
|
dyn, err := dynfunc.Build(fn, *service)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s '%s' handler: %w", method, path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.handlers = append(b.handlers, &apiHandler{
|
||||||
|
Path: path,
|
||||||
|
Method: method,
|
||||||
|
dyn: dyn,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a fully-featured HTTP server
|
||||||
|
func (b Builder) Build() (http.Handler, error) {
|
||||||
|
|
||||||
|
for _, service := range b.conf.Services {
|
||||||
|
var hasAssociatedHandler bool
|
||||||
|
for _, handler := range b.handlers {
|
||||||
|
if handler.Method == service.Method && handler.Path == service.Pattern {
|
||||||
|
hasAssociatedHandler = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasAssociatedHandler {
|
||||||
|
return nil, fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, ErrMissingHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Server(b), nil
|
||||||
|
}
|
106
http.go
106
http.go
|
@ -1,106 +0,0 @@
|
||||||
package aicra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
|
||||||
"git.xdrm.io/go/aicra/internal/reqdata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// httpHandler wraps the aicra server to allow handling http requests
|
|
||||||
type httpHandler Builder
|
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler and has to be called on each request
|
|
||||||
func (server httpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
|
||||||
defer req.Body.Close()
|
|
||||||
|
|
||||||
// 1. find a matching service in the config
|
|
||||||
service := server.conf.Find(req)
|
|
||||||
if service == nil {
|
|
||||||
response := api.EmptyResponse().WithError(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 {
|
|
||||||
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. extract query data
|
|
||||||
err = dataset.ExtractQuery(req)
|
|
||||||
if err != nil {
|
|
||||||
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. extract form/json data
|
|
||||||
err = dataset.ExtractForm(req)
|
|
||||||
if err != nil {
|
|
||||||
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
logError(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. find a matching handler
|
|
||||||
var handler *apiHandler
|
|
||||||
for _, h := range server.handlers {
|
|
||||||
if h.Method == service.Method && h.Path == service.Pattern {
|
|
||||||
handler = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. fail if found no handler
|
|
||||||
if handler == nil {
|
|
||||||
r := api.EmptyResponse().WithError(api.ErrorUnknownService)
|
|
||||||
r.ServeHTTP(res, req)
|
|
||||||
logError(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. build api.Request from http.Request
|
|
||||||
apireq, err := api.NewRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9. feed request with scope & parameters
|
|
||||||
apireq.Scope = service.Scope
|
|
||||||
apireq.Param = dataset.Data
|
|
||||||
|
|
||||||
// 10. execute
|
|
||||||
returned, apiErr := handler.dyn.Handle(dataset.Data)
|
|
||||||
response := api.EmptyResponse().WithError(apiErr)
|
|
||||||
for key, value := range returned {
|
|
||||||
|
|
||||||
// find original name from rename
|
|
||||||
for name, param := range service.Output {
|
|
||||||
if param.Rename == key {
|
|
||||||
response.SetData(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 11. apply headers
|
|
||||||
res.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
for key, values := range response.Headers {
|
|
||||||
for _, value := range values {
|
|
||||||
res.Header().Add(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 12. write to response
|
|
||||||
response.ServeHTTP(res, req)
|
|
||||||
}
|
|
161
server.go
161
server.go
|
@ -1,99 +1,106 @@
|
||||||
package aicra
|
package aicra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/datatype"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/dynfunc"
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Builder for an aicra server
|
// Server hides the builder and allows handling http requests
|
||||||
type Builder struct {
|
type Server Builder
|
||||||
conf *config.Server
|
|
||||||
handlers []*apiHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// represents an server handler
|
// ServeHTTP implements http.Handler and is called on each request
|
||||||
type apiHandler struct {
|
func (server Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
Method string
|
defer req.Body.Close()
|
||||||
Path string
|
|
||||||
dyn *dynfunc.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddType adds an available datatype to the api definition
|
|
||||||
func (b *Builder) AddType(t datatype.T) {
|
|
||||||
if b.conf == nil {
|
|
||||||
b.conf = &config.Server{}
|
|
||||||
}
|
|
||||||
if b.conf.Services != nil {
|
|
||||||
panic(ErrLateType)
|
|
||||||
}
|
|
||||||
b.conf.Types = append(b.conf.Types, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the builder with its api definition
|
|
||||||
// panics if already setup
|
|
||||||
func (b *Builder) Setup(r io.Reader) error {
|
|
||||||
if b.conf == nil {
|
|
||||||
b.conf = &config.Server{}
|
|
||||||
}
|
|
||||||
if b.conf.Services != nil {
|
|
||||||
panic(ErrAlreadySetup)
|
|
||||||
}
|
|
||||||
return b.conf.Parse(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind a dynamic handler to a REST service
|
|
||||||
func (b *Builder) Bind(method, path string, fn interface{}) error {
|
|
||||||
if b.conf.Services == nil {
|
|
||||||
return ErrNotSetup
|
|
||||||
}
|
|
||||||
|
|
||||||
// find associated service
|
|
||||||
var service *config.Service
|
|
||||||
for _, s := range b.conf.Services {
|
|
||||||
if method == s.Method && path == s.Pattern {
|
|
||||||
service = s
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 1. find a matching service in the config
|
||||||
|
service := server.conf.Find(req)
|
||||||
if service == nil {
|
if service == nil {
|
||||||
return fmt.Errorf("%s '%s': %w", method, path, ErrUnknownService)
|
response := api.EmptyResponse().WithError(api.ErrorUnknownService)
|
||||||
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dyn, err := dynfunc.Build(fn, *service)
|
// 2. build input parameter receiver
|
||||||
|
dataset := reqdata.New(service)
|
||||||
|
|
||||||
|
// 3. extract URI data
|
||||||
|
err := dataset.ExtractURI(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s '%s' handler: %w", method, path, err)
|
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
||||||
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b.handlers = append(b.handlers, &apiHandler{
|
// 4. extract query data
|
||||||
Path: path,
|
err = dataset.ExtractQuery(req)
|
||||||
Method: method,
|
if err != nil {
|
||||||
dyn: dyn,
|
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
||||||
})
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a fully-featured HTTP server
|
// 5. extract form/json data
|
||||||
func (b Builder) Build() (http.Handler, error) {
|
err = dataset.ExtractForm(req)
|
||||||
|
if err != nil {
|
||||||
|
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
||||||
|
response.ServeHTTP(res, req)
|
||||||
|
logError(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, service := range b.conf.Services {
|
// 6. find a matching handler
|
||||||
var hasAssociatedHandler bool
|
var handler *apiHandler
|
||||||
for _, handler := range b.handlers {
|
for _, h := range server.handlers {
|
||||||
if handler.Method == service.Method && handler.Path == service.Pattern {
|
if h.Method == service.Method && h.Path == service.Pattern {
|
||||||
hasAssociatedHandler = true
|
handler = h
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasAssociatedHandler {
|
|
||||||
return nil, fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, ErrMissingHandler)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpHandler(b), nil
|
// 7. fail if found no handler
|
||||||
|
if handler == nil {
|
||||||
|
r := api.EmptyResponse().WithError(api.ErrorUnknownService)
|
||||||
|
r.ServeHTTP(res, req)
|
||||||
|
logError(r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. build api.Request from http.Request
|
||||||
|
apireq, err := api.NewRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. feed request with scope & parameters
|
||||||
|
apireq.Scope = service.Scope
|
||||||
|
apireq.Param = dataset.Data
|
||||||
|
|
||||||
|
// 10. execute
|
||||||
|
returned, apiErr := handler.dyn.Handle(dataset.Data)
|
||||||
|
response := api.EmptyResponse().WithError(apiErr)
|
||||||
|
for key, value := range returned {
|
||||||
|
|
||||||
|
// find original name from rename
|
||||||
|
for name, param := range service.Output {
|
||||||
|
if param.Rename == key {
|
||||||
|
response.SetData(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. apply headers
|
||||||
|
res.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
for key, values := range response.Headers {
|
||||||
|
for _, value := range values {
|
||||||
|
res.Header().Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. write to response
|
||||||
|
response.ServeHTTP(res, req)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue