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

Add support for spec compatible paths in generated server code #257

Merged
merged 2 commits into from
Sep 14, 2020

Conversation

ofpiyush
Copy link
Contributor

@ofpiyush ofpiyush commented Jul 30, 2020

Issue #, if available:
#244
Description of changes:

  • Adds support for spec compatible paths in generated server code.
  • Updates protoc to 3.12.4

No backwards incompatible changes have been made. Specifically:

  • Go generated clients haven't been changed and will continue to send requests to the same URL as before.
  • Go generated server will continue to serve on the CamelCased URLs it did before.
  • <ServiceName>PathPrefix constant is unchanged as of now.
  • svc.PathPrefix() still returns the CamelCased value.

Caveat: If someone is using a mux, they'll need to add the literal cased URL for handling manually as the intent was to not add anything to the API surface. For context: start reading from here

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

protoc-gen-twirp/generator.go Outdated Show resolved Hide resolved
protoc-gen-twirp/generator.go Outdated Show resolved Hide resolved
@ofpiyush ofpiyush changed the title Change server to accept both spec compatible and old paths. Change generated server to accept spec compatible method names. Aug 14, 2020
@ofpiyush
Copy link
Contributor Author

protobuf@3.11.0 is not available on brew anymore.

protobuf@3.12.4 didn't seem to break anything so I've updated that in this PR as well.

stdin stdout code in pycompat broke with python 3.8 on my local so I've updated it.

Let me know if I should force install 3.11.0 or change pycompat in any way.

Copy link
Contributor

@dpolansky dpolansky left a comment

Choose a reason for hiding this comment

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

The changes to the protoc version and pycompat seem fine. I had some minor feedback about testing, should be a quick change. Thanks for your help with this!

protoc-gen-twirp/generator.go Outdated Show resolved Hide resolved
@ofpiyush
Copy link
Contributor Author

I've moved test to it's own package. Waiting for word on changes for service name and client hook.

I'll copy paste relevant parts from the comments to have context here.

Server

This will need work from the user as well in cases where they're using a mux. We can manage method names without any changes from the user, but service name will need changes from the user.

One possible path which will contain this to 1 user change only, is to add a new method:

example.RegisterHaberdasherServer(mux, handler)

Then we can keep changing code inside that function over releases and eventually into the new major version(if that ever happens) while making sure backwards compatibility is maintained.

Client

The next best option I have is to generate a compatibility client hook and use a map to override URL path like I did on the test here.

User experience could look something like:

client := example.NewHaberdasherProtobufClient(url, client, twirp.WithClientHooks(example.CompatibilityHook))

@dpolansky
Copy link
Contributor

Thanks for the refresher @ofpiyush! I read those comments, but didn't quite understand what you meant with regard to the server-side aspect until now. I think we should proceed with just addressing the method names for now, as addressing the service names will be complex and expands the API surface-area, which we try to be cautious about. Does that sound reasonable to you?

With regard to the compatibility hook, I would be hesitant to recommend it as a general solution to this problem because although it gets the job done, the hooks aren't intended for this purpose and it could lead to confusion. Hooks are generally not intended to mutate requests or responses that significantly. For now, I'd feel better about telling users to change their protobuf definitions if this issue affects them than tell them to use hooks to solve the problem. My opinion is that if this issue is painful enough for many Twirp users, we would then release a new major version with either changes to the spec or changes to the Go implementation.


Thanks for moving the test case to a separate package. Once we publish the docs PR, could we just update that comment to direct users there as we discussed previously? Thanks again for addressing the feedback.

@ofpiyush
Copy link
Contributor Author

I think we should proceed with just addressing the method names for now

We could do slightly better and add code to handle it in non-mux cases and we could drop a line of gotcha of documentation for people who'd use a mux.

The next best option to a new method is to add a new PathPrefix constant.

Mux user experience would look something like:

mux.Handle(example.HaberdasherPathPrefix, twirpHandler)
mux.Handle(example.HaberdasherSomeNamingConventionPathPrefix, twirpHandler)

I see the RegisterHaberDasherServer as a better alternative because that is a one time change for the user. It would open up path for other changes as well like #263 and eventually a smoother transition to v6.

because although it gets the job done, the hooks aren't intended for this purpose and it could lead to confusion

Then I should change my test as well. What alternative would you suggest for the test?

The next best option I have is to do it as a http client wrapper. User experience would look something like:

client := example.NewHaberdasherProtobufClient(url, example.HaberdasherCompatibilityClient(client))

For now, I'd feel better about telling users to change their protobuf definitions

I think the fundamental assumption you're working with is that these poorly named services (even in go itself) are not in production or given out to third party.

I come from a world where some APIs are already public, our python servers were live with other python clients before go clients were introduced. We weren't even explicitly following protobuf style guide. We just happened to be fortunate that the random convention I followed isn't incompatible.

I think making that requirement part of the spec would be a much better alternative if adding small amounts of code to the API surface is not acceptable.

@dpolansky
Copy link
Contributor

@ofpiyush I'd like to focus on just making this incremental improvement to handle the method names for now.

Then I should change my test as well. What alternative would you suggest for the test?

