-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support proxy with custom dialer #1098
Conversation
ca88a5e
to
eccdd15
Compare
356db94
to
1394ad5
Compare
proxy/proxy.go
Outdated
// MapAddress is called before we connect to the target address. | ||
// It can be used to programmatically override the address that we will connect to. | ||
// It returns the address of the proxy, and the header to be sent in the request. | ||
MapAddress(ctx context.Context, address string) (string, map[string][]string, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the return value be an http.Header instead of a map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to avoid introduce http as a dependency of the exported APIs.
I'm using http.Header in the implementation though...
I can change this if you think the dependency is OK.
proxy/proxy.go
Outdated
// as indicated by the environment variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY | ||
// (or the lowercase versions thereof) when mapping address, and does HTTP connect handshake on | ||
// the connection. | ||
func NewEnvironmentVariableHTTPConnectProxy() Proxyer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this be used by default? If not, would this be confusing for users, since basically everything respects these environment variables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gRPC should check those env variables by default. And this will be the implementation of the default behavior in gRPC.
proxy/proxy.go
Outdated
var ErrIneffective = errors.New("MapAddress function is not effective") | ||
|
||
// Proxyer defines the interface gRPC uses to map the proxy address. | ||
type Proxyer interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer "Proxy" (even though it stutters), or "Interface". There are examples of both in the go standard libraries (hash.Hash; sort.Interface and heap.Interface).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change containing this type was reverted
proxy/proxy.go
Outdated
* | ||
*/ | ||
|
||
// Package proxy defines interfaces to support proxyies in gRPC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and an implementation of a proxy that uses environment variables for configuration.
(e.g.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
proxy/proxy.go
Outdated
) | ||
|
||
// ErrIneffective indicates the mapper function is not effective. | ||
var ErrIneffective = errors.New("MapAddress function is not effective") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: ErrDisabled? Indicates the proxy is not enabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
proxy/proxy.go
Outdated
return doHTTPConnectHandshake(ctx, conn, addr, header) | ||
} | ||
|
||
type bufConn struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment explaining why this type exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
proxy/proxy.go
Outdated
} | ||
|
||
func (pm *environmentProxyMapper) Handshake(ctx context.Context, conn net.Conn, addr string, header http.Header) (net.Conn, error) { | ||
return doHTTPConnectHandshake(ctx, conn, addr, header) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it should be possible to share additional code (header formulation, bufConn creation) and limit the version-specific code to just sending the request. For maintenance, in general, it would be better to minimize version-specific code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Was thinking that we will eventually get rid of the specific code for old versions, and to git rid of that, we can simply delete those files ;)
proxy/util_pre_go16.go
Outdated
r := bufio.NewReader(conn) | ||
resp, err := http.ReadResponse(r, nil) | ||
if err != nil { | ||
return conn, fmt.Errorf("reading server HTTP response: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return nil, ?
Disregard if this becomes shared code per earlier comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/end2end_test.go
Outdated
t.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil) | ||
} | ||
if _, err := stream.Recv(); err != io.EOF { | ||
t.Fatalf("%v failed to complele the FullDuplexCall: %v", stream, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
complele -> complete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
transport/http2_client.go
Outdated
newAddr = addr | ||
} | ||
|
||
conn, err := fn(ctx, newAddr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the proxyer was responsible for connecting? In that case, the proxyer would be mostly a wrapper, and the logic around here could be completely offloaded to it. I.e.:
package proxy
type Dialer interface {
Dial(ctx context.Context, addr string, dialFunc func(context.Context, string) (net.Conn, error)) (net.Conn, error)
}
A proxy's Dial method would dial, determine the headers and new address if necessary, and perform whatever handshaking is required. If the proxy was disabled/ineffective, it would be responsible for doing simply: "return dialFunc(ctx, addr)". This avoids the need to pass around the headers / new address / disabled error, and should allow more flexibility in what Proxies could implement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored as discussed
fc67b61
to
a9c135c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not very fluent in Go yet, but the overall approach here looks fine for now.
I do think that we need to have some follow-up discussions about how the name resolution and load balancing code is structured in Go relative to the other languages, and that might affect some of this code. But that issue also affects a lot of existing code as well, so it doesn't need to block merging this PR.
Anyway, I'd say that once someone who is more fluent in Go has reviewed this, it should be fine to submit.
Thanks for doing this!
23885fe
to
68f5e21
Compare
7d6c251
to
7976d92
Compare
ed19663
to
b61bb39
Compare
b61bb39
to
802045b
Compare
ping |
transport/proxy.go
Outdated
req := (&http.Request{ | ||
Method: http.MethodConnect, | ||
URL: &url.URL{Host: addr}, | ||
Header: map[string][]string{"User-Agent": {"gRPC-go"}}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this missing the Host header, or will that be copied from the URL automatically by go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if will be copied from the URL.
transport/proxy.go
Outdated
} | ||
resp.Body.Close() | ||
if resp.StatusCode != http.StatusOK { | ||
return nil, fmt.Errorf("failed to do connect handshake, status code: %s", resp.Status) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd highly suggest including 1k or so of the response body (or have a way to log it). It helps a ton when debugging failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This reverts commit 99fa459.
9d7c174
to
b20836f
Compare
Is there a dumb dialer which doesn't use proxy envs? (for moby/moby#34825). Thanks |
You can override the dialer with grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout("tcp", addr, timeout)
}) |
Add a function to create custom dialer to do dial and HTTP CONNECT handshake.