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

Consolidation of HTTP codecs with gRPC #1198

Merged
merged 3 commits into from
Dec 3, 2020
Merged

Conversation

tkountis
Copy link
Contributor

@tkountis tkountis commented Nov 5, 2020

Motivation:

Consolidate HTTP codec work with gRPC codecs for a unified API and implementation.

Modifications:

Re-use HTTP codecs for the non streaming version of HTTP.
Adhere to the same changes applied for HTTP, to allow for codec ordering.

Result:

Unified API & implementation between HTTP. & gRPC.

@tkountis tkountis self-assigned this Nov 5, 2020
* API for <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#message-encoding"> message
* coding schemes</a>.
*/
public interface GrpcMessageEncoding {
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 am on the fence about removing this. I guess we could keep this around and use this interface for gRPC (delegating to the HTTP codec), allowing for logic separation if we need it in the future. @idelpivnitskiy thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Can you please clarify why you expect the need for gRPC-specific type?

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 can't think of something, that's why I removed it.
I am just considering whether keeping it allows for some flexibility on doing changes to one without affecting both.

Copy link
Member

@idelpivnitskiy idelpivnitskiy Nov 6, 2020

Choose a reason for hiding this comment

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

After thinking about it more, I'm agreed, it may be safer to have protocol-specific types that delegate the actual compression/decompression work to protocol-agnostic types.
We may have servicetalk-content-encoding-api module that will have ContentCodec class and identity, gzip, deflate implementations. Later we can create servicetalk-content-encoding-netty for netty-based implementations.

Then we may have HttpContentEncoding for http-api and GrpcMessageEncoding for grpc modules. Each will delegate to the necessary API of ContentCodec + have an additional method to set up required header value:

For HttpContentEncoding:

default HttpContentEncoding populateHeaders(final HttpHeader headers) {
    headers.add(CONTENT_ENCODING, name());
    headers.add(VARY, CONTENT_ENCODING);
    return this;
}

For GrpcMessageEncoding (should it extend HttpContentEncoding?):

@Override
default GrpcMessageEncoding populateHeaders(final HttpHeader headers) {
    headers.set(GRPC_MESSAGE_ENCODING_KEY, name());
    return this;
}

WDYT?

@tkountis tkountis changed the title Grpc encoding concolidation Consolidation of HTTP codecs with gRPC Nov 5, 2020
* API for <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#message-encoding"> message
* coding schemes</a>.
*/
public interface GrpcMessageEncoding {
Copy link
Member

Choose a reason for hiding this comment

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

Can you please clarify why you expect the need for gRPC-specific type?

Base automatically changed from http-compression to main November 19, 2020 10:18
@tkountis tkountis force-pushed the grpc-encoding-concolidation branch 3 times, most recently from 6701c4a to 28b12dd Compare November 23, 2020 13:04
@tkountis
Copy link
Contributor Author

test this please

@tkountis tkountis force-pushed the grpc-encoding-concolidation branch from 28b12dd to c50b348 Compare November 23, 2020 15:41
Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

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

Overall looks great!
Mostly minor comments:


@Override
public long skip(long n) {
int skipped = min(buffer.readableBytes(), (int) min(Integer.MAX_VALUE, n));
Copy link
Member

Choose a reason for hiding this comment

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

buffer.readableBytes() is int. Can we simplify this to (int) min(buffer.readableBytes(), n)?

We should also account for negative n. The original skip method from the superclass does:

if (n <= 0) {
    return 0;
}

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 added the negative guard, but the cast to integer might not be the right way.
Currently the max allowed input is INT_MAX, if I relax that and cast the input, that could mean that an arbitrary input of (long 4294967297 -> int 1) can be used to skip 1 bytes rather than all available bytes.
Unexpected behavior.

Copy link
Member

Choose a reason for hiding this comment

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

You don't need to cast the input, min(buffer.readableBytes(), n) will cast readableBytes to long. Because readableBytes is int, it can not be more than Integer.MAX_VALUE. Then the long result of the min function can safely be cast back to int: (int) min(buffer.readableBytes(), n).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ofc 🤦

* @param length the total length available for reading
* @param allocator the {@link BufferAllocator} to use for allocating auxiliary buffers or the returned buffer
* @return {@link Buffer} the result buffer with the content encoded
*/
Buffer encode(Buffer src, int offset, int length, BufferAllocator allocator);
Buffer encode(Buffer src, int length, BufferAllocator allocator);
Copy link
Member

Choose a reason for hiding this comment

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

Discussed offline and confirmed that we actually need the variant with length for gRPC because the received Buffer may contain more data that we want to encode/decode. In this case, the offset, limit is more standard API approach. Can you please reverth the offset? Sorry for back and forth.

Copy link
Contributor Author

@tkountis tkountis Nov 24, 2020

Choose a reason for hiding this comment

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

It is more standard API, but in our case it seems (to me) more confusing.
Buffer has the reader/writer indexes, which these methods mutate. So we typically mutate the readerIndex after consuming the buffer, to current_readerIndex + limit. If we support offset, that can be different than the readerIndex, (before or after), what happens to the readerIndex if the offset + limit < current_readerIndex? Do we move it back to adhere to the contract or keep it unchanged?
I think since the Buffer API allows easy positioning of the readerIndex, we should allow the caller to rely on that for offsetting.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Good point. Let's discuss offline what should be the default behavior of all codecs and deserializers. We are currently inconsistent. If we will decide to move indexes everywhere, then having only length here is reasonable. If we decide not to move indexes, then offset may be important.

@tkountis tkountis force-pushed the grpc-encoding-concolidation branch from 257b545 to c49ceb4 Compare November 25, 2020 16:06
@tkountis tkountis force-pushed the grpc-encoding-concolidation branch from c49ceb4 to ad3c67e Compare November 25, 2020 17:13
@tkountis
Copy link
Contributor Author

test this please

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

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

A few minor comments and good to go. We can discuss the indexes move and offset param for aggregated API in a follow-up.

@@ -1,5 +1,5 @@
/*
* Copyright © 2018 Apple Inc. and the ServiceTalk project authors
* Copyright © 2020 Apple Inc. and the ServiceTalk project authors
Copy link
Member

Choose a reason for hiding this comment

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

We should not override the year, we can append or change a region. Examples:
2018 -> 2018, 2020
2019 -> 2019-2020
2018-2019 -> 2018-2020

final Buffer dst = allocator.newBuffer(chunkSize);
DeflaterOutputStream output = null;
try {
output = newDeflaterOutputStream(Buffer.asOutputStream(dst));

if (src.hasArray()) {
output.write(src.array(), src.arrayOffset() + src.readerIndex(), length);
src.readerIndex(src.readerIndex() + length);
Copy link
Member

Choose a reason for hiding this comment

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

Good catch to make both paths consistent 🎖️

@@ -246,6 +262,7 @@ public void onNext(@Nullable final Buffer src) {
// Not enough data to decompress, ask for more
subscription.request(1);
} catch (Exception e) {
LOGGER.error("Error while decoding with " + name(), e);
Copy link
Member

Choose a reason for hiding this comment

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

Here and in all other places: consider using logger API to build a String instead of concatenating manually:
LOGGER.error("Error while decoding with {}", name(), e);


@Override
public long skip(long n) {
int skipped = min(buffer.readableBytes(), (int) min(Integer.MAX_VALUE, n));
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to cast the input, min(buffer.readableBytes(), n) will cast readableBytes to long. Because readableBytes is int, it can not be more than Integer.MAX_VALUE. Then the long result of the min function can safely be cast back to int: (int) min(buffer.readableBytes(), n).

@@ -67,6 +72,7 @@ default Buffer decode(Buffer src, BufferAllocator allocator) {

/**
* Take a {@link Buffer} and decode its contents resulting in a {@link Buffer} with the decoded content.
* This call increases the {@code readerIndex} of the {@code src} with the number of bytes read {@code length}.
Copy link
Member

Choose a reason for hiding this comment

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

Let's defer adding these details in javadoc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we concluded on moving indexes, I kept these changes too.
I think this is now consistent with other Buffer APIs.

* @param length the total length available for reading
* @param allocator the {@link BufferAllocator} to use for allocating auxiliary buffers or the returned buffer
* @return {@link Buffer} the result buffer with the content encoded
*/
Buffer encode(Buffer src, int offset, int length, BufferAllocator allocator);
Buffer encode(Buffer src, int length, BufferAllocator allocator);
Copy link
Member

Choose a reason for hiding this comment

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

Good point. Let's discuss offline what should be the default behavior of all codecs and deserializers. We are currently inconsistent. If we will decide to move indexes everywhere, then having only length here is reasonable. If we decide not to move indexes, then offset may be important.

* List of supported {@link ContentCodec}s for this {@link GrpcSerializationProvider}.
* Content codings will be used to encoded and decode gRPC messages according to configuration of client and server.
*
* @return list of supported {@link ContentCodec}s for this {@link GrpcSerializationProvider}
Copy link
Member

Choose a reason for hiding this comment

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

This is not fixed, reminder :)

@tkountis tkountis merged commit bb2a77c into main Dec 3, 2020
@tkountis tkountis deleted the grpc-encoding-concolidation branch December 3, 2020 23:41
@tkountis tkountis restored the grpc-encoding-concolidation branch December 3, 2020 23:42
@tkountis tkountis deleted the grpc-encoding-concolidation branch December 4, 2020 00:00
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.

2 participants