Skip to content

Commit

Permalink
Merge pull request #73 from thslopes/master
Browse files Browse the repository at this point in the history
feat: tenant by host middleware
  • Loading branch information
rodrigodc07 authored Jun 1, 2023
2 parents 5556b0f + f01a0e4 commit 36cffd0
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ testdata/

restql.yml

.DS_Store
.DS_Store
.vscode/
19 changes: 19 additions & 0 deletions docs/restql/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ You can use the `pprof` tool to investigate restQL performance. To enable it set
- Request ID: this middleware generates a unique id for each request restQL API receives. The `http.server.middlewares.requestId.header` field define the header name use to return the generated id. The `http.server.middlewares.requestId.strategy` defines how the id will be generated and can be either `base64` or `uuid`.
- Timeout: this middleware limits the maximum time any request can take. The `http.server.middlewares.timeout.duration` field accept a time duration value.
- Request Cancellation: this middleware stops query execution when the client drops the connection. This improves fault response as it avoids unnecessary computation and reduces traffic on downstream APIs. You can also manage the connection watching interval with the field `http.server.middlewares.requestCancellation.watchingInterval`, which accepts a duration string.
- Tenant By Host (From version 6.7.0): this middleware allows to configure default tenants by host.
You can configure your own tenants via the configuration file:
```yaml
http:
server:
middlewares:
tenantByHost:
enable: true
defaultTenant: acom-npf
tenantsByHost:
"americanas.teste": acom-npf
"localhost": dev
```
Or via environment variables:
```shell script
RESTQL_TENANT_BY_HOST_ENABLED=${enable}
RESTQL_TENANT_BY_HOST_DEFAULT_TENANT=${default_tenant}
RESTQL_TENANT_BY_HOST_MAP=${host_tenant_pairs}
```
- CORS: Cross-Origin Resource Sharing is a specification that enables truly open access across domain-boundaries.
You can configure your own CORS headers either via the configuration file:
```yaml
Expand Down
12 changes: 10 additions & 2 deletions internal/platform/conf/conf.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package conf

import (
"github.com/caarlos0/env/v6"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"

"github.com/caarlos0/env/v6"
"gopkg.in/yaml.v2"
)

const configFileName = "restql.yml"
Expand Down Expand Up @@ -39,6 +40,12 @@ type requestCancellationConf struct {
WatchInterval time.Duration `yaml:"watchInterval"`
}

type tenantByHostConf struct {
Enable bool `yaml:"enable" env:"RESTQL_TENANT_BY_HOST_ENABLED"`
DefaultTenant string `yaml:"defaultTenant" env:"RESTQL_TENANT_BY_HOST_DEFAULT_TENANT"`
TenantsByHost map[string]string `yaml:"tenantsByHost" env:"RESTQL_TENANT_BY_HOST_MAP"`
}

// Config represents all parameters allowed in restQL runtime.
type Config struct {
HTTP struct {
Expand Down Expand Up @@ -67,6 +74,7 @@ type Config struct {
Timeout timeoutConf `yaml:"timeout"`
Cors corsConf `yaml:"cors"`
RequestCancellation requestCancellationConf `yaml:"requestCancellation"`
TenantByHost tenantByHostConf `yaml:"tenantByHost"`
} `yaml:"middlewares"`
} `yaml:"server"`

Expand Down
6 changes: 6 additions & 0 deletions internal/platform/web/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package middleware

import (
"fmt"

"github.com/b2wdigital/restQL-golang/v6/internal/platform/conf"
"github.com/b2wdigital/restQL-golang/v6/internal/platform/plugins"
"github.com/b2wdigital/restQL-golang/v6/pkg/restql"
Expand Down Expand Up @@ -69,6 +70,11 @@ func (d *Decorator) fetchEnabled() []Middleware {
mws = append(mws, newRequestID(mwCfg.RequestID.Header, mwCfg.RequestID.Strategy, d.log))
}

if mwCfg.TenantByHost.Enable {
d.log.Info("url tenant middleware enabled")
mws = append(mws, newTenantByHost(d.log, mwCfg.TenantByHost.DefaultTenant, mwCfg.TenantByHost.TenantsByHost))
}

if mwCfg.Cors.Enable {
cors := newCors(d.log, corsOptions{
AllowedOrigins: mwCfg.Cors.AllowOrigin,
Expand Down
48 changes: 48 additions & 0 deletions internal/platform/web/middleware/tenant_by_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package middleware

import (
"strings"

"github.com/b2wdigital/restQL-golang/v6/pkg/restql"

"github.com/valyala/fasthttp"
)

type tenantByHost struct {
log restql.Logger
tenantsByHost map[string]string
defaultTenant string
}

func newTenantByHost(log restql.Logger, defaultTenant string, tenantsByHost map[string]string) Middleware {
return tenantByHost{
log: log,
tenantsByHost: tenantsByHost,
defaultTenant: defaultTenant,
}
}

func (r tenantByHost) Apply(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
r.setTenant(ctx)
h(ctx)
}
}

func (u tenantByHost) setTenant(ctx *fasthttp.RequestCtx) {
if string(ctx.QueryArgs().Peek("tenant")) != "" {
u.log.Debug("tenant already set", "tenant", string(ctx.QueryArgs().Peek("tenant")))
return
}

for k, v := range u.tenantsByHost {
if strings.Contains(string(ctx.Request.Host()), k) {
u.log.Debug("setting tenant", "tenant", v, "host", string(ctx.Request.Host()))
ctx.QueryArgs().Set("tenant", v)
return
}
}
u.log.Debug("setting default tenant", "tenant", u.defaultTenant, "host", string(ctx.Request.Host()))
ctx.QueryArgs().Set("tenant", u.defaultTenant)

}
87 changes: 87 additions & 0 deletions internal/platform/web/middleware/tenant_by_host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package middleware

import (
"testing"

"github.com/b2wdigital/restQL-golang/v6/pkg/restql"
"github.com/valyala/fasthttp"
)

func Test_tenantByHost_setTenant(t *testing.T) {
type fields struct {
log restql.Logger
tenantsByHosts map[string]string
defaultTenant string
}
tests := []struct {
name string
fields fields
host string
tenant string
wantTenant string
}{
{
name: "should set tenant",
fields: fields{
log: noOpLogger{},
tenantsByHosts: map[string]string{
"americanas.test": "acom-npf",
},
defaultTenant: "default-tenant",
},
host: "americanas.test",
wantTenant: "acom-npf",
},
{
name: "should not set tenant if already set",
fields: fields{
log: noOpLogger{},
tenantsByHosts: map[string]string{
"americanas.test": "acom-npf",
},
defaultTenant: "default-tenant",
},
tenant: "previous-set-tenant",
host: "americanas.test",
wantTenant: "previous-set-tenant",
},
{
name: "should set default tenant",
fields: fields{
log: noOpLogger{},
tenantsByHosts: map[string]string{
"americanas.test": "acom-npf",
},
defaultTenant: "default-tenant",
},
host: "unknown.test",
wantTenant: "default-tenant",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := tenantByHost{
log: tt.fields.log,
tenantsByHost: tt.fields.tenantsByHosts,
defaultTenant: tt.fields.defaultTenant,
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost(tt.host)
ctx.QueryArgs().Add("tenant", tt.tenant)
u.setTenant(ctx)
if got := string(ctx.QueryArgs().Peek("tenant")); got != tt.wantTenant {
t.Errorf("tenantByHost.setTenant() = %v, want %v", got, tt.wantTenant)
}
})
}
}

type noOpLogger struct{}

func (n noOpLogger) Panic(msg string, fields ...interface{}) {}
func (n noOpLogger) Fatal(msg string, fields ...interface{}) {}
func (n noOpLogger) Error(msg string, err error, fields ...interface{}) {}
func (n noOpLogger) Warn(msg string, fields ...interface{}) {}
func (n noOpLogger) Info(msg string, fields ...interface{}) {}
func (n noOpLogger) Debug(msg string, fields ...interface{}) {}
func (n noOpLogger) With(key string, value interface{}) restql.Logger { return n }

0 comments on commit 36cffd0

Please sign in to comment.