Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.dgate
cov.out
go.work.sum
.env
.env
coverage.txt
17 changes: 5 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ http://dgate.io/docs/getting-started

```bash
# requires go 1.22+

# install dgate-server
go install github.com/dgate-io/dgate-api/cmd/dgate-server@latest

# install dgate-cli
go install github.com/dgate-io/dgate-api/cmd/dgate-cli@latest
```

## Application Architecture
Expand All @@ -33,15 +38,3 @@ DGate Server is proxy and admin server bundled into one. the admin server is res
### DGate CLI (dgate-cli)

DGate CLI is a command-line interface that can be used to interact with the DGate Server. It can be used to deploy modules, manage the state of the cluster, and more.

#### Proxy Modules

- Fetch Upstream Module (`fetchUpstream`) - executed before the request is sent to the upstream server. This module is used to decided which upstream server to send the current request to. (Essentially a custom load balancer module)

- Request Modifier Module (`requestModifier`) - executed before the request is sent to the upstream server. This module is used to modify the request before it is sent to the upstream server.

- Response Modifier Module (`responseModifier`) - executed after the response is received from the upstream server. This module is used to modify the response before it is sent to the client.

- Error Handler Module (`errorHandler`) - executed when an error occurs when sending a request to the upstream server. This module is used to modify the response before it is sent to the client.

- Request Handler Module (`requestHandler`) - executed when a request is received from the client. This module is used to handle arbitrary requests, instead of using an upstream service.
2 changes: 1 addition & 1 deletion config.dgate.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version: v1
debug: true
log_level: ${LOG_LEVEL:-info}
log_level: ${LOG_LEVEL:-debug}
disable_default_namespace: true
tags: [debug, local, test]
storage:
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type (
DisableXForwardedHeaders bool `koanf:"disable_x_forwarded_headers"`
StrictMode bool `koanf:"strict_mode"`
XForwardedForDepth int `koanf:"x_forwarded_for_depth"`
AllowList []string `koanf:"allow_list"`

// WARN: debug use only
InitResources *DGateResources `koanf:"init_resources"`
Expand Down
10 changes: 10 additions & 0 deletions internal/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"strings"

"github.com/dgate-io/dgate-api/pkg/util"
"github.com/dgate-io/dgate-api/pkg/util/iplist"
"github.com/hashicorp/raft"
kjson "github.com/knadh/koanf/parsers/json"
ktoml "github.com/knadh/koanf/parsers/toml"
Expand Down Expand Up @@ -158,6 +159,15 @@
kDefault(k, "proxy.enable_http2", false)
kDefault(k, "proxy.console_log_level", k.Get("log_level"))

if k.Exists("proxy.allow_list") {
var ips []string = k.Get("proxy.allow_list").([]string)
ipList := iplist.NewIPList()
err = ipList.AddAll(ips)
if err != nil {
return nil, errors.New("proxy.allow_list error: " + err.Error())
}

Check warning on line 168 in internal/config/loader.go

View check run for this annotation

Codecov / codecov/patch

internal/config/loader.go#L163-L168

Added lines #L163 - L168 were not covered by tests
}

if k.Get("proxy.enable_h2c") == true &&
k.Get("proxy.enable_http2") == false {
return nil, errors.New("proxy: enable_h2c is true but enable_http2 is false")
Expand Down
27 changes: 13 additions & 14 deletions internal/proxy/change_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)

// processChangeLog - processes a change log and applies the change to the proxy state
func (ps *ProxyState) processChangeLog(cl *spec.ChangeLog, reload, store bool) (err error) {
func (ps *ProxyState) processChangeLog(cl *spec.ChangeLog, reload, store bool) (restartNeeded bool, err error) {
if reload {
defer func(start time.Time) {
if err != nil {
Expand Down Expand Up @@ -68,12 +68,6 @@
} else if !ps.changeHash.CompareAndSwap(oldHash, newHash) {
goto hash_retry
}
} else {
go ps.restartState(func(err error) {
if err != nil {
ps.Stop()
}
})
}
}()
if cl.Cmd.Resource() == spec.Documents {
Expand All @@ -99,24 +93,25 @@

// apply state changes to the proxy
if reload {
ps.logger.Debug("Reloading change log", zap.String("id", cl.ID))
overrideReload := cl.Cmd.IsNoop() || ps.pendingChanges
if overrideReload || cl.Cmd.Resource().IsRelatedTo(spec.Routes) {
if err := ps.storeCachedDocuments(); err != nil {
ps.logger.Error("error storing cached documents", zap.Error(err))
return err
return false, err

Check warning on line 101 in internal/proxy/change_log.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/change_log.go#L101

Added line #L101 was not covered by tests
}
ps.logger.Debug("Reloading change log", zap.String("id", cl.ID))
if err = ps.reconfigureState(cl); err != nil {
ps.logger.Error("Error registering change log", zap.Error(err))
return
return false, err

Check warning on line 106 in internal/proxy/change_log.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/change_log.go#L106

Added line #L106 was not covered by tests
}
ps.pendingChanges = false
}
} else if !cl.Cmd.IsNoop() {
ps.pendingChanges = true
}

return nil
return restartNeeded, nil
}

func decode[T any](input any) (T, error) {
Expand Down Expand Up @@ -353,7 +348,8 @@
if cl.Cmd.Resource() == spec.Documents {
continue
}
if err = ps.processChangeLog(cl, false, false); err != nil {
_, err = ps.processChangeLog(cl, false, false)
if err != nil {

Check warning on line 352 in internal/proxy/change_log.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/change_log.go#L351-L352

Added lines #L351 - L352 were not covered by tests
ps.logger.Error("error processing change log",
zap.Bool("skip", ps.debugMode),
zap.Error(err),
Expand All @@ -371,12 +367,15 @@
if err = ps.reconfigureState(cl); err != nil {
return err
}
} else if err = ps.processChangeLog(cl, true, false); err != nil {
return err
} else {
_, err = ps.processChangeLog(cl, true, false)
if err != nil {
return err
}

Check warning on line 374 in internal/proxy/change_log.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/change_log.go#L370-L374

Added lines #L370 - L374 were not covered by tests
}

// DISABLED: compaction of change logs needs to have better testing
if len(logs) < 0 {
if false {
removed, err := ps.compactChangeLogs(logs)
if err != nil {
ps.logger.Error("failed to compact state change logs", zap.Error(err))
Expand Down
34 changes: 29 additions & 5 deletions internal/proxy/dynamic_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
"math"
"net/http"
"os"
"strings"
"time"

"github.com/dgate-io/dgate-api/internal/router"
"github.com/dgate-io/dgate-api/pkg/modules/extractors"
"github.com/dgate-io/dgate-api/pkg/spec"
"github.com/dgate-io/dgate-api/pkg/typescript"
"github.com/dgate-io/dgate-api/pkg/util/iplist"
"github.com/dgate-io/dgate-api/pkg/util/tree/avl"
"github.com/dop251/goja"
"go.uber.org/zap"
Expand Down Expand Up @@ -391,11 +393,33 @@
}
}

func (ps *ProxyState) HandleRoute(requestCtxProvider *RequestContextProvider, pattern string) http.HandlerFunc {
func (ps *ProxyState) HandleRoute(ctxProvider *RequestContextProvider, pattern string) http.HandlerFunc {
ipList := iplist.NewIPList()
if len(ps.config.ProxyConfig.AllowList) > 0 {
for _, address := range ps.config.ProxyConfig.AllowList {
if strings.Contains(address, "/") {
if err := ipList.AddCIDRString(address); err != nil {
panic(fmt.Errorf("invalid cidr address in proxy.allow_list: %s", address))

Check warning on line 402 in internal/proxy/dynamic_proxy.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/dynamic_proxy.go#L399-L402

Added lines #L399 - L402 were not covered by tests
}
} else {
if err := ipList.AddIPString(address); err != nil {
panic(fmt.Errorf("invalid ip address in proxy.allow_list: %s", address))

Check warning on line 406 in internal/proxy/dynamic_proxy.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/dynamic_proxy.go#L404-L406

Added lines #L404 - L406 were not covered by tests
}
}
}
}
return func(w http.ResponseWriter, r *http.Request) {
// ctx, cancel := context.WithCancel(requestCtxPrdovider.ctx)
// defer cancel()
ps.ProxyHandler(ps, requestCtxProvider.
CreateRequestContext(requestCtxProvider.ctx, w, r, pattern))
if ipList.Len() > 0 {
allowed, err := ipList.Contains(r.RemoteAddr)
if err != nil {
ps.logger.Error("Error checking ")
}

Check warning on line 416 in internal/proxy/dynamic_proxy.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/dynamic_proxy.go#L412-L416

Added lines #L412 - L416 were not covered by tests

if !allowed {
http.Error(w, "Forbidden", http.StatusForbidden)
}

Check warning on line 420 in internal/proxy/dynamic_proxy.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/dynamic_proxy.go#L418-L420

Added lines #L418 - L420 were not covered by tests
}
reqContext := ctxProvider.CreateRequestContext(w, r, pattern)
ps.ProxyHandler(ps, reqContext)

Check warning on line 423 in internal/proxy/dynamic_proxy.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/dynamic_proxy.go#L422-L423

Added lines #L422 - L423 were not covered by tests
}
}
12 changes: 6 additions & 6 deletions internal/proxy/proxy_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
}

func handleServiceProxy(ps *ProxyState, reqCtx *RequestContext, modExt ModuleExtractor) {
var host string
var upstreamUrlString string
if fetchUpstreamUrl, ok := modExt.FetchUpstreamUrlFunc(); ok {
fetchUpstreamStart := time.Now()
hostUrl, err := fetchUpstreamUrl(modExt.ModuleContext())
Expand All @@ -98,17 +98,17 @@
util.WriteStatusCodeError(reqCtx.rw, http.StatusInternalServerError)
return
}
host = hostUrl.String()
upstreamUrlString = hostUrl.String()
} else {
if reqCtx.route.Service.URLs == nil || len(reqCtx.route.Service.URLs) == 0 {
if len(reqCtx.route.Service.URLs) == 0 {

Check warning on line 103 in internal/proxy/proxy_handler.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/proxy_handler.go#L103

Added line #L103 was not covered by tests
ps.logger.Error("Error getting service urls",
zap.String("service", reqCtx.route.Service.Name),
zap.String("namespace", reqCtx.route.Namespace.Name),
)
util.WriteStatusCodeError(reqCtx.rw, http.StatusInternalServerError)
return
}
host = reqCtx.route.Service.URLs[0].String()
upstreamUrlString = reqCtx.route.Service.URLs[0].String()

Check warning on line 111 in internal/proxy/proxy_handler.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/proxy_handler.go#L111

Added line #L111 was not covered by tests
}

if reqCtx.route.Service.HideDGateHeaders {
Expand All @@ -122,10 +122,10 @@

// downstream headers
if ps.debugMode {
reqCtx.rw.Header().Set("X-Upstream-URL", host)
reqCtx.rw.Header().Set("X-Upstream-URL", upstreamUrlString)

Check warning on line 125 in internal/proxy/proxy_handler.go

View check run for this annotation

Codecov / codecov/patch

internal/proxy/proxy_handler.go#L125

Added line #L125 was not covered by tests
}
}
upstreamUrl, err := url.Parse(host)
upstreamUrl, err := url.Parse(upstreamUrlString)
if err != nil {
ps.logger.Error("Error parsing upstream url",
zap.String("error", err.Error()),
Expand Down
12 changes: 4 additions & 8 deletions internal/proxy/proxy_handler_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package proxy_test

import (
"context"
"errors"
"io"
"net/http"
Expand Down Expand Up @@ -57,8 +56,7 @@ func TestProxyHandler_ReverseProxy(t *testing.T) {
wr.SetWriteFallThrough()
wr.On("Header").Return(http.Header{})
wr.On("Write", mock.Anything).Return(0, nil).Maybe()
reqCtx := reqCtxProvider.CreateRequestContext(
context.Background(), wr, req, "/")
reqCtx := reqCtxProvider.CreateRequestContext(wr, req, "/")

modExt := NewMockModuleExtractor()
modExt.ConfigureDefaultMock(req, wr, ps, rt)
Expand All @@ -78,7 +76,7 @@ func TestProxyHandler_ReverseProxy(t *testing.T) {
modPool.AssertExpectations(t)
modExt.AssertExpectations(t)
rpBuilder.AssertExpectations(t)
// rpe.AssertExpectations(t)
rpe.AssertExpectations(t)
}
}

Expand Down Expand Up @@ -129,8 +127,7 @@ func TestProxyHandler_ProxyHandler(t *testing.T) {
modPool.On("Return", modExt).Return().Once()
reqCtxProvider.UpdateModulePool(modPool)

reqCtx := reqCtxProvider.CreateRequestContext(
context.Background(), wr, req, "/")
reqCtx := reqCtxProvider.CreateRequestContext(wr, req, "/")
ps.ProxyHandler(ps, reqCtx)

wr.AssertExpectations(t)
Expand Down Expand Up @@ -181,8 +178,7 @@ func TestProxyHandler_ProxyHandlerError(t *testing.T) {
modPool.On("Return", modExt).Return().Once()
reqCtxProvider := proxy.NewRequestContextProvider(rt, ps)
reqCtxProvider.UpdateModulePool(modPool)
reqCtx := reqCtxProvider.CreateRequestContext(
context.Background(), wr, req, "/")
reqCtx := reqCtxProvider.CreateRequestContext(wr, req, "/")
ps.ProxyHandler(ps, reqCtx)

wr.AssertExpectations(t)
Expand Down
Loading
Loading