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 and Zstandard compression #1082

Merged
merged 3 commits into from
Jul 23, 2019

Conversation

imiric
Copy link
Contributor

@imiric imiric commented Jul 16, 2019

This was fairly straightforward to add, thanks to the existing code structure.

It unfortunately adds a lot of 3rd party code because of the new dependencies, but I agree with your current convention of keeping that in the repo, since it makes it very visible.

Cheers,

Ivan

Closes: #776, #1080

@CLAassistant
Copy link

CLAassistant commented Jul 16, 2019

CLA assistant check
All committers have signed the CLA.

@codecov
Copy link

codecov bot commented Jul 17, 2019

Codecov Report

Merging #1082 into master will increase coverage by 0.08%.
The diff coverage is 94.87%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1082      +/-   ##
==========================================
+ Coverage   72.79%   72.87%   +0.08%     
==========================================
  Files         133      133              
  Lines        9890     9929      +39     
==========================================
+ Hits         7199     7236      +37     
- Misses       2276     2277       +1     
- Partials      415      416       +1
Impacted Files Coverage Δ
lib/netext/httpext/compression_type_gen.go 86.66% <ø> (ø) ⬆️
lib/netext/httpext/request.go 89.01% <100%> (+0.52%) ⬆️
lib/testutils/httpmultibin.go 94.78% <92.59%> (-0.68%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c854389...f295044. Read the comment docs.

@codecov
Copy link

codecov bot commented Jul 17, 2019

Codecov Report

Merging #1082 into master will increase coverage by 0.12%.
The diff coverage is 93.65%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1082      +/-   ##
==========================================
+ Coverage   72.79%   72.91%   +0.12%     
==========================================
  Files         133      133              
  Lines        9890     9949      +59     
==========================================
+ Hits         7199     7254      +55     
- Misses       2276     2278       +2     
- Partials      415      417       +2
Impacted Files Coverage Δ
lib/netext/httpext/compression_type_gen.go 86.66% <ø> (ø) ⬆️
lib/testutils/httpmultibin.go 94.87% <93.1%> (-0.59%) ⬇️
lib/netext/httpext/request.go 89% <94.11%> (+0.51%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c854389...1ea3827. Read the comment docs.

@codecov
Copy link

codecov bot commented Jul 17, 2019

Codecov Report

Merging #1082 into master will increase coverage by 0.08%.
The diff coverage is 94.87%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1082      +/-   ##
==========================================
+ Coverage   72.79%   72.87%   +0.08%     
==========================================
  Files         133      133              
  Lines        9890     9929      +39     
==========================================
+ Hits         7199     7236      +37     
- Misses       2276     2277       +1     
- Partials      415      416       +1
Impacted Files Coverage Δ
lib/netext/httpext/compression_type_gen.go 86.66% <ø> (ø) ⬆️
lib/netext/httpext/request.go 89.01% <100%> (+0.52%) ⬆️
lib/testutils/httpmultibin.go 94.78% <92.59%> (-0.68%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c854389...f295044. Read the comment docs.

1 similar comment
@codecov
Copy link

codecov bot commented Jul 17, 2019

Codecov Report

Merging #1082 into master will increase coverage by 0.08%.
The diff coverage is 94.87%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1082      +/-   ##
==========================================
+ Coverage   72.79%   72.87%   +0.08%     
==========================================
  Files         133      133              
  Lines        9890     9929      +39     
==========================================
+ Hits         7199     7236      +37     
- Misses       2276     2277       +1     
- Partials      415      416       +1
Impacted Files Coverage Δ
lib/netext/httpext/compression_type_gen.go 86.66% <ø> (ø) ⬆️
lib/netext/httpext/request.go 89.01% <100%> (+0.52%) ⬆️
lib/testutils/httpmultibin.go 94.78% <92.59%> (-0.68%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c854389...f295044. Read the comment docs.

Copy link
Member

@na-- na-- left a comment

Choose a reason for hiding this comment

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

Thanks for this! I've noted a few minor things that need to be changed in inline comments, but otherwise LGTM. The huge brotli dependency is unfortunately unavoidable, so for now we'd have to live with it in our vendor folder and git repo.

case CompressionTypeBr:
brotlireader := brotli.NewReader(resp.Body)
// brotli.Reader doesn't implement io.ReadCloser, so wrap it
resp.Body = ioutil.NopCloser(brotlireader)
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, we need to close the original resp.Body, so we can reuse the network connection. From the golang http docs:

If the returned error is nil, the Response will contain a non-nil Body which the user is expected to close. If the Body is not both read to EOF and closed, the Client's underlying RoundTripper (typically Transport) may not be able to re-use a persistent TCP connection to the server for a subsequent "keep-alive" request.

And now, when I was reviewing this code, I noticed that the old k6 code above for gzip and zlib also has the same error :man_facepalming... zlib.NewReader() and gzip.NewReader() return io.ReadCloser implementations, but they don't actually close the underlying. For gzip:

Close closes the Reader. It does not close the underlying io.Reader.

And for zlib:

Calling Close does not close the wrapped io.Reader originally passed to NewReader.

Copy link
Member

Choose a reason for hiding this comment

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

All of the cases (the new ones and the original) could probably be fixed with a simple wrapper struct around an io.Reader (the compression readers) and an io.ReadCloser (the original body). Its Close() method can optionally close the compression reader (by type asserting if it's actually io.ReadCloser) and then always close the original body (so we can reuse connections). Basically, instead of NopCloser, we should have a OneOrBothCloser 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

It is also very important that we actually read the whole body which might not happen if there are errors (for example in the decoding).
So I propose that a defer with Reading the whole body and closing it after that is added, and for all decoders to get a NopClosers so that they don't close the body prematurely

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestions. Take a look at 3249d6d. I think the added defer at line 215 handles @mstoykov's comment, though I didn't use NopCloser since the decoders need to be closed as well.

lib/testutils/httpmultibin.go Outdated Show resolved Hide resolved
@imiric imiric force-pushed the feat/http-zstd-brotli-compression branch from f295044 to 78154b8 Compare July 17, 2019 19:46
@imiric imiric force-pushed the feat/http-zstd-brotli-compression branch from 78154b8 to 3249d6d Compare July 17, 2019 21:08
@imiric
Copy link
Contributor Author

imiric commented Jul 17, 2019

BTW, I'm a big fan of Conventional Commits, which I used here, but let me know if you prefer plain messages as currently used in the repo.

Copy link
Member

@na-- na-- left a comment

Choose a reason for hiding this comment

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

I can't comment on the previous body closing discussion, since it's "outdated" in the github interface, so I'll do it here. Beside the issue I noted inline about the closure parameters, there's another another very minor one with the current code - if there's no compression of the response, we'll be closing the body twice - on lines 217 (after the closure is fixed) and 256. Though I'm not sure if that is actually a real issue, since maybe the second one will be a no-op?

But what I think @mstoykov meant was that we can just handle the body closing with defers only where it's required, i.e. keep the defer on line 215 (after fixing it), remove the closing on line 256 and add defers for closing only the compression decoders that need closing? To me your current solution seems more readable, and since out of the decompressors only zstd implements io.WriterTo (a faster path io.Copy takes when its source argument supports it), I don't think we need to change it.

Regarding the CLA, we fixed the typo you found in it and now apparently you have to accept it again, sorry 😄

And thanks for mentioning conventional commits, I've added an issue about investigating and potentially adopting them (and alternative commit styles): #1086

rc := &readCloser{resp.Body}
// Ensure that the entire response body is read and closed, e.g. in case of decoding errors
defer func() {
_, _ = io.Copy(ioutil.Discard, resp.Body)
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately this won't work - by the time this deferred function is executed, resp.Body might have been changed by the code below. You need to capture the current value and pass it as an argument to the closure.

@imiric imiric force-pushed the feat/http-zstd-brotli-compression branch from 3249d6d to 1ea3827 Compare July 18, 2019 19:35
Copy link
Member

@na-- na-- left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@na-- na-- requested a review from mstoykov July 19, 2019 11:41
Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

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

LGTM.
I prefer if we don't tie our hands with wrappers which will make the fast path of io.Copy not possible. But we will need some kind of wrapper either way as in my proposed scenario the decompressors would've been getting the wrapped Body ...

While testing I found out we haven't tested the support for unsupported compression algorithms ( ;) ). While it doesn't throw an error and you can decompress in JS (which is what we wanted:)) it also doesn't enter this switch statement as it was expected instead it just sets err and then that isn't checked. Which means that it won't set any kind of error that can be checked in JS.

The whole thing needs even more work because when we get out of the readResponse if there is an error we will currently panic instead of returning the error and the response ... so .. I don't know. Open an issue @na-- ?

@mstoykov mstoykov merged commit f8efe4f into grafana:master Jul 23, 2019
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.

Support for brotli as response content-encoding and request body compression
4 participants