move aicra builder and server into their own files

This commit is contained in:
Adrien Marquès 2020-04-04 11:50:01 +02:00
parent 09362aad83
commit c5cdba8007
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
3 changed files with 178 additions and 178 deletions

99
builder.go Normal file
View File

@ -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
View File

@ -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
View File

@ -1,99 +1,106 @@
package aicra
import (
"fmt"
"io"
"log"
"net/http"
"git.xdrm.io/go/aicra/datatype"
"git.xdrm.io/go/aicra/dynfunc"
"git.xdrm.io/go/aicra/internal/config"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/internal/reqdata"
)
// Builder for an aicra server
type Builder struct {
conf *config.Server
handlers []*apiHandler
}
// Server hides the builder and allows handling http requests
type Server Builder
// 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
}
}
// ServeHTTP implements http.Handler and is called on each request
func (server Server) 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 {
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 {
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{
Path: path,
Method: method,
dyn: dyn,
})
return nil
// 4. extract query data
err = dataset.ExtractQuery(req)
if err != nil {
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
response.ServeHTTP(res, req)
logError(response)
return
}
// Build a fully-featured HTTP server
func (b Builder) Build() (http.Handler, error) {
// 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
}
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)
// 6. find a matching handler
var handler *apiHandler
for _, h := range server.handlers {
if h.Method == service.Method && h.Path == service.Pattern {
handler = h
}
}
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)
}