Skip to content

Commit

Permalink
ING-870: Added ability to forward requests to underlying services.
Browse files Browse the repository at this point in the history
  • Loading branch information
Brett Lawson committed Aug 7, 2024
1 parent 023d2f3 commit eb2bdc3
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 4 deletions.
8 changes: 7 additions & 1 deletion gateway/dapiimpl/dapiimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/couchbase/gocbcorex"
"github.com/couchbase/stellar-gateway/dataapiv1"
"github.com/couchbase/stellar-gateway/gateway/auth"
"github.com/couchbase/stellar-gateway/gateway/dapiimpl/proxy"
"github.com/couchbase/stellar-gateway/gateway/dapiimpl/server_v1"
"go.uber.org/zap"
)
Expand All @@ -18,6 +19,7 @@ type NewOptions struct {
}

type Servers struct {
DataApiProxy *proxy.DataApiProxy
DataApiV1Server dataapiv1.StrictServerInterface
}

Expand All @@ -35,8 +37,12 @@ func New(opts *NewOptions) *Servers {
}

return &Servers{
DataApiProxy: proxy.NewDataApiProxy(
opts.Logger.Named("dapi-proxy"),
opts.CbClient,
opts.Debug),
DataApiV1Server: server_v1.NewDataApiServer(
opts.Logger.Named("dapi-server"),
opts.Logger.Named("dapi-serverv1"),
v1ErrHandler,
v1AuthHandler),
}
Expand Down
167 changes: 167 additions & 0 deletions gateway/dapiimpl/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package proxy

import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/couchbase/gocbcorex"
"go.uber.org/zap"
)

type DataApiProxy struct {
logger *zap.Logger
cbClient *gocbcorex.BucketsTrackingAgentManager
debugMode bool
mux *http.ServeMux
}

func NewDataApiProxy(
logger *zap.Logger,
cbClient *gocbcorex.BucketsTrackingAgentManager,
debugMode bool,
) *DataApiProxy {
mux := http.NewServeMux()

proxy := &DataApiProxy{
logger: logger,
cbClient: cbClient,
debugMode: debugMode,
mux: mux,
}

mux.HandleFunc("/proxy/mgmt/", proxy.ProxyMgmt)
mux.HandleFunc("/proxy/query/", proxy.ProxyQuery)
mux.HandleFunc("/proxy/search/", proxy.ProxySearch)
mux.HandleFunc("/proxy/analytics/", proxy.ProxyAnalytics)

return proxy
}

func (p *DataApiProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.mux.ServeHTTP(w, r)
}

func (p *DataApiProxy) writeError(w http.ResponseWriter, err error, msg string) {
p.logger.Debug(msg, zap.Error(err))

w.WriteHeader(502)

if !p.debugMode {
fmt.Fprintf(w, "%s", msg)
} else {
fmt.Fprintf(w, "%s: %s", msg, err)
}
}

func (p *DataApiProxy) proxyService(
w http.ResponseWriter,
r *http.Request,
serviceName string,
) {
ctx := r.Context()

agent, err := p.cbClient.GetClusterAgent(ctx)
if err != nil {
p.writeError(w, err, "failed to get agent")
return
}

var roundTripper http.RoundTripper
var svcEndpoint string
var pathPrefix string
if serviceName == "mgmt" {
endpointInfo, err := agent.GetMgmtEndpoint(ctx)
if err != nil {
p.writeError(w, err, "failed to select mgmt endpoint")
return
}

roundTripper = endpointInfo.RoundTripper
svcEndpoint = endpointInfo.Endpoint
pathPrefix = "/proxy/mgmt"
} else if serviceName == "query" {
endpointInfo, err := agent.GetQueryEndpoint(ctx)
if err != nil {
p.writeError(w, err, "failed to select query endpoint")
return
}

roundTripper = endpointInfo.RoundTripper
svcEndpoint = endpointInfo.Endpoint
pathPrefix = "/proxy/query"
} else if serviceName == "search" {
endpointInfo, err := agent.GetSearchEndpoint(ctx)
if err != nil {
p.writeError(w, err, "failed to select search endpoint")
return
}

roundTripper = endpointInfo.RoundTripper
svcEndpoint = endpointInfo.Endpoint
pathPrefix = "/proxy/search"
} else if serviceName == "analytics" {
endpointInfo, err := agent.GetAnalyticsEndpoint(ctx)
if err != nil {
p.writeError(w, err, "failed to select analytics endpoint")
return
}

roundTripper = endpointInfo.RoundTripper
svcEndpoint = endpointInfo.Endpoint
pathPrefix = "/proxy/analytics"
} else {
p.writeError(w, nil, "unexpected service name")
return
}

relPath := r.URL.Path[len(pathPrefix):]
newUrl, _ := url.Parse(svcEndpoint + relPath)
newUrl.RawQuery = r.URL.RawQuery

proxyReq, err := http.NewRequest(r.Method, newUrl.String(), r.Body)
if err != nil {
p.writeError(w, err, "failed to create proxy request")
return
}

// copy some other details
proxyReq.Header = r.Header

proxyResp, err := roundTripper.RoundTrip(proxyReq)
if err != nil {
p.writeError(w, err, "failed to receive response")
return
}

for k, vs := range proxyResp.Header {
for _, v := range vs {
w.Header().Add(k, v)
}
}

w.WriteHeader(proxyResp.StatusCode)
_, err = io.Copy(w, proxyResp.Body)
if err != nil {
// we cannot actually write an error here as we've already started writing the response
p.logger.Debug("failed to write response body", zap.Error(err))
return
}
}

