Skip to content

Commit

Permalink
Update to v1 of reflection schema
Browse files Browse the repository at this point in the history
The gRPC folks graduated server reflection to V1 without making any
changes to the schema. This PR updates our schema to v1, fixes the
protobuf package path to match the directory, and retains the gRPC
license (as we should).

Many tools still use the v1alpha1 API, so this PR also adds a second
exported handler constructor for the alpha API. I considered doing this
with HTTP middleware, but it seems more straighforward to just offer two
handlers.
  • Loading branch information
akshayjshah committed Mar 26, 2022
1 parent d9dd98a commit 012dfb0
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 309 deletions.
13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ COPYRIGHT_YEARS := 2022
# Which commit of bufbuild/makego should we source checknodiffgenerated.bash
# from?
MAKEGO_COMMIT := 383cdab9b837b1fba0883948ff54ed20eedbd611
LICENSE_IGNORE := -e internal/proto/connectext

.PHONY: help
help: ## Describe useful make targets
Expand Down Expand Up @@ -45,7 +46,7 @@ lint: $(BIN)/gofmt $(BIN)/buf ## Lint Go and protobuf
@# Configure staticcheck to target the correct Go version and enable
@# ST1020, ST1021, and ST1022.
$(GO) vet ./...
$(BIN)/buf lint
$(BIN)/buf lint --exclude-path internal/proto/connectext

.PHONY: lintfix
lintfix: $(BIN)/gofmt $(BIN)/buf ## Automatically fix some lint errors
Expand All @@ -56,11 +57,19 @@ lintfix: $(BIN)/gofmt $(BIN)/buf ## Automatically fix some lint errors
generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/license-header ## Regenerate code and licenses
rm -rf internal/gen
PATH=$(BIN) $(BIN)/buf generate
@# We want to operate on a list of modified and new files, excluding
@# deleted and ignored files. git-ls-files can't do this alone. comm -23 takes
@# two files and prints the union, dropping lines common to both (-3) and
@# those only in the second file (-2). We make one git-ls-files call for
@# the modified, cached, and new (--others) files, and a second for the
@# deleted files.
@$(BIN)/license-header \
--license-type apache \
--copyright-holder "Buf Technologies, Inc." \
--year-range "$(COPYRIGHT_YEARS)" \
$(shell git ls-files) $(shell git ls-files --exclude-standard --others)
$(shell comm -23 \
<(git ls-files --cached --modified --others --no-empty-directory --exclude-standard | sort -u | grep -v $(LICENSE_IGNORE)) \
<(git ls-files --deleted | sort -u))

.PHONY: upgrade
upgrade: ## Upgrade dependencies
Expand Down
85 changes: 50 additions & 35 deletions grpcreflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,32 @@ import (
"sort"

"github.com/bufbuild/connect"
reflectionv1alpha1 "connectrpc.com/grpcreflect/internal/gen/go/connectext/grpc/reflection/v1alpha"
reflectionv1 "connectrpc.com/grpcreflect/internal/gen/go/connectext/grpc/reflection/v1"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)

// NewHandler constructs an implementation of the gRPC server reflection API.
// It returns an HTTP handler and the path on which to mount it. Note that
// because the reflection API requires bidirectional streaming, the returned
// handler doesn't support HTTP/1.1 (including gRPC-Web requests from a
// browser).
// NewHandler constructs an implementation of v1 of the gRPC server reflection
// API. It returns an HTTP handler and the path on which to mount it.
//
// Note that because the reflection API requires bidirectional streaming, the
// returned handler doesn't support HTTP/1.1. If your server must also support
// older tools that use the v1alpha1 server reflection API, see NewHandlerAlpha.
func NewHandler(reflector *Reflector, options ...connect.HandlerOption) (string, http.Handler) {
const serviceName = "/grpc.reflection.v1alpha.ServerReflection/"
return serviceName, connect.NewBidiStreamHandler(
serviceName+"ServerReflectionInfo",
reflector.serverReflectionInfo,
options...,
)
return newHandler(reflector, "/grpc.reflection.v1.ServerReflection/", options)
}

// NewHandlerAlpha constructs an implementation of v1alpha1 of the gRPC server
// reflection API. It returns an HTTP handler and the path on which to mount
// it.
//
// If your server must support older tools that expect v1alpha1 of the server
// reflection API, you should use NewHandlerAlpha in addition to NewHandler.
func NewHandlerAlpha(reflector *Reflector, options ...connect.HandlerOption) (string, http.Handler) {
// v1 is binary-compatible with v1alpha1, so we only need to change paths.
return newHandler(reflector, "/grpc.reflection.v1alpha1.ServerReflection/", options)
}

// Reflectors implement the underlying logic for gRPC's protobuf server
Expand Down Expand Up @@ -107,8 +114,8 @@ func NewStaticReflector(services ...string) *Reflector {
func (r *Reflector) serverReflectionInfo(
ctx context.Context,
stream *connect.BidiStream[
reflectionv1alpha1.ServerReflectionRequest,
reflectionv1alpha1.ServerReflectionResponse,
reflectionv1.ServerReflectionRequest,
reflectionv1.ServerReflectionResponse,
],
) error {
fileDescriptorsSent := &fileDescriptorNameSet{}
Expand All @@ -121,63 +128,63 @@ func (r *Reflector) serverReflectionInfo(
}
// The server reflection API sends file descriptors as uncompressed
// protobuf-serialized bytes.
response := &reflectionv1alpha1.ServerReflectionResponse{
response := &reflectionv1.ServerReflectionResponse{
ValidHost: request.Host,
OriginalRequest: request,
}
switch messageRequest := request.MessageRequest.(type) {
case *reflectionv1alpha1.ServerReflectionRequest_FileByFilename:
case *reflectionv1.ServerReflectionRequest_FileByFilename:
data, err := r.getFileByFilename(messageRequest.FileByFilename, fileDescriptorsSent)
if err != nil {
response.MessageResponse = newNotFoundResponse(err)
} else {
response.MessageResponse = &reflectionv1alpha1.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &reflectionv1alpha1.FileDescriptorResponse{FileDescriptorProto: data},
response.MessageResponse = &reflectionv1.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &reflectionv1.FileDescriptorResponse{FileDescriptorProto: data},
}
}
case *reflectionv1alpha1.ServerReflectionRequest_FileContainingSymbol:
case *reflectionv1.ServerReflectionRequest_FileContainingSymbol:
data, err := r.getFileContainingSymbol(
messageRequest.FileContainingSymbol,
fileDescriptorsSent,
)
if err != nil {
response.MessageResponse = newNotFoundResponse(err)
} else {
response.MessageResponse = &reflectionv1alpha1.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &reflectionv1alpha1.FileDescriptorResponse{FileDescriptorProto: data},
response.MessageResponse = &reflectionv1.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &reflectionv1.FileDescriptorResponse{FileDescriptorProto: data},
}
}
case *reflectionv1alpha1.ServerReflectionRequest_FileContainingExtension:
case *reflectionv1.ServerReflectionRequest_FileContainingExtension:
msgFQN := messageRequest.FileContainingExtension.ContainingType
extNumber := messageRequest.FileContainingExtension.ExtensionNumber
data, err := r.getFileContainingExtension(msgFQN, extNumber, fileDescriptorsSent)
if err != nil {
response.MessageResponse = newNotFoundResponse(err)
} else {
response.MessageResponse = &reflectionv1alpha1.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &reflectionv1alpha1.FileDescriptorResponse{FileDescriptorProto: data},
response.MessageResponse = &reflectionv1.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &reflectionv1.FileDescriptorResponse{FileDescriptorProto: data},
}
}
case *reflectionv1alpha1.ServerReflectionRequest_AllExtensionNumbersOfType:
case *reflectionv1.ServerReflectionRequest_AllExtensionNumbersOfType:
nums, err := r.getAllExtensionNumbersOfType(messageRequest.AllExtensionNumbersOfType)
if err != nil {
response.MessageResponse = newNotFoundResponse(err)
} else {
response.MessageResponse = &reflectionv1alpha1.ServerReflectionResponse_AllExtensionNumbersResponse{
AllExtensionNumbersResponse: &reflectionv1alpha1.ExtensionNumberResponse{
response.MessageResponse = &reflectionv1.ServerReflectionResponse_AllExtensionNumbersResponse{
AllExtensionNumbersResponse: &reflectionv1.ExtensionNumberResponse{
BaseTypeName: messageRequest.AllExtensionNumbersOfType,
ExtensionNumber: nums,
},
}
}
case *reflectionv1alpha1.ServerReflectionRequest_ListServices:
case *reflectionv1.ServerReflectionRequest_ListServices:
services := r.namer.Names()
serviceResponses := make([]*reflectionv1alpha1.ServiceResponse, len(services))
serviceResponses := make([]*reflectionv1.ServiceResponse, len(services))
for i, name := range services {
serviceResponses[i] = &reflectionv1alpha1.ServiceResponse{Name: name}
serviceResponses[i] = &reflectionv1.ServiceResponse{Name: name}
}
response.MessageResponse = &reflectionv1alpha1.ServerReflectionResponse_ListServicesResponse{
ListServicesResponse: &reflectionv1alpha1.ListServiceResponse{Service: serviceResponses},
response.MessageResponse = &reflectionv1.ServerReflectionResponse_ListServicesResponse{
ListServicesResponse: &reflectionv1.ListServiceResponse{Service: serviceResponses},
}
default:
return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf(
Expand Down Expand Up @@ -323,15 +330,23 @@ func fileDescriptorWithDependencies(fd protoreflect.FileDescriptor, sent *fileDe
return results, nil
}

func newNotFoundResponse(err error) *reflectionv1alpha1.ServerReflectionResponse_ErrorResponse {
return &reflectionv1alpha1.ServerReflectionResponse_ErrorResponse{
ErrorResponse: &reflectionv1alpha1.ErrorResponse{
func newNotFoundResponse(err error) *reflectionv1.ServerReflectionResponse_ErrorResponse {
return &reflectionv1.ServerReflectionResponse_ErrorResponse{
ErrorResponse: &reflectionv1.ErrorResponse{
ErrorCode: int32(connect.CodeNotFound),
ErrorMessage: err.Error(),
},
}
}

func newHandler(reflector *Reflector, serviceName string, options []connect.HandlerOption) (string, http.Handler) {
return serviceName, connect.NewBidiStreamHandler(
serviceName+"ServerReflectionInfo",
reflector.serverReflectionInfo,
options...,
)
}

type extensionResolverOption struct {
resolver ExtensionResolver
}
Expand Down
Loading

0 comments on commit 012dfb0

Please sign in to comment.