-
Notifications
You must be signed in to change notification settings - Fork 37
Added native Brotli support. #92
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
Conversation
* Native support if Brotli is available in Node. * Separate options via `brotliOptions`. * `brotliOptions` is `null` => skip Brotli. * New CI targets to test more platforms. * README is amended with the Brotli-specific docs. * New comprehensive tests for all new functionality.
This is a minimalistic PR for Brotli with all docs and tests. It is backward compatible, allows suppressing It will take care of #21 and will bring us in line with modern versions of Node (Brotli works starting from Node 10, we are at 13 now). |
Codecov Report
@@ Coverage Diff @@
## master #92 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 1 1
Lines 32 36 +4
Branches 15 19 +4
=========================================
+ Hits 32 36 +4
Continue to review full report at Codecov.
|
Just in case — differences with #80:
|
@jonathanong is this safe to land? Would love to have Brotli support for Every.org! |
If you don't have a caching layer above this, be careful... Brotli is much slower than gz if you want better compression, and even if you relax the compression level, still slower than higher level gz for less compression. Brotli is generally best for ahead of time compression of static assets. I went through the trouble of creating a brotli fork myself to work on, and once I tested the impact it wasn't good. |
The patch is about a choice. Nobody forces people to use deflate, gzip, or brotli. If a certain compression makes sense for your particular setup (cache or not, or CloudFront, or any other distribution technologies) — it is there with no extra dependencies. |
@tracker1 it is not. You probably think about zopfli. Brotli is faster than gzip on moderate compression levels still giving better results see https://engineering.linkedin.com/blog/2017/05/boosting-site-speed-using-brotli-compression fort example (my tests show the same) Another excessive test: https://community.centminmod.com/threads/round-3-compression-comparison-benchmarks-zstd-vs-brotli-vs-pigz-vs-bzip2-vs-xz-etc.17259/ |
index.js
Outdated
@@ -29,7 +32,7 @@ const encodingMethods = { | |||
*/ | |||
|
|||
module.exports = (options = {}) => { | |||
let { filter = compressible, threshold = 1024 } = options | |||
let { brotliOptions, filter = compressible, threshold = 1024 } = options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd prefer if it were just options.brotli
but i will leave it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am totally open to change it. Please think about it and if that's your preference I will rename.
Personally I don't like brotliOptions
either — too long.
index.js
Outdated
const encoding = ctx.acceptsEncodings('gzip', 'deflate', 'identity') | ||
if (!encoding) ctx.throw(406, 'supported encodings: gzip, deflate, identity') | ||
let encoding = null | ||
if (brotliSupport && brotliOptions !== null) encoding = ctx.acceptsEncodings('br') // 1st choice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this first choice? shouldn't we honor what the client requests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. we should be doing like...
const acceptedEncodings = ['gzip', 'deflate', 'identity']
if (brotliSupport && brotliOptions !== null) acceptedEncodings.unshift('br')
const encoding = ctx.acceptsEncodings(...acceptedEncodings)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is my only concern as this is implementation is technically against specifications
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I will update the PR with:
brotliOptions
⇒brotli
- Rely on
acceptsEncodings()
for selecting the encoding.
Please let me know, if you change your mind on number 1 above.
A sidenote: when I was working on the patch I looked at #80 and it used a similar mechanism for other compressors: options.gzip
, options.deflate
. Your proposal options.brotli
follows the same pattern. Do you want me to add a support for options.XXX
, where XXX
is a compressor name as defined in the standard? It could be a separate PR, if that's what you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess for your sidenote, we should just stick with either br
or brotli
(not both) in our code except for acceptsEncodings
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, why not.
* `brotliOptions` => `br` * `options.XXX` style for custom options, where XXX is an encoding: br, gzip, or deflate. * One test always fails because brotli is never selected. * Obviously I can force it for the test, but... * No PR is updated yet pending the decision on how to proceed.
I updated my code as we discussed above, one test has failed, and now I remember why I did this two-step The standard defines
Does the order imply the client's preference? No. In order to do that the standard defines an ability to add weights using To wit:
My patch gives an edge to I considered other ways to specify my preference with parameters:
I decided against this approach and decided to handle it differently (see my patch). Obviously this problem is beyond simple engineering — we can do it this way or that way. What is your position on that? I prepared the new code as discussed. I hold off on it until the decision is made. You can look at it here: uhop@8f073b2 PS: The name |
Aside:
|
Heh, I forgot that the PR is automatically updated. So you can look at changes here as well. |
i believe |
Look at https://github.com/koajs/compress/pull/92/files#diff-518498ef0535a2e4c194b95d1cff9c3fR341 — this test now fails. It shouldn't. How to repro:
It will execute What am I doing wrong? It almost looks like I need to test for an encoding like that (meta code): const acceptedEncodings = []
// collect encodings, example:
if (brotliIsAvailable && options.br !== null) acceptedEncodings.push('br')
// ...
// now we have something like: ['br', 'gzip', 'deflate', identity']
let encoding = null
for (const encodingName of acceptedEncodings) {
encoding = ctx.acceptsEncodings(encodingName)
if (encoding) break
}
// now we have it |
I'm guessing you are "pushing" after acceptedEncodings.unshift('br')
|
in repl in this repo: > Negotiator = require('negotiator')
[Function: Negotiator] { Negotiator: [Circular] }
> x = new Negotiator({ headers: {'accept-encoding': 'gzip, deflate, br'}})
Negotiator {
request: { headers: { 'accept-encoding': 'gzip, deflate, br' } }
}
> x.encodings(['br', 'gzip'])
[ 'gzip', 'br' ]
> x = new Negotiator({ headers: {'accept-encoding': 'br, gzip, deflate'}})
Negotiator {
request: { headers: { 'accept-encoding': 'br, gzip, deflate' } }
}
> x.encodings(['br', 'gzip'])
[ 'br', 'gzip' ] |
o i c negotiator doesn't prioritize the output based on input if they all have the same weights. i'd rather do that, but let me see if that's according to spec |
another issue is that if the client specifies |
No. I do exactly what you suggested: https://github.com/koajs/compress/pull/92/files#diff-168726dbe96b3ce427e7fedce31bb0bcR56 |
You can see that Negotiator ignores the order of parameters and always returns in the order of |
What does it mean? If tomorrow everybody and their dog decided to support I started to pay attention to
How can we negotiate with Personally I don't care for
|
brotliOptions
.brotliOptions
isnull
=> skip Brotli.