func (p *DataApiProxy) ProxyMgmt(w http.ResponseWriter, r *http.Request) {
p.proxyService(w, r, "mgmt")
}

func (p *DataApiProxy) ProxyQuery(w http.ResponseWriter, r *http.Request) {
p.proxyService(w, r, "query")
}

func (p *DataApiProxy) ProxySearch(w http.ResponseWriter, r *http.Request) {
p.proxyService(w, r, "search")
}

func (p *DataApiProxy) ProxyAnalytics(w http.ResponseWriter, r *http.Request) {
p.proxyService(w, r, "analytics")
}
8 changes: 6 additions & 2 deletions gateway/system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,16 @@ func NewSystem(opts *SystemOptions) (*System, error) {
sh := dataapiv1.NewStrictHandlerWithOptions(dapiImpl.DataApiV1Server, nil, dataapiv1.StrictHTTPServerOptions{
ResponseErrorHandlerFunc: dapiimpl.StatusErrorHttpHandler,
})
h := dataapiv1.Handler(sh)

mux := http.NewServeMux()
mux.Handle("/v1/", dataapiv1.Handler(sh))
mux.Handle("/proxy/", dapiImpl.DataApiProxy)

dapiSrv := &http.Server{
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: h,
Handler: mux,
}

s := &System{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toolchain go1.22.3
require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/couchbase/gocbcore/v10 v10.5.0
github.com/couchbase/gocbcorex v0.0.0-20240717185313-4c69cffb9781
github.com/couchbase/gocbcorex v0.0.0-20240807195331-d6a4a53f5e0b
github.com/couchbase/goprotostellar v1.0.3-0.20240514104501-9958dd1cee8a
github.com/couchbaselabs/gocbconnstr v1.0.5
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/couchbase/gocbcore/v10 v10.5.0 h1:rgm8cPEDZVgegbBf4dIPZD4Ng52/qkn0XU3
github.com/couchbase/gocbcore/v10 v10.5.0/go.mod h1:rulbgUK70EuyRUiLQ0LhQAfSI/Rl+jWws8tTbHzvB6M=
github.com/couchbase/gocbcorex v0.0.0-20240717185313-4c69cffb9781 h1:rQCQDwZO1Y/2wDwm9zlTTXbJGg8/GU4tk5CwfzAtMP4=
github.com/couchbase/gocbcorex v0.0.0-20240717185313-4c69cffb9781/go.mod h1:YPpfb1dcXqIv3TIkka8eSK6hXi5gpMuB0pmV1vaCXA8=
github.com/couchbase/gocbcorex v0.0.0-20240807195331-d6a4a53f5e0b h1:4GY6JfTAa7f4DFQDPGGuxQ6Q0U1I7v3HLgJ1BPayD9U=
github.com/couchbase/gocbcorex v0.0.0-20240807195331-d6a4a53f5e0b/go.mod h1:YPpfb1dcXqIv3TIkka8eSK6hXi5gpMuB0pmV1vaCXA8=
github.com/couchbase/goprotostellar v1.0.3-0.20240514104501-9958dd1cee8a h1:rM0iLd4i8+pOSM/f+Denugpi7eY3C7k5Fo/AeAgX1H0=
github.com/couchbase/goprotostellar v1.0.3-0.20240514104501-9958dd1cee8a/go.mod h1:5/yqVnZlW2/NSbAWu1hPJCFBEwjxgpe0PFFOlRixnp4=
github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=
Expand Down

0 comments on commit eb2bdc3

Please sign in to comment.