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 Brotli compression #563

Merged
merged 12 commits into from
Sep 29, 2021
Merged

Add support for Brotli compression #563

merged 12 commits into from
Sep 29, 2021

Conversation

vbuberen
Copy link
Collaborator

@vbuberen vbuberen commented Feb 14, 2021

📷 Screenshots

Screenshot 2021-02-14 at 19 56 25

Screenshot 2021-02-14 at 19 54 20

📄 Context

There is a very old issue with such feature request #202. Since we are now on OkHttp 4 it is time to add Brotli support.

📝 Changes

  • Added new dependency OkHttp Brotli
  • Updated OkHttpUtils to support uncompress operations for Brotli content

🚫 Breaking

Nothing breaking expected

🛠️ How to test

Run sample and check brotli transaction response.

⏱️ Next steps

I would like to add tests to this feature, but not sure how to do it properly to match our test suit. Would gladly discuss it here or in Slack and appreciate help/directions.

Closes #202

@vbuberen vbuberen added the enhancement New feature or improvement to the library label Feb 14, 2021
@vbuberen vbuberen added this to the 4.0.0 milestone Feb 14, 2021
Copy link
Contributor

@MiSikora MiSikora left a comment

Choose a reason for hiding this comment

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

Great addition! I left small comments. Also, one minor thing, we should mention dependencies and the feature in the changelog.

As for the question about testing – unfortunately package that we depend on does not offer BrotliOutputStream so it is not that straightforward. I see two ways we can test this.

  1. Depend on some other package for tests only and use it to compress the data like in gzip tests. I found this with some quick Google search but there might be better options.
  2. Use hardcoded Brotli bytes and use them for requests and responses in tests and verify plain–text in transactions.

library/build.gradle Outdated Show resolved Hide resolved
this
internal fun Source.uncompress(headers: Headers) = when {
headers.containsGzip -> gzip()
headers.containsBrotli -> BrotliInputStream(this.buffer().inputStream()).source()
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should add a list of supported compression types in ChuckerInterceptor KDoc. I'm saying that because BinaryDecoder mentions that it the body will be gunzipped but now we also understand Brotli. This way in BinaryDecoder KDoc we could just mention that body will be uncompressed and to see which compression types are supported see ChuckerInterceptor.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. We should mention in KDoc or add something in our Readme, because now it is not clear for end users that Chucker supports compressed payloads, so even having Gzip is something unclear.

@vbuberen
Copy link
Collaborator Author

Also, one minor thing, we should mention dependencies and the feature in the changelog.

🤦🏻 Forgot about it despite planning to add a few hours ago. Switched to that other issue with bodies and forgot. Will fix it.

2. Use hardcoded Brotli bytes and use them for requests and responses in tests and verify plain–text in transactions.

So far I looked at this direction due to having no classes available to create Brotli compression ourselves.
Might take inspiration from https://github.com/square/okhttp/blob/master/okhttp-brotli/src/test/java/okhttp3/brotli/BrotliInterceptorTest.kt

@MiSikora
Copy link
Contributor

One more thing that we have to consider is sharing cURL. Should we add support for Brotli there as well?

@cortinico
Copy link
Member

One more thing that we have to consider is sharing cURL. Should we add support for Brotli there as well?

Potentially yes, but I don't think is a hard blocker for this PR.

@vbuberen
Copy link
Collaborator Author

One more thing that we have to consider is sharing cURL. Should we add support for Brotli there as well?

Yes, we should add add there as well. Can do in a follow-up PR to not block other opened PRs.

@cortinico
Copy link
Member

Can we merge this?

@vbuberen
Copy link
Collaborator Author

vbuberen commented Sep 22, 2021

Can we merge this?

Not yet. Want to do a few minor updates later today. Updated the PR since decided to revive the development a little bit)

@vbuberen
Copy link
Collaborator Author

vbuberen commented Sep 22, 2021

Now it can be checked and merged if everything is fine.

I decided not to test uncompression since it would mean testing brotli library, which is already tested quite well.

@MiSikora
Copy link
Contributor

I decided not to test uncompression since it would mean testing brotli library, which is already tested quite well.

I think a sanity test that a Brotli compressed response is understood by Chucker would be helpful. It's easy to make a mistake during refactoring or some other changes and introduce a bug this way.

@vbuberen
Copy link
Collaborator Author

vbuberen commented Sep 27, 2021

Added such test, which replicates the same test in Brotli library. @cortinico @MiSikora Please take another look.

MiSikora
MiSikora previously approved these changes Sep 27, 2021
@MiSikora
Copy link
Contributor

MiSikora commented Sep 27, 2021

I approved accidentally because I read test code wrongly. What I meant by test is to set Brotli encoded body in MockWebServer and then check if it is uncompressed by ChuckerInterceptor. Something similar to this.

@ParameterizedTest
@EnumSource(value = ClientFactory::class)
fun `gzipped response body is gunzipped for Chucker`(factory: ClientFactory) {
val bytes = Buffer().apply { writeUtf8("Hello, world!") }
val gzippedBytes = Buffer().apply {
GzipSink(this).use { sink -> sink.write(bytes, bytes.size) }
}
server.enqueue(MockResponse().addHeader("Content-Encoding: gzip").setBody(gzippedBytes))
val request = Request.Builder().url(serverUrl).build()
val client = factory.create(chuckerInterceptor)
client.newCall(request).execute().readByteStringBody()
val transaction = chuckerInterceptor.expectTransaction()
assertThat(transaction.isRequestBodyEncoded).isFalse()
assertThat(transaction.responseBody).isEqualTo("Hello, world!")
}
@ParameterizedTest
@EnumSource(value = ClientFactory::class)
fun `gzipped response body is gunzipped for consumer`(factory: ClientFactory) {
val bytes = Buffer().apply { writeUtf8("Hello, world!") }
val gzippedBytes = Buffer().apply {
GzipSink(this).use { sink -> sink.write(bytes, bytes.size) }
}
server.enqueue(MockResponse().addHeader("Content-Encoding: gzip").setBody(gzippedBytes))
val request = Request.Builder().url(serverUrl).build()
val client = factory.create(chuckerInterceptor)
val responseBody = client.newCall(request).execute().readByteStringBody()!!
assertThat(responseBody.utf8()).isEqualTo("Hello, world!")
}

@MiSikora MiSikora dismissed their stale review September 27, 2021 16:26

Dismissed due to my error. See comment for more info.

@vbuberen
Copy link
Collaborator Author

Ok, understood. Agree, makes sense to test in ChuckerInterceptorTest as well

@vbuberen
Copy link
Collaborator Author

@MiSikora I am not sure I understand how to test the case as it is done for Gzip:

@ParameterizedTest
@EnumSource(value = ClientFactory::class)
fun `gzipped response body is gunzipped for consumer`(factory: ClientFactory) {
val bytes = Buffer().apply { writeUtf8("Hello, world!") }
val gzippedBytes = Buffer().apply {
GzipSink(this).use { sink -> sink.write(bytes, bytes.size) }
}
server.enqueue(MockResponse().addHeader("Content-Encoding: gzip").setBody(gzippedBytes))
val request = Request.Builder().url(serverUrl).build()
val client = factory.create(chuckerInterceptor)
val responseBody = client.newCall(request).execute().readByteStringBody()!!
assertThat(responseBody.utf8()).isEqualTo("Hello, world!")
}

For Chucker it is clearly uncompressed and I added a test case for it. But for consumer - should we get the same uncompressed result with plain text there as well as it is in Gzip case? Quite confused on how to test it right, since in case of simple replication of such Gzip case I am getting that responseBody being encoded string I used initially. Not sure it should be like that. Could you help with that?

@MiSikora
Copy link
Contributor

Quite confused on how to test it right, since in case of simple replication of such Gzip case I am getting that responseBody being encoded string I used initially. Not sure it should be like that. Could you help with that?

IMO that's a good thing. Gzip is built-in into OkHttp so users get decoded responses. Brotli, on the other hand, is not. So this test shows that ChuckerInterceptor does not interfere with the user's configuration and they need to install a Brotli interceptor themselves to read responses.

@vbuberen
Copy link
Collaborator Author

Understood, thanks.

So we are adding a second test which in contrary to gzip should check that the response stays untouched and still compressed with brotli, right?

@MiSikora
Copy link
Contributor

Correct.

@vbuberen vbuberen merged commit 45292da into develop Sep 29, 2021
@vbuberen vbuberen deleted the feature/brotli branch September 29, 2021 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or improvement to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support brotli compression
3 participants