Compare commits
No commits in common. "114608931bebcdd28bc11758c4a5485edf914802" and "9d902a749400494ff181044768c739c7a900ef94" have entirely different histories.
114608931b
...
9d902a7494
|
@ -4,4 +4,3 @@ Vous trouverez ci-dessous la liste des entités "Layer" activables sur vos entit
|
||||||
|
|
||||||
- [Authn (`authn-*`)](./authn/README.md) - Authentification des accès (SSO)
|
- [Authn (`authn-*`)](./authn/README.md) - Authentification des accès (SSO)
|
||||||
- [Queue](./queue.md) - File d'attente dynamique
|
- [Queue](./queue.md) - File d'attente dynamique
|
||||||
- [Rewriter](./rewriter.md) - Réécriture dynamiques des attributs des requêtes/réponses
|
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
# Layer "Rewriter"
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Ce layer permet de modifier dynamiquement certains attributs de requêtes/réponses transitant par le proxy.
|
|
||||||
|
|
||||||
## Type
|
|
||||||
|
|
||||||
`rewriter`
|
|
||||||
|
|
||||||
## Schéma des options
|
|
||||||
|
|
||||||
Les options disponibles pour le layer sont décrites via un [schéma JSON](https://json-schema.org/specification). Elles sont documentées dans le [schéma visible ici](../../../../internal/proxy/director/layer/rewriter/layer-options.json).
|
|
||||||
|
|
||||||
## Moteur de règles
|
|
||||||
|
|
||||||
Les options `rules.request` et `rules.response` permettent de définir des listes de règles utilisant un DSL modifiant de manière dynamique les attributs des requêtes/réponses transitant par le proxy.
|
|
||||||
|
|
||||||
Les listes d'instructions sont exécutées séquentiellement.
|
|
||||||
|
|
||||||
Bouncer utilise le projet [`expr`](https://expr-lang.org/) comme DSL. En plus des fonctionnalités natives du langage, Bouncer ajoute un certain nombre de fonctions spécifiques au contexte d'utilisation.
|
|
||||||
|
|
||||||
### Fonctions
|
|
||||||
|
|
||||||
#### Communes
|
|
||||||
|
|
||||||
##### `add_header(name string, value string)`
|
|
||||||
|
|
||||||
Ajouter une valeur à un entête HTTP via son nom `name` et sa valeur `value`.
|
|
||||||
|
|
||||||
##### `set_header(name string, value string)`
|
|
||||||
|
|
||||||
Définir la valeur d'un entête HTTP via son nom `name` et sa valeur `value`. La valeur précédente est écrasée.
|
|
||||||
|
|
||||||
##### `del_headers(pattern string)`
|
|
||||||
|
|
||||||
Supprimer un ou plusieurs entêtes HTTP dont le nom correspond au patron `pattern`.
|
|
||||||
|
|
||||||
Le patron est défini par une chaîne comprenant un ou plusieurs caractères `*`, signifiant un ou plusieurs caractères arbitraires.
|
|
||||||
|
|
||||||
#### Requête
|
|
||||||
|
|
||||||
##### `set_host(host string)`
|
|
||||||
|
|
||||||
Modifier la valeur de l'entête `Host` de la requête.
|
|
||||||
|
|
||||||
##### `set_url(url string)`
|
|
||||||
|
|
||||||
Modifier l'URL du serveur cible.
|
|
||||||
|
|
||||||
#### Réponse
|
|
||||||
|
|
||||||
_Pas de fonctions spécifiques._
|
|
||||||
|
|
||||||
### Environnement
|
|
||||||
|
|
||||||
Les règles ont accès aux variables suivantes pendant leur exécution. **Ces données sont en lecture seule.**
|
|
||||||
|
|
||||||
#### Requête
|
|
||||||
|
|
||||||
##### `request`
|
|
||||||
|
|
||||||
La requête en cours de traitement.
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
method: "string", // Méthode HTTP
|
|
||||||
host: "string", // Nom d'hôte (`Host`) associé à la requête
|
|
||||||
url: "string", // URL associée à la requête
|
|
||||||
proto: "string", // Numéro de version du protocole utilisé
|
|
||||||
protoMajor: "int", // Numéro de version majeure du protocole utilisé
|
|
||||||
protoMinor: "int", // Numéro de version mineur du protocole utilisé
|
|
||||||
header: { // Table associative des entêtes HTTP associés à la requête
|
|
||||||
"string": ["string"]
|
|
||||||
},
|
|
||||||
contentLength: "int", // Taille du corps de la requête
|
|
||||||
transferEncoding: ["string"], // MIME-Type(s) d'encodage du corps de la requête
|
|
||||||
trailer: { // Table associative des entêtes HTTP associés à la requête, transmises après le corps de la requête
|
|
||||||
"string": ["string"]
|
|
||||||
},
|
|
||||||
remoteAddr: "string", // Adresse du client HTTP à l'origine de la requête
|
|
||||||
requestUri: "string" // URL "brute" associée à la requêtes (avant opérations d'assainissement, utiliser "url" plutôt)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Réponse
|
|
||||||
|
|
||||||
##### `response`
|
|
||||||
|
|
||||||
La réponse en cours de traitement.
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
statusCode: "int", // Code de statut de la réponse
|
|
||||||
status: "string", // Message associé au code de statut
|
|
||||||
proto: "string", // Numéro de version du protocole utilisé
|
|
||||||
protoMajor: "int", // Numéro de version majeure du protocole utilisé
|
|
||||||
protoMinor: "int", // Numéro de version mineur du protocole utilisé
|
|
||||||
header: { // Table associative des entêtes HTTP associés à la requête
|
|
||||||
"string": ["string"]
|
|
||||||
},
|
|
||||||
contentLength: "int", // Taille du corps de la réponse
|
|
||||||
transferEncoding: ["string"], // MIME-Type(s) d'encodage du corps de la requête
|
|
||||||
trailer: { // Table associative des entêtes HTTP associés à la requête, transmises après le corps de la requête
|
|
||||||
"string": ["string"]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `request`
|
|
||||||
|
|
||||||
_Voir section précédente._
|
|
||||||
|
|
||||||
## Métriques
|
|
||||||
|
|
||||||
_Pas de métriques spécifiques._
|
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"rules": {
|
|
||||||
"title": "Règles appliquées aux requêtes/réponses transitant par le proxy",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"request": {
|
|
||||||
"title": "Règles appliquées aux requêtes transitant par le proxy",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"response": {
|
|
||||||
"title": "Règles appliquées aux réponses transitant par le proxy",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"matchURLs": {
|
|
||||||
"title": "Liste de filtrage des URLs sur lesquelles le layer est actif",
|
|
||||||
"description": "Par exemple, si vous souhaitez limiter votre layer à l'ensemble d'une section '`/blog`' d'un site, vous pouvez déclarer la valeur `['*/blog*']`. Les autres URLs du site ne seront pas affectées par ce layer.",
|
|
||||||
"default": [
|
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package rewriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
proxy "forge.cadoles.com/Cadoles/go-proxy"
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
const LayerType store.LayerType = "rewriter"
|
|
||||||
|
|
||||||
type Layer struct{}
|
|
||||||
|
|
||||||
func (l *Layer) LayerType() store.LayerType {
|
|
||||||
return LayerType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
options, err := fromStoreOptions(layer.Options)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err)))
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := wildcard.MatchAny(r.URL.String(), options.MatchURLs...)
|
|
||||||
if !matches {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.applyRequestRules(r, options); err != nil {
|
|
||||||
logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err)))
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.HandlerFunc(fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseTransformer implements director.ResponseTransformerLayer.
|
|
||||||
func (l *Layer) ResponseTransformer(layer *store.Layer) proxy.ResponseTransformer {
|
|
||||||
return func(r *http.Response) error {
|
|
||||||
options, err := fromStoreOptions(layer.Options)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := wildcard.MatchAny(r.Request.URL.String(), options.MatchURLs...)
|
|
||||||
if !matches {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.applyResponseRules(r, options); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *Layer {
|
|
||||||
return &Layer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ director.MiddlewareLayer = &Layer{}
|
|
||||||
_ director.ResponseTransformerLayer = &Layer{}
|
|
||||||
)
|
|
|
@ -1,56 +0,0 @@
|
||||||
package rewriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LayerOptions struct {
|
|
||||||
MatchURLs []string `mapstructure:"matchURLs"`
|
|
||||||
Rules Rules `mapstructure:"rules"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rules struct {
|
|
||||||
Request []string `mapstructure:"request"`
|
|
||||||
Response []string `mapstructure:"response"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultLayerOptions() LayerOptions {
|
|
||||||
return LayerOptions{
|
|
||||||
MatchURLs: []string{"*"},
|
|
||||||
Rules: Rules{
|
|
||||||
Request: []string{},
|
|
||||||
Response: []string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromStoreOptions(storeOptions store.LayerOptions) (*LayerOptions, error) {
|
|
||||||
layerOptions := DefaultLayerOptions()
|
|
||||||
|
|
||||||
if err := FromStoreOptions(storeOptions, &layerOptions); err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &layerOptions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromStoreOptions(storeOptions store.LayerOptions, dest any) error {
|
|
||||||
config := mapstructure.DecoderConfig{
|
|
||||||
Result: dest,
|
|
||||||
ZeroFields: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(&config)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := decoder.Decode(storeOptions); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package rewriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestEnv struct {
|
|
||||||
Request RequestInfo `expr:"request"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestInfo struct {
|
|
||||||
Method string `expr:"method"`
|
|
||||||
URL string `expr:"url"`
|
|
||||||
Proto string `expr:"proto"`
|
|
||||||
ProtoMajor int `expr:"protoMajor"`
|
|
||||||
ProtoMinor int `expr:"protoMinor"`
|
|
||||||
Header map[string][]string `expr:"header"`
|
|
||||||
ContentLength int64 `expr:"contentLength"`
|
|
||||||
TransferEncoding []string `expr:"transferEncoding"`
|
|
||||||
Host string `expr:"host"`
|
|
||||||
Trailer map[string][]string `expr:"trailer"`
|
|
||||||
RemoteAddr string `expr:"remoteAddr"`
|
|
||||||
RequestURI string `expr:"requestUri"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Layer) applyRequestRules(r *http.Request, options *LayerOptions) error {
|
|
||||||
rules := options.Rules.Request
|
|
||||||
if len(rules) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
engine, err := rule.NewEngine[*RequestEnv](
|
|
||||||
ruleHTTP.WithRequestFuncs(r),
|
|
||||||
rule.WithRules(options.Rules.Request...),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
env := &RequestEnv{
|
|
||||||
Request: RequestInfo{
|
|
||||||
Method: r.Method,
|
|
||||||
URL: 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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := engine.Apply(env); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseEnv struct {
|
|
||||||
Request RequestInfo `expr:"request"`
|
|
||||||
Response ResponseInfo `expr:"response"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseInfo struct {
|
|
||||||
Status string `expr:"status"`
|
|
||||||
StatusCode int `expr:"statusCode"`
|
|
||||||
Proto string `expr:"proto"`
|
|
||||||
ProtoMajor int `expr:"protoMajor"`
|
|
||||||
ProtoMinor int `expr:"protoMinor"`
|
|
||||||
Header map[string][]string `expr:"header"`
|
|
||||||
ContentLength int64 `expr:"contentLength"`
|
|
||||||
TransferEncoding []string `expr:"transferEncoding"`
|
|
||||||
Uncompressed bool `expr:"uncompressed"`
|
|
||||||
Trailer map[string][]string `expr:"trailer"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Layer) applyResponseRules(r *http.Response, options *LayerOptions) error {
|
|
||||||
rules := options.Rules.Request
|
|
||||||
if len(rules) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
engine, err := rule.NewEngine[*ResponseEnv](
|
|
||||||
rule.WithRules(options.Rules.Response...),
|
|
||||||
ruleHTTP.WithResponseFuncs(r),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
env := &ResponseEnv{
|
|
||||||
Request: RequestInfo{
|
|
||||||
Method: r.Request.Method,
|
|
||||||
URL: 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: ResponseInfo{
|
|
||||||
Proto: r.Proto,
|
|
||||||
ProtoMajor: r.ProtoMajor,
|
|
||||||
ProtoMinor: r.ProtoMinor,
|
|
||||||
Header: r.Header,
|
|
||||||
ContentLength: r.ContentLength,
|
|
||||||
TransferEncoding: r.TransferEncoding,
|
|
||||||
Trailer: r.Trailer,
|
|
||||||
Status: r.Status,
|
|
||||||
StatusCode: r.StatusCode,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := engine.Apply(env); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package rewriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed layer-options.json
|
|
||||||
var RawLayerOptionsSchema []byte
|
|
|
@ -1,45 +0,0 @@
|
||||||
package rule
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/expr-lang/expr"
|
|
||||||
"github.com/expr-lang/expr/vm"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Engine[E any] struct {
|
|
||||||
rules []*vm.Program
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Engine[E]) Apply(env E) ([]any, error) {
|
|
||||||
results := make([]any, 0, len(e.rules))
|
|
||||||
for i, r := range e.rules {
|
|
||||||
result, err := expr.Run(r, env)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "could not run rule #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEngine[E any](funcs ...OptionFunc) (*Engine[E], error) {
|
|
||||||
opts := NewOptions(funcs...)
|
|
||||||
|
|
||||||
engine := &Engine[E]{
|
|
||||||
rules: make([]*vm.Program, 0, len(opts.Rules)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, r := range opts.Rules {
|
|
||||||
|
|
||||||
program, err := expr.Compile(r, opts.Expr...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "could not compile rule #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.rules = append(engine.rules, program)
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine, nil
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/expr-lang/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WithRequestFuncs(r *http.Request) rule.OptionFunc {
|
|
||||||
return func(opts *rule.Options) {
|
|
||||||
funcs := []expr.Option{
|
|
||||||
setRequestURL(r),
|
|
||||||
setRequestHeaderFunc(r),
|
|
||||||
addRequestHeaderFunc(r),
|
|
||||||
delRequestHeadersFunc(r),
|
|
||||||
setRequestHostFunc(r),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
|
||||||
opts.Expr = make([]expr.Option, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.Expr = append(opts.Expr, funcs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithResponseFuncs(r *http.Response) rule.OptionFunc {
|
|
||||||
return func(opts *rule.Options) {
|
|
||||||
funcs := []expr.Option{
|
|
||||||
setResponseHeaderFunc(r),
|
|
||||||
addResponseHeaderFunc(r),
|
|
||||||
delResponseHeadersFunc(r),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
|
||||||
opts.Expr = make([]expr.Option, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.Expr = append(opts.Expr, funcs...)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
|
||||||
"github.com/expr-lang/expr"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setRequestHostFunc(r *http.Request) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"set_host",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
host := params[0].(string)
|
|
||||||
r.Host = host
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRequestURL(r *http.Request) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"set_url",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
rawURL := params[0].(string)
|
|
||||||
|
|
||||||
url, err := url.Parse(rawURL)
|
|
||||||
if err != nil {
|
|
||||||
return false, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.URL = url
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRequestHeaderFunc(r *http.Request) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"add_header",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
name := params[0].(string)
|
|
||||||
rawValue := params[1]
|
|
||||||
|
|
||||||
var value string
|
|
||||||
switch v := rawValue.(type) {
|
|
||||||
case []string:
|
|
||||||
value = strings.Join(v, ",")
|
|
||||||
case time.Time:
|
|
||||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
|
||||||
case time.Duration:
|
|
||||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
|
||||||
default:
|
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Add(name, value)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(string, string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRequestHeaderFunc(r *http.Request) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"set_header",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
name := params[0].(string)
|
|
||||||
rawValue := params[1]
|
|
||||||
|
|
||||||
var value string
|
|
||||||
switch v := rawValue.(type) {
|
|
||||||
case []string:
|
|
||||||
value = strings.Join(v, ",")
|
|
||||||
case time.Time:
|
|
||||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
|
||||||
case time.Duration:
|
|
||||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
|
||||||
default:
|
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Set(name, value)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(string, string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func delRequestHeadersFunc(r *http.Request) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"del_headers",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
pattern := params[0].(string)
|
|
||||||
deleted := false
|
|
||||||
|
|
||||||
for key := range r.Header {
|
|
||||||
if !wildcard.Match(key, pattern) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Del(key)
|
|
||||||
deleted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return deleted, nil
|
|
||||||
},
|
|
||||||
new(func(string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
|
||||||
"github.com/expr-lang/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addResponseHeaderFunc(r *http.Response) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"add_header",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
name := params[0].(string)
|
|
||||||
rawValue := params[1]
|
|
||||||
|
|
||||||
var value string
|
|
||||||
switch v := rawValue.(type) {
|
|
||||||
case []string:
|
|
||||||
value = strings.Join(v, ",")
|
|
||||||
case time.Time:
|
|
||||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
|
||||||
case time.Duration:
|
|
||||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
|
||||||
default:
|
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Add(name, value)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(string, string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setResponseHeaderFunc(r *http.Response) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"set_header",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
name := params[0].(string)
|
|
||||||
rawValue := params[1]
|
|
||||||
|
|
||||||
var value string
|
|
||||||
switch v := rawValue.(type) {
|
|
||||||
case []string:
|
|
||||||
value = strings.Join(v, ",")
|
|
||||||
case time.Time:
|
|
||||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
|
||||||
case time.Duration:
|
|
||||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
|
||||||
default:
|
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Set(name, value)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
},
|
|
||||||
new(func(string, string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func delResponseHeadersFunc(r *http.Response) expr.Option {
|
|
||||||
return expr.Function(
|
|
||||||
"del_headers",
|
|
||||||
func(params ...any) (any, error) {
|
|
||||||
pattern := params[0].(string)
|
|
||||||
deleted := false
|
|
||||||
|
|
||||||
for key := range r.Header {
|
|
||||||
if !wildcard.Match(key, pattern) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Del(key)
|
|
||||||
deleted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return deleted, nil
|
|
||||||
},
|
|
||||||
new(func(string) bool),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package rule
|
|
||||||
|
|
||||||
import "github.com/expr-lang/expr"
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Rules []string
|
|
||||||
Expr []expr.Option
|
|
||||||
}
|
|
||||||
|
|
||||||
type OptionFunc func(opts *Options)
|
|
||||||
|
|
||||||
func NewOptions(funcs ...OptionFunc) *Options {
|
|
||||||
opts := &Options{
|
|
||||||
Expr: make([]expr.Option, 0),
|
|
||||||
Rules: make([]string, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range funcs {
|
|
||||||
fn(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithRules(rules ...string) OptionFunc {
|
|
||||||
return func(opts *Options) {
|
|
||||||
opts.Rules = rules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithExpr(options ...expr.Option) OptionFunc {
|
|
||||||
return func(opts *Options) {
|
|
||||||
opts.Expr = options
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package setup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/rewriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterLayer(rewriter.LayerType, setupRewriterLayer, rewriter.RawLayerOptionsSchema)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRewriterLayer(conf *config.Config) (director.Layer, error) {
|
|
||||||
return rewriter.New(), nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue