mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-16 19:29:26 +02:00
MUL-3320: feat(lark): add proxy support for WebSocket connections (#4165)
* feat(lark): add proxy support for WebSocket connections - Add Proxy field to GorillaDialer (func(*http.Request) (*url.URL, error)) - Default to http.ProxyFromEnvironment when Proxy is nil, so standard HTTPS_PROXY/HTTP_PROXY/NO_PROXY env vars are respected automatically - Allow explicit override via GorillaDialer.Proxy for custom proxy auth or fixed proxy URLs - Add unit tests for proxy defaults and error forwarding Closes #4032 Co-authored-by: multica-agent <github@multica.ai> * fix(lark): add missing net/url import in ws_connector_test.go TestGorillaDialerProxyDefaults and TestGorillaDialerProxyForwardsError use *url.URL in their Proxy func signatures but net/url was not imported. Co-authored-by: multica-agent <github@multica.ai> * fix(lark): preserve configured websocket proxy Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: J <j@multica.ai>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -533,6 +534,14 @@ func (f FrameDecoderFunc) Decode(payload []byte, inst db.LarkInstallation) (Inbo
|
||||
// GorillaDialer is the production WSDialer.
|
||||
type GorillaDialer struct {
|
||||
Dialer *websocket.Dialer
|
||||
|
||||
// Proxy is the proxy function for WebSocket connections. When nil
|
||||
// (the zero value), the dialer defaults to http.ProxyFromEnvironment
|
||||
// so standard HTTPS_PROXY / HTTP_PROXY / NO_PROXY environment
|
||||
// variables are respected. Set Proxy to a non-nil func to override
|
||||
// (e.g. for custom proxy auth or a fixed proxy URL). To disable proxy
|
||||
// entirely, pass a func that returns (nil, nil).
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
}
|
||||
|
||||
func NewGorillaDialer() *GorillaDialer {
|
||||
@@ -548,7 +557,15 @@ func (g *GorillaDialer) DialContext(ctx context.Context, urlStr string, requestH
|
||||
if d == nil {
|
||||
d = websocket.DefaultDialer
|
||||
}
|
||||
c, resp, err := d.DialContext(ctx, urlStr, requestHeader)
|
||||
// Shallow copy so we don't mutate the shared dialer's Proxy field.
|
||||
dd := *d
|
||||
if g.Proxy != nil {
|
||||
dd.Proxy = g.Proxy
|
||||
}
|
||||
if dd.Proxy == nil {
|
||||
dd.Proxy = http.ProxyFromEnvironment
|
||||
}
|
||||
c, resp, err := dd.DialContext(ctx, urlStr, requestHeader)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -651,6 +652,70 @@ func TestWSConnectorReassemblesChunkedDataFrame(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGorillaDialerPreservesConfiguredDialerProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
proxyErr := errors.New("configured proxy refused")
|
||||
d := &GorillaDialer{
|
||||
Dialer: &websocket.Dialer{
|
||||
Proxy: func(*http.Request) (*url.URL, error) {
|
||||
return nil, proxyErr
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
_, _, err := d.DialContext(ctx, "ws://127.0.0.1:1", nil)
|
||||
if !errors.Is(err, proxyErr) {
|
||||
t.Fatalf("DialContext error = %v, want %v", err, proxyErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGorillaDialerProxyOverridesConfiguredDialerProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configuredProxyErr := errors.New("configured proxy refused")
|
||||
overrideProxyErr := errors.New("override proxy refused")
|
||||
d := &GorillaDialer{
|
||||
Dialer: &websocket.Dialer{
|
||||
Proxy: func(*http.Request) (*url.URL, error) {
|
||||
return nil, configuredProxyErr
|
||||
},
|
||||
},
|
||||
Proxy: func(*http.Request) (*url.URL, error) {
|
||||
return nil, overrideProxyErr
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
_, _, err := d.DialContext(ctx, "ws://127.0.0.1:1", nil)
|
||||
if !errors.Is(err, overrideProxyErr) {
|
||||
t.Fatalf("DialContext error = %v, want %v", err, overrideProxyErr)
|
||||
}
|
||||
if errors.Is(err, configuredProxyErr) {
|
||||
t.Fatalf("DialContext used configured proxy error %v instead of override", configuredProxyErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGorillaDialerProxyForwardsError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := NewGorillaDialer()
|
||||
proxyErr := errors.New("proxy refused")
|
||||
d.Proxy = func(r *http.Request) (*url.URL, error) {
|
||||
return nil, proxyErr
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
_, _, err := d.DialContext(ctx, "ws://127.0.0.1:1", nil)
|
||||
if !errors.Is(err, proxyErr) {
|
||||
t.Fatalf("DialContext error = %v, want %v", err, proxyErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSConnectorCredentialsErrorIsReturned(t *testing.T) {
|
||||
t.Parallel()
|
||||
credsErr := errors.New("decrypt failed")
|
||||
|
||||
Reference in New Issue
Block a user