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 colons in path for server-side #5676

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

jrhee17
Copy link
Contributor

@jrhee17 jrhee17 commented May 13, 2024

Motivation:

Currently, gRPC verb suffix is supported by introducing a VerbSuffixPathMapping.
There were several issues with this approach:

  • VerbSuffixPathMapping was applied inconsistently.
    • For instance, RouteBuilder path(String pathPattern) applies VerbSuffixPathMapping, but RouteBuilder path(String prefix, String pathPattern) doesn't.
  • In the context of ExactPathMapping, VerbSuffixPathMapping was acting as a workaround to support colons in the path. Additionally the support for colons is incomplete since only the last colon is correctly handled.
    • Another side-effect of this is route collisions are incorrectly reported since the verb suffix isn't taken into account.
  • Continued support of VerbSuffixPathMapping makes supporting colons natively difficult.
  • Misc) A path such ending with an escape character failed to start up the server: /path\\

The original motivation for #5613 was that the following patterns weren't supported.

  • /path/{param}:verb
  • /path/literal:verb

I propose that the first case be handled via using REGEX PathMapping types, and the second case be handled by natively supporting colons in our path parameters.
Note that colons are supported per the rfc3986, and we already have an issue #4577.

  1. Supporting /path/{param}:verb

I propose that we support this simply by introducing a new PathMappingType.REGEX. We can simply check if the last PathSegment is a VerbPathSegment. If it is a VerbPathSegment, then we can just use PathMappingType.REGEX.

  1. Supporting /path/literal:verb

Internally, we represent colons as parameterized variables both in ParameterizedPathMapping and RoutingTrieBuilder. This makes it difficult to support colons, so I propose that we modify the internal representation to a different character (\0). This character isn't a valid path character per rfc3986, so I believe the impact is minimal.

One side effect of this approach is that ParameterizedPathMapping#paths will return null character delimited paths.
e.g. /v0/:/path -> /v0/\0/path
Having said this, I believe the normal user path doesn't really use this value so it shouldn't matter.

  1. Supporting colons in general

When one calls RouteBuilder#path(String), we determine whether to use ParameterizedPathMapping or ExactPathMapping depending on whether a colon is used.

if (!pathPattern.contains("{") && !pathPattern.contains(":")) {

  • If the colon does not start the segment (e.g. /a:b), it is trivial to assume that the colon should be matched exactly.
  • If the colon does start the segment (e.g. /:param), it is ambiguous whether the user intended this to be a literal or parameter.

For this case, I propose the following:

  • If a colon is used as-is, /:param, then the segment will be used to represent a parameter
  • If a colon is escaped, /\\:param, then the segment will be treated as a literal

Optimization) ExactPathMapping is more performant than ParameterizedPathMapping because a simple equality check is done. I propose as an optimization we modify the condition to check if /: is contained instead of :. As a downside of this approach, the colon escape logic needs to also be added to ExactPathMapping. This can be undone if this logic seems too complicated though.

Modifications:

  • When VerbPathSegment is used, use RegexPathMapping for gRPC transcoding.
  • Removed VerbSuffixPathMapping and related changes in RoutingTrieBuilder, RouteBuilder, and ParameterizedPathMapping
  • Modified RoutingTrieBuilder and ParameterizedPathMapping to use \0 instead of ':' to represent parameterized path segments. This allows us to use ':' in most PathMapping types.
  • Relaxed ParameterizedPathMapping to act like ExactPathMapping when a path segment isn't capturing a path parameter.
  • Replace /\\: to /: in ParameterizedPathMapping and ExactPathMapping to give users an option to use the first colon as a literal.

Result:

@jrhee17 jrhee17 added this to the 1.29.0 milestone May 13, 2024
@@ -74,13 +74,13 @@ service HttpJsonTranscodingTestService {

rpc EchoTimestampAndDuration(EchoTimestampAndDurationRequest) returns (EchoTimestampAndDurationResponse) {
option (google.api.http) = {
get: "/v1/echo/{timestamp}/{duration}"
get: "/v1/echo/timestamp/{timestamp}/{duration}"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These paths can be a source of flakiness since it clashes with the following path:

get: "/v1/echo/response_body/value"

get: "/v1/echo/response_body/repeated"

};
}

rpc EchoTimestamp(EchoTimestampRequest) returns (EchoTimestampResponse) {
option (google.api.http) = {
post: "/v1/echo/{timestamp}:get",
post: "/v1/echo/timestamp/{timestamp}:get",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

post: "/v1/echo/{timestamp}:get",

sortedEndpoints.stream().map(httpEndpoint -> httpEndpoint.spec().route().patternString())
sortedEndpoints.stream()
.map(httpEndpoint -> httpEndpoint.spec().route())
.filter(route -> route.pathType() != RoutePathType.REGEX)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is necessary because GrpcDocServicePluginTest.servicesTest fails.

When the path type is regex, a question mark is included in the path. e.g. /(?<p0>[^/])/hello

However, the regex path is incorrectly cleansed in MethodInfo.

final RequestTarget reqTarget = RequestTarget.forServer(path);

Since this automatic behavior is done for gRPC only, I've added a logic to filter out this case.

Copy link
Member

Choose a reason for hiding this comment

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

Can't we create the method info with the examplepath as is?
I'm a bit worried about the situation that all paths that have verb suffixes wouldn't have the example paths. 🤔

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 see, I guess I didn't think example paths were too important.
Having said this, I do think it's possible to replace regex patterns with the capturing group name at the cost of extra complexity. Pushed a commit to address this.

@jrhee17 jrhee17 marked this pull request as ready for review May 16, 2024 09:43
@jrhee17 jrhee17 changed the title Refactor gRPC verb suffix mapping Support colons in path for server-side May 17, 2024
Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Looks straightforward! Left small suggestions. 👍

sortedEndpoints.stream().map(httpEndpoint -> httpEndpoint.spec().route().patternString())
sortedEndpoints.stream()
.map(httpEndpoint -> httpEndpoint.spec().route())
.filter(route -> route.pathType() != RoutePathType.REGEX)
Copy link
Member

Choose a reason for hiding this comment

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

Can't we create the method info with the examplepath as is?
I'm a bit worried about the situation that all paths that have verb suffixes wouldn't have the example paths. 🤔

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

👍 👍 👍 👍 👍

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

Successfully merging this pull request may close these issues.

None yet

2 participants