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

credentials: add RequestInfo to context passed to GetRequestMetadata #3057

Merged
merged 12 commits into from Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 31 additions & 1 deletion credentials/credentials.go
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/golang/protobuf/proto"
"google.golang.org/grpc/credentials/internal"
ginternal "google.golang.org/grpc/internal"
)

// PerRPCCredentials defines the common interface for the credentials which need to
Expand All @@ -45,7 +46,8 @@ type PerRPCCredentials interface {
// context. If a status code is returned, it will be used as the status
// for the RPC. uri is the URI of the entry point for the request.
// When supported by the underlying implementation, ctx can be used for
// timeout and cancellation.
// timeout and cancellation. Additionally, RequestInfo data will be
// available via ctx to this call.
// TODO(zhaoq): Define the set of the qualified keys instead of leaving
// it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
Expand Down Expand Up @@ -334,3 +336,31 @@ func cloneTLSConfig(cfg *tls.Config) *tls.Config {

return cfg.Clone()
}

// RequestInfo contains request data attached to the context passed to GetRequestMetadata calls.
//
// This API is experimental.
type RequestInfo struct {
// The method passed to Invoke or NewStream for this RPC. (For proto methods, this has the format "/some.Service/Method")
Method string
}

// requestInfoKey is a struct to be used as the key when attaching a RequestInfo to a context object.
type requestInfoKey struct{}

// RequestInfoFromContext extracts the RequestInfo from the context.
//
// This API is experimental.
func RequestInfoFromContext(ctx context.Context) RequestInfo {
Copy link
Member

Choose a reason for hiding this comment

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

Bah, I missed this before, sorry: it's typical to also return an ok bool here, in case the RequestInfo is not present in ctx. Please add this. The comment can be updated to match peer.FromContext's wording by simply adding "if it 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.

ri, ok := ctx.Value(requestInfoKey{}).(RequestInfo)
if !ok {
return RequestInfo{}
}
return ri
}

func init() {
ginternal.NewRequestInfoContext = func(ctx context.Context, ri RequestInfo) context.Context {
return context.WithValue(ctx, requestInfoKey{}, ri)
}
}
3 changes: 3 additions & 0 deletions internal/internal.go
Expand Up @@ -47,6 +47,9 @@ var (
// call to proto.Clone(). The returned Status proto should not be mutated by
// the caller.
StatusRawProto interface{} // func (*status.Status) *spb.Status
// NewRequestInfoContext creates a new context based on the argument context attaching
// the passed in RequestInfo to the new context.
NewRequestInfoContext interface{} // func(context.Context, credentials.RequestInfo) context.Context
)

// HealthChecker defines the signature of the client-side LB channel health checking function.
Expand Down
9 changes: 7 additions & 2 deletions internal/transport/http2_client.go
Expand Up @@ -35,6 +35,7 @@ import (

"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/syscall"
"google.golang.org/grpc/keepalive"
Expand Down Expand Up @@ -397,11 +398,15 @@ func (t *http2Client) getPeer() *peer.Peer {

func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) ([]hpack.HeaderField, error) {
aud := t.createAudience(callHdr)
authData, err := t.getTrAuthData(ctx, aud)
ri := credentials.RequestInfo{
Method: callHdr.Method,
}
ctxWithRequestInfo := internal.NewRequestInfoContext.(func(context.Context, credentials.RequestInfo) context.Context)(ctx, ri)
authData, err := t.getTrAuthData(ctxWithRequestInfo, aud)
if err != nil {
return nil, err
}
callAuthData, err := t.getCallAuthData(ctx, aud, callHdr)
callAuthData, err := t.getCallAuthData(ctxWithRequestInfo, aud, callHdr)
if err != nil {
return nil, err
}
Expand Down
27 changes: 27 additions & 0 deletions test/end2end_test.go
Expand Up @@ -7484,3 +7484,30 @@ func parseCfg(s string) serviceconfig.Config {
}
return c
}

type methodTestCreds struct{}

func (m methodTestCreds) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
ri := credentials.RequestInfoFromContext(ctx)
return nil, status.Errorf(codes.Unknown, ri.Method)
}

func (m methodTestCreds) RequireTransportSecurity() bool {
return false
}

func (s) TestGRPCMethodAccessibleToCredsViaContextRequestInfo(t *testing.T) {
const wantMethod = "/grpc.testing.TestService/EmptyCall"
ss := &stubServer{}
if err := ss.Start(nil, grpc.WithPerRPCCredentials(methodTestCreds{})); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

if _, err := ss.client.EmptyCall(ctx, &testpb.Empty{}); status.Convert(err).Message() != wantMethod {
t.Fatalf("ss.client.EmptyCall(_, _) = _, %v; want _, _.Message()=%q", err, wantMethod)
}
}