3
0

Added json postfilter, HttpRequest custom type

This commit is contained in:
Peter Grlica
2021-11-30 20:00:34 +01:00
parent a2091dbbf6
commit e433f3303a
9 changed files with 688 additions and 22 deletions
+127 -2
View File
@@ -7,11 +7,18 @@ import (
"net/http"
"net/url"
"github.com/cortezaproject/corteza-server/automation/service"
atypes "github.com/cortezaproject/corteza-server/automation/types"
agctx "github.com/cortezaproject/corteza-server/pkg/apigw/ctx"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
pe "github.com/cortezaproject/corteza-server/pkg/errors"
errors "github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/expr"
)
type (
typesRegistry interface {
Type(ref string) expr.Type
}
redirection struct {
types.FilterMeta
@@ -33,6 +40,17 @@ type (
}
}
jsonResponse struct {
types.FilterMeta
reg typesRegistry
params struct {
Exp *atypes.Expr
Evaluable expr.Evaluable
}
}
defaultJsonResponse struct {
types.FilterMeta
}
@@ -80,6 +98,10 @@ func (h redirection) Weight() int {
func (h *redirection) Merge(params []byte) (types.Handler, error) {
err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h.params)
if err != nil {
return h, err
}
loc, err := url.ParseRequestURI(h.params.Location)
if err != nil {
@@ -135,7 +157,7 @@ func (j defaultJsonResponse) Handler() types.HandlerFunc {
rw.WriteHeader(http.StatusAccepted)
if _, err := rw.Write([]byte(`{}`)); err != nil {
return pe.Internal("could not write to body: %v", err)
return errors.Internal("could not write to body: %v", err)
}
return nil
@@ -150,3 +172,106 @@ func checkStatus(typ string, status int) bool {
return true
}
}
func NewJsonResponse(reg typesRegistry) (e *jsonResponse) {
e = &jsonResponse{}
e.Name = "jsonResponse"
e.Label = "JSON response"
e.Kind = types.PostFilter
e.Args = []*types.FilterMetaArg{
{
Type: "input",
Label: "input",
Options: map[string]interface{}{},
},
}
e.reg = reg
return
}
func (j jsonResponse) New() types.Handler {
return NewJsonResponse(service.Registry())
}
func (j jsonResponse) String() string {
return fmt.Sprintf("apigw filter %s (%s)", j.Name, j.Label)
}
func (j jsonResponse) Meta() types.FilterMeta {
return j.FilterMeta
}
func (j *jsonResponse) Merge(params []byte) (h types.Handler, err error) {
var (
parser = expr.NewParser()
)
err = json.NewDecoder(bytes.NewBuffer(params)).Decode(&j.params.Exp)
if err != nil {
return j, err
}
j.params.Evaluable, err = parser.Parse(j.params.Exp.Expr)
if err != nil {
return j, fmt.Errorf("could not evaluate expression: %s", err)
}
j.params.Exp.SetEval(j.params.Evaluable)
return j, err
}
func (j jsonResponse) Handler() types.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) (err error) {
var (
ctx = r.Context()
scope = agctx.ScopeFromContext(ctx)
evald interface{}
)
in, err := expr.NewVars(scope.Dict())
if err != nil {
return errors.Internal("could not validate request data: %v", err)
}
// set type to the registered expression from
// any of the already registered types
j.params.Exp.SetType(func(name string) (e expr.Type, err error) {
if name == "" {
name = "Any"
}
if typ := j.reg.Type(name); typ != nil {
return typ, nil
} else {
return nil, errors.Internal("unknown or unregistered type %s", name)
}
})
evald, err = j.params.Exp.Eval(ctx, in)
if err != nil {
return
}
rw.Header().Add("Content-Type", "application/json")
switch v := evald.(type) {
case string:
rw.Write([]byte(v))
default:
e := json.NewEncoder(rw)
err = e.Encode(v)
}
return
}
}
+6 -6
View File
@@ -91,16 +91,17 @@ func (h workflow) Handler() types.HandlerFunc {
scope = agctx.ScopeFromContext(ctx)
)
payload, err := scope.Get("payload")
// original request with body as io.Reader
// read-only
ar, err := scope.Get("request")
if err != nil {
return pe.Internal("could not get payload: %v", err)
return err
}
// setup scope for workflow
vv := map[string]interface{}{
"payload": payload,
"request": r,
"request": ar,
}
// get the request data and put it into vars
@@ -152,8 +153,7 @@ func (h workflow) Handler() types.HandlerFunc {
scope = ss
scope.Set("request", r)
scope.Set("payload", payload)
scope.Set("request", ar)
return nil
}
+1
View File
@@ -69,6 +69,7 @@ func (r *Registry) Preload() {
// postfilters
r.Add("redirection", filter.NewRedirection())
r.Add("jsonResponse", filter.NewJsonResponse(service.Registry()))
r.Add("defaultJsonResponse", filter.NewDefaultJsonResponse())
}
+8 -14
View File
@@ -1,18 +1,15 @@
package apigw
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"time"
actx "github.com/cortezaproject/corteza-server/pkg/apigw/ctx"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/system/automation"
"go.uber.org/zap"
)
@@ -45,19 +42,16 @@ func (r route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.log.Debug("started serving route")
b, _ := io.ReadAll(req.Body)
body := string(b)
// create a new automation HttpRequest
ar, err := automation.NewHttpRequest(req)
// write again
req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
if err != nil {
r.log.Error("could not prepare a request holder", zap.Error(err))
return
}
scope.Set("opts", r.opts)
scope.Set("payload", body)
if r.opts.LogEnabled {
o, _ := httputil.DumpRequest(req, r.opts.LogRequestBody)
r.log.Debug("incoming request", zap.Any("request", string(o)))
}
scope.Set("request", ar)
req = req.WithContext(actx.ScopeToContext(ctx, &scope))
+9
View File
@@ -65,6 +65,10 @@ func (s Scp) Get(k string) (v interface{}, err error) {
return
}
func (s *Scp) Dict() map[string]interface{} {
return *s
}
func (s *Scp) Filter(fn func(k string, v interface{}) bool) *Scp {
ss := Scp{}
@@ -78,3 +82,8 @@ func (s *Scp) Filter(fn func(k string, v interface{}) bool) *Scp {
return &ss
}
func (s Scp) Has(k string) (has bool) {
_, has = s[k]
return
}
+436
View File
@@ -119,6 +119,203 @@ func (t *DocumentType) Assign(val interface{}) error {
}
}
// HttpRequest is an expression type, wrapper for *types.HttpRequest type
type HttpRequest struct {
value *types.HttpRequest
mux sync.RWMutex
}
// NewHttpRequest creates new instance of HttpRequest expression type
func NewHttpRequest(val interface{}) (*HttpRequest, error) {
if c, err := CastToHttpRequest(val); err != nil {
return nil, fmt.Errorf("unable to create HttpRequest: %w", err)
} else {
return &HttpRequest{value: c}, nil
}
}
// Get return underlying value on HttpRequest
func (t *HttpRequest) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on HttpRequest
func (t *HttpRequest) GetValue() *types.HttpRequest {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (HttpRequest) Type() string { return "HttpRequest" }
// Cast converts value to *types.HttpRequest
func (HttpRequest) Cast(val interface{}) (TypedValue, error) {
return NewHttpRequest(val)
}
// Assign new value to HttpRequest
//
// value is first passed through CastToHttpRequest
func (t *HttpRequest) Assign(val interface{}) error {
if c, err := CastToHttpRequest(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *HttpRequest) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToHttpRequest(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access HttpRequest's underlying value (*types.HttpRequest)
// and it's fields
//
func (t *HttpRequest) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestGValSelector(t.value, k)
}
// Select is field accessor for *types.HttpRequest
//
// Similar to SelectGVal but returns typed values
func (t *HttpRequest) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestTypedValueSelector(t.value, k)
}
func (t *HttpRequest) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "Method":
return true
case "URL":
return true
case "Header":
return true
case "Body":
return true
case "Form":
return true
case "PostForm":
return true
}
return false
}
// httpRequestGValSelector is field accessor for *types.HttpRequest
func httpRequestGValSelector(res *types.HttpRequest, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "Method":
return res.Method, nil
case "URL":
return res.URL, nil
case "Header":
return res.Header, nil
case "Body":
return res.Body, nil
case "Form":
return res.Form, nil
case "PostForm":
return res.PostForm, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// httpRequestTypedValueSelector is field accessor for *types.HttpRequest
func httpRequestTypedValueSelector(res *types.HttpRequest, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "Method":
return NewString(res.Method)
case "URL":
return NewUrl(res.URL)
case "Header":
return NewKVV(res.Header)
case "Body":
return NewReader(res.Body)
case "Form":
return NewKVV(res.Form)
case "PostForm":
return NewKVV(res.PostForm)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToHttpRequest is field value setter for *types.HttpRequest
func assignToHttpRequest(res *types.HttpRequest, k string, val interface{}) error {
switch k {
case "Method":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Method = aux
return nil
case "URL":
aux, err := CastToUrl(val)
if err != nil {
return err
}
res.URL = aux
return nil
case "Header":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.Header = aux
return nil
case "Body":
aux, err := CastToReader(val)
if err != nil {
return err
}
res.Body = aux
return nil
case "Form":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.Form = aux
return nil
case "PostForm":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.PostForm = aux
return nil
}
return fmt.Errorf("unknown field '%s'", k)
}
// QueueMessage is an expression type, wrapper for *types.QueueMessage type
type QueueMessage struct {
value *types.QueueMessage
@@ -1102,6 +1299,245 @@ func assignToTemplateMeta(res types.TemplateMeta, k string, val interface{}) err
return fmt.Errorf("unknown field '%s'", k)
}
// Url is an expression type, wrapper for *types.Url type
type Url struct {
value *types.Url
mux sync.RWMutex
}
// NewUrl creates new instance of Url expression type
func NewUrl(val interface{}) (*Url, error) {
if c, err := CastToUrl(val); err != nil {
return nil, fmt.Errorf("unable to create Url: %w", err)
} else {
return &Url{value: c}, nil
}
}
// Get return underlying value on Url
func (t *Url) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on Url
func (t *Url) GetValue() *types.Url {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (Url) Type() string { return "Url" }
// Cast converts value to *types.Url
func (Url) Cast(val interface{}) (TypedValue, error) {
return NewUrl(val)
}
// Assign new value to Url
//
// value is first passed through CastToUrl
func (t *Url) Assign(val interface{}) error {
if c, err := CastToUrl(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *Url) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToUrl(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access Url's underlying value (*types.Url)
// and it's fields
//
func (t *Url) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return urlGValSelector(t.value, k)
}
// Select is field accessor for *types.Url
//
// Similar to SelectGVal but returns typed values
func (t *Url) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return urlTypedValueSelector(t.value, k)
}
func (t *Url) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "Scheme":
return true
case "Opaque":
return true
case "Host":
return true
case "Path":
return true
case "RawPath":
return true
case "ForceQuery":
return true
case "RawQuery":
return true
case "Fragment":
return true
case "RawFragment":
return true
}
return false
}
// urlGValSelector is field accessor for *types.Url
func urlGValSelector(res *types.Url, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "Scheme":
return res.Scheme, nil
case "Opaque":
return res.Opaque, nil
case "Host":
return res.Host, nil
case "Path":
return res.Path, nil
case "RawPath":
return res.RawPath, nil
case "ForceQuery":
return res.ForceQuery, nil
case "RawQuery":
return res.RawQuery, nil
case "Fragment":
return res.Fragment, nil
case "RawFragment":
return res.RawFragment, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// urlTypedValueSelector is field accessor for *types.Url
func urlTypedValueSelector(res *types.Url, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "Scheme":
return NewString(res.Scheme)
case "Opaque":
return NewString(res.Opaque)
case "Host":
return NewString(res.Host)
case "Path":
return NewString(res.Path)
case "RawPath":
return NewString(res.RawPath)
case "ForceQuery":
return NewBoolean(res.ForceQuery)
case "RawQuery":
return NewString(res.RawQuery)
case "Fragment":
return NewString(res.Fragment)
case "RawFragment":
return NewString(res.RawFragment)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToUrl is field value setter for *types.Url
func assignToUrl(res *types.Url, k string, val interface{}) error {
switch k {
case "Scheme":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Scheme = aux
return nil
case "Opaque":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Opaque = aux
return nil
case "Host":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Host = aux
return nil
case "Path":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Path = aux
return nil
case "RawPath":
aux, err := CastToString(val)
if err != nil {
return err
}
res.RawPath = aux
return nil
case "ForceQuery":
aux, err := CastToBoolean(val)
if err != nil {
return err
}
res.ForceQuery = aux
return nil
case "RawQuery":
aux, err := CastToString(val)
if err != nil {
return err
}
res.RawQuery = aux
return nil
case "Fragment":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Fragment = aux
return nil
case "RawFragment":
aux, err := CastToString(val)
if err != nil {
return err
}
res.RawFragment = aux
return nil
}
return fmt.Errorf("unknown field '%s'", k)
}
// User is an expression type, wrapper for *types.User type
type User struct {
value *types.User
+50
View File
@@ -5,6 +5,8 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/expr"
@@ -184,6 +186,54 @@ func CastToQueueMessage(val interface{}) (out *types.QueueMessage, err error) {
}
}
func CastToHttpRequest(val interface{}) (out *types.HttpRequest, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.HttpRequest{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToHttpRequest(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *http.Request:
rr := &types.HttpRequest{}
assignToHttpRequest(rr, "Method", val.Method)
assignToHttpRequest(rr, "URL", val.URL)
assignToHttpRequest(rr, "Header", val.Header)
assignToHttpRequest(rr, "Body", val.Body)
assignToHttpRequest(rr, "Form", val.Form)
assignToHttpRequest(rr, "PostForm", val.PostForm)
return rr, nil
case *types.HttpRequest:
return val, nil
case nil:
return &types.HttpRequest{}, nil
default:
return &types.HttpRequest{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToUrl(val interface{}) (out *types.Url, err error) {
switch val := expr.UntypedValue(val).(type) {
case *url.URL:
u := &types.Url{}
m, _ := json.Marshal(val)
_ = json.Unmarshal(m, u)
return u, nil
case *types.Url:
return val, nil
case nil:
return &types.Url{}, nil
default:
return &types.Url{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func (doc renderedDocument) String() string {
aux, _ := ioutil.ReadAll(doc.Document)
return string(aux)
+23
View File
@@ -72,6 +72,29 @@ types:
- { name: 'Queue', exprType: 'String', goType: 'string' }
- { name: 'Payload', exprType: 'Bytes', goType: '[]byte' }
HttpRequest:
as: '*types.HttpRequest'
struct:
- { name: 'Method', exprType: 'String', goType: 'string' }
- { name: 'URL', exprType: 'Url', goType: 'url.Url' }
- { name: 'Header', exprType: 'KVV', goType: 'map[string][]string' }
- { name: 'Body', exprType: 'Reader', goType: 'io.Reader' }
- { name: 'Form', exprType: 'KVV', goType: 'map[string][]string' }
- { name: 'PostForm', exprType: 'KVV', goType: 'map[string][]string' }
Url:
as: '*types.Url'
struct:
- { name: 'Scheme', exprType: 'String', goType: 'string' }
- { name: 'Opaque', exprType: 'String', goType: 'string' }
- { name: 'Host', exprType: 'String', goType: 'string' }
- { name: 'Path', exprType: 'String', goType: 'string' }
- { name: 'RawPath', exprType: 'String', goType: 'string' }
- { name: 'ForceQuery', exprType: 'Boolean', goType: 'bool' }
- { name: 'RawQuery', exprType: 'String', goType: 'string' }
- { name: 'Fragment', exprType: 'String', goType: 'string' }
- { name: 'RawFragment', exprType: 'String', goType: 'string' }
RbacResource:
as: 'rbac.Resource'
+28
View File
@@ -0,0 +1,28 @@
package types
import (
"io"
)
type (
HttpRequest struct {
Method string `json:"method"`
URL *Url `json:"url"`
Header map[string][]string `json:"header"`
Body io.Reader `json:"body"`
Form map[string][]string `json:"form"`
PostForm map[string][]string `json:"post_form"`
}
Url struct {
Scheme string
Opaque string
Host string
Path string
RawPath string
ForceQuery bool
RawQuery string
Fragment string
RawFragment string
}
)