The next best option I have is to do it as a http client wrapper. User experience would look something like:

client := example.NewHaberdasherProtobufClient(url, example.HaberdasherCompatibilityClient(client))

I think this is preferable to modifying the request through hooks, so the test could use this method. I'd like to avoid adding additional scope to this PR beyond that though, such as providing the HTTP client implementation in the generated code as you demonstrated here.

@ofpiyush
Copy link
Contributor Author

ofpiyush commented Aug 21, 2020

I realised that if I don't make urls with literal casing for service names as well then even non-mux cases will not work.

Changes:

  • Go generated server now works with complete literal cased routes and go client compatible routes.
  • Updated the comment to better reflect the current stand and linked to the documentation.
  • Updated tests to use a wrapper on http.Client and now we check for literal cased URLs.

Copy link
Contributor

@dpolansky dpolansky left a comment

Choose a reason for hiding this comment

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

@ofpiyush could you squash your commits into a single commit please? the squash-and-merge through github messes with the commit author data unfortunately

@marioizquierdo could you take a look as well?

@marioizquierdo
Copy link
Contributor

On first glance everything looks good. Although it may be useful to think of better variable names and comments to make this non-trivial logic easier to grasp by future contributors. I'll give it a second look tonight, test it locally to make sure everything is good, and think of ways to document this behavior in code.

@ofpiyush ofpiyush force-pushed the spec_compat branch 3 times, most recently from 6f7f7d4 to 57b91e9 Compare August 22, 2020 02:16
@ofpiyush
Copy link
Contributor Author

@dpolansky done.

@marioizquierdo I agree! I tried to improve naming and added a couple of comments. Feel free to send in suggestions. 🙌

Copy link
Contributor

@marioizquierdo marioizquierdo left a comment

Choose a reason for hiding this comment

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

After reviewing more carefully, I understand better what is going on here:

  • Go services will handle literal route paths (according to spec) in addition to CamelCased paths (to support the mistake made by early Twirp versions).
  • Go clients will not change (for now), they still send requests to CamelCased paths.
  • The generated method svc.PathPrefix() will not change, it will still return the CamelCased path.
  • The generated constant <Service>PathPrefix will not change, it will still return the CamelCased path.

With this, we want to make a first step for having Twirp handling routes that have Service and Method names that are not CamelCased, what we are calling "literal casing" here. And while we can't just change Go clients to do that in one step, we can start making the transition easier by handling both literal (spec compliant) and CamelCased (Go-clients compliant) in the server 👍

I added some suggestions to make this case more explicit in the code.

protoc-gen-twirp/generator.go Outdated Show resolved Hide resolved
protoc-gen-twirp/generator.go Outdated Show resolved Hide resolved
@ofpiyush ofpiyush changed the title Change generated server to accept spec compatible method names. Add support for spec compatible paths in generated server code Aug 24, 2020
Copy link
Contributor

@marioizquierdo marioizquierdo left a comment

Choose a reason for hiding this comment

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

Good job. Thank you for your contribution!

Two minor comments, but it is ready to merge when you are ready. Remember to squash your commits into a single commit please if you can, so Github better keeps your author data when merging into the main branch.

protoc-gen-twirp/generator.go Outdated Show resolved Hide resolved
@@ -1078,7 +1097,7 @@ func (t *twirp) generateServerRouting(servStruct string, file *descriptor.FileDe
t.P(`// `, pathPrefixConst, ` is used for all URL paths on a twirp `, servName, ` server.`)
t.P(`// Requests are always: POST `, pathPrefixConst, `/method`)
t.P(`// It can be used in an HTTP mux to route twirp requests along with non-twirp requests on other routes.`)
t.P(`const `, pathPrefixConst, ` = `, strconv.Quote(pathPrefix(file, service)))
t.P(`const `, pathPrefixConst, ` = `, strconv.Quote(pathPrefix(file, stringutils.CamelCase(service.GetName()))))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the same as using servName, but it's fine, being explicit may be more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I've been trying to keep strings and go variables separate so that later one can change these easier if necessary.

@marioizquierdo
Copy link
Contributor

This PR will be part of the upcoming v7 release.
@ofpiyush are you happy with all the changes? I can merge this in the release branch and get it ready with all other changes.

@ofpiyush
Copy link
Contributor Author

Yes. Feel free to merge it.

V7?

@marioizquierdo
Copy link
Contributor

Yes, we are going to skip v6 because there was a previous version draft that was never fully implemented.

@ofpiyush
Copy link
Contributor Author

This PR can safely be added to V5. Can we add client support in V7 as well?

@marioizquierdo
Copy link
Contributor

marioizquierdo commented Sep 10, 2020

Yes, this can safely be added to v5 because it is backwards compatible. And the client support needs to be backwards compatible as well (through a client option, with default value that works like before). I'm including this on next release because it is easier to manage; I'll spend some time this week managing the v7 release and this seems a change worth of mentioning together with the other improvements. If you want, please submit another PR with the client side, so we can bundle it together with the v7 release 🙏 😺 . If you don't have time right now, it's fine, we can handle that part later as well.

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

Successfully merging this pull request may close these issues.

3 participants