Compare commits
No commits in common. "eea51c6030e6337e2c9271576cd50564a03fb170" and "cb9260ac2b25ae7840b308b5bbe24ebc33906425" have entirely different histories.
eea51c6030
...
cb9260ac2b
|
@ -38,33 +38,6 @@ Supprimer un ou plusieurs entêtes HTTP dont le nom correspond au patron `patter
|
||||||
|
|
||||||
Le patron est défini par une chaîne comprenant un ou plusieurs caractères `*`, signifiant un ou plusieurs caractères arbitraires.
|
Le patron est défini par une chaîne comprenant un ou plusieurs caractères `*`, signifiant un ou plusieurs caractères arbitraires.
|
||||||
|
|
||||||
##### `get_cookie(ctx, name string) Cookie`
|
|
||||||
|
|
||||||
Récupère un cookie depuis la requête/réponse (en fonction du contexte d'utilisation).
|
|
||||||
Retourne `nil` si le cookie n'existe pas.
|
|
||||||
|
|
||||||
**Cookie**
|
|
||||||
|
|
||||||
```js
|
|
||||||
// Plus d'informations sur https://pkg.go.dev/net/http#Cookie
|
|
||||||
{
|
|
||||||
name: "string", // Nom du cookie
|
|
||||||
value: "string", // Valeur associée au cookie
|
|
||||||
path: "string", // Chemin associé au cookie (présent uniquement dans un contexte de réponse)
|
|
||||||
domain: "string", // Domaine associé au cookie (présent uniquement dans un contexte de réponse)
|
|
||||||
expires: "string", // Date d'expiration du cookie (présent uniquement dans un contexte de réponse)
|
|
||||||
max_age: "string", // Age maximum du cookie (présent uniquement dans un contexte de réponse)
|
|
||||||
secure: "boolean", // Le cookie doit-il être présent uniquement en HTTPS ? (présent uniquement dans un contexte de réponse)
|
|
||||||
http_only: "boolean", // Le cookie est il accessible en Javascript ? (présent uniquement dans un contexte de réponse)
|
|
||||||
same_site: "int" // Voir https://pkg.go.dev/net/http#SameSite (présent uniquement dans un contexte de réponse)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `set_cookie(ctx, cookie Cookie)`
|
|
||||||
|
|
||||||
Définit un cookie sur la requête/réponse (en fonction du contexte d'utilisation).
|
|
||||||
Voir la méthode `get_cookie()` pour voir les attributs potentiels.
|
|
||||||
|
|
||||||
#### Requête
|
#### Requête
|
||||||
|
|
||||||
##### `set_host(ctx, host string)`
|
##### `set_host(ctx, host string)`
|
||||||
|
@ -75,12 +48,6 @@ Modifier la valeur de l'entête `Host` de la requête.
|
||||||
|
|
||||||
Modifier l'URL du serveur cible.
|
Modifier l'URL du serveur cible.
|
||||||
|
|
||||||
##### `redirect(ctx, statusCode int, url string)`
|
|
||||||
|
|
||||||
Interrompt la requête et retourne une redirection HTTP au client.
|
|
||||||
|
|
||||||
Le code HTTP utilisé doit être supérieur ou égale à `300` et inférieur à `400` (non inclus).
|
|
||||||
|
|
||||||
#### Réponse
|
#### Réponse
|
||||||
|
|
||||||
_Pas de fonctions spécifiques._
|
_Pas de fonctions spécifiques._
|
||||||
|
@ -105,7 +72,7 @@ L'URL originale, avant réécriture du `Host` par Bouncer.
|
||||||
},
|
},
|
||||||
host: "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
host: "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
||||||
path: "string", // Chemin de l'URL (format assaini)
|
path: "string", // Chemin de l'URL (format assaini)
|
||||||
raw_path: "string", // Chemin de l'URL (format brut)
|
rawPath: "string", // Chemin de l'URL (format brut)
|
||||||
raw_query: "string", // Variables d'URL (format brut)
|
raw_query: "string", // Variables d'URL (format brut)
|
||||||
fragment : "string", // Fragment d'URL (format assaini)
|
fragment : "string", // Fragment d'URL (format assaini)
|
||||||
raw_fragment : "string" // Fragment d'URL (format brut)
|
raw_fragment : "string" // Fragment d'URL (format brut)
|
||||||
|
@ -129,7 +96,7 @@ La requête en cours de traitement.
|
||||||
},
|
},
|
||||||
host: "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
host: "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
||||||
path: "string", // Chemin de l'URL (format assaini)
|
path: "string", // Chemin de l'URL (format assaini)
|
||||||
raw_path: "string", // Chemin de l'URL (format brut)
|
rawPath: "string", // Chemin de l'URL (format brut)
|
||||||
raw_query: "string", // Variables d'URL (format brut)
|
raw_query: "string", // Variables d'URL (format brut)
|
||||||
fragment : "string", // Fragment d'URL (format assaini)
|
fragment : "string", // Fragment d'URL (format assaini)
|
||||||
raw_fragment : "string" // Fragment d'URL (format brut)
|
raw_fragment : "string" // Fragment d'URL (format brut)
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -1,8 +1,8 @@
|
||||||
module forge.cadoles.com/cadoles/bouncer
|
module forge.cadoles.com/cadoles/bouncer
|
||||||
|
|
||||||
go 1.23
|
go 1.22
|
||||||
|
|
||||||
toolchain go1.23.0
|
toolchain go1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20240626132607-e1db6466a926
|
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20240626132607-e1db6466a926
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
package rewriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/expr-lang/expr"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type errRedirect struct {
|
|
||||||
statusCode int
|
|
||||||
url string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errRedirect) StatusCode() int {
|
|
||||||
return e.statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errRedirect) URL() string {
|
|
||||||
return e.url
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errRedirect) Error() string {
|
|
||||||
return fmt.Sprintf("redirect %d %s", e.statusCode, e.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newErrRedirect(statusCode int, url string) *errRedirect {
|
|
||||||
return &errRedirect{
|
|
||||||
url: url,
|
|
||||||
statusCode: statusCode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ error = &errRedirect{}
|
|
||||||
|
|
||||||
func redirectFunc() expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"redirect",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
_, err := rule.Assert[context.Context](params[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
statusCode, err := rule.Assert[int](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusCode < 300 || statusCode >= 400 {
|
|
||||||
return nil, errors.Errorf("unexpected redirect status code '%d'", statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := rule.Assert[string](params[2])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, newErrRedirect(statusCode, url)
|
|
||||||
},
|
|
||||||
new(func(context.Context, int, string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithRewriterFuncs() rule.OptionFunc {
|
|
||||||
return func(opts *rule.Options) {
|
|
||||||
funcs := []expr.Option{
|
|
||||||
redirectFunc(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
|
||||||
opts.Expr = make([]expr.Option, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.Expr = append(opts.Expr, funcs...)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -46,12 +46,6 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.applyRequestRules(ctx, r, layer.Revision, options); err != nil {
|
if err := l.applyRequestRules(ctx, r, layer.Revision, options); err != nil {
|
||||||
var redirect *errRedirect
|
|
||||||
if errors.As(err, &redirect) {
|
|
||||||
http.Redirect(w, r, redirect.URL(), redirect.StatusCode())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err)))
|
logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err)))
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
@ -94,7 +88,6 @@ func New(funcs ...OptionFunc) *Layer {
|
||||||
engine, err := rule.NewEngine[*RequestVars](
|
engine, err := rule.NewEngine[*RequestVars](
|
||||||
rule.WithRules(options.Rules.Request...),
|
rule.WithRules(options.Rules.Request...),
|
||||||
ruleHTTP.WithRequestFuncs(),
|
ruleHTTP.WithRequestFuncs(),
|
||||||
WithRewriterFuncs(),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package rewriter
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||||
|
@ -28,26 +27,6 @@ type URLVar struct {
|
||||||
RawFragment string `expr:"raw_fragment"`
|
RawFragment string `expr:"raw_fragment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromURL(url *url.URL) URLVar {
|
|
||||||
return URLVar{
|
|
||||||
Scheme: url.Scheme,
|
|
||||||
Opaque: url.Opaque,
|
|
||||||
User: UserVar{
|
|
||||||
Username: url.User.Username(),
|
|
||||||
Password: func() string {
|
|
||||||
passwd, _ := url.User.Password()
|
|
||||||
return passwd
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
Host: url.Host,
|
|
||||||
Path: url.Path,
|
|
||||||
RawPath: url.RawPath,
|
|
||||||
RawQuery: url.RawQuery,
|
|
||||||
Fragment: url.Fragment,
|
|
||||||
RawFragment: url.RawFragment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserVar struct {
|
type UserVar struct {
|
||||||
Username string `expr:"username"`
|
Username string `expr:"username"`
|
||||||
Password string `expr:"password"`
|
Password string `expr:"password"`
|
||||||
|
@ -69,24 +48,6 @@ type RequestVar struct {
|
||||||
RequestURI string `expr:"request_uri"`
|
RequestURI string `expr:"request_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromRequest(r *http.Request) RequestVar {
|
|
||||||
return RequestVar{
|
|
||||||
Method: r.Method,
|
|
||||||
URL: fromURL(r.URL),
|
|
||||||
RawURL: r.URL.String(),
|
|
||||||
Proto: r.Proto,
|
|
||||||
ProtoMajor: r.ProtoMajor,
|
|
||||||
ProtoMinor: r.ProtoMinor,
|
|
||||||
Header: r.Header,
|
|
||||||
ContentLength: r.ContentLength,
|
|
||||||
TransferEncoding: r.TransferEncoding,
|
|
||||||
Host: r.Host,
|
|
||||||
Trailer: r.Trailer,
|
|
||||||
RemoteAddr: r.RemoteAddr,
|
|
||||||
RequestURI: r.RequestURI,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRevision int, options *LayerOptions) error {
|
func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRevision int, options *LayerOptions) error {
|
||||||
rules := options.Rules.Request
|
rules := options.Rules.Request
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
|
@ -104,8 +65,54 @@ func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRev
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := &RequestVars{
|
vars := &RequestVars{
|
||||||
OriginalURL: fromURL(originalURL),
|
OriginalURL: URLVar{
|
||||||
Request: fromRequest(r),
|
Scheme: originalURL.Scheme,
|
||||||
|
Opaque: originalURL.Opaque,
|
||||||
|
User: UserVar{
|
||||||
|
Username: originalURL.User.Username(),
|
||||||
|
Password: func() string {
|
||||||
|
passwd, _ := originalURL.User.Password()
|
||||||
|
return passwd
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
Host: originalURL.Host,
|
||||||
|
Path: originalURL.Path,
|
||||||
|
RawPath: originalURL.RawPath,
|
||||||
|
RawQuery: originalURL.RawQuery,
|
||||||
|
Fragment: originalURL.Fragment,
|
||||||
|
RawFragment: originalURL.RawFragment,
|
||||||
|
},
|
||||||
|
Request: RequestVar{
|
||||||
|
Method: r.Method,
|
||||||
|
URL: URLVar{
|
||||||
|
Scheme: r.URL.Scheme,
|
||||||
|
Opaque: r.URL.Opaque,
|
||||||
|
User: UserVar{
|
||||||
|
Username: r.URL.User.Username(),
|
||||||
|
Password: func() string {
|
||||||
|
passwd, _ := r.URL.User.Password()
|
||||||
|
return passwd
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
Host: r.URL.Host,
|
||||||
|
Path: r.URL.Path,
|
||||||
|
RawPath: r.URL.RawPath,
|
||||||
|
RawQuery: r.URL.RawQuery,
|
||||||
|
Fragment: r.URL.Fragment,
|
||||||
|
RawFragment: r.URL.RawFragment,
|
||||||
|
},
|
||||||
|
RawURL: r.URL.String(),
|
||||||
|
Proto: r.Proto,
|
||||||
|
ProtoMajor: r.ProtoMajor,
|
||||||
|
ProtoMinor: r.ProtoMinor,
|
||||||
|
Header: r.Header,
|
||||||
|
ContentLength: r.ContentLength,
|
||||||
|
TransferEncoding: r.TransferEncoding,
|
||||||
|
Host: r.Host,
|
||||||
|
Trailer: r.Trailer,
|
||||||
|
RemoteAddr: r.RemoteAddr,
|
||||||
|
RequestURI: r.RequestURI,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ruleHTTP.WithRequest(ctx, r)
|
ctx = ruleHTTP.WithRequest(ctx, r)
|
||||||
|
@ -162,8 +169,54 @@ func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerR
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := &ResponseVars{
|
vars := &ResponseVars{
|
||||||
OriginalURL: fromURL(originalURL),
|
OriginalURL: URLVar{
|
||||||
Request: fromRequest(r.Request),
|
Scheme: originalURL.Scheme,
|
||||||
|
Opaque: originalURL.Opaque,
|
||||||
|
User: UserVar{
|
||||||
|
Username: originalURL.User.Username(),
|
||||||
|
Password: func() string {
|
||||||
|
passwd, _ := originalURL.User.Password()
|
||||||
|
return passwd
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
Host: originalURL.Host,
|
||||||
|
Path: originalURL.Path,
|
||||||
|
RawPath: originalURL.RawPath,
|
||||||
|
RawQuery: originalURL.RawQuery,
|
||||||
|
Fragment: originalURL.Fragment,
|
||||||
|
RawFragment: originalURL.RawFragment,
|
||||||
|
},
|
||||||
|
Request: RequestVar{
|
||||||
|
Method: r.Request.Method,
|
||||||
|
URL: URLVar{
|
||||||
|
Scheme: r.Request.URL.Scheme,
|
||||||
|
Opaque: r.Request.URL.Opaque,
|
||||||
|
User: UserVar{
|
||||||
|
Username: r.Request.URL.User.Username(),
|
||||||
|
Password: func() string {
|
||||||
|
passwd, _ := r.Request.URL.User.Password()
|
||||||
|
return passwd
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
Host: r.Request.URL.Host,
|
||||||
|
Path: r.Request.URL.Path,
|
||||||
|
RawPath: r.Request.URL.RawPath,
|
||||||
|
RawQuery: r.Request.URL.RawQuery,
|
||||||
|
Fragment: r.Request.URL.Fragment,
|
||||||
|
RawFragment: r.Request.URL.RawFragment,
|
||||||
|
},
|
||||||
|
RawURL: r.Request.URL.String(),
|
||||||
|
Proto: r.Request.Proto,
|
||||||
|
ProtoMajor: r.Request.ProtoMajor,
|
||||||
|
ProtoMinor: r.Request.ProtoMinor,
|
||||||
|
Header: r.Request.Header,
|
||||||
|
ContentLength: r.Request.ContentLength,
|
||||||
|
TransferEncoding: r.Request.TransferEncoding,
|
||||||
|
Host: r.Request.Host,
|
||||||
|
Trailer: r.Request.Trailer,
|
||||||
|
RemoteAddr: r.Request.RemoteAddr,
|
||||||
|
RequestURI: r.Request.RequestURI,
|
||||||
|
},
|
||||||
Response: ResponseVar{
|
Response: ResponseVar{
|
||||||
Proto: r.Proto,
|
Proto: r.Proto,
|
||||||
ProtoMajor: r.ProtoMajor,
|
ProtoMajor: r.ProtoMajor,
|
||||||
|
@ -178,7 +231,6 @@ func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerR
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ruleHTTP.WithResponse(ctx, r)
|
ctx = ruleHTTP.WithResponse(ctx, r)
|
||||||
ctx = ruleHTTP.WithRequest(ctx, r.Request)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
if _, err := engine.Apply(ctx, vars); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
|
|
@ -22,10 +22,10 @@ func WithResponse(ctx context.Context, r *http.Response) context.Context {
|
||||||
return context.WithValue(ctx, contextKeyResponse, r)
|
return context.WithValue(ctx, contextKeyResponse, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CtxRequest(ctx context.Context) (*http.Request, bool) {
|
func ctxRequest(ctx context.Context) (*http.Request, bool) {
|
||||||
return rule.Context[*http.Request](ctx, contextKeyRequest)
|
return rule.Context[*http.Request](ctx, contextKeyRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CtxResponse(ctx context.Context) (*http.Response, bool) {
|
func ctxResponse(ctx context.Context) (*http.Response, bool) {
|
||||||
return rule.Context[*http.Response](ctx, contextKeyResponse)
|
return rule.Context[*http.Response](ctx, contextKeyResponse)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,6 @@ func WithRequestFuncs() rule.OptionFunc {
|
||||||
addRequestHeaderFunc(),
|
addRequestHeaderFunc(),
|
||||||
delRequestHeadersFunc(),
|
delRequestHeadersFunc(),
|
||||||
setRequestHostFunc(),
|
setRequestHostFunc(),
|
||||||
getRequestCookieFunc(),
|
|
||||||
addRequestCookieFunc(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
if len(opts.Expr) == 0 {
|
||||||
|
@ -31,8 +29,6 @@ func WithResponseFuncs() rule.OptionFunc {
|
||||||
setResponseHeaderFunc(),
|
setResponseHeaderFunc(),
|
||||||
addResponseHeaderFunc(),
|
addResponseHeaderFunc(),
|
||||||
delResponseHeadersFunc(),
|
delResponseHeadersFunc(),
|
||||||
addResponseCookieFunc(),
|
|
||||||
getResponseCookieFunc(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
if len(opts.Expr) == 0 {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package http
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -29,7 +28,7 @@ func setRequestHostFunc() expr.Option {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
r, ok := ctxRequest(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http request in context")
|
return nil, errors.New("could not find http request in context")
|
||||||
}
|
}
|
||||||
|
@ -61,7 +60,7 @@ func setRequestURLFunc() expr.Option {
|
||||||
return false, errors.WithStack(err)
|
return false, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
r, ok := ctxRequest(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http request in context")
|
return nil, errors.New("could not find http request in context")
|
||||||
}
|
}
|
||||||
|
@ -90,7 +89,7 @@ func addRequestHeaderFunc() expr.Option {
|
||||||
|
|
||||||
value := formatValue(params[2])
|
value := formatValue(params[2])
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
r, ok := ctxRequest(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http request in context")
|
return nil, errors.New("could not find http request in context")
|
||||||
}
|
}
|
||||||
|
@ -119,7 +118,7 @@ func setRequestHeaderFunc() expr.Option {
|
||||||
|
|
||||||
value := formatValue(params[2])
|
value := formatValue(params[2])
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
r, ok := ctxRequest(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http request in context")
|
return nil, errors.New("could not find http request in context")
|
||||||
}
|
}
|
||||||
|
@ -146,7 +145,7 @@ func delRequestHeadersFunc() expr.Option {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
r, ok := ctxRequest(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http request in context")
|
return nil, errors.New("could not find http request in context")
|
||||||
}
|
}
|
||||||
|
@ -168,145 +167,6 @@ func delRequestHeadersFunc() expr.Option {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CookieVar struct {
|
|
||||||
Name string `expr:"name"`
|
|
||||||
Value string `expr:"value"`
|
|
||||||
Path string `expr:"path"`
|
|
||||||
Domain string `expr:"domain"`
|
|
||||||
Expires time.Time `expr:"expires"`
|
|
||||||
MaxAge int `expr:"max_age"`
|
|
||||||
Secure bool `expr:"secure"`
|
|
||||||
HttpOnly bool `expr:"http_only"`
|
|
||||||
SameSite http.SameSite `expr:"same_site"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRequestCookieFunc() expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"get_cookie",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http request in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie, err := r.Cookie(name)
|
|
||||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cookie == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return CookieVar{
|
|
||||||
Name: cookie.Name,
|
|
||||||
Value: cookie.Value,
|
|
||||||
Path: cookie.Path,
|
|
||||||
Domain: cookie.Domain,
|
|
||||||
Expires: cookie.Expires,
|
|
||||||
MaxAge: cookie.MaxAge,
|
|
||||||
Secure: cookie.Secure,
|
|
||||||
HttpOnly: cookie.HttpOnly,
|
|
||||||
SameSite: cookie.SameSite,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
new(func(context.Context, string) CookieVar),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRequestCookieFunc() expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"add_cookie",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
values, err := rule.Assert[map[string]any](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie, err := cookieFrom(values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := CtxRequest(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http request in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.AddCookie(cookie)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(context.Context, map[string]any) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cookieFrom(values map[string]any) (*http.Cookie, error) {
|
|
||||||
cookie := &http.Cookie{}
|
|
||||||
|
|
||||||
if name, ok := values["name"].(string); ok {
|
|
||||||
cookie.Name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := values["value"].(string); ok {
|
|
||||||
cookie.Value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
if domain, ok := values["domain"].(string); ok {
|
|
||||||
cookie.Domain = domain
|
|
||||||
}
|
|
||||||
|
|
||||||
if path, ok := values["path"].(string); ok {
|
|
||||||
cookie.Path = path
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpOnly, ok := values["http_only"].(bool); ok {
|
|
||||||
cookie.HttpOnly = httpOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
if maxAge, ok := values["max_age"].(int); ok {
|
|
||||||
cookie.MaxAge = maxAge
|
|
||||||
}
|
|
||||||
|
|
||||||
if secure, ok := values["secure"].(bool); ok {
|
|
||||||
cookie.Secure = secure
|
|
||||||
}
|
|
||||||
|
|
||||||
if sameSite, ok := values["same_site"].(http.SameSite); ok {
|
|
||||||
cookie.SameSite = sameSite
|
|
||||||
} else if sameSite, ok := values["same_site"].(int); ok {
|
|
||||||
cookie.SameSite = http.SameSite(sameSite)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expires, ok := values["expires"].(time.Time); ok {
|
|
||||||
cookie.Expires = expires
|
|
||||||
} else if rawExpires, ok := values["expires"].(string); ok {
|
|
||||||
expires, err := time.Parse(http.TimeFormat, rawExpires)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie.Expires = expires
|
|
||||||
}
|
|
||||||
|
|
||||||
return cookie, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatValue(v any) string {
|
func formatValue(v any) string {
|
||||||
var value string
|
var value string
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -186,134 +185,6 @@ func TestDelRequestHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRequestCookie(t *testing.T) {
|
|
||||||
type TestCase struct {
|
|
||||||
Cookie map[string]any
|
|
||||||
Check func(t *testing.T, tc TestCase, req *http.Request)
|
|
||||||
ShouldFail bool
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []TestCase{
|
|
||||||
{
|
|
||||||
Cookie: map[string]any{
|
|
||||||
"name": "test",
|
|
||||||
},
|
|
||||||
Check: func(t *testing.T, tc TestCase, req *http.Request) {
|
|
||||||
cookie, err := req.Cookie(tc.Cookie["name"].(string))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%+v", errors.WithStack(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["name"], cookie.Name; e != g {
|
|
||||||
t.Errorf("cookie.Name: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Cookie: map[string]any{
|
|
||||||
"name": "foo",
|
|
||||||
"value": "test",
|
|
||||||
},
|
|
||||||
Check: func(t *testing.T, tc TestCase, req *http.Request) {
|
|
||||||
cookie, err := req.Cookie(tc.Cookie["name"].(string))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%+v", errors.WithStack(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["name"], cookie.Name; e != g {
|
|
||||||
t.Errorf("cookie.Name: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["value"], cookie.Value; e != g {
|
|
||||||
t.Errorf("cookie.Value: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("Case_%d", idx), func(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
NewCookie map[string]any `expr:"new_cookie"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(addRequestCookieFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
`add_cookie(ctx, vars.new_cookie)`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
NewCookie: tc.Cookie,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.ShouldFail {
|
|
||||||
t.Error("engine.Apply() should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.Check != nil {
|
|
||||||
tc.Check(t, tc, req)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRequestCookie(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
CookieName string `expr:"cookieName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(getRequestCookieFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"let cookie = get_cookie(ctx, vars.cookieName); cookie.value",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
CookieName: "foo",
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: vars.CookieName,
|
|
||||||
Value: "bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
req.AddCookie(cookie)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
results, err := engine.Apply(ctx, vars)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := cookie.Value, results[0]; e != g {
|
|
||||||
t.Errorf("result[0]: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRuleEngine[V any](t *testing.T, funcs ...rule.OptionFunc) *rule.Engine[V] {
|
func createRuleEngine[V any](t *testing.T, funcs ...rule.OptionFunc) *rule.Engine[V] {
|
||||||
engine, err := rule.NewEngine[V](funcs...)
|
engine, err := rule.NewEngine[V](funcs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package http
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -42,7 +41,7 @@ func addResponseHeaderFunc() expr.Option {
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
value = fmt.Sprintf("%v", rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := CtxResponse(ctx)
|
r, ok := ctxResponse(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http response in context")
|
return nil, errors.New("could not find http response in context")
|
||||||
}
|
}
|
||||||
|
@ -83,7 +82,7 @@ func setResponseHeaderFunc() expr.Option {
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
value = fmt.Sprintf("%v", rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := CtxResponse(ctx)
|
r, ok := ctxResponse(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http response in context")
|
return nil, errors.New("could not find http response in context")
|
||||||
}
|
}
|
||||||
|
@ -110,7 +109,7 @@ func delResponseHeadersFunc() expr.Option {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := CtxResponse(ctx)
|
r, ok := ctxResponse(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not find http response in context")
|
return nil, errors.New("could not find http response in context")
|
||||||
}
|
}
|
||||||
|
@ -131,84 +130,3 @@ func delResponseHeadersFunc() expr.Option {
|
||||||
new(func(context.Context, string) bool),
|
new(func(context.Context, string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addResponseCookieFunc() expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"add_cookie",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
values, err := rule.Assert[map[string]any](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie, err := cookieFrom(values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := CtxResponse(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http request in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Add("Set-Cookie", cookie.String())
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(context.Context, map[string]any) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getResponseCookieFunc() expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"get_cookie",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, ok := CtxResponse(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http response in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookie *http.Cookie
|
|
||||||
for _, c := range res.Cookies() {
|
|
||||||
if c.Name != name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if cookie == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return CookieVar{
|
|
||||||
Name: cookie.Name,
|
|
||||||
Value: cookie.Value,
|
|
||||||
Path: cookie.Path,
|
|
||||||
Domain: cookie.Domain,
|
|
||||||
Expires: cookie.Expires,
|
|
||||||
MaxAge: cookie.MaxAge,
|
|
||||||
Secure: cookie.Secure,
|
|
||||||
HttpOnly: cookie.HttpOnly,
|
|
||||||
SameSite: cookie.SameSite,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
new(func(context.Context, string) CookieVar),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,11 +2,9 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -126,182 +124,6 @@ func TestResponseDelHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddResponseCookie(t *testing.T) {
|
|
||||||
type TestCase struct {
|
|
||||||
Cookie map[string]any
|
|
||||||
Check func(t *testing.T, tc TestCase, res *http.Response)
|
|
||||||
ShouldFail bool
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []TestCase{
|
|
||||||
{
|
|
||||||
Cookie: map[string]any{
|
|
||||||
"name": "foo",
|
|
||||||
"value": "test",
|
|
||||||
"domain": "example.net",
|
|
||||||
"path": "/custom",
|
|
||||||
"same_site": http.SameSiteStrictMode,
|
|
||||||
"http_only": true,
|
|
||||||
"secure": false,
|
|
||||||
"expires": time.Now().UTC().Truncate(time.Second),
|
|
||||||
},
|
|
||||||
Check: func(t *testing.T, tc TestCase, res *http.Response) {
|
|
||||||
var cookie *http.Cookie
|
|
||||||
for _, c := range res.Cookies() {
|
|
||||||
if c.Name == tc.Cookie["name"] {
|
|
||||||
cookie = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cookie == nil {
|
|
||||||
t.Errorf("could not find cookie '%s'", tc.Cookie["name"])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["name"], cookie.Name; e != g {
|
|
||||||
t.Errorf("cookie.Name: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["value"], cookie.Value; e != g {
|
|
||||||
t.Errorf("cookie.Value: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["domain"], cookie.Domain; e != g {
|
|
||||||
t.Errorf("cookie.Domain: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["path"], cookie.Path; e != g {
|
|
||||||
t.Errorf("cookie.Path: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["secure"], cookie.Secure; e != g {
|
|
||||||
t.Errorf("cookie.Secure: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["http_only"], cookie.HttpOnly; e != g {
|
|
||||||
t.Errorf("cookie.HttpOnly: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["same_site"], cookie.SameSite; e != g {
|
|
||||||
t.Errorf("cookie.SameSite: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["expires"], cookie.Expires; e != g {
|
|
||||||
t.Errorf("cookie.Expires: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Cookie: map[string]any{
|
|
||||||
"name": "foo",
|
|
||||||
"expires": time.Now().UTC().Format(http.TimeFormat),
|
|
||||||
},
|
|
||||||
Check: func(t *testing.T, tc TestCase, res *http.Response) {
|
|
||||||
var cookie *http.Cookie
|
|
||||||
for _, c := range res.Cookies() {
|
|
||||||
if c.Name == tc.Cookie["name"] {
|
|
||||||
cookie = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cookie == nil {
|
|
||||||
t.Errorf("could not find cookie '%s'", tc.Cookie["name"])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := tc.Cookie["expires"], cookie.Expires.Format(http.TimeFormat); e != g {
|
|
||||||
t.Errorf("cookie.Expires: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("Case_%d", idx), func(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
NewCookie map[string]any `expr:"new_cookie"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(addResponseCookieFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
`add_cookie(ctx, vars.new_cookie)`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := createResponse(req, http.StatusOK, nil)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
NewCookie: tc.Cookie,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
ctx = WithResponse(ctx, resp)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.ShouldFail {
|
|
||||||
t.Error("engine.Apply() should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.Check != nil {
|
|
||||||
tc.Check(t, tc, resp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetResponseCookie(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
CookieName string `expr:"cookieName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(getResponseCookieFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"let cookie = get_cookie(ctx, vars.cookieName); cookie.value",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := createResponse(req, http.StatusOK, nil)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
CookieName: "foo",
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: vars.CookieName,
|
|
||||||
Value: "bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Header.Add("Set-Cookie", cookie.String())
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithResponse(ctx, resp)
|
|
||||||
|
|
||||||
results, err := engine.Apply(ctx, vars)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := cookie.Value, results[0]; e != g {
|
|
||||||
t.Errorf("result[0]: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createResponse(req *http.Request, statusCode int, body io.Reader) *http.Response {
|
func createResponse(req *http.Request, statusCode int, body io.Reader) *http.Response {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
Status: http.StatusText(statusCode),
|
Status: http.StatusText(statusCode),
|
||||||
|
|
Loading…
Reference in New Issue