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 Protobuf serialization in WebFlux [SPR-15776] #20331

Closed
spring-projects-issues opened this issue Jul 15, 2017 · 11 comments
Closed

Support Protobuf serialization in WebFlux [SPR-15776] #20331

spring-projects-issues opened this issue Jul 15, 2017 · 11 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jul 15, 2017

Greg Laubenstein opened SPR-15776 and commented

Currently there is no built in means to serialize and deserialize data objects with RPC/protobuf with reactive-web/webflux.


Affects: 5.0.4

Issue Links:

Referenced from: commits 3899b7a, 36bbbab, 36a07aa, 6b6384a

4 votes, 13 watchers

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Greg Laubenstein Protobuf, even in GRPC, seems mainly based on InputStream / OutputStream blocking APIs. Are you aware of some non-blocking API we could leverage for proper integration in WebFlux?

@spring-projects-issues
Copy link
Collaborator Author

Greg Laubenstein commented

I believe: https://github.com/zeroc-ice has a generated async API.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Feb 4, 2018

Vitaly Romashkin commented

Hi, guys. And what about Thrift RPC? Is it possible to make integration async Thrift RPC with Webflux? Created additional feature request - #21011

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Indeed the gRPC Marshaller uses InputStream and OutputStream. Of course those represent gRPC frames, so the input or output stream is naturally broken up into discrete messages. To achieve a similar effect, I found some relevant information on streaming and large data sets on the official Protobuf site:

If you want to write multiple messages to a single file or stream, it is up to you to keep track of where one message ends and the next begins. TheThe Protocol Buffer wire format is not self-delimiting, so protocol buffer parsers cannot determine where a message ends on their own. The easiest way to solve this problem is to write the size of each message before you write the message itself. When you read the messages back in, you read the size, then read the bytes into a separate buffer, then parse from that buffer.

Then I looked for some media types to indicate protobuf streming with the length-delimited format, and found a couple examples:

1. https://prometheus.io/docs/instrumenting/exposition_formats/
application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited

1. https://www.charlesproxy.com/documentation/using-charles/protocol-buffers/
application/x-protobuf; desc="http://localhost/Model.desc"; messageType="com.xk72.sample.PurchaseOrder"; delimited=true

There doesn't seem to be a standard but we can look around some more and pick something. After all supported media types are easy to customize.

For the implementation, some useful notes here led me to MessageLite#mergeFrom which checks the size and returns true or false depending on whether it has read the full message or not. We probably still have to do extra work to buffer data chunks until a full message is parsed, but that hopefully not too bad.

Using "protobuf" and "delimited" as keywords, there may be more insight or examples out there for all of the above, but it seems we have enough here to give it a try.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Scheduling tentatively for 5.1 RC1 to give this a try.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 8, 2018

Rossen Stoyanchev commented

I've updated the title a bit, since I think this ticket is about Protobuf (de)serialization over HTTP, not as part of gRPC. Let me know if I've misunderstood, but in any case we have a separate ticket (#20905) for gRPC.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Rossen Stoyanchev Arjen Poutsma Brian Clozel I have worked on a draft commit implementing such support.

Streams are expected to use delimited Protobuf messages with the size of each message specified before the message itself as suggested by Rossen. Single values are expected to use regular Protobuf message format (without the size prepended before the message).
I have copied the behavior of mergeDelimitedFrom / writeDelimitedTo instead of using directly these high-level methods because it allows us to retrieve the size of the message before parsing it which is useful for buffer allocation and parsing + we use the same low level Protobuf primitives. It also give us the opportunity to read/write the message using ByteBuffer based Protobuf API instead of InputStream / OutputStream one, which is likely to be more efficient.

In addition to battle test stream decoding, here is the list of open questions I have identified:

  • Should we use X-Protobuf-Schema and X-Protobuf-Message like Spring MVC, or customized Content-Type ?
  • If we should use customized Content-Type, we need to think how can we specify delimited=true only for streams without breaking current API which seems not obvious. What about using application/stream+x-protobuf instead ?
  • Support text format (and maybe JSON) at least on encoding side
  • Does text/JSON stream decoding makes sense?

Thanks in advance for your feedback.

@spring-projects-issues
Copy link
Collaborator Author

Piotr Dudkiewicz commented

Sébastien Deleuze, thanks for draft commit, I've adapted it in my project and it works well.

I've found one issue though - when service returns default instance of protobuf message (which is serialized to an empty byte array by protobuf), WebClient decodes it to an empty Mono, not to Mono with default instance as I would expect (like gRPC does).

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Piotr Dudkiewicz Reading https://developers.google.com/protocol-buffers/docs/proto3#default and thinking about Mono semantics, this is not obvious to me that we should return a Mono with default instance, especially if we have no way on decoding side to differentiate it with empty Mono. Any thoughts?

@spring-projects-issues
Copy link
Collaborator Author

Piotr Dudkiewicz commented

Indeed there is no way to differentiate. If webmvc version of protobuf decoder works the same way
(meaning it doesn't return default instance of message) I guess it's fine for now.
It would be nice to add some info about this behaviour in docs though.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

An improved version of Protobuf support will be available in Spring Framework 5.1 RC2, see this commit for more detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants