Skip to content
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

Merged
merged 27 commits into from
Apr 17, 2017
Merged

Conversation

menghanl
Copy link
Contributor

@menghanl menghanl commented Mar 1, 2017

Add a function to create custom dialer to do dial and HTTP CONNECT handshake.

@menghanl menghanl force-pushed the connect_handshaker branch 4 times, most recently from ca88a5e to eccdd15 Compare March 1, 2017 22:36
@menghanl menghanl mentioned this pull request Mar 1, 2017
@menghanl menghanl requested a review from markdroth March 1, 2017 23:23
@menghanl menghanl force-pushed the connect_handshaker branch from 356db94 to 1394ad5 Compare March 2, 2017 01:57
@menghanl menghanl requested review from ejona86, MakMukhi and dfawley March 6, 2017 20:01
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)
Copy link
Member

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?

Copy link
Contributor Author

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 {
Copy link
Member

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?

Copy link
Contributor Author

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 {
Copy link
Member

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).

Copy link
Contributor Author

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.
Copy link
Member

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.)

Copy link
Contributor Author

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")
Copy link
Member

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?

Copy link
Contributor Author

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 {
Copy link
Member

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.

Copy link
Contributor Author

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)
Copy link
Member

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.

Copy link
Contributor Author

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 ;)

r := bufio.NewReader(conn)
resp, err := http.ReadResponse(r, nil)
if err != nil {
return conn, fmt.Errorf("reading server HTTP response: %v", err)
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

complele -> complete

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

newAddr = addr
}

conn, err := fn(ctx, newAddr)
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored as discussed

@menghanl menghanl force-pushed the connect_handshaker branch 4 times, most recently from fc67b61 to a9c135c Compare March 8, 2017 19:34
Copy link
Member

@markdroth markdroth left a 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!

@menghanl menghanl force-pushed the connect_handshaker branch 3 times, most recently from 23885fe to 68f5e21 Compare March 14, 2017 00:31
@dfawley dfawley added 1.3 and removed 1.2 labels Mar 20, 2017
@menghanl menghanl force-pushed the connect_handshaker branch 2 times, most recently from 7d6c251 to 7976d92 Compare March 27, 2017 21:47
@menghanl menghanl force-pushed the connect_handshaker branch from ed19663 to b61bb39 Compare March 28, 2017 18:12
@menghanl menghanl force-pushed the connect_handshaker branch from b61bb39 to 802045b Compare April 5, 2017 18:13
@menghanl
Copy link
Contributor Author

menghanl commented Apr 7, 2017

ping

req := (&http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: addr},
Header: map[string][]string{"User-Agent": {"gRPC-go"}},
Copy link
Member

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?

Copy link
Contributor Author

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.

}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to do connect handshake, status code: %s", resp.Status)
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@menghanl menghanl force-pushed the connect_handshaker branch from 9d7c174 to b20836f Compare April 17, 2017 21:02
@menghanl menghanl merged commit 955c867 into grpc:master Apr 17, 2017
@menghanl menghanl deleted the connect_handshaker branch April 17, 2017 23:08
@menghanl menghanl added the Type: Feature New features or improvements in behavior label Apr 22, 2017
@sathieu
Copy link

sathieu commented Oct 18, 2017

Is there a dumb dialer which doesn't use proxy envs? (for moby/moby#34825). Thanks

@menghanl
Copy link
Contributor Author

You can override the dialer with WithDialer().

grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
  return net.DialTimeout("tcp", addr, timeout)
})

@lock lock bot locked as resolved and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Type: Feature New features or improvements in behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants