This is an automated email from the ASF dual-hosted git repository.

AlexStocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git


The following commit(s) were added to refs/heads/develop by this push:
     new 22afda40d fix(grpcproxy): load local proto files under the default 
AUTO strategy (#1032)
22afda40d is described below

commit 22afda40d593c35ed355829676249eafd4fbd829
Author: Yui(ゆい) <[email protected]>
AuthorDate: Sun Sep 6 18:01:31 2026 +0900

    fix(grpcproxy): load local proto files under the default AUTO strategy 
(#1032)
    
    `initDescriptorSource` only called `initFileDescriptorSource` for the LOCAL
    strategy, so under AUTO — the default one (`default:"auto"` on
    `descriptor_source_strategy`) — the local proto files were never loaded.
    `getDescriptorCompose` then assigned that nil `*fileSource` to the
    `DescriptorSource` interface field, which yields a non-nil interface 
holding a
    nil pointer. The documented `file + reflection` fallback therefore 
dereferenced
    nil instead of returning an error, and `compositeSource.FindSymbol` 
panicked on
    every request whose server-reflection lookup failed.
    
    Load the local proto files for AUTO as well as LOCAL, keep a nil file 
source out
    of the interface, and guard the `compositeSource` fallbacks so a missing 
file
    source surfaces an error. `getFileDescriptorCompose` now reports an unusable
    `path` instead of handing back a nil source, and `AllExtensionsForType` 
returns
    the file-source result rather than computing and discarding it when there 
is no
    reflection source.
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 pkg/filter/http/grpcproxy/descriptor.go        |  18 +++-
 pkg/filter/http/grpcproxy/descriptor_source.go |  51 ++++++----
 pkg/filter/http/grpcproxy/descriptor_test.go   | 129 +++++++++++++++++++++++++
 3 files changed, 174 insertions(+), 24 deletions(-)

diff --git a/pkg/filter/http/grpcproxy/descriptor.go 
b/pkg/filter/http/grpcproxy/descriptor.go
index 495b371d6..2e0abcdff 100644
--- a/pkg/filter/http/grpcproxy/descriptor.go
+++ b/pkg/filter/http/grpcproxy/descriptor.go
@@ -111,14 +111,22 @@ func (dr *Descriptor) getDescriptorCompose(ctx 
context.Context, cfg *Config) (De
 
        cs := &compositeSource{}
        cs.reflection, err = dr.getServerDescriptorSourceCtx(ctx, cfg)
-       cs.file = dr.getFileSource()
+       // Never store a nil *fileSource: it would leave cs.file a non-nil 
interface
+       // holding a nil pointer, so the fallback in compositeSource would 
dereference
+       // it instead of reporting that the local proto files are unavailable.
+       if fs := dr.getFileSource(); fs != nil {
+               cs.file = fs
+       }
 
        return cs, err
 }
 
 func (dr *Descriptor) initDescriptorSource(cfg *Config) *Descriptor {
 
-       if cfg.DescriptorSourceStrategy.String() == LOCAL {
+       switch strings.ToLower(cfg.DescriptorSourceStrategy.String()) {
+       case LOCAL, AUTO:
+               // AUTO is `file + reflection`, so the local proto files have 
to be loaded
+               // as well, otherwise the fallback of the reflection lookup has 
no source.
                dr.initFileDescriptorSource(cfg)
        }
 
@@ -248,7 +256,11 @@ func (dr *Descriptor) getMethodDescriptor(source 
DescriptorSource, cc *grpc.Clie
 
 func (dr *Descriptor) getFileDescriptorCompose(ctx context.Context, cfg 
*Config) (DescriptorSource, error) {
        dr.initFileDescriptorSource(cfg)
-       return dr.getFileSource(), nil
+       fs := dr.getFileSource()
+       if fs == nil {
+               return nil, errors.New("the local proto file descriptor source 
is not available")
+       }
+       return fs, nil
 }
 
 func (dr *Descriptor) initFileDescriptorSource(cfg *Config) *Descriptor {
diff --git a/pkg/filter/http/grpcproxy/descriptor_source.go 
b/pkg/filter/http/grpcproxy/descriptor_source.go
index 06e6bf9df..7cf05711f 100644
--- a/pkg/filter/http/grpcproxy/descriptor_source.go
+++ b/pkg/filter/http/grpcproxy/descriptor_source.go
@@ -160,36 +160,45 @@ func (cs *compositeSource) FindSymbol(fullyQualifiedName 
string) (desc.Descripto
                }
        }
 
+       if cs.file == nil {
+               return nil, fmt.Errorf("could not found symbol %v", 
fullyQualifiedName)
+       }
        return cs.file.FindSymbol(fullyQualifiedName)
 }
 
 func (cs *compositeSource) AllExtensionsForType(typeName string) 
([]*desc.FieldDescriptor, error) {
 
        if cs.reflection == nil {
-               fileExts, err := cs.file.AllExtensionsForType(typeName)
-               if err != nil {
-                       return fileExts, nil
-               }
-       } else {
-               exts, err := cs.reflection.AllExtensionsForType(typeName)
-               if err != nil {
-                       return cs.file.AllExtensionsForType(typeName)
-               }
-               tags := make(map[int32]bool)
-               for _, ext := range exts {
-                       tags[ext.GetNumber()] = true
+               if cs.file == nil {
+                       return nil, nil
                }
+               return cs.file.AllExtensionsForType(typeName)
+       }
 
-               fileExts, err := cs.file.AllExtensionsForType(typeName)
-               if err != nil {
-                       return exts, nil
-               }
-               for _, ext := range fileExts {
-                       if !tags[ext.GetNumber()] {
-                               exts = append(exts, ext)
-                       }
+       exts, err := cs.reflection.AllExtensionsForType(typeName)
+       if err != nil {
+               if cs.file == nil {
+                       return nil, err
                }
+               return cs.file.AllExtensionsForType(typeName)
+       }
+       if cs.file == nil {
                return exts, nil
        }
-       return nil, nil
+
+       tags := make(map[int32]bool)
+       for _, ext := range exts {
+               tags[ext.GetNumber()] = true
+       }
+
+       fileExts, err := cs.file.AllExtensionsForType(typeName)
+       if err != nil {
+               return exts, nil
+       }
+       for _, ext := range fileExts {
+               if !tags[ext.GetNumber()] {
+                       exts = append(exts, ext)
+               }
+       }
+       return exts, nil
 }
diff --git a/pkg/filter/http/grpcproxy/descriptor_test.go 
b/pkg/filter/http/grpcproxy/descriptor_test.go
new file mode 100644
index 000000000..5e5876b3b
--- /dev/null
+++ b/pkg/filter/http/grpcproxy/descriptor_test.go
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package grpcproxy
+
+import (
+       "context"
+       "os"
+       "path/filepath"
+       "testing"
+       "time"
+)
+
+import (
+       "github.com/jhump/protoreflect/desc"            //nolint:staticcheck // 
legacy descriptor API used by grpcproxy.
+       "github.com/jhump/protoreflect/desc/protoparse" //nolint:staticcheck // 
legacy parser used by grpcproxy.
+
+       "github.com/stretchr/testify/require"
+
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/credentials/insecure"
+)
+
+import (
+       ct "github.com/apache/dubbo-go-pixiu/pkg/context"
+)
+
+const testGreeterProto = `syntax = "proto3";
+package test;
+
+service Greeter {
+  rpc Hello(Request) returns (Response);
+}
+
+message Request {}
+message Response {}
+`
+
+// writeTestProtoDir writes a proto file to a temporary directory usable as 
Config.Path.
+func writeTestProtoDir(t *testing.T) string {
+       t.Helper()
+
+       dir := t.TempDir()
+       require.NoError(t, os.WriteFile(filepath.Join(dir, "test.proto"), 
[]byte(testGreeterProto), 0o600))
+       return dir
+}
+
+// TestDescriptorAutoStrategyFallsBackToLocalProtoFiles covers the AUTO 
strategy, which is
+// the default one, against a backend that does not implement the server 
reflection API.
+// The documented `file + reflection` fallback has to resolve the method from 
the local
+// proto files instead of dereferencing an uninitialized file source.
+func TestDescriptorAutoStrategyFallsBackToLocalProtoFiles(t *testing.T) {
+       cfg := &Config{DescriptorSourceStrategy: AUTO, Path: 
writeTestProtoDir(t)}
+       descriptor := (&Descriptor{}).initDescriptorSource(cfg)
+       require.NotNil(t, descriptor.getFileSource())
+
+       conn, err := grpc.NewClient(startTestGRPCServer(t), 
grpc.WithTransportCredentials(insecure.NewCredentials()))
+       require.NoError(t, err)
+       t.Cleanup(func() { require.NoError(t, conn.Close()) })
+
+       ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+       defer cancel()
+       source, err := descriptor.getDescriptorSource(context.WithValue(ctx, 
ct.ContextKey(GrpcClientConnKey), conn), cfg)
+       require.NoError(t, err)
+
+       method, err := descriptor.getMethodDescriptor(source, conn, 
"test.Greeter", "Hello")
+       require.NoError(t, err)
+       require.Equal(t, "Hello", method.GetName())
+}
+
+// TestDescriptorLocalStrategyReportsUnloadableProtoFiles keeps a 
misconfigured `path` from
+// producing a descriptor source that only fails once it is dereferenced.
+func TestDescriptorLocalStrategyReportsUnloadableProtoFiles(t *testing.T) {
+       cfg := &Config{DescriptorSourceStrategy: LOCAL, Path: 
filepath.Join(t.TempDir(), "missing")}
+       descriptor := (&Descriptor{}).initDescriptorSource(cfg)
+
+       source, err := descriptor.getDescriptorSource(context.Background(), cfg)
+       require.Error(t, err)
+       require.Nil(t, source)
+}
+
+// TestCompositeSourceFindSymbolWithoutFileSource keeps the fallback safe when 
the local
+// proto files are unavailable altogether.
+func TestCompositeSourceFindSymbolWithoutFileSource(t *testing.T) {
+       cs := &compositeSource{}
+       _, err := cs.FindSymbol("test.Greeter")
+       require.Error(t, err)
+}
+
+// TestCompositeSourceAllExtensionsForTypeWithoutReflection asserts the 
extensions found in
+// the local proto files are returned instead of being computed and then 
discarded.
+func TestCompositeSourceAllExtensionsForTypeWithoutReflection(t *testing.T) {
+       files, err := (protoparse.Parser{
+               Accessor: protoparse.FileContentsFromMap(map[string]string{
+                       "extension.proto": `syntax = "proto2";
+package test;
+
+message Request {
+  extensions 100 to 200;
+}
+
+extend Request {
+  optional string note = 100;
+}
+`,
+               }),
+       }).ParseFiles("extension.proto")
+       require.NoError(t, err)
+
+       cs := &compositeSource{file: &fileSource{files: 
map[string]*desc.FileDescriptor{"extension.proto": files[0]}}}
+       exts, err := cs.AllExtensionsForType("test.Request")
+       require.NoError(t, err)
+       require.Len(t, exts, 1)
+       require.Equal(t, "test.note", exts[0].GetFullyQualifiedName())
+}

Reply via email to