Skip to content

fix: support gRPC endpoints hosted under a URL subpath#7736

Draft
sanish-bruno wants to merge 1 commit intousebruno:mainfrom
sanish-bruno:fix/support-sub-paths
Draft

fix: support gRPC endpoints hosted under a URL subpath#7736
sanish-bruno wants to merge 1 commit intousebruno:mainfrom
sanish-bruno:fix/support-sub-paths

Conversation

@sanish-bruno
Copy link
Copy Markdown
Collaborator

@sanish-bruno sanish-bruno commented Apr 10, 2026

Fixes #7699

Summary

  • Fixed gRPC reflection failing when the server is hosted behind a URL subpath (e.g., grpc://host/api/grpc)
  • Fixed url.toLowerCase() incorrectly lowercasing case-sensitive URL subpaths
  • Fixed grpcurl command protocol detection to use parsed protocol instead of raw URL string matching

Problem

When a gRPC service is exposed behind a URL subpath (e.g., grpc://localhost:4001/api/grpc), Bruno failed with status code 12 (UNIMPLEMENTED). The root cause was that the reflection client did not include the subpath prefix in its method paths — so loading methods via reflection failed before any request could be made.

Additionally, getParsedGrpcUrlObject() called url.toLowerCase() on the entire URL, which could break case-sensitive subpath routing.

Changes

packages/bruno-requests/src/grpc/grpc-client.js:

  • New #applyPathPrefix method: Replaces the GrpcReflection library's internal gRPC client with one whose method paths include the URL subpath prefix. This enables reflection to work behind subpath proxies.
  • #getReflectionClient: Accepts and forwards a pathPrefix parameter.
  • loadMethodsFromReflection: Passes the parsed URL path to #getReflectionClient.
  • getParsedGrpcUrlObject: Removed url.toLowerCase() to preserve case-sensitive subpaths. Protocol is normalized to lowercase separately.
  • generateGrpcurlCommand: Protocol detection now uses the parsed protocol field instead of url.startsWith().

packages/bruno-requests/src/grpc/grpc-client.spec.js:

  • Added tests for subpath inclusion in request paths, case sensitivity preservation, standard URLs without subpath, and channel target extraction.

How it works

For a URL like grpc://localhost:4001/api/grpc:

  1. Channel connects to localhost:4001 (host only, no subpath)
  2. Reflection calls use /api/grpc/grpc.reflection.v1.ServerReflection/ServerReflectionInfo
  3. Request calls use /api/grpc/package.Service/Method
  4. URLs without a subpath continue to work as before

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

- Enhanced the gRPC client to handle URL subpaths, allowing for proper request path construction when connecting to servers behind a subpath.
- Introduced a new method to apply path prefixes to gRPC reflection clients.
- Updated tests to verify the correct inclusion and case sensitivity of subpaths in gRPC request paths.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Walkthrough

Updated gRPC URL parsing to preserve mixed-case URLs and detect protocol from parsed components. Added pathPrefix support to reflection client, enabling method path rewriting for servers hosted behind URL subpaths. Reflection now prefixes service methods with the URL subpath and performs proper channel cleanup.

Changes

Cohort / File(s) Summary
gRPC Client URL Parsing & Reflection
packages/bruno-requests/src/grpc/grpc-client.js
Modified getParsedGrpcUrlObject to lowercase only protocol/host checks while preserving original URL case; updated #getReflectionClient to accept pathPrefix parameter and rewrite service method paths via new #applyPathPrefix helper; added channel closure for memory leak prevention; adjusted generateGrpcurlCommand to check parsed protocol field instead of URL string patterns.
gRPC Client Test Suite
packages/bruno-requests/src/grpc/grpc-client.spec.js
Enhanced mock makeUnaryRequest to capture path arguments; added test suite validating URL subpath preservation in gRPC request paths, case sensitivity, channel host correctness, and behavior when subpath is absent.

Sequence Diagram(s)

sequenceDiagram
    participant Client as GrpcClient
    participant Parser as URL Parser
    participant Reflection as GrpcReflection
    participant gRPC as gRPC Server

    Client->>Parser: startConnection(url: "....:443/my-subpath")
    Parser-->>Client: { protocol, host, path: "/my-subpath" }
    
    Client->>Reflection: `#getReflectionClient`(url, pathPrefix: "/my-subpath")
    Reflection->>Reflection: Load service definitions
    Reflection->>Reflection: `#applyPathPrefix` (rewrite paths)
    Note over Reflection: /my-subpath prefixed to all method paths
    Reflection-->>Client: Updated reflection client
    
    Client->>gRPC: makeUnaryRequest(path: "/my-subpath/Service/Method")
    gRPC-->>Client: Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size/M

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • naman-bruno
  • bijin-bruno

Poem

🔗 URLs find their path through proxy veils,
Each subpath preserved, case-sensitive trails,
Reflection rewrites with prefix care,
Channels close clean—no leaks anywhere,
gRPC flows true, behind every gate.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding support for gRPC endpoints hosted under a URL subpath, which aligns with the core functionality modifications across grpc-client.js and its test suite.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanish-bruno sanish-bruno marked this pull request as draft April 10, 2026 17:26
@sanish-bruno sanish-bruno changed the title feat: add support for URL subpaths in gRPC requests fix: support gRPC endpoints hosted under a URL subpath Apr 10, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-requests/src/grpc/grpc-client.spec.js`:
- Around line 724-807: The test suite adds URL subpath coverage for
startConnection but misses a similar test for reflection-based method discovery,
so add a spec that calls loadMethodsFromReflection with a request URL containing
a subpath (e.g., 'grpcs://host:443/prefix'), using the same baseCollection and
mocked reflection response, then assert that capturedRequestPath includes the
subpath (e.g.,
'/prefix/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo' or the
appropriate reflection method path), that capturedHost is 'host:443', and that
grpcClient loaded the expected methods; reference loadMethodsFromReflection,
startConnection, capturedRequestPath, capturedHost, and the mocked
grpcClient.methods.setup used in the file to locate where to add the new test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6cc43efb-6fc7-4b8e-be24-560a02b0b8cd

📥 Commits

Reviewing files that changed from the base of the PR and between cd06f28 and 582c9b0.

📒 Files selected for processing (2)
  • packages/bruno-requests/src/grpc/grpc-client.js
  • packages/bruno-requests/src/grpc/grpc-client.spec.js

Comment on lines +724 to +807
describe('URL subpath support in startConnection', () => {
const baseCollection = {
uid: 'test-collection-uid',
pathname: '/test/path'
};

beforeEach(() => {
grpcClient.methods.set('/test.Service/TestMethod', {
path: '/test.Service/TestMethod',
requestStream: false,
responseStream: false,
requestSerialize: (val) => Buffer.from(JSON.stringify(val)),
responseDeserialize: (val) => JSON.parse(val.toString())
});
});

test('should include URL subpath in the gRPC request path', async () => {
const request = {
url: 'grpcs://myserver:443/my-subpath',
uid: 'test-request-uid',
method: '/test.Service/TestMethod',
headers: {},
body: { grpc: [{ content: '{}' }] }
};

await grpcClient.startConnection({
request,
collection: baseCollection
});

expect(capturedRequestPath).toBe('/my-subpath/test.Service/TestMethod');
});

test('should preserve URL subpath case sensitivity', async () => {
const request = {
url: 'grpcs://myserver:443/MySubPath',
uid: 'test-request-uid',
method: '/test.Service/TestMethod',
headers: {},
body: { grpc: [{ content: '{}' }] }
};

await grpcClient.startConnection({
request,
collection: baseCollection
});

expect(capturedRequestPath).toBe('/MySubPath/test.Service/TestMethod');
});

test('should work without subpath (standard URL)', async () => {
const request = {
url: 'grpc://myserver:50051',
uid: 'test-request-uid',
method: '/test.Service/TestMethod',
headers: {},
body: { grpc: [{ content: '{}' }] }
};

await grpcClient.startConnection({
request,
collection: baseCollection
});

expect(capturedRequestPath).toBe('/test.Service/TestMethod');
});

test('should connect to host without subpath in channel target', async () => {
const request = {
url: 'grpcs://myserver:443/my-subpath',
uid: 'test-request-uid',
method: '/test.Service/TestMethod',
headers: {},
body: { grpc: [{ content: '{}' }] }
};

await grpcClient.startConnection({
request,
collection: baseCollection
});

expect(capturedHost).toBe('myserver:443');
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a reflection-specific subpath test as well.

These cases cover startConnection, but the new behavior in this PR also changes loadMethodsFromReflection() for subpath-hosted servers. Without one test around reflection on a URL like grpcs://host/prefix, method discovery can regress while this suite still passes.

As per coding guidelines, **/*.{test,spec}.{js,jsx,ts,tsx}: Add tests for any new functionality or meaningful changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-requests/src/grpc/grpc-client.spec.js` around lines 724 - 807,
The test suite adds URL subpath coverage for startConnection but misses a
similar test for reflection-based method discovery, so add a spec that calls
loadMethodsFromReflection with a request URL containing a subpath (e.g.,
'grpcs://host:443/prefix'), using the same baseCollection and mocked reflection
response, then assert that capturedRequestPath includes the subpath (e.g.,
'/prefix/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo' or the
appropriate reflection method path), that capturedHost is 'host:443', and that
grpcClient loaded the expected methods; reference loadMethodsFromReflection,
startConnection, capturedRequestPath, capturedHost, and the mocked
grpcClient.methods.setup used in the file to locate where to add the new test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support gRPC endpoints hosted under a URL subpath

1 participant