Skip to content

Commit

Permalink
feat: enable using HTTP(S)_PROXY in router (#1136)
Browse files Browse the repository at this point in the history
Co-authored-by: Jens Neuse <jens.neuse@gmx.de>
  • Loading branch information
AndreasZeissner and jensneuse authored Sep 5, 2024
1 parent 41cbcad commit 4600fdf
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 6 deletions.
24 changes: 24 additions & 0 deletions docker-compose.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ services:
GRAPHQL_METRICS_COLLECTOR_ENDPOINT: http://graphqlmetrics:4005
GRAPH_API_TOKEN: ${ROUTER_TOKEN}
CDN_URL: http://cdn:11000
HTTPS_PROXY: ${HTTPS_PROXY}
HTTP_PROXY: ${HTTP_PROXY}
NO_PROXY: ${NO_PROXY}
restart: on-failure
volumes:
# Mount the example config from the repo into the working dir of the router binary location
Expand Down Expand Up @@ -396,6 +399,27 @@ services:
- default
restart: on-failure

# Use this to intercept request e.g. from the router by setting the HTTP(S)_PROXY env var to http://mitmproxy:9051.
# docker compose -f docker-compose.full.yml --profile proxy up -d mitmproxy
mitmproxy:
image: mitmproxy/mitmproxy:latest
command:
- mitmweb
- --web-host
- 0.0.0.0
- --web-port
- '8081'
- --mode
- regular@8080
restart: on-failure
profiles:
- proxy
networks:
- primary
ports:
- '9051:8080' # proxy
- '9050:8081' # web interface

# This network is shared between this file and docker-compose.yml to
# allow the demo subgraphs to communicate with the rest of the infra-networks:
networks:
Expand Down
3 changes: 3 additions & 0 deletions helm/cosmo/charts/router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ This is the official Helm Chart for the WunderGraph Cosmo Router.
| configuration.executionConfig | string | `""` | The execution config file to statically configure the router. If set, polling of the config is disabled. If your config exceeds 1MB (Kubernetes limit), you have to mount it as a file and set the path in routerConfigPath instead |
| configuration.graphApiToken | string | `"replace-me"` | The router token is used to authenticate the router against the controlplane (required) |
| configuration.graphqlMetricsCollectorUrl | string | `""` | The URL of the Cosmo GraphQL Metrics Collector. Should be internal to the cluster. Default to cloud if not set. |
| configuration.httpProxy | string | `""` | The URL of the HTTP proxy server. Default is an empty string. |
| configuration.httpsProxy | string | `""` | The URL of the HTTPS proxy server. Default is an empty string. |
| configuration.logLevel | string | `"info"` | The log level of the router. Default to info if not set. |
| configuration.noProxy | string | `""` | NO_PROXY is a comma-separated list of hosts or domains for which the proxy should not be used. |
| configuration.otelCollectorUrl | string | `""` | The URL of the Cosmo GraphQL OTEL Collector. Should be internal to the cluster. Default to cloud if not set. |
| configuration.prometheus.enabled | bool | `true` | Enables prometheus metrics support. Default is true. |
| configuration.prometheus.path | string | `"/metrics"` | The HTTP path where metrics are exposed. Default is "/metrics". |
Expand Down
21 changes: 21 additions & 0 deletions helm/cosmo/charts/router/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,27 @@ spec:
name: {{ include "router.secretName" . }}
key: graphApiToken
{{- end }}
{{- if .Values.configuration.httpsProxy }}
- name: HTTPS_PROXY
valueFrom:
secretKeyRef:
name: {{ include "router.secretName" . }}
key: httpsProxy
{{- end }}
{{- if .Values.configuration.httpProxy }}
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: {{ include "router.secretName" . }}
key: httpProxy
{{- end }}
{{- if .Values.configuration.noProxy }}
- name: NO_PROXY
valueFrom:
secretKeyRef:
name: {{ include "router.secretName" . }}
key: noProxy
{{- end }}
- name: PROMETHEUS_ENABLED
valueFrom:
configMapKeyRef:
Expand Down
9 changes: 9 additions & 0 deletions helm/cosmo/charts/router/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,13 @@ metadata:
{{- include "router.labels" . | nindent 4 }}
data:
graphApiToken: {{ .Values.configuration.graphApiToken | b64enc | quote }}
{{- if .Values.configuration.httpsProxy }}
httpsProxy: "{{ .Values.configuration.httpsProxy }}"
{{- end }}
{{- if .Values.configuration.httpProxy }}
httpProxy: "{{ .Values.configuration.httpProxy }}"
{{- end }}
{{- if .Values.configuration.noProxy }}
noProxy: "{{ .Values.configuration.noProxy }}"
{{- end }}
{{- end }}
11 changes: 10 additions & 1 deletion helm/cosmo/charts/router/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,13 @@ configuration:
# -- The port where metrics are exposed. Default is port 8088.
port: 8088
# -- The HTTP path where metrics are exposed. Default is "/metrics".
path: "/metrics"
path: "/metrics"

# Use this section to configure the router's HTTP(S) proxy settings.
# When set the proxy is enabled.
# -- The URL of the HTTPS proxy server. Default is an empty string.
httpsProxy: ''
# -- The URL of the HTTP proxy server. Default is an empty string.
httpProxy: ''
# -- NO_PROXY is a comma-separated list of hosts or domains for which the proxy should not be used.
noProxy: ''
31 changes: 31 additions & 0 deletions router-tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"io"
"math/rand"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -303,6 +306,34 @@ func TestAnonymousQuery(t *testing.T) {
})
}

func TestProxy(t *testing.T) {

fakeSubgraph := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"data":{"employees":[{"id":1234}]}}`))
}))

u, err := url.Parse(fakeSubgraph.URL)
require.NoError(t, err)

proxy := httptest.NewServer(httputil.NewSingleHostReverseProxy(u))
require.NoError(t, err)

testenv.Run(t, &testenv.Config{
RouterOptions: []core.Option{
core.WithProxy(func(req *http.Request) (*url.URL, error) {
return url.Parse(proxy.URL)
}),
},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `{ employees { id } }`,
})
require.Equal(t, `{"data":{"employees":[{"id":1234}]}}`, res.Body)
})
}

func TestTracing(t *testing.T) {
t.Parallel()

Expand Down
7 changes: 6 additions & 1 deletion router/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ GRAPH_API_TOKEN=<generated_with_wgc>
CDN_URL=http://127.0.0.1:11000
NATS_URL=nats://localhost:4222
#CONFIG_PATH=debug.config.yaml
#GRAPH_CONFIG_SIGN_KEY=7kZKCz7DaLpvHKtaFEupDsBvDD9EEmUB
#GRAPH_CONFIG_SIGN_KEY=7kZKCz7DaLpvHKtaFEupDsBvDD9EEmUB

# HTTP(S)_PROXY configuration
HTTPS_PROXY=""
HTTP_PROXY=""
NO_PROXY=""
13 changes: 13 additions & 0 deletions router/cmd/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"net/http"
"os"

"github.com/KimMachineGun/automemlimit/memlimit"
Expand Down Expand Up @@ -170,6 +171,11 @@ func NewRouter(params Params, additionalOptions ...core.Option) (*core.Router, e
core.WithRateLimitConfig(&cfg.RateLimit),
}

// HTTP_PROXY, HTTPS_PROXY and NO_PROXY
if hasProxyConfigured() {
core.WithProxy(http.ProxyFromEnvironment)
}

options = append(options, additionalOptions...)

if cfg.RouterRegistration && cfg.Graph.Token != "" {
Expand Down Expand Up @@ -206,3 +212,10 @@ func NewRouter(params Params, additionalOptions ...core.Option) (*core.Router, e

return core.NewRouter(options...)
}

func hasProxyConfigured() bool {
_, httpProxy := os.LookupEnv("HTTP_PROXY")
_, httpsProxy := os.LookupEnv("HTTPS_PROXY")
_, noProxy := os.LookupEnv("NO_PROXY")
return httpProxy || httpsProxy || noProxy
}
4 changes: 2 additions & 2 deletions router/core/graph_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ type (
)

// newGraphServer creates a new server instance.
func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterConfig) (*graphServer, error) {
func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterConfig, proxy ProxyFunc) (*graphServer, error) {
ctx, cancel := context.WithCancel(ctx)
s := &graphServer{
context: ctx,
cancelFunc: cancel,
Config: &r.Config,
websocketStats: r.WebsocketStats,
metricStore: rmetric.NewNoopMetrics(),
executionTransport: newHTTPTransport(r.subgraphTransportOptions),
executionTransport: newHTTPTransport(r.subgraphTransportOptions, proxy),
playgroundHandler: r.playgroundHandler,
baseRouterConfigVersion: routerConfig.GetVersion(),
inFlightRequests: &atomic.Uint64{},
Expand Down
16 changes: 14 additions & 2 deletions router/core/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type (
modules []Module
WebsocketStats WebSocketsStatistics
playgroundHandler func(http.Handler) http.Handler
proxy ProxyFunc
}

SubgraphTransportOptions struct {
Expand Down Expand Up @@ -482,7 +483,7 @@ func NewRouter(opts ...Option) (*Router, error) {

// newGraphServer creates a new server.
func (r *Router) newServer(ctx context.Context, cfg *nodev1.RouterConfig) error {
server, err := newGraphServer(ctx, r, cfg)
server, err := newGraphServer(ctx, r, cfg, r.proxy)
if err != nil {
r.logger.Error("Failed to create graph server. Keeping the old server", zap.Error(err))
return err
Expand Down Expand Up @@ -1413,6 +1414,12 @@ func WithHealthChecks(healthChecks health.Checker) Option {
}
}

func WithProxy(proxy ProxyFunc) Option {
return func(r *Router) {
r.proxy = proxy
}
}

func WithReadinessCheckPath(path string) Option {
return func(r *Router) {
r.readinessCheckPath = path
Expand Down Expand Up @@ -1635,7 +1642,9 @@ func WithStorageProviders(cfg config.StorageProviders) Option {
}
}

func newHTTPTransport(opts *SubgraphTransportOptions) *http.Transport {
type ProxyFunc func(req *http.Request) (*url.URL, error)

func newHTTPTransport(opts *SubgraphTransportOptions, proxy ProxyFunc) *http.Transport {
dialer := &net.Dialer{
Timeout: opts.DialTimeout,
KeepAlive: opts.KeepAliveProbeInterval,
Expand All @@ -1661,6 +1670,9 @@ func newHTTPTransport(opts *SubgraphTransportOptions) *http.Transport {
TLSHandshakeTimeout: opts.TLSHandshakeTimeout,
ResponseHeaderTimeout: opts.ResponseHeaderTimeout,
ExpectContinueTimeout: opts.ExpectContinueTimeout,
// Will return nil when HTTP(S)_PROXY does not exist or is empty.
// This will prevent the transport from handling the proxy when it is not needed.
Proxy: proxy,
}
}

Expand Down

0 comments on commit 4600fdf

Please sign in to comment.