├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── accepts.go ├── accepts_test.go ├── adapter.go ├── adapter_test.go ├── compressor.go ├── content_types.go ├── contrib ├── andybalholm │ └── brotli │ │ ├── brotli.go │ │ ├── brotli_race_test.go │ │ ├── brotli_test.go │ │ └── export_test.go ├── compress │ ├── gzip │ │ ├── export_test.go │ │ ├── gzip.go │ │ ├── gzip_race_test.go │ │ └── gzip_test.go │ └── zlib │ │ ├── deflate.go │ │ ├── deflate_race_test.go │ │ ├── deflate_test.go │ │ └── export_test.go ├── gin-gonic │ └── gin │ │ ├── gin.go │ │ ├── go.mod │ │ └── go.sum ├── gofiber │ └── fiber │ │ └── v2 │ │ ├── fiber.go │ │ ├── go.mod │ │ └── go.sum ├── google │ └── cbrotli │ │ ├── brotli.go │ │ ├── brotli_race_test.go │ │ ├── brotli_test.go │ │ └── export_test.go ├── internal │ ├── test_util.go │ └── utils │ │ ├── check.go │ │ └── error_writecloser.go ├── klauspost │ ├── gzip │ │ ├── export_test.go │ │ ├── gzip.go │ │ ├── gzip_race_test.go │ │ └── gzip_test.go │ ├── pgzip │ │ ├── export_test.go │ │ ├── pgzip.go │ │ ├── pgzip_race_test.go │ │ └── pgzip_test.go │ ├── zlib │ │ ├── deflate.go │ │ ├── deflate_race_test.go │ │ ├── deflate_test.go │ │ └── export_test.go │ └── zstd │ │ ├── export_test.go │ │ ├── zstd.go │ │ ├── zstd_race_test.go │ │ └── zstd_test.go ├── labstack │ └── echo │ │ ├── echo.go │ │ ├── go.mod │ │ └── go.sum ├── pierrec │ └── lz4 │ │ ├── export_test.go │ │ ├── lz4.go │ │ ├── lz4_race_test.go │ │ └── lz4_test.go ├── ulikunitz │ └── xz │ │ ├── export_test.go │ │ ├── xz.go │ │ ├── xz_race_test.go │ │ └── xz_test.go └── valyala │ └── gozstd │ ├── export_test.go │ ├── zstd.go │ ├── zstd_race_test.go │ └── zstd_test.go ├── example_test.go ├── go.mod ├── go.sum ├── handle.go ├── prefer.go ├── response_writer.go ├── results.md ├── test ├── dictionary └── server.go └── testdata └── benchmark.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: '>=1.17.0' 20 | 21 | - name: Install brotli 22 | run: sudo apt-get install libbrotli-dev 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: 29 | go test -race -count=5 -coverprofile=coverage.txt -covermode=atomic ./... 30 | 31 | (cd contrib/gin-gonic/gin && go test -race) 32 | 33 | (cd contrib/gofiber/fiber/v2 && go test -race) 34 | 35 | (cd contrib/labstack/echo && go test -race) 36 | 37 | - name: Profile memory 38 | run: 39 | go test -run=^$ -bench=. -short -memprofile mem.prof 40 | 41 | go tool pprof -sample_index alloc_objects -top mem.prof 42 | 43 | go tool pprof -sample_index alloc_space -top mem.prof 44 | 45 | - name: Codecov 46 | uses: codecov/codecov-action@v1.1.1 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /vendor/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: code-of-conduct 3 | version: v1.0 4 | --- 5 | 6 | This code of conduct outlines our expectations for participants within the **CAFxX/httpcompression** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. 7 | 8 | Our open source community strives to: 9 | 10 | * **Be friendly and patient.** 11 | * **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. 12 | * **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. 13 | * **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. 14 | * **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. 15 | * **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. 16 | 17 | ## Definitions 18 | 19 | Harassment includes, but is not limited to: 20 | 21 | - Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation 22 | - Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment 23 | - Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle 24 | - Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop 25 | - Threats of violence, both physical and psychological 26 | - Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm 27 | - Deliberate intimidation 28 | - Stalking or following 29 | - Harassing photography or recording, including logging online activity for harassment purposes 30 | - Sustained disruption of discussion 31 | - Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour 32 | - Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others 33 | - Continued one-on-one communication after requests to cease 34 | - Deliberate “outing” of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse 35 | - Publication of non-harassing private communication 36 | 37 | Our open source community prioritizes marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding: 38 | 39 | - ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’ 40 | - Reasonable communication of boundaries, such as “leave me alone,” “go away,” or “I’m not discussing this with you” 41 | - Refusal to explain or debate social justice concepts 42 | - Communicating in a ‘tone’ you don’t find congenial 43 | - Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions 44 | 45 | 46 | ### Diversity Statement 47 | 48 | We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. 49 | 50 | Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected 51 | characteristics above, including participants with disabilities. 52 | 53 | ### Reporting Issues 54 | 55 | If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via **cafxx+code@strayorange.com**. All reports will be handled with discretion. In your report please include: 56 | 57 | - Your contact information. 58 | - Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please 59 | include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. 60 | - Any additional information that may be helpful. 61 | 62 | After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse. 63 | 64 | ### Attribution & Acknowledgements 65 | 66 | We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration: 67 | 68 | * [Django](https://www.djangoproject.com/conduct/reporting/) 69 | * [Python](https://www.python.org/community/diversity/) 70 | * [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct) 71 | * [Contributor Covenant](http://contributor-covenant.org/) 72 | * [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/) 73 | * [Citizen Code of Conduct](http://citizencodeofconduct.org/) 74 | 75 | This Code of Conduct was based on https://github.com/todogroup/opencodeofconduct 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CAFxX/httpcompression 2 | 3 | This is an open source project started by handful of developers at The New York Times, forked by Carlo Alberto Ferraris and open to the entire Go community. 4 | 5 | All help is appreciated! 6 | 7 | ## Filing issues 8 | 9 | When filing an issue, make sure to answer these five questions: 10 | 11 | 1. What version of Go are you using (`go version`)? 12 | 2. What operating system and processor architecture are you using? 13 | 3. What did you do? 14 | 4. What did you expect to see? 15 | 5. What did you see instead? 16 | 17 | ## Contributing code 18 | 19 | Before submitting changes, please follow these guidelines: 20 | 21 | 1. Check the open issues and pull requests for existing discussions. 22 | 2. Open an issue to discuss a new feature. 23 | 3. Write tests. 24 | 4. Make sure code follows the ['Go Code Review Comments'](https://github.com/golang/go/wiki/CodeReviewComments). 25 | 5. Make sure your changes pass `go test`. 26 | 6. Make sure the entire test suite passes locally and on Travis CI. 27 | 7. Open a Pull Request. 28 | 8. [Squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) after receiving feedback and add a [great commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 29 | 30 | Unless otherwise noted, the httpcompression source files are distributed under the Apache 2.0-style license found in the LICENSE.md file. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016-2017 The New York Times Company 190 | Copyright 2019 Carlo Alberto Ferraris 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang HTTP server middleware for gzip/brotli/zstandard compression 2 | 3 | [](https://github.com/CAFxX/httpcompression/actions) 4 | [](https://codecov.io/gh/CAFxX/httpcompression) 5 | [](https://goreportcard.com/report/github.com/CAFxX/httpcompression) 6 | [](https://www.codefactor.io/repository/github/cafxx/httpcompression) 7 | [](https://pkg.go.dev/github.com/CAFxX/httpcompression) 8 | 9 | This is a small Go package which wraps HTTP handlers to transparently compress 10 | response bodies using zstd, brotli, gzip or deflate - for clients which support them. 11 | Although it's usually simpler to leave that to a reverse proxy (like nginx or Varnish), 12 | this package is useful when that is undesirable. In addition, this package allows 13 | users to extend it by plugging in third-party or custom compression encoders. 14 | 15 | **Note: This package was recently forked from the dead NYTimes/gziphandler. 16 | Maintaining drop-in compatibility is not a goal of this fork, as the scope of this fork 17 | is significantly wider than the original package.** 18 | 19 | :warning: As we have not reached 1.0 yet, API is still subject to changes. 20 | 21 | ## Features 22 | 23 | - gzip, deflate, brotli, and zstd compression by default, alternate (faster) gzip, zstd implementations are optional 24 | - Apply compression only if response body size is greater than a threshold 25 | - Apply compression only to a allowlist/denylist of MIME content types 26 | - Define encoding priority (e.g. give brotli a higher priority than gzip) 27 | - Control whether the client or the server defines the encoder priority 28 | - Plug in third-party/custom compression schemes or implementations 29 | - Custom dictionary compression for zstd and deflate 30 | - Low memory alliocations via transparent encoder reuse 31 | 32 | ## Install 33 | 34 | ```bash 35 | go get github.com/CAFxX/httpcompression 36 | ``` 37 | 38 | ## Usage 39 | 40 | Call `httpcompression.DefaultAdapter` to get an adapter that can be used to wrap 41 | any handler (an object which implements the `http.Handler` interface), 42 | to transparently provide response body compression. 43 | Note that `httpcompression` automatically compresses using Zstandard, Brotli, Deflate, 44 | and Gzip depending on the capabilities of the client (`Accept-Encoding`) 45 | and the configuration of this handler (by default, Zstandard, Brotli and gzip are 46 | all enabled and, conditional on client support, used in that order of preference). 47 | 48 | As a simple example: 49 | 50 | ```go 51 | package main 52 | 53 | import ( 54 | "io" 55 | "net/http" 56 | "github.com/CAFxX/httpcompression" 57 | ) 58 | 59 | func main() { 60 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 61 | w.Header().Set("Content-Type", "text/plain") 62 | io.WriteString(w, "Hello, World") 63 | }) 64 | compress, _ := httpcompression.DefaultAdapter() // Use the default configuration 65 | http.Handle("/", compress(handler)) 66 | http.ListenAndServe("0.0.0.0:8080", nil) 67 | } 68 | ``` 69 | 70 | ### Pluggable compressors 71 | 72 | It is possible to use custom compressor implementations by specifying a `CompressorProvider` 73 | for each of the encodings the adapter should support. This also allows to support arbitrary 74 | `Content-Encoding` schemes (e.g. `lzma`, or zstd with a static dictionary - see the 75 | [examples](example_test.go)). 76 | 77 | ```go 78 | pgz, err := httpcompression.Compressor("gzip", 0, pgzip.New(pgzip.Options{Level: 6})) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | compress, err := httpcompression.Adapter( 83 | // use klauspost/pgzip as compressor for the "gzip" content-encoding, with priority 0 84 | pgz, 85 | ) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | http.Handle("/", compress(handler)) 90 | ``` 91 | 92 | The `contrib/` directory contains a number of bundled implementations that are ready for use: 93 | 94 | | `Content-Encoding` | Provider package | Implementation package | Notes | Dictionary | Go/cgo | Default | [IANA registry] | 95 | | ------------------ | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | ----------------------------------------- | ---------- | ------ | ------- | --------------- | 96 | | `deflate` | [contrib/compress/zlib](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/compress/zlib) | compress/zlib | Slower than klauspost/zlib | ✅ | Go | ✅ | ✅ | 97 | | `deflate` | [contrib/klauspost/zlib](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/klauspost/zlib) | [github.com/klauspost/compress/zlib](https://github.com/klauspost/compress) | | ✅ | Go | | ✅ | 98 | | `gzip` | [contrib/compress/gzip](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/compress/gzip) | compress/gzip | Slower than klauspost/gzip | | Go | ✅ | ✅ | 99 | | `gzip` | [contrib/klauspost/gzip](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/klauspost/gzip) | [github.com/klauspost/compress/gzip](https://github.com/klauspost/compress) | | | Go | | ✅ | 100 | | `gzip` | [contrib/klauspost/pgzip](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/klauspost/pgzip) | [github.com/klauspost/pgzip](https://github.com/klauspost/pgzip) | Parallel compression | | Go | | ✅ | 101 | | `zstd` | [contrib/klauspost/zstd](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/klauspost/zstd) | [github.com/klauspost/compress/zstd](https://github.com/klauspost/compress) | | ✅ | Go | ✅ | ✅ | 102 | | `zstd` | [contrib/valyala/gozstd](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/valyala/gozstd) | [github.com/valyala/gozstd](https://github.com/valyala/gozstd) | Slower than klauspost/zstd | ✅ | cgo | | ✅ | 103 | | `brotli` | [contrib/andybalholm/brotli](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/andybalholm/brotli) | [github.com/andybalholm/brotli](https://github.com/andybalholm/brotli) | Slower than google/brotli | | Go | ✅ | ✅ | 104 | | `brotli` | [contrib/google/cbrotli](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/google/cbrotli) | [github.com/google/brotli](https://github.com/google/brotli) | Requires brotli libraries to be installed | | cgo | | ✅ | 105 | | `lz4` | [contrib/pierrec/lz4](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/pierrec/lz4) | [github.com/pierrec/lz4/v4](https://github.com/pierrec/lz4) | | | Go | | | 106 | | `xz` | [contrib/ulikunitz/xz](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/ulikunitz/xz) | [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) | | | Go | | | 107 | 108 | ## Framework integration 109 | 110 | In addition to the default support for `net/http`, `httpcompression` provides adapters for the following web frameworks: 111 | 112 | | Framework | Adapter | 113 | | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | 114 | | [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber) | [contrib/gofiber/fiber/v2](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/gofiber/fiber/v2) | 115 | | [github.com/labstack/echo](https://github.com/labstack/echo) | [contrib/labstack/echo](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/labstack/echo) | 116 | | [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) | [contrib/gin-gonic/gin](https://pkg.go.dev/github.com/CAFxX/httpcompression/contrib/gin-gonic/gin) | 117 | 118 | ## Benchmark 119 | 120 | See the [benchmark results](results.md) to get an idea of the relative performance and 121 | compression efficiency of gzip, brotli and zstd in the current implementation. 122 | 123 | ## TODO 124 | 125 | - Add dictionary support to brotli (zstd and deflate already support it, gzip does not allow dictionaries) 126 | - Allow to choose dictionary based on content-type 127 | - Provide additional implementations based on the bindings to the original native implementations 128 | - Add compressed payload caching (if the same payload has already been compressed and is present in the cache, skip compression) 129 | - Add write buffering (compress larger chunks at once) 130 | - Add decompression (if the payload is already compressed but the client supports better algorithms, or does not support a certain algorithm) 131 | - Add other, non-standardized content encodings (lzma/lzma2/xz, snappy, bzip2, etc.) 132 | - Dynamically tune MinSize (and possibly also ContentTypes, level/quality, ...) 133 | - Automatically generate and serve dictionaries 134 | 135 | ## License 136 | 137 | [Apache 2.0][license]. 138 | 139 | [docs]: https://godoc.org/github.com/CAFxX/httpcompression 140 | [license]: https://github.com/CAFxX/httpcompression/blob/master/LICENSE 141 | 142 | [IANA registry]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding 143 | -------------------------------------------------------------------------------- /accepts.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | // defaultQValue is the default qvalue to assign to an encoding if no explicit qvalue is set. 11 | // This is actually kind of ambiguous in RFC 2616, so hopefully it's correct. 12 | // The examples seem to indicate that it is. 13 | defaultQValue = 1.0 14 | ) 15 | 16 | // acceptedCompression returns the list of common compression scheme supported by client and server. 17 | func acceptedCompression(accept codings, comps comps) []string { 18 | var s []string 19 | // pick smallest N to do O(N) iterations 20 | if len(accept) < len(comps) { 21 | for k, v := range accept { 22 | if v > 0 && comps[k].comp != nil { 23 | s = append(s, k) 24 | } 25 | } 26 | } else { 27 | for k, v := range comps { 28 | if v.comp != nil && accept[k] > 0 { 29 | s = append(s, k) 30 | } 31 | } 32 | } 33 | return s 34 | } 35 | 36 | // parseEncodings attempts to parse a list of codings, per RFC 2616, as might 37 | // appear in an Accept-Encoding header. It returns a map of content-codings to 38 | // quality values. 39 | // Errors encountered during parsing the codings are ignored. 40 | // 41 | // See: http://tools.ietf.org/html/rfc2616#section-14.3. 42 | func parseEncodings(vv []string) codings { 43 | c := make(codings) 44 | for _, v := range vv { 45 | for _, sv := range strings.Split(v, ",") { 46 | coding, qvalue := parseCoding(sv) 47 | if coding == "" { 48 | continue 49 | } 50 | c[coding] = qvalue 51 | } 52 | } 53 | return c 54 | } 55 | 56 | // parseCoding parses a single conding (content-coding with an optional qvalue), 57 | // as might appear in an Accept-Encoding header. It attempts to forgive minor 58 | // formatting errors. 59 | func parseCoding(s string) (coding string, qvalue float64) { 60 | qvalue = defaultQValue 61 | 62 | p := strings.IndexRune(s, ';') 63 | if p != -1 { 64 | if part := strings.Replace(s[p+1:], " ", "", -1); strings.HasPrefix(part, "q=") { 65 | qvalue, _ = strconv.ParseFloat(part[2:], 64) 66 | if qvalue < 0.0 || math.IsNaN(qvalue) { 67 | qvalue = 0.0 68 | } else if qvalue > 1.0 { 69 | qvalue = 1.0 70 | } 71 | } 72 | } else { 73 | p = len(s) 74 | } 75 | coding = strings.ToLower(strings.TrimSpace(s[:p])) 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /accepts_test.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestAcceptedCompression(t *testing.T) { 13 | t.Parallel() 14 | cases := []struct { 15 | accept codings 16 | onlyGzip []string 17 | onlyBrotli []string 18 | gzipAndBrotli []string 19 | }{ 20 | {codings{}, nil, nil, nil}, 21 | {codings{"identity": 1}, nil, nil, nil}, 22 | {codings{"yadda": 1}, nil, nil, nil}, 23 | {codings{"gzip": 1}, []string{"gzip"}, nil, []string{"gzip"}}, 24 | {codings{"gzip": 0.5}, []string{"gzip"}, nil, []string{"gzip"}}, 25 | {codings{"gzip_0": 1}, nil, nil, nil}, 26 | {codings{"gzip": 1, "identity": 1}, []string{"gzip"}, nil, []string{"gzip"}}, 27 | {codings{"gzip": 0}, nil, nil, nil}, 28 | {codings{"br": 1}, nil, []string{"br"}, []string{"br"}}, 29 | {codings{"gzip": 1, "br": 1}, []string{"gzip"}, []string{"br"}, []string{"gzip", "br"}}, 30 | {codings{"gzip": 1, "br": 0.5}, []string{"gzip"}, []string{"br"}, []string{"gzip", "br"}}, 31 | {codings{"gzip": 1, "br": 0}, []string{"gzip"}, nil, []string{"gzip"}}, 32 | } 33 | onlyGzip := comps{"gzip": comp{comp: fakeCompressor{}}} 34 | onlyBrotli := comps{"br": comp{comp: fakeCompressor{}}} 35 | gzipAndBrotli := comps{"gzip": comp{comp: fakeCompressor{}}, "br": comp{comp: fakeCompressor{}}} 36 | for _, c := range cases { 37 | t.Run(fmt.Sprintf("%v", c.accept), func(t *testing.T) { 38 | t.Run("onlyGzip", func(t *testing.T) { 39 | a := acceptedCompression(c.accept, onlyGzip) 40 | sort.Strings(a) 41 | sort.Strings(c.onlyGzip) 42 | assert.Equal(t, a, c.onlyGzip) 43 | }) 44 | t.Run("onlyBrotli", func(t *testing.T) { 45 | a := acceptedCompression(c.accept, onlyBrotli) 46 | sort.Strings(a) 47 | sort.Strings(c.onlyBrotli) 48 | assert.Equal(t, a, c.onlyBrotli) 49 | }) 50 | t.Run("gzipAndBrotli", func(t *testing.T) { 51 | a := acceptedCompression(c.accept, gzipAndBrotli) 52 | sort.Strings(a) 53 | sort.Strings(c.gzipAndBrotli) 54 | assert.Equal(t, a, c.gzipAndBrotli) 55 | }) 56 | }) 57 | } 58 | } 59 | 60 | type fakeCompressor struct{} 61 | 62 | func (fakeCompressor) Get(_ io.Writer) io.WriteCloser { return nil } 63 | -------------------------------------------------------------------------------- /adapter.go: -------------------------------------------------------------------------------- 1 | package httpcompression // import "github.com/CAFxX/httpcompression" 2 | 3 | import ( 4 | "compress/gzip" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/CAFxX/httpcompression/contrib/andybalholm/brotli" 11 | cgzip "github.com/CAFxX/httpcompression/contrib/compress/gzip" 12 | "github.com/CAFxX/httpcompression/contrib/compress/zlib" 13 | "github.com/CAFxX/httpcompression/contrib/klauspost/zstd" 14 | ) 15 | 16 | const ( 17 | vary = "Vary" 18 | acceptEncoding = "Accept-Encoding" 19 | acceptRanges = "Accept-Ranges" 20 | contentEncoding = "Content-Encoding" 21 | contentType = "Content-Type" 22 | contentLength = "Content-Length" 23 | _range = "Range" 24 | ) 25 | 26 | type codings map[string]float64 27 | 28 | const ( 29 | // DefaultMinSize is the default minimum response body size for which we enable compression. 30 | // 31 | // 200 is a somewhat arbitrary number; in experiments compressing short text/markup-like sequences 32 | // with different compressors we saw that sequences shorter that ~180 the output generated by the 33 | // compressor would sometime be larger than the input. 34 | // This default may change between versions. 35 | // In general there can be no one-size-fits-all value: you will want to measure if a different 36 | // minimum size improves end-to-end performance for your workloads. 37 | DefaultMinSize = 200 38 | ) 39 | 40 | // Adapter returns a HTTP handler wrapping function (a.k.a. middleware) 41 | // which can be used to wrap an HTTP handler to transparently compress the response 42 | // body if the client supports it (via the Accept-Encoding header). 43 | // It is possible to pass one or more options to modify the middleware configuration. 44 | // If no options are provided, no compressors are enabled and therefore the adapter 45 | // is a no-op. 46 | // An error will be returned if invalid options are given. 47 | func Adapter(opts ...Option) (func(http.Handler) http.Handler, error) { 48 | c := config{ 49 | prefer: PreferServer, 50 | compressor: comps{}, 51 | } 52 | for _, o := range opts { 53 | err := o(&c) 54 | if err != nil { 55 | return nil, err 56 | } 57 | } 58 | 59 | if len(c.compressor) == 0 { 60 | // No compressors have been configured, so there is no useful work 61 | // that this adapter can do. 62 | return func(h http.Handler) http.Handler { 63 | return h 64 | }, nil 65 | } 66 | 67 | bufPool := &sync.Pool{} 68 | writerPool := &sync.Pool{} 69 | 70 | return func(h http.Handler) http.Handler { 71 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 72 | addVaryHeader(w.Header(), acceptEncoding) 73 | 74 | accept := parseEncodings(r.Header.Values(acceptEncoding)) 75 | common := acceptedCompression(accept, c.compressor) 76 | if len(common) == 0 { 77 | h.ServeHTTP(w, r) 78 | return 79 | } 80 | 81 | // We do not handle range requests when compression is used, as the 82 | // range specified applies to the compressed data, not to the uncompressed one. 83 | // So we would need to (1) ensure that compressors are deterministic and (2) 84 | // generate the whole uncompressed response anyway, compress it, and then discard 85 | // the bits outside of the range. 86 | // Let's keep it simple, and simply ignore completely the range header. 87 | // We also need to remove the Accept: Range header from any response that is 88 | // compressed; this is done in the ResponseWriter. 89 | // See https://github.com/nytimes/gziphandler/issues/83. 90 | r.Header.Del(_range) 91 | 92 | gw, _ := writerPool.Get().(*compressWriter) 93 | if gw == nil { 94 | gw = &compressWriter{} 95 | } 96 | *gw = compressWriter{ 97 | ResponseWriter: w, 98 | config: c, 99 | accept: accept, 100 | common: common, 101 | pool: bufPool, 102 | } 103 | defer func() { 104 | // Important: gw.Close() must be called *always*, as this will 105 | // in turn Close() the compressor. This is important because 106 | // it is guaranteed by the CompressorProvider interface, and 107 | // because some compressors may be implemented via cgo, and they 108 | // may rely on Close() being called to release memory resources. 109 | // TODO: expose the error 110 | _ = gw.Close() // expose the error 111 | *gw = compressWriter{} 112 | writerPool.Put(gw) 113 | }() 114 | 115 | if _, ok := w.(http.CloseNotifier); ok { 116 | w = compressWriterWithCloseNotify{gw} 117 | } else { 118 | w = gw 119 | } 120 | 121 | h.ServeHTTP(w, r) 122 | }) 123 | }, nil 124 | } 125 | 126 | func addVaryHeader(h http.Header, value string) { 127 | for _, v := range h.Values(vary) { 128 | if strings.EqualFold(value, v) { 129 | return 130 | } 131 | } 132 | h.Add(vary, value) 133 | } 134 | 135 | // DefaultAdapter is like Adapter, but it includes sane defaults for general usage. 136 | // Currently the defaults enable gzip and brotli compression, and set a minimum body size 137 | // of 200 bytes. 138 | // The provided opts override the defaults. 139 | // The defaults are not guaranteed to remain constant over time: if you want to avoid this 140 | // use Adapter directly. 141 | func DefaultAdapter(opts ...Option) (func(http.Handler) http.Handler, error) { 142 | defaults := []Option{ 143 | DeflateCompressionLevel(zlib.DefaultCompression), 144 | GzipCompressionLevel(gzip.DefaultCompression), 145 | BrotliCompressionLevel(brotli.DefaultCompression), 146 | defaultZstandardCompressor(), 147 | MinSize(DefaultMinSize), 148 | } 149 | opts = append(defaults, opts...) 150 | return Adapter(opts...) 151 | } 152 | 153 | // Used for functional configuration. 154 | type config struct { 155 | minSize int // Specifies the minimum response size to gzip. If the response length is bigger than this value, it is compressed. 156 | contentTypes []parsedContentType // Only compress if the response is one of these content-types. All are accepted if empty. 157 | blacklist bool 158 | prefer PreferType 159 | compressor comps 160 | } 161 | 162 | type comps map[string]comp 163 | 164 | type comp struct { 165 | comp CompressorProvider 166 | priority int 167 | } 168 | 169 | // Option can be passed to Handler to control its configuration. 170 | type Option func(c *config) error 171 | 172 | // MinSize is an option that controls the minimum size of payloads that 173 | // should be compressed. The default is DefaultMinSize. 174 | func MinSize(size int) Option { 175 | return func(c *config) error { 176 | if size < 0 { 177 | return fmt.Errorf("minimum size can not be negative: %d", size) 178 | } 179 | c.minSize = size 180 | return nil 181 | } 182 | } 183 | 184 | // DeflateCompressionLevel is an option that controls the Deflate compression 185 | // level to be used when compressing payloads. 186 | // The default is flate.DefaultCompression. 187 | func DeflateCompressionLevel(level int) Option { 188 | c, err := zlib.New(zlib.Options{Level: level}) 189 | if err != nil { 190 | return errorOption(err) 191 | } 192 | return DeflateCompressor(c) 193 | } 194 | 195 | // GzipCompressionLevel is an option that controls the Gzip compression 196 | // level to be used when compressing payloads. 197 | // The default is gzip.DefaultCompression. 198 | func GzipCompressionLevel(level int) Option { 199 | c, err := NewDefaultGzipCompressor(level) 200 | if err != nil { 201 | return errorOption(err) 202 | } 203 | return GzipCompressor(c) 204 | } 205 | 206 | // BrotliCompressionLevel is an option that controls the Brotli compression 207 | // level to be used when compressing payloads. 208 | // The default is 3 (the same default used in the reference brotli C 209 | // implementation). 210 | func BrotliCompressionLevel(level int) Option { 211 | c, err := brotli.New(brotli.Options{Quality: level}) 212 | if err != nil { 213 | return errorOption(err) 214 | } 215 | return BrotliCompressor(c) 216 | } 217 | 218 | // DeflateCompressor is an option to specify a custom compressor factory for Deflate. 219 | func DeflateCompressor(g CompressorProvider) Option { 220 | return Compressor(zlib.Encoding, -300, g) 221 | } 222 | 223 | // GzipCompressor is an option to specify a custom compressor factory for Gzip. 224 | func GzipCompressor(g CompressorProvider) Option { 225 | return Compressor(cgzip.Encoding, -200, g) 226 | } 227 | 228 | // BrotliCompressor is an option to specify a custom compressor factory for Brotli. 229 | func BrotliCompressor(b CompressorProvider) Option { 230 | return Compressor(brotli.Encoding, -100, b) 231 | } 232 | 233 | // ZstandardCompressor is an option to specify a custom compressor factory for Zstandard. 234 | func ZstandardCompressor(b CompressorProvider) Option { 235 | return Compressor(zstd.Encoding, -50, b) 236 | } 237 | 238 | func NewDefaultGzipCompressor(level int) (CompressorProvider, error) { 239 | return cgzip.New(cgzip.Options{Level: level}) 240 | } 241 | 242 | func defaultZstandardCompressor() Option { 243 | zstdComp, err := zstd.New() 244 | if err != nil { 245 | return errorOption(fmt.Errorf("initializing zstd compressor: %w", err)) 246 | } 247 | return ZstandardCompressor(zstdComp) 248 | } 249 | 250 | func errorOption(err error) Option { 251 | return func(_ *config) error { 252 | return err 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /compressor.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // CompressorProvider is the interface for compression implementations. 8 | type CompressorProvider interface { 9 | // Get returns a writer that writes compressed output to the supplied parent io.Writer. 10 | // Callers of Get() must ensure to always call Close() when the compressor is not needed 11 | // anymore. Callers of Close() must also ensure to not use the io.WriteCloser once Close() 12 | // is called. 13 | // Implementations of CompressorProvider are allowed to recycle the compressor (e.g. put the 14 | // WriteCloser in a pool to be reused by a later call to Get) when Close() is called. 15 | // The returned io.WriteCloser can optionally implement the Flusher interface if it is 16 | // able to flush data buffered internally. 17 | Get(parent io.Writer) (compressor io.WriteCloser) 18 | } 19 | 20 | // Flusher is an optional interface that can be implemented by the compressors returned by 21 | // CompressorProvider.Get(). 22 | type Flusher interface { 23 | // Flush flushes the data buffered internally by the Writer. 24 | // Implementations of Flush do not need to internally flush the parent Writer. 25 | Flush() error 26 | } 27 | 28 | // Compressor returns an Option that sets the CompressorProvider for a specific Content-Encoding. 29 | // If multiple CompressorProviders are set for the same Content-Encoding, the last one is used. 30 | // If compressor is nil, it disables the specified Content-Encoding. 31 | // Priority is used to specify the priority of the Content-Encoding. A higher number means higher 32 | // priority. See PreferType to understand how priority is used to select the Content-Encoding for 33 | // a specific request. 34 | func Compressor(contentEncoding string, priority int, compressor CompressorProvider) Option { 35 | return func(c *config) error { 36 | if compressor == nil { 37 | delete(c.compressor, contentEncoding) 38 | return nil 39 | } 40 | c.compressor[contentEncoding] = comp{compressor, priority} 41 | return nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /content_types.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import "mime" 4 | 5 | // text/html text/xml text/plain text/css text/javascript application/javascript application/json application/xml 6 | // text/css text/xml application/javascript application/atom+xml application/rss+xml text/mathml text/plain text/x-component image/svg+xml application/json application/vnd.google-earth.kml+xml application/x-perl application/xhtml+xml application/xspf+xml 7 | // ^text/ 8 | // [+](xml|json|cbor)$ 9 | 10 | // ContentTypes specifies a list of content types to compare 11 | // the Content-Type header to before compressing. If none 12 | // match, and blacklist is false, the response will be returned as-is. 13 | // 14 | // Content types are compared in a case-insensitive, whitespace-ignored 15 | // manner. 16 | // 17 | // A MIME type without any other directive will match a content type 18 | // that has the same MIME type, regardless of that content type's other 19 | // directives. I.e., "text/html" will match both "text/html" and 20 | // "text/html; charset=utf-8". 21 | // 22 | // A MIME type with any other directive will only match a content type 23 | // that has the same MIME type and other directives. I.e., 24 | // "text/html; charset=utf-8" will only match "text/html; charset=utf-8". 25 | // 26 | // If blacklist is true then only content types that do not match the 27 | // provided list of types are compressed. If blacklist is false, only 28 | // content types that match the provided list are compressed. 29 | // 30 | // By default, responses are compressed regardless of Content-Type. 31 | func ContentTypes(types []string, blacklist bool) Option { 32 | return func(c *config) error { 33 | c.contentTypes = []parsedContentType{} 34 | for _, v := range types { 35 | mediaType, params, err := mime.ParseMediaType(v) 36 | if err != nil { 37 | return err 38 | } 39 | c.contentTypes = append(c.contentTypes, parsedContentType{mediaType, params}) 40 | } 41 | c.blacklist = blacklist 42 | return nil 43 | } 44 | } 45 | 46 | // Parsed representation of one of the inputs to ContentTypes. 47 | // See https://golang.org/pkg/mime/#ParseMediaType 48 | type parsedContentType struct { 49 | mediaType string 50 | params map[string]string 51 | } 52 | 53 | // equals returns whether this content type matches another content type. 54 | func (pct *parsedContentType) equals(mediaType string, params map[string]string) bool { 55 | if pct.mediaType != mediaType { 56 | return false 57 | } 58 | // if pct has no params, don't care about other's params 59 | if len(pct.params) == 0 { 60 | return true 61 | } 62 | // FIXME: the slow path is asinine, unnecessary, and should be eradicated. 63 | return pct.equalsSlow(mediaType, params) 64 | } 65 | 66 | func (pct *parsedContentType) equalsSlow(mediaType string, params map[string]string) bool { 67 | // if pct has any params, they must be identical to other's. 68 | if len(pct.params) != len(params) { 69 | return false 70 | } 71 | for k, v := range pct.params { 72 | if w, ok := params[k]; !ok || v != w { 73 | return false 74 | } 75 | } 76 | return true 77 | } 78 | -------------------------------------------------------------------------------- /contrib/andybalholm/brotli/brotli.go: -------------------------------------------------------------------------------- 1 | package brotli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/andybalholm/brotli" 10 | ) 11 | 12 | const ( 13 | Encoding = "br" 14 | DefaultCompression = brotli.DefaultCompression 15 | ) 16 | 17 | type Options = brotli.WriterOptions 18 | 19 | type compressor struct { 20 | pool sync.Pool 21 | opts Options 22 | } 23 | 24 | func New(opts Options) (c *compressor, err error) { 25 | defer func() { 26 | if r := recover(); r != nil { 27 | c, err = nil, fmt.Errorf("panic: %v", r) 28 | } 29 | }() 30 | 31 | tw := brotli.NewWriterOptions(io.Discard, opts) 32 | if err := utils.CheckWriter(tw); err != nil { 33 | return nil, fmt.Errorf("brotli: writer initialization: %w", err) 34 | } 35 | 36 | c = &compressor{opts: opts} 37 | return c, nil 38 | } 39 | 40 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 41 | if gw, ok := c.pool.Get().(*writer); ok { 42 | gw.Reset(w) 43 | return gw 44 | } 45 | gw := brotli.NewWriterOptions(w, c.opts) 46 | return &writer{ 47 | Writer: gw, 48 | c: c, 49 | } 50 | } 51 | 52 | type writer struct { 53 | *brotli.Writer 54 | c *compressor 55 | } 56 | 57 | func (w *writer) Close() error { 58 | err := w.Writer.Close() 59 | w.Reset(nil) 60 | w.c.pool.Put(w) 61 | return err 62 | } 63 | -------------------------------------------------------------------------------- /contrib/andybalholm/brotli/brotli_race_test.go: -------------------------------------------------------------------------------- 1 | // +build race 2 | 3 | package brotli_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/andybalholm/brotli" 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | ) 11 | 12 | func TestZstdRace(t *testing.T) { 13 | t.Parallel() 14 | c, _ := brotli.New(brotli.Options{}) 15 | internal.RaceTestCompressionProvider(c, 100) 16 | } 17 | -------------------------------------------------------------------------------- /contrib/andybalholm/brotli/brotli_test.go: -------------------------------------------------------------------------------- 1 | package brotli_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | "github.com/CAFxX/httpcompression/contrib/andybalholm/brotli" 10 | 11 | _brotli "github.com/andybalholm/brotli" 12 | ) 13 | 14 | var _ httpcompression.CompressorProvider = &brotli.Compressor{} 15 | 16 | func TestBrotli(t *testing.T) { 17 | t.Parallel() 18 | 19 | s := []byte("hello world!") 20 | 21 | c, err := brotli.New(brotli.Options{}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | b := &bytes.Buffer{} 26 | w := c.Get(b) 27 | w.Write(s) 28 | w.Close() 29 | 30 | r := _brotli.NewReader(b) 31 | d, err := ioutil.ReadAll(r) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | if bytes.Compare(s, d) != 0 { 36 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contrib/andybalholm/brotli/export_test.go: -------------------------------------------------------------------------------- 1 | package brotli 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/compress/gzip/export_test.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/compress/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "compress/gzip" 5 | "fmt" 6 | "io" 7 | "sync" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 10 | ) 11 | 12 | const ( 13 | Encoding = "gzip" 14 | DefaultCompression = gzip.DefaultCompression 15 | ) 16 | 17 | type Options struct { 18 | Level int 19 | } 20 | 21 | type compressor struct { 22 | pool sync.Pool 23 | opt Options 24 | } 25 | 26 | func New(opt Options) (*compressor, error) { 27 | tw, err := gzip.NewWriterLevel(io.Discard, opt.Level) 28 | if err != nil { 29 | return nil, err 30 | } 31 | err = utils.CheckWriter(tw) 32 | if err != nil { 33 | return nil, fmt.Errorf("gzip: writer initialization: %w", err) 34 | } 35 | 36 | c := &compressor{opt: opt} 37 | return c, nil 38 | } 39 | 40 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 41 | if gw, ok := c.pool.Get().(*gzipWriter); ok { 42 | gw.Reset(w) 43 | return gw 44 | } 45 | gw, err := gzip.NewWriterLevel(w, c.opt.Level) 46 | if err != nil { 47 | return utils.ErrorWriteCloser{Err: err} 48 | } 49 | return &gzipWriter{ 50 | Writer: gw, 51 | c: c, 52 | } 53 | } 54 | 55 | type gzipWriter struct { 56 | *gzip.Writer 57 | c *compressor 58 | } 59 | 60 | func (w *gzipWriter) Close() error { 61 | err := w.Writer.Close() 62 | w.Reset(nil) 63 | w.c.pool.Put(w) 64 | return err 65 | } 66 | -------------------------------------------------------------------------------- /contrib/compress/gzip/gzip_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package gzip_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/compress/gzip" 10 | "github.com/CAFxX/httpcompression/contrib/internal" 11 | ) 12 | 13 | func TestZstdRace(t *testing.T) { 14 | t.Parallel() 15 | c, _ := gzip.New(gzip.Options{}) 16 | internal.RaceTestCompressionProvider(c, 100) 17 | } 18 | -------------------------------------------------------------------------------- /contrib/compress/gzip/gzip_test.go: -------------------------------------------------------------------------------- 1 | package gzip_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | "github.com/CAFxX/httpcompression/contrib/compress/gzip" 10 | 11 | kpgzip "github.com/klauspost/compress/gzip" 12 | ) 13 | 14 | var _ httpcompression.CompressorProvider = &gzip.Compressor{} 15 | 16 | func TestGzip(t *testing.T) { 17 | t.Parallel() 18 | 19 | s := []byte("hello world!") 20 | 21 | c, err := gzip.New(gzip.Options{}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | b := &bytes.Buffer{} 26 | w := c.Get(b) 27 | w.Write(s) 28 | w.Close() 29 | 30 | r, err := kpgzip.NewReader(b) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | d, err := ioutil.ReadAll(r) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if !bytes.Equal(s, d) { 39 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contrib/compress/zlib/deflate.go: -------------------------------------------------------------------------------- 1 | package zlib 2 | 3 | import ( 4 | "compress/zlib" 5 | "fmt" 6 | "io" 7 | "sync" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 10 | ) 11 | 12 | const ( 13 | Encoding = "deflate" 14 | DefaultCompression = zlib.DefaultCompression 15 | ) 16 | 17 | type Options struct { 18 | Level int 19 | Dictionary []byte 20 | } 21 | 22 | type compressor struct { 23 | pool sync.Pool 24 | opt Options 25 | } 26 | 27 | func New(opt Options) (*compressor, error) { 28 | tw, err := zlib.NewWriterLevelDict(io.Discard, opt.Level, opt.Dictionary) 29 | if err != nil { 30 | return nil, err 31 | } 32 | err = utils.CheckWriter(tw) 33 | if err != nil { 34 | return nil, fmt.Errorf("deflate: writer initialization: %w", err) 35 | } 36 | 37 | c := &compressor{opt: opt} 38 | return c, nil 39 | } 40 | 41 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 42 | if gw, ok := c.pool.Get().(*deflateWriter); ok { 43 | gw.Reset(w) 44 | return gw 45 | } 46 | gw, err := zlib.NewWriterLevelDict(w, c.opt.Level, c.opt.Dictionary) 47 | if err != nil { 48 | return utils.ErrorWriteCloser{Err: err} 49 | } 50 | return &deflateWriter{ 51 | Writer: gw, 52 | c: c, 53 | } 54 | } 55 | 56 | type deflateWriter struct { 57 | *zlib.Writer 58 | c *compressor 59 | } 60 | 61 | func (w *deflateWriter) Close() error { 62 | err := w.Writer.Close() 63 | w.Reset(nil) 64 | w.c.pool.Put(w) 65 | return err 66 | } 67 | -------------------------------------------------------------------------------- /contrib/compress/zlib/deflate_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package zlib_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/compress/zlib" 10 | "github.com/CAFxX/httpcompression/contrib/internal" 11 | ) 12 | 13 | func TestZstdRace(t *testing.T) { 14 | t.Parallel() 15 | c, _ := zlib.New(zlib.Options{}) 16 | internal.RaceTestCompressionProvider(c, 100) 17 | } 18 | -------------------------------------------------------------------------------- /contrib/compress/zlib/deflate_test.go: -------------------------------------------------------------------------------- 1 | package zlib_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | stdzlib "compress/zlib" 9 | 10 | "github.com/CAFxX/httpcompression" 11 | "github.com/CAFxX/httpcompression/contrib/compress/zlib" 12 | ) 13 | 14 | var _ httpcompression.CompressorProvider = &zlib.Compressor{} 15 | 16 | func TestDeflate(t *testing.T) { 17 | t.Parallel() 18 | 19 | s := []byte("hello world!") 20 | 21 | c, err := zlib.New(zlib.Options{Dictionary: s}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | b := &bytes.Buffer{} 26 | w := c.Get(b) 27 | w.Write(s) 28 | w.Close() 29 | 30 | r, err := stdzlib.NewReaderDict(b, s) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | d, err := ioutil.ReadAll(r) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if !bytes.Equal(s, d) { 39 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contrib/compress/zlib/export_test.go: -------------------------------------------------------------------------------- 1 | package zlib 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/gin-gonic/gin/gin.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "github.com/CAFxX/httpcompression" 5 | "github.com/gin-gonic/gin" 6 | wraphh "github.com/turtlemonvh/gin-wraphh" 7 | ) 8 | 9 | func Adapter(opts ...httpcompression.Option) (gin.HandlerFunc, error) { 10 | hh, err := httpcompression.Adapter(opts...) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return wraphh.WrapHH(hh), nil 15 | } 16 | 17 | func DefaultAdapter(opts ...httpcompression.Option) (gin.HandlerFunc, error) { 18 | hh, err := httpcompression.DefaultAdapter(opts...) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return wraphh.WrapHH(hh), nil 23 | } 24 | -------------------------------------------------------------------------------- /contrib/gin-gonic/gin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/CAFxX/httpcompression/contrib/gin-gonic/gin 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/CAFxX/httpcompression v0.0.8 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/turtlemonvh/gin-wraphh v0.0.0-20160304035037-ea8e4927b3a6 9 | ) 10 | 11 | require ( 12 | github.com/NYTimes/gziphandler v1.1.1 // indirect 13 | github.com/andybalholm/brotli v1.0.4 // indirect 14 | github.com/bytedance/sonic v1.9.1 // indirect 15 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 16 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 17 | github.com/gin-contrib/sse v0.1.0 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.14.0 // indirect 21 | github.com/goccy/go-json v0.10.2 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/justinas/nosurf v1.1.1 // indirect 24 | github.com/klauspost/compress v1.14.1 // indirect 25 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 26 | github.com/leodido/go-urn v1.2.4 // indirect 27 | github.com/mattn/go-isatty v0.0.19 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 31 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 32 | github.com/ugorji/go/codec v1.2.11 // indirect 33 | golang.org/x/arch v0.3.0 // indirect 34 | golang.org/x/crypto v0.21.0 // indirect 35 | golang.org/x/net v0.23.0 // indirect 36 | golang.org/x/sys v0.18.0 // indirect 37 | golang.org/x/text v0.14.0 // indirect 38 | google.golang.org/protobuf v1.33.0 // indirect 39 | gopkg.in/yaml.v3 v3.0.1 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /contrib/gin-gonic/gin/go.sum: -------------------------------------------------------------------------------- 1 | github.com/CAFxX/httpcompression v0.0.8 h1:UBWojERnpCS6X7whJkGGZeCC3ruZBRwkwkcnfGfb0ko= 2 | github.com/CAFxX/httpcompression v0.0.8/go.mod h1:bVd1taHK1vYb5SWe9lwNDCqrfj2ka+C1Zx7JHzxuHnU= 3 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= 4 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 5 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 6 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 7 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 8 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 9 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 10 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 11 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 17 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 18 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 19 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 20 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 21 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 22 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 23 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 24 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 25 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 26 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 27 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 28 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 29 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 30 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 31 | github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36 h1:qg5qEpjk1P1EMnInOCpxOpWSPRsspXJDT7P80y/JfFA= 32 | github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= 33 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 34 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 35 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 36 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 37 | github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= 38 | github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= 39 | github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds= 40 | github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 41 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 42 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 43 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 44 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 45 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 46 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 47 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 48 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 49 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 53 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 54 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 55 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 56 | github.com/pierrec/lz4/v4 v4.1.12 h1:44l88ehTZAUGW4VlO1QC4zkilL99M6Y9MXNwEs0uzP8= 57 | github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 58 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 59 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 60 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 61 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 62 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 63 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 64 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 65 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 66 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 67 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 68 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 69 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 70 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 71 | github.com/turtlemonvh/gin-wraphh v0.0.0-20160304035037-ea8e4927b3a6 h1:LXn2Epw3+6TJxFwYbUo0M32riVbVfSTqCf41kJCmLb8= 72 | github.com/turtlemonvh/gin-wraphh v0.0.0-20160304035037-ea8e4927b3a6/go.mod h1:9ACh8xdjPLhq9E8Rp80s6QqYzuAAx75ZNPLnjgtei6Q= 73 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 74 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 75 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 76 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 77 | github.com/valyala/gozstd v1.11.0 h1:VV6qQFt+4sBBj9OJ7eKVvsFAMy59Urcs9Lgd+o5FOw0= 78 | github.com/valyala/gozstd v1.11.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= 79 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 80 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 81 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 82 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 83 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 84 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 85 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 86 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 87 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 88 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 89 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 90 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 91 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 92 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 93 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 94 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 95 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 96 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 97 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 98 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 99 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 100 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 101 | -------------------------------------------------------------------------------- /contrib/gofiber/fiber/v2/fiber.go: -------------------------------------------------------------------------------- 1 | package fiber 2 | 3 | import ( 4 | "github.com/CAFxX/httpcompression" 5 | "github.com/gofiber/adaptor/v2" 6 | "github.com/gofiber/fiber/v2" 7 | ) 8 | 9 | func Adapter(opts ...httpcompression.Option) (fiber.Handler, error) { 10 | mw, err := httpcompression.Adapter(opts...) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return adaptor.HTTPMiddleware(mw), nil 15 | } 16 | 17 | func DefaultAdapter(opts ...httpcompression.Option) (fiber.Handler, error) { 18 | mw, err := httpcompression.DefaultAdapter(opts...) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return adaptor.HTTPMiddleware(mw), nil 23 | } 24 | -------------------------------------------------------------------------------- /contrib/gofiber/fiber/v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/CAFxX/httpcompression/contrib/gofiber/fiber/v2 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/CAFxX/httpcompression v0.0.8 7 | github.com/gofiber/adaptor/v2 v2.1.24 8 | github.com/gofiber/fiber/v2 v2.52.5 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.0.5 // indirect 13 | github.com/gofiber/utils v0.1.2 // indirect 14 | github.com/google/uuid v1.5.0 // indirect 15 | github.com/klauspost/compress v1.17.0 // indirect 16 | github.com/mattn/go-colorable v0.1.13 // indirect 17 | github.com/mattn/go-isatty v0.0.20 // indirect 18 | github.com/mattn/go-runewidth v0.0.15 // indirect 19 | github.com/rivo/uniseg v0.2.0 // indirect 20 | github.com/valyala/bytebufferpool v1.0.0 // indirect 21 | github.com/valyala/fasthttp v1.51.0 // indirect 22 | github.com/valyala/tcplisten v1.0.0 // indirect 23 | golang.org/x/sys v0.15.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /contrib/gofiber/fiber/v2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/CAFxX/httpcompression v0.0.8 h1:UBWojERnpCS6X7whJkGGZeCC3ruZBRwkwkcnfGfb0ko= 2 | github.com/CAFxX/httpcompression v0.0.8/go.mod h1:bVd1taHK1vYb5SWe9lwNDCqrfj2ka+C1Zx7JHzxuHnU= 3 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 4 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 5 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 6 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/gofiber/adaptor/v2 v2.1.24 h1:EdQWVODtOTRAHZRuNMbmNrlvgY+4liPO/JOwKk/Dkm0= 9 | github.com/gofiber/adaptor/v2 v2.1.24/go.mod h1:4g5V9/lhwLwgHHlQNEWXa3WUigzNjDXNmEN9BmQ2VME= 10 | github.com/gofiber/fiber/v2 v2.34.0/go.mod h1:ozRQfS+D7EL1+hMH+gutku0kfx1wLX4hAxDCtDzpj4U= 11 | github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= 12 | github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 13 | github.com/gofiber/utils v0.1.2 h1:1SH2YEz4RlNS0tJlMJ0bGwO0JkqPqvq6TbHK9tXZKtk= 14 | github.com/gofiber/utils v0.1.2/go.mod h1:pacRFtghAE3UoknMOUiXh2Io/nLWSUHtQCi/3QASsOc= 15 | github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36 h1:qg5qEpjk1P1EMnInOCpxOpWSPRsspXJDT7P80y/JfFA= 16 | github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= 17 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= 18 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 19 | github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 20 | github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 21 | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= 22 | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 23 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 24 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 25 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 26 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 27 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 28 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 29 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 30 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 31 | github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= 32 | github.com/pierrec/lz4/v4 v4.1.12 h1:44l88ehTZAUGW4VlO1QC4zkilL99M6Y9MXNwEs0uzP8= 33 | github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 37 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 38 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 39 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 40 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 41 | github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= 42 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 43 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 44 | github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= 45 | github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= 46 | github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= 47 | github.com/valyala/gozstd v1.11.0 h1:VV6qQFt+4sBBj9OJ7eKVvsFAMy59Urcs9Lgd+o5FOw0= 48 | github.com/valyala/gozstd v1.11.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= 49 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 50 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 51 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 52 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 53 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 54 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 55 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 56 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 57 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 58 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 59 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 60 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 61 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 62 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 63 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 64 | golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 65 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 66 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 67 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 68 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 69 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 70 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 71 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 72 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 73 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 77 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 78 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 79 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 85 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 86 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 87 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 88 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 89 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 90 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 91 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 92 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 93 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 94 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 95 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 96 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 97 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 98 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 99 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 100 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 101 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 102 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 103 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 104 | golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 105 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 106 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 107 | -------------------------------------------------------------------------------- /contrib/google/cbrotli/brotli.go: -------------------------------------------------------------------------------- 1 | package cbrotli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "runtime" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/google/brotli/go/cbrotli" 10 | ) 11 | 12 | const ( 13 | Encoding = "br" 14 | ) 15 | 16 | type compressor struct { 17 | opts cbrotli.WriterOptions 18 | } 19 | 20 | func New(opts cbrotli.WriterOptions) (c *compressor, err error) { 21 | tw := cbrotli.NewWriter(io.Discard, opts) 22 | if err := utils.CheckWriter(tw); err != nil { 23 | return nil, fmt.Errorf("cbrotli: writer initialization: %w", err) 24 | } 25 | 26 | c = &compressor{opts: opts} 27 | return c, nil 28 | } 29 | 30 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 31 | gw := cbrotli.NewWriter(w, c.opts) 32 | 33 | cw := &writer{ 34 | Writer: gw, 35 | } 36 | runtime.SetFinalizer(cw, finalizer) 37 | return cw 38 | } 39 | 40 | type writer struct { 41 | *cbrotli.Writer 42 | } 43 | 44 | func (w *writer) Close() error { 45 | defer runtime.SetFinalizer(w, nil) 46 | return w.Writer.Close() 47 | } 48 | 49 | var finalizerHook func(io.WriteCloser) 50 | 51 | func finalizer(cw *writer) { 52 | go func(cw *writer) { 53 | defer func() { 54 | if finalizerHook != nil { 55 | finalizerHook(cw) // for testing only 56 | } 57 | recover() 58 | }() 59 | _ = cw.Close() 60 | }(cw) 61 | } 62 | -------------------------------------------------------------------------------- /contrib/google/cbrotli/brotli_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package cbrotli_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/google/cbrotli" 10 | "github.com/CAFxX/httpcompression/contrib/internal" 11 | gcbrotli "github.com/google/brotli/go/cbrotli" 12 | ) 13 | 14 | func TestBrotliRace(t *testing.T) { 15 | t.Parallel() 16 | c, _ := cbrotli.New(gcbrotli.WriterOptions{}) 17 | internal.RaceTestCompressionProvider(c, 100) 18 | } 19 | -------------------------------------------------------------------------------- /contrib/google/cbrotli/brotli_test.go: -------------------------------------------------------------------------------- 1 | package cbrotli_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "runtime" 9 | "testing" 10 | 11 | "github.com/CAFxX/httpcompression" 12 | "github.com/CAFxX/httpcompression/contrib/google/cbrotli" 13 | gcbrotli "github.com/google/brotli/go/cbrotli" 14 | ) 15 | 16 | var _ httpcompression.CompressorProvider = &cbrotli.Compressor{} 17 | 18 | func TestBrotli(t *testing.T) { 19 | t.Parallel() 20 | 21 | s := []byte("hello world!") 22 | 23 | c, err := cbrotli.New(gcbrotli.WriterOptions{}) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | b := &bytes.Buffer{} 28 | w := c.Get(b) 29 | w.Write(s) 30 | w.Close() 31 | 32 | r := gcbrotli.NewReader(b) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | d, err := ioutil.ReadAll(r) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if !bytes.Equal(s, d) { 41 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 42 | } 43 | } 44 | 45 | func TestBrotliFinalize(t *testing.T) { 46 | // no t.Parallel() as finalizerHook is global and we need no other 47 | // allocations between the GC() calls 48 | 49 | runtime.GC() 50 | runtime.GC() 51 | 52 | finalizerWriter := make(chan string) 53 | *cbrotli.FinalizerHook = func(cw io.WriteCloser) { 54 | finalizerWriter <- fmt.Sprintf("%p", cw) 55 | } 56 | defer func() { 57 | *cbrotli.FinalizerHook = nil 58 | }() 59 | 60 | c, err := cbrotli.New(gcbrotli.WriterOptions{}) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | cw := c.Get(io.Discard) 66 | if cw == nil { 67 | t.Fatal("nil writer") 68 | } 69 | 70 | writer := fmt.Sprintf("%p", cw) 71 | 72 | cw = nil // cw is now dead 73 | 74 | runtime.GC() 75 | runtime.GC() 76 | 77 | if fw := <-finalizerWriter; writer != fw { 78 | t.Fatalf("writer: %q, finalizer writer: %q", writer, fw) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /contrib/google/cbrotli/export_test.go: -------------------------------------------------------------------------------- 1 | package cbrotli 2 | 3 | type Compressor = compressor 4 | 5 | var FinalizerHook = &finalizerHook 6 | -------------------------------------------------------------------------------- /contrib/internal/test_util.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "runtime" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | ) 10 | 11 | func RaceTestCompressionProvider(c httpcompression.CompressorProvider, n int) { 12 | var wg sync.WaitGroup 13 | for i := runtime.GOMAXPROCS(0); i >= 0; i-- { 14 | wg.Add(1) 15 | go func() { 16 | defer wg.Done() 17 | b := &bytes.Buffer{} 18 | for j := 0; j < n; j++ { 19 | b.Reset() 20 | w := c.Get(b) 21 | w.Write([]byte("hello world")) 22 | w.Close() 23 | } 24 | }() 25 | } 26 | wg.Wait() 27 | } 28 | -------------------------------------------------------------------------------- /contrib/internal/utils/check.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func CheckWriter(w io.Writer) error { 9 | const data = "test" 10 | n, err := w.Write([]byte(data)) 11 | if err != nil { 12 | return fmt.Errorf("write: %w", err) 13 | } 14 | if n != len(data) { 15 | return fmt.Errorf("write: short write: %d", n) 16 | } 17 | if w, ok := w.(io.Closer); ok { 18 | err := w.Close() 19 | if err != nil { 20 | return fmt.Errorf("close: %w", err) 21 | } 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /contrib/internal/utils/error_writecloser.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type ErrorWriteCloser struct { 4 | Err error 5 | } 6 | 7 | func (e ErrorWriteCloser) Write(_ []byte) (int, error) { 8 | return 0, e.Err 9 | } 10 | 11 | func (e ErrorWriteCloser) Close() error { 12 | return e.Err 13 | } 14 | -------------------------------------------------------------------------------- /contrib/klauspost/gzip/export_test.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/klauspost/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/klauspost/compress/gzip" 10 | ) 11 | 12 | const ( 13 | Encoding = "gzip" 14 | DefaultCompression = gzip.DefaultCompression 15 | ) 16 | 17 | type compressor struct { 18 | pool sync.Pool 19 | opts Options 20 | } 21 | 22 | type Options struct { 23 | Level int 24 | } 25 | 26 | func New(opts Options) (c *compressor, err error) { 27 | defer func() { 28 | if r := recover(); r != nil { 29 | c, err = nil, fmt.Errorf("panic: %v", r) 30 | } 31 | }() 32 | 33 | tw, err := gzip.NewWriterLevel(io.Discard, opts.Level) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if err := utils.CheckWriter(tw); err != nil { 38 | return nil, fmt.Errorf("gzip: writer initialization: %w", err) 39 | } 40 | 41 | c = &compressor{opts: opts} 42 | return c, nil 43 | } 44 | 45 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 46 | if gw, ok := c.pool.Get().(*writer); ok { 47 | gw.Reset(w) 48 | return gw 49 | } 50 | gw, err := gzip.NewWriterLevel(w, c.opts.Level) 51 | if err != nil { 52 | return utils.ErrorWriteCloser{Err: err} 53 | } 54 | return &writer{ 55 | Writer: gw, 56 | c: c, 57 | } 58 | } 59 | 60 | type writer struct { 61 | *gzip.Writer 62 | c *compressor 63 | } 64 | 65 | func (w *writer) Close() error { 66 | err := w.Writer.Close() 67 | w.Reset(nil) 68 | w.c.pool.Put(w) 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /contrib/klauspost/gzip/gzip_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package gzip_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | "github.com/CAFxX/httpcompression/contrib/klauspost/gzip" 11 | ) 12 | 13 | func TestGzipRace(t *testing.T) { 14 | t.Parallel() 15 | c, _ := gzip.New(gzip.Options{}) 16 | internal.RaceTestCompressionProvider(c, 100) 17 | } 18 | -------------------------------------------------------------------------------- /contrib/klauspost/gzip/gzip_test.go: -------------------------------------------------------------------------------- 1 | package gzip_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | stdgzip "compress/gzip" 9 | 10 | "github.com/CAFxX/httpcompression" 11 | "github.com/CAFxX/httpcompression/contrib/klauspost/gzip" 12 | ) 13 | 14 | var _ httpcompression.CompressorProvider = &gzip.Compressor{} 15 | 16 | func TestGzip(t *testing.T) { 17 | t.Parallel() 18 | 19 | s := []byte("hello world!") 20 | 21 | c, err := gzip.New(gzip.Options{}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | b := &bytes.Buffer{} 26 | w := c.Get(b) 27 | w.Write(s) 28 | w.Close() 29 | 30 | r, err := stdgzip.NewReader(b) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | d, err := ioutil.ReadAll(r) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if !bytes.Equal(s, d) { 39 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contrib/klauspost/pgzip/export_test.go: -------------------------------------------------------------------------------- 1 | package pgzip 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/klauspost/pgzip/pgzip.go: -------------------------------------------------------------------------------- 1 | package pgzip 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/klauspost/pgzip" 10 | ) 11 | 12 | const ( 13 | Encoding = "gzip" 14 | DefaultCompression = pgzip.DefaultCompression 15 | ) 16 | 17 | type compressor struct { 18 | pool sync.Pool 19 | opts Options 20 | } 21 | 22 | type Options struct { 23 | Level int 24 | BlockSize int 25 | Blocks int 26 | } 27 | 28 | func New(opts Options) (c *compressor, err error) { 29 | defer func() { 30 | if r := recover(); r != nil { 31 | c, err = nil, fmt.Errorf("panic: %v", r) 32 | } 33 | }() 34 | 35 | tw, err := pgzip.NewWriterLevel(io.Discard, opts.Level) 36 | if err != nil { 37 | return nil, err 38 | } 39 | err = tw.SetConcurrency(opts.BlockSize, opts.Blocks) 40 | if err != nil { 41 | return nil, err 42 | } 43 | if err := utils.CheckWriter(tw); err != nil { 44 | return nil, fmt.Errorf("pgzip: writer initialization: %w", err) 45 | } 46 | 47 | c = &compressor{opts: opts} 48 | return c, nil 49 | } 50 | 51 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 52 | if gw, ok := c.pool.Get().(*writer); ok { 53 | gw.Reset(w) 54 | return gw 55 | } 56 | gw, err := pgzip.NewWriterLevel(w, c.opts.Level) 57 | if err != nil { 58 | return utils.ErrorWriteCloser{Err: err} 59 | } 60 | err = gw.SetConcurrency(c.opts.BlockSize, c.opts.Blocks) 61 | if err != nil { 62 | return utils.ErrorWriteCloser{Err: err} 63 | } 64 | return &writer{ 65 | Writer: gw, 66 | c: c, 67 | } 68 | } 69 | 70 | type writer struct { 71 | *pgzip.Writer 72 | c *compressor 73 | } 74 | 75 | func (w *writer) Close() error { 76 | err := w.Writer.Close() 77 | w.Reset(nil) 78 | w.c.pool.Put(w) 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /contrib/klauspost/pgzip/pgzip_race_test.go: -------------------------------------------------------------------------------- 1 | // +build race 2 | 3 | package pgzip_test 4 | 5 | import ( 6 | "runtime" 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | "github.com/CAFxX/httpcompression/contrib/klauspost/pgzip" 11 | ) 12 | 13 | func TestPgzipRace(t *testing.T) { 14 | t.Parallel() 15 | c, _ := pgzip.New(pgzip.Options{BlockSize: 1 << 20, Blocks: runtime.GOMAXPROCS(0)}) 16 | internal.RaceTestCompressionProvider(c, 100) 17 | } 18 | -------------------------------------------------------------------------------- /contrib/klauspost/pgzip/pgzip_test.go: -------------------------------------------------------------------------------- 1 | package pgzip_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "runtime" 7 | "testing" 8 | 9 | stdgzip "compress/gzip" 10 | 11 | "github.com/CAFxX/httpcompression" 12 | "github.com/CAFxX/httpcompression/contrib/klauspost/pgzip" 13 | ) 14 | 15 | var _ httpcompression.CompressorProvider = &pgzip.Compressor{} 16 | 17 | func TestPgzip(t *testing.T) { 18 | t.Parallel() 19 | 20 | s := []byte("hello world!") 21 | 22 | c, err := pgzip.New(pgzip.Options{BlockSize: 1 << 20, Blocks: runtime.GOMAXPROCS(0)}) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | b := &bytes.Buffer{} 27 | w := c.Get(b) 28 | w.Write(s) 29 | w.Close() 30 | 31 | r, err := stdgzip.NewReader(b) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | d, err := ioutil.ReadAll(r) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | if !bytes.Equal(s, d) { 40 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contrib/klauspost/zlib/deflate.go: -------------------------------------------------------------------------------- 1 | package zlib 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/klauspost/compress/zlib" 10 | ) 11 | 12 | const ( 13 | Encoding = "gzip" 14 | DefaultCompression = zlib.DefaultCompression 15 | ) 16 | 17 | type compressor struct { 18 | pool sync.Pool 19 | opts Options 20 | } 21 | 22 | type Options struct { 23 | Level int 24 | Dictionary []byte 25 | } 26 | 27 | func New(opts Options) (c *compressor, err error) { 28 | defer func() { 29 | if r := recover(); r != nil { 30 | c, err = nil, fmt.Errorf("panic: %v", r) 31 | } 32 | }() 33 | 34 | tw, err := zlib.NewWriterLevelDict(io.Discard, opts.Level, opts.Dictionary) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if err := utils.CheckWriter(tw); err != nil { 39 | return nil, fmt.Errorf("deflate: writer initialization: %w", err) 40 | } 41 | 42 | c = &compressor{opts: opts} 43 | return c, nil 44 | } 45 | 46 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 47 | if gw, ok := c.pool.Get().(*writer); ok { 48 | gw.Reset(w) 49 | return gw 50 | } 51 | gw, err := zlib.NewWriterLevelDict(w, c.opts.Level, c.opts.Dictionary) 52 | if err != nil { 53 | return utils.ErrorWriteCloser{Err: err} 54 | } 55 | return &writer{ 56 | Writer: gw, 57 | c: c, 58 | } 59 | } 60 | 61 | type writer struct { 62 | *zlib.Writer 63 | c *compressor 64 | } 65 | 66 | func (w *writer) Close() error { 67 | err := w.Writer.Close() 68 | w.Reset(nil) 69 | w.c.pool.Put(w) 70 | return err 71 | } 72 | -------------------------------------------------------------------------------- /contrib/klauspost/zlib/deflate_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package zlib_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | "github.com/CAFxX/httpcompression/contrib/klauspost/zlib" 11 | ) 12 | 13 | func TestDeflateRace(t *testing.T) { 14 | t.Parallel() 15 | c, _ := zlib.New(zlib.Options{}) 16 | internal.RaceTestCompressionProvider(c, 100) 17 | } 18 | -------------------------------------------------------------------------------- /contrib/klauspost/zlib/deflate_test.go: -------------------------------------------------------------------------------- 1 | package zlib_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | stdzlib "compress/zlib" 9 | 10 | "github.com/CAFxX/httpcompression" 11 | "github.com/CAFxX/httpcompression/contrib/klauspost/zlib" 12 | ) 13 | 14 | var _ httpcompression.CompressorProvider = &zlib.Compressor{} 15 | 16 | func TestDeflate(t *testing.T) { 17 | t.Parallel() 18 | 19 | s := []byte("hello world!") 20 | 21 | c, err := zlib.New(zlib.Options{Dictionary: s}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | b := &bytes.Buffer{} 26 | w := c.Get(b) 27 | w.Write(s) 28 | w.Close() 29 | 30 | r, err := stdzlib.NewReaderDict(b, s) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | d, err := ioutil.ReadAll(r) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if !bytes.Equal(s, d) { 39 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contrib/klauspost/zlib/export_test.go: -------------------------------------------------------------------------------- 1 | package zlib 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/klauspost/zstd/export_test.go: -------------------------------------------------------------------------------- 1 | package zstd 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/klauspost/zstd/zstd.go: -------------------------------------------------------------------------------- 1 | package zstd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/klauspost/compress/zstd" 10 | ) 11 | 12 | const ( 13 | Encoding = "zstd" 14 | DefaultCompression = zstd.SpeedDefault 15 | ) 16 | 17 | type compressor struct { 18 | pool sync.Pool 19 | opts []zstd.EOption 20 | } 21 | 22 | func New(opts ...zstd.EOption) (c *compressor, err error) { 23 | defer func() { 24 | if r := recover(); r != nil { 25 | c, err = nil, fmt.Errorf("panic: %v", r) 26 | } 27 | }() 28 | 29 | opts = append([]zstd.EOption(nil), opts...) 30 | 31 | tw, err := zstd.NewWriter(io.Discard, opts...) 32 | if err != nil { 33 | return nil, err 34 | } 35 | if err := utils.CheckWriter(tw); err != nil { 36 | return nil, fmt.Errorf("zstd: writer initialization: %w", err) 37 | } 38 | 39 | c = &compressor{opts: opts} 40 | return c, nil 41 | } 42 | 43 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 44 | if gw, ok := c.pool.Get().(*zstdWriter); ok { 45 | gw.Reset(w) 46 | return gw 47 | } 48 | gw, err := zstd.NewWriter(w, c.opts...) 49 | if err != nil { 50 | return utils.ErrorWriteCloser{Err: err} 51 | } 52 | return &zstdWriter{ 53 | Encoder: gw, 54 | c: c, 55 | } 56 | } 57 | 58 | type zstdWriter struct { 59 | *zstd.Encoder 60 | c *compressor 61 | } 62 | 63 | func (w *zstdWriter) Close() error { 64 | err := w.Encoder.Close() 65 | w.Reset(nil) 66 | w.c.pool.Put(w) 67 | return err 68 | } 69 | -------------------------------------------------------------------------------- /contrib/klauspost/zstd/zstd_race_test.go: -------------------------------------------------------------------------------- 1 | // +build race 2 | 3 | package zstd_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal" 9 | "github.com/CAFxX/httpcompression/contrib/klauspost/zstd" 10 | ) 11 | 12 | func TestZstdRace(t *testing.T) { 13 | t.Parallel() 14 | c, _ := zstd.New() 15 | internal.RaceTestCompressionProvider(c, 100) 16 | } 17 | -------------------------------------------------------------------------------- /contrib/klauspost/zstd/zstd_test.go: -------------------------------------------------------------------------------- 1 | package zstd_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | "github.com/CAFxX/httpcompression/contrib/klauspost/zstd" 10 | kpzstd "github.com/klauspost/compress/zstd" 11 | ) 12 | 13 | var _ httpcompression.CompressorProvider = &zstd.Compressor{} 14 | 15 | func TestZstd(t *testing.T) { 16 | t.Parallel() 17 | 18 | s := []byte("hello world!") 19 | 20 | c, err := zstd.New() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | b := &bytes.Buffer{} 25 | w := c.Get(b) 26 | w.Write(s) 27 | w.Close() 28 | 29 | r, err := kpzstd.NewReader(b) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | d, err := ioutil.ReadAll(r) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if !bytes.Equal(s, d) { 38 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contrib/labstack/echo/echo.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "github.com/CAFxX/httpcompression" 5 | "github.com/labstack/echo" 6 | ) 7 | 8 | func Adapter(opts ...httpcompression.Option) (echo.MiddlewareFunc, error) { 9 | mw, err := httpcompression.Adapter(opts...) 10 | if err != nil { 11 | return nil, err 12 | } 13 | return echo.WrapMiddleware(mw), nil 14 | } 15 | 16 | func DefaultAdapter(opts ...httpcompression.Option) (echo.MiddlewareFunc, error) { 17 | mw, err := httpcompression.DefaultAdapter(opts...) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return echo.WrapMiddleware(mw), nil 22 | } 23 | -------------------------------------------------------------------------------- /contrib/labstack/echo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/CAFxX/httpcompression/contrib/labstack/echo 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/CAFxX/httpcompression v0.0.8 7 | github.com/labstack/echo v3.3.10+incompatible 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/brotli v1.0.4 // indirect 12 | github.com/klauspost/compress v1.14.1 // indirect 13 | github.com/labstack/gommon v0.3.1 // indirect 14 | github.com/mattn/go-colorable v0.1.11 // indirect 15 | github.com/mattn/go-isatty v0.0.14 // indirect 16 | github.com/valyala/bytebufferpool v1.0.0 // indirect 17 | github.com/valyala/fasttemplate v1.2.1 // indirect 18 | golang.org/x/crypto v0.21.0 // indirect 19 | golang.org/x/net v0.23.0 // indirect 20 | golang.org/x/sys v0.18.0 // indirect 21 | golang.org/x/text v0.14.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /contrib/labstack/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/CAFxX/httpcompression v0.0.8 h1:UBWojERnpCS6X7whJkGGZeCC3ruZBRwkwkcnfGfb0ko= 2 | github.com/CAFxX/httpcompression v0.0.8/go.mod h1:bVd1taHK1vYb5SWe9lwNDCqrfj2ka+C1Zx7JHzxuHnU= 3 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 4 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36 h1:qg5qEpjk1P1EMnInOCpxOpWSPRsspXJDT7P80y/JfFA= 9 | github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= 10 | github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds= 11 | github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 12 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 13 | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= 14 | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= 15 | github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= 16 | github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 17 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= 18 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 19 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 20 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 21 | github.com/pierrec/lz4/v4 v4.1.12 h1:44l88ehTZAUGW4VlO1QC4zkilL99M6Y9MXNwEs0uzP8= 22 | github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 27 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 28 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 29 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 30 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 31 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 32 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 33 | github.com/valyala/gozstd v1.11.0 h1:VV6qQFt+4sBBj9OJ7eKVvsFAMy59Urcs9Lgd+o5FOw0= 34 | github.com/valyala/gozstd v1.11.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= 35 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 38 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 39 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 40 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 41 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 42 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 43 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 44 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 45 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 46 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 47 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 48 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 49 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 50 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 51 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 52 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 53 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 55 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 60 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 61 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 62 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 63 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 64 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 65 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 66 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 67 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 68 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 69 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 70 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 71 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 72 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 73 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 74 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 75 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 76 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 77 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 78 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 79 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 80 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 81 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 82 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 83 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 84 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 85 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 86 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 87 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 88 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 89 | -------------------------------------------------------------------------------- /contrib/pierrec/lz4/export_test.go: -------------------------------------------------------------------------------- 1 | package lz4 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/pierrec/lz4/lz4.go: -------------------------------------------------------------------------------- 1 | package lz4 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | lz4 "github.com/pierrec/lz4/v4" 10 | ) 11 | 12 | const ( 13 | Encoding = "lz4" 14 | ) 15 | 16 | type compressor struct { 17 | pool sync.Pool 18 | opts []lz4.Option 19 | } 20 | 21 | func New(opts ...lz4.Option) (c *compressor, err error) { 22 | defer func() { 23 | if r := recover(); r != nil { 24 | c, err = nil, fmt.Errorf("panic: %v", r) 25 | } 26 | }() 27 | 28 | opts = append([]lz4.Option(nil), opts...) 29 | 30 | tw := lz4.NewWriter(io.Discard) 31 | err = tw.Apply(opts...) 32 | if err != nil { 33 | return nil, fmt.Errorf("lz4: apply options: %w", err) 34 | } 35 | if err := utils.CheckWriter(tw); err != nil { 36 | return nil, fmt.Errorf("lz4: writer initialization: %w", err) 37 | } 38 | 39 | c = &compressor{opts: opts} 40 | return c, nil 41 | } 42 | 43 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 44 | if gw, ok := c.pool.Get().(*writer); ok { 45 | gw.Reset(w) 46 | return gw 47 | } 48 | gw := lz4.NewWriter(w) 49 | err := gw.Apply(c.opts...) 50 | if err != nil { 51 | return utils.ErrorWriteCloser{Err: err} 52 | } 53 | return &writer{ 54 | Writer: gw, 55 | c: c, 56 | } 57 | } 58 | 59 | type writer struct { 60 | *lz4.Writer 61 | c *compressor 62 | } 63 | 64 | func (w *writer) Close() error { 65 | err := w.Writer.Close() 66 | w.Reset(nil) 67 | w.c.pool.Put(w) 68 | return err 69 | } 70 | -------------------------------------------------------------------------------- /contrib/pierrec/lz4/lz4_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package lz4_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | "github.com/CAFxX/httpcompression/contrib/pierrec/lz4" 11 | ) 12 | 13 | func TestLz4Race(t *testing.T) { 14 | t.Parallel() 15 | c, _ := lz4.New() 16 | internal.RaceTestCompressionProvider(c, 100) 17 | } 18 | -------------------------------------------------------------------------------- /contrib/pierrec/lz4/lz4_test.go: -------------------------------------------------------------------------------- 1 | package lz4_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | "github.com/CAFxX/httpcompression/contrib/pierrec/lz4" 10 | plz4 "github.com/pierrec/lz4/v4" 11 | ) 12 | 13 | var _ httpcompression.CompressorProvider = &lz4.Compressor{} 14 | 15 | func TestLz4(t *testing.T) { 16 | t.Parallel() 17 | 18 | s := []byte("hello world!") 19 | 20 | c, err := lz4.New() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | b := &bytes.Buffer{} 25 | w := c.Get(b) 26 | w.Write(s) 27 | w.Close() 28 | 29 | r := plz4.NewReader(b) 30 | d, err := ioutil.ReadAll(r) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | if !bytes.Equal(s, d) { 35 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contrib/ulikunitz/xz/export_test.go: -------------------------------------------------------------------------------- 1 | package xz 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/ulikunitz/xz/xz.go: -------------------------------------------------------------------------------- 1 | package xz 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 8 | "github.com/ulikunitz/xz" 9 | ) 10 | 11 | const ( 12 | Encoding = "xz" 13 | ) 14 | 15 | type compressor struct { 16 | opts xz.WriterConfig 17 | } 18 | 19 | func New(opts xz.WriterConfig) (c *compressor, err error) { 20 | defer func() { 21 | if r := recover(); r != nil { 22 | c, err = nil, fmt.Errorf("panic: %v", r) 23 | } 24 | }() 25 | 26 | tw, err := opts.NewWriter(io.Discard) 27 | if err != nil { 28 | return nil, fmt.Errorf("xz: writer initialization: NewWriter: %w", err) 29 | } 30 | if err := utils.CheckWriter(tw); err != nil { 31 | return nil, fmt.Errorf("xz: writer initialization: %w", err) 32 | } 33 | 34 | c = &compressor{opts: opts} 35 | return c, nil 36 | } 37 | 38 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 39 | gw, _ := c.opts.NewWriter(w) 40 | return &xzWriter{gw} 41 | } 42 | 43 | type xzWriter struct { 44 | *xz.Writer 45 | } 46 | -------------------------------------------------------------------------------- /contrib/ulikunitz/xz/xz_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package xz_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | "github.com/CAFxX/httpcompression/contrib/ulikunitz/xz" 11 | pxz "github.com/ulikunitz/xz" 12 | ) 13 | 14 | func TestXzRace(t *testing.T) { 15 | t.Parallel() 16 | c, _ := xz.New(pxz.WriterConfig{}) 17 | internal.RaceTestCompressionProvider(c, 100) 18 | } 19 | -------------------------------------------------------------------------------- /contrib/ulikunitz/xz/xz_test.go: -------------------------------------------------------------------------------- 1 | package xz_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | "github.com/CAFxX/httpcompression/contrib/ulikunitz/xz" 10 | pxz "github.com/ulikunitz/xz" 11 | ) 12 | 13 | var _ httpcompression.CompressorProvider = &xz.Compressor{} 14 | 15 | func TestXz(t *testing.T) { 16 | t.Parallel() 17 | 18 | s := []byte("hello world!") 19 | 20 | c, err := xz.New(pxz.WriterConfig{}) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | b := &bytes.Buffer{} 25 | w := c.Get(b) 26 | w.Write(s) 27 | w.Close() 28 | 29 | r, err := pxz.NewReader(b) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | d, err := io.ReadAll(r) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if !bytes.Equal(s, d) { 38 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contrib/valyala/gozstd/export_test.go: -------------------------------------------------------------------------------- 1 | package gozstd 2 | 3 | type Compressor = compressor 4 | -------------------------------------------------------------------------------- /contrib/valyala/gozstd/zstd.go: -------------------------------------------------------------------------------- 1 | package gozstd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sync" 7 | 8 | "github.com/CAFxX/httpcompression/contrib/internal/utils" 9 | "github.com/valyala/gozstd" 10 | ) 11 | 12 | const ( 13 | Encoding = "zstd" 14 | ) 15 | 16 | type compressor struct { 17 | pool sync.Pool 18 | opts gozstd.WriterParams 19 | } 20 | 21 | func New(opts gozstd.WriterParams) (c *compressor, err error) { 22 | defer func() { 23 | if r := recover(); r != nil { 24 | c, err = nil, fmt.Errorf("panic: %v", r) 25 | } 26 | }() 27 | 28 | tw := gozstd.NewWriterParams(io.Discard, &opts) 29 | if err := utils.CheckWriter(tw); err != nil { 30 | return nil, fmt.Errorf("gozstd: writer initialization: %w", err) 31 | } 32 | 33 | c = &compressor{opts: opts} 34 | return c, nil 35 | } 36 | 37 | func (c *compressor) Get(w io.Writer) io.WriteCloser { 38 | if gw, ok := c.pool.Get().(*zstdWriter); ok { 39 | gw.ResetWriterParams(w, &c.opts) 40 | return gw 41 | } 42 | gw := gozstd.NewWriterParams(w, &c.opts) 43 | return &zstdWriter{ 44 | Writer: gw, 45 | c: c, 46 | } 47 | } 48 | 49 | type zstdWriter struct { 50 | *gozstd.Writer 51 | c *compressor 52 | } 53 | 54 | func (w *zstdWriter) Close() error { 55 | err := w.Writer.Close() 56 | w.ResetWriterParams(nil, &w.c.opts) // drop reference to parent writer 57 | w.c.pool.Put(w) 58 | return err 59 | } 60 | -------------------------------------------------------------------------------- /contrib/valyala/gozstd/zstd_race_test.go: -------------------------------------------------------------------------------- 1 | //go:build race 2 | // +build race 3 | 4 | package gozstd_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/CAFxX/httpcompression/contrib/internal" 10 | "github.com/CAFxX/httpcompression/contrib/valyala/gozstd" 11 | vzstd "github.com/valyala/gozstd" 12 | ) 13 | 14 | func TestZstdRace(t *testing.T) { 15 | t.Parallel() 16 | c, _ := gozstd.New(vzstd.WriterParams{}) 17 | internal.RaceTestCompressionProvider(c, 100) 18 | } 19 | -------------------------------------------------------------------------------- /contrib/valyala/gozstd/zstd_test.go: -------------------------------------------------------------------------------- 1 | package gozstd_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/CAFxX/httpcompression" 9 | "github.com/CAFxX/httpcompression/contrib/valyala/gozstd" 10 | vzstd "github.com/valyala/gozstd" 11 | ) 12 | 13 | var _ httpcompression.CompressorProvider = &gozstd.Compressor{} 14 | 15 | func TestZstd(t *testing.T) { 16 | t.Parallel() 17 | 18 | s := []byte("hello world!") 19 | 20 | c, err := gozstd.New(vzstd.WriterParams{}) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | b := &bytes.Buffer{} 25 | w := c.Get(b) 26 | w.Write(s) 27 | w.Close() 28 | 29 | r := vzstd.NewReader(b) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | d, err := ioutil.ReadAll(r) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if !bytes.Equal(s, d) { 38 | t.Fatalf("decoded string mismatch\ngot: %q\nexp: %q", string(s), string(d)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package httpcompression_test 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/CAFxX/httpcompression" 12 | "github.com/CAFxX/httpcompression/contrib/andybalholm/brotli" 13 | "github.com/CAFxX/httpcompression/contrib/klauspost/gzip" 14 | "github.com/CAFxX/httpcompression/contrib/klauspost/zstd" 15 | "github.com/CAFxX/httpcompression/contrib/pierrec/lz4" 16 | kpzstd "github.com/klauspost/compress/zstd" 17 | ) 18 | 19 | func Example() { 20 | // Create a compression adapter with default configuration 21 | compress, err := httpcompression.DefaultAdapter() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | // Define your handler, and apply the compression adapter. 26 | http.Handle("/", compress(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | w.Write([]byte("Hello world!")) 28 | }))) 29 | // ... 30 | } 31 | 32 | func ExampleCustom() { 33 | brEnc, err := brotli.New(brotli.Options{}) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | gzEnc, err := gzip.New(gzip.Options{}) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | _, _ = httpcompression.Adapter( 42 | httpcompression.Compressor(brotli.Encoding, 1, brEnc), 43 | httpcompression.Compressor(gzip.Encoding, 0, gzEnc), 44 | httpcompression.Prefer(httpcompression.PreferServer), 45 | httpcompression.MinSize(100), 46 | httpcompression.ContentTypes([]string{ 47 | "image/jpeg", 48 | "image/gif", 49 | "image/png", 50 | }, true), 51 | ) 52 | } 53 | 54 | func ExampleWithDictionary() { 55 | // Default zstd compressor 56 | zEnc, err := zstd.New() 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | // zstd compressor with custom dictionary 61 | dict, coding, err := readZstdDictionary("tests/dictionary") 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | zdEnc, err := zstd.New(kpzstd.WithEncoderDict(dict)) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | _, _ = httpcompression.DefaultAdapter( 70 | // Add the zstd compressor with the dictionary. 71 | // We need to pick a custom content-encoding name. It is recommended to: 72 | // - avoid names that contain standard names (e.g. "gzip", "deflate", "br" or "zstd") 73 | // - include the dictionary ID, so that multiple dictionaries can be used (including 74 | // e.g. multiple versions of the same dictionary) 75 | httpcompression.Compressor(coding, 3, zdEnc), 76 | httpcompression.Compressor(zstd.Encoding, 2, zEnc), 77 | httpcompression.Prefer(httpcompression.PreferServer), 78 | httpcompression.MinSize(0), 79 | httpcompression.ContentTypes([]string{ 80 | "image/jpeg", 81 | "image/gif", 82 | "image/png", 83 | }, true), 84 | ) 85 | } 86 | 87 | func readZstdDictionary(file string) (dict []byte, coding string, err error) { 88 | dictFile, err := os.Open(file) 89 | if err != nil { 90 | return nil, "", err 91 | } 92 | dict, err = ioutil.ReadAll(dictFile) 93 | if err != nil { 94 | return nil, "", err 95 | } 96 | if len(dict) < 8 { 97 | return nil, "", fmt.Errorf("invalid dictionary") 98 | } 99 | dictID := binary.LittleEndian.Uint32(dict[4:8]) // read the dictionary ID 100 | // Build the encoding name: z_XXXXXXXX (where XXXXXXXX is the dictionary ID in hex lowercase). 101 | // There is no standard way to communicate the use of a dictionary with a content-encoding: 102 | // so we simply use a non-standard name to identify the encoding-dictionary pair in use. 103 | // This naming scheme is arbitrary and as long as it is not one of those in the IANA registry 104 | // (https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding) 105 | // anything should work. It is recommended not to include one of the standard names even as a 106 | // substring of the chosen name as some poorly-configured proxies may simply perform a case 107 | // insensitive substring match for e.g. "deflate", in which case the name e.g. 108 | // "deflate_12345678" would still match, even though it should not as a deflate decompressor 109 | // without the dictionary will fail to decompress the contents. 110 | coding = fmt.Sprintf("z_%08x", dictID) 111 | return 112 | } 113 | 114 | // ExampleCustomCompressor shows how to create an httpcompression adapter with a custom compressor. 115 | // In this case we use the pierrec/lz4 compressor in contrib, but it could be replaced with any 116 | // other compressor, as long as it implements the CompressorProvider interface. 117 | func ExampleCustomCompressor() { 118 | c, err := lz4.New() 119 | if err != nil { 120 | log.Fatal(err) 121 | } 122 | _, _ = httpcompression.Adapter( 123 | // Only enable the custom compressor; other options/compressors can be added if needed, 124 | // or DefaultAdapter could be used as a baseline. 125 | httpcompression.Compressor(lz4.Encoding, 0, c), 126 | ) 127 | } 128 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/CAFxX/httpcompression 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/andybalholm/brotli v1.1.1 7 | github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f 8 | github.com/klauspost/compress v1.17.9 9 | github.com/klauspost/pgzip v1.2.6 10 | github.com/pierrec/lz4/v4 v4.1.21 11 | github.com/stretchr/testify v1.9.0 12 | github.com/ulikunitz/xz v0.5.12 13 | github.com/valyala/gozstd v1.21.2 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 2 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k= 7 | github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= 8 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 9 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 10 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 11 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 12 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 13 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 18 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 19 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 20 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 22 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 23 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 24 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 25 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 26 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 27 | github.com/valyala/gozstd v1.21.2 h1:SBZ6sYA9y+u32XSds1TwOJJatcqmA3TgfLwGtV78Fcw= 28 | github.com/valyala/gozstd v1.21.2/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= 29 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 30 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /handle.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import "mime" 4 | 5 | // returns true if we've been configured to compress the specific content type. 6 | func handleContentType(ct string, contentTypes []parsedContentType, blacklist bool) bool { 7 | // If contentTypes is empty we handle all content types. 8 | if len(contentTypes) == 0 { 9 | return !blacklist 10 | } 11 | return handleContentTypeSlow(ct, contentTypes, blacklist) 12 | } 13 | 14 | func handleContentTypeSlow(ct string, contentTypes []parsedContentType, blacklist bool) bool { 15 | mediaType, params, err := mime.ParseMediaType(ct) 16 | if err != nil { 17 | return false 18 | } 19 | 20 | for _, c := range contentTypes { 21 | if c.equals(mediaType, params) { 22 | return !blacklist 23 | } 24 | } 25 | 26 | return blacklist 27 | } 28 | -------------------------------------------------------------------------------- /prefer.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | // Prefer controls the behavior of the middleware in case both Gzip and Brotli 9 | // can be used to compress a response (i.e. in case the client supports both 10 | // encodings, and the MIME type of the response is allowed for both encodings). 11 | // See the comments on the PreferType constants for the supported values. 12 | func Prefer(prefer PreferType) Option { 13 | return func(c *config) error { 14 | switch prefer { 15 | case PreferServer, PreferClient: 16 | c.prefer = prefer 17 | return nil 18 | default: 19 | return fmt.Errorf("unknown prefer type: %v", prefer) 20 | } 21 | } 22 | } 23 | 24 | // PreferType allows to control the choice of compression algorithm when 25 | // multiple algorithms are allowed by both client and server. 26 | type PreferType byte 27 | 28 | const ( 29 | // PreferServer prefers compressors in the order specified on the server. 30 | // If two or more compressors have the same priority on the server, the client preference is taken into consideration. 31 | // If both server and client do no specify a preference between two or more compressors, the order is determined by the name of the encoding. 32 | // PreferServer is the default. 33 | PreferServer PreferType = iota 34 | 35 | // PreferClient prefers compressors in the order specified by the client. 36 | // If two or more compressors have the same priority according to the client, the server priority is taken into consideration. 37 | // If both server and client do no specify a preference between two or more compressors, the order is determined by the name of the encoding. 38 | PreferClient 39 | ) 40 | 41 | func preferredEncoding(accept codings, comps comps, common []string, prefer PreferType) string { 42 | if len(common) == 0 { 43 | panic("no common encoding") 44 | } 45 | switch prefer { 46 | case PreferServer: 47 | sort.Slice(common, func(i, j int) bool { 48 | ci, cj := comps[common[i]].priority, comps[common[j]].priority 49 | if ci != cj { 50 | return ci > cj // desc 51 | } 52 | ai, aj := accept[common[i]], accept[common[j]] 53 | if ai != aj { 54 | return ai > aj // desc 55 | } 56 | return common[i] < common[j] // asc 57 | }) 58 | case PreferClient: 59 | sort.Slice(common, func(i, j int) bool { 60 | ai, aj := accept[common[i]], accept[common[j]] 61 | if ai != aj { 62 | return ai > aj // desc 63 | } 64 | ci, cj := comps[common[i]].priority, comps[common[j]].priority 65 | if ci != cj { 66 | return ci > cj // desc 67 | } 68 | return common[i] < common[j] // asc 69 | }) 70 | default: 71 | panic("unknown prefer type") 72 | } 73 | return common[0] 74 | } 75 | -------------------------------------------------------------------------------- /response_writer.go: -------------------------------------------------------------------------------- 1 | package httpcompression 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "strconv" 10 | "sync" 11 | ) 12 | 13 | // compressWriter provides an http.ResponseWriter interface, which gzips 14 | // bytes before writing them to the underlying response. This doesn't close the 15 | // writers, so don't forget to do that. 16 | // It can be configured to skip response smaller than minSize. 17 | type compressWriter struct { 18 | http.ResponseWriter 19 | 20 | config config 21 | accept codings 22 | common []string 23 | pool *sync.Pool // pool of buffers (buf []byte); max size of each buf is maxBuf 24 | 25 | w io.Writer 26 | enc string 27 | code int // Saves the WriteHeader value. 28 | buf *[]byte // Holds the first part of the write before reaching the minSize or the end of the write. 29 | } 30 | 31 | var ( 32 | _ io.WriteCloser = &compressWriter{} 33 | _ http.Flusher = &compressWriter{} 34 | _ http.Hijacker = &compressWriter{} 35 | _ io.StringWriter = &compressWriter{} 36 | ) 37 | 38 | type compressWriterWithCloseNotify struct { 39 | *compressWriter 40 | } 41 | 42 | func (w compressWriterWithCloseNotify) CloseNotify() <-chan bool { 43 | return w.ResponseWriter.(http.CloseNotifier).CloseNotify() 44 | } 45 | 46 | var ( 47 | _ io.WriteCloser = compressWriterWithCloseNotify{} 48 | _ http.Flusher = compressWriterWithCloseNotify{} 49 | _ http.Hijacker = compressWriterWithCloseNotify{} 50 | _ io.StringWriter = compressWriterWithCloseNotify{} 51 | ) 52 | 53 | const maxBuf = 1 << 16 // maximum size of recycled buffer 54 | 55 | // Write compresses and appends the given byte slice to the underlying ResponseWriter. 56 | func (w *compressWriter) Write(b []byte) (int, error) { 57 | if w.w != nil { 58 | // The responseWriter is already initialized: use it. 59 | return w.w.Write(b) 60 | } 61 | 62 | var ( 63 | ct = w.Header().Get(contentType) 64 | ce = w.Header().Get(contentEncoding) 65 | cl = 0 66 | ) 67 | if clv := w.Header().Get(contentLength); clv != "" { 68 | cl, _ = strconv.Atoi(clv) 69 | } 70 | 71 | // Fast path: we have enough information to know whether we will compress 72 | // or not this response from the first write, so we don't need to buffer 73 | // writes to defer the decision until we have more data. 74 | if w.buf == nil && (ct != "" || len(w.config.contentTypes) == 0) && (cl > 0 || len(b) >= w.config.minSize) { 75 | if ce == "" && (cl >= w.config.minSize || len(b) >= w.config.minSize) && handleContentType(ct, w.config.contentTypes, w.config.blacklist) { 76 | enc := preferredEncoding(w.accept, w.config.compressor, w.common, w.config.prefer) 77 | if err := w.startCompress(enc, b); err != nil { 78 | return 0, err 79 | } 80 | return len(b), nil 81 | } 82 | if err := w.startPlain(b); err != nil { 83 | return 0, err 84 | } 85 | return len(b), nil 86 | } 87 | 88 | // Slow path: we don't have yet enough information to decide whether we should 89 | // compress this response. Append the data to a temporary buffer and then try again. 90 | if w.buf == nil { 91 | w.buf = w.getBuffer() 92 | } 93 | *w.buf = append(*w.buf, b...) 94 | 95 | // Only continue if they didn't already choose an encoding or a known unhandled content length or type. 96 | if ce == "" && (cl == 0 || cl >= w.config.minSize) && (ct == "" || handleContentType(ct, w.config.contentTypes, w.config.blacklist)) { 97 | // If the current buffer is less than minSize and a Content-Length isn't set, then wait until we have more data. 98 | if len(*w.buf) < w.config.minSize && cl == 0 { 99 | return len(b), nil 100 | } 101 | // If the Content-Length is larger than minSize or the current buffer is larger than minSize, then continue. 102 | if cl >= w.config.minSize || len(*w.buf) >= w.config.minSize { 103 | // If a Content-Type wasn't specified, infer it from the current buffer. 104 | if ct == "" { 105 | ct = http.DetectContentType(*w.buf) 106 | if ct != "" { 107 | // net/http by default performs content sniffing but this is disabled if content-encoding is set. 108 | // Since we set content-encoding, if content-type was not set and we successfully sniffed it, 109 | // set the content-type. 110 | w.Header().Set(contentType, ct) 111 | } 112 | } 113 | if handleContentType(ct, w.config.contentTypes, w.config.blacklist) { 114 | enc := preferredEncoding(w.accept, w.config.compressor, w.common, w.config.prefer) 115 | if err := w.startCompress(enc, *w.buf); err != nil { 116 | return 0, err 117 | } 118 | return len(b), nil 119 | } 120 | } 121 | } 122 | // If we got here, we should not GZIP this response. 123 | if err := w.startPlain(*w.buf); err != nil { 124 | return 0, err 125 | } 126 | return len(b), nil 127 | } 128 | 129 | // WriteString compresses and appends the given string to the underlying ResponseWriter. 130 | // 131 | // This makes use of an optional method (WriteString) exposed by the compressors, or by 132 | // the underlying ResponseWriter. 133 | func (w *compressWriter) WriteString(s string) (int, error) { 134 | // Since WriteString is an optional interface of the compressor, and the actual compressor 135 | // is chosen only after the first call to Write, we can't statically know whether the interface 136 | // is supported. We therefore have to check dynamically. 137 | if ws, _ := w.w.(io.StringWriter); ws != nil { 138 | // The responseWriter is already initialized and it implements WriteString. 139 | return ws.WriteString(s) 140 | } 141 | // Fallback: the writer has not been initialized yet, or it has been initialized 142 | // and it does not implement WriteString. We could in theory do something unsafe 143 | // here but for now let's keep it simple and fallback to Write. 144 | // TODO: in case the string is large, we should avoid allocating a full copy: 145 | // instead we should copy the string in chunks. 146 | return w.Write([]byte(s)) 147 | } 148 | 149 | // startCompress initializes a compressing writer and writes the buffer. 150 | func (w *compressWriter) startCompress(enc string, buf []byte) error { 151 | comp, ok := w.config.compressor[enc] 152 | if !ok { 153 | panic("unknown compressor") 154 | } 155 | 156 | w.Header().Set(contentEncoding, enc) 157 | 158 | // if the Content-Length is already set, then calls to Write on gzip 159 | // will fail to set the Content-Length header since its already set 160 | // See: https://github.com/golang/go/issues/14975. 161 | w.Header().Del(contentLength) 162 | 163 | // See the comment about ranges in adapter.go 164 | w.Header().Del(acceptRanges) 165 | 166 | // Write the header to gzip response. 167 | if w.code != 0 { 168 | w.ResponseWriter.WriteHeader(w.code) 169 | // Ensure that no other WriteHeader's happen 170 | w.code = 0 171 | } 172 | 173 | defer w.recycleBuffer() 174 | 175 | // Initialize and flush the buffer into the gzip response if there are any bytes. 176 | // If there aren't any, we shouldn't initialize it yet because on Close it will 177 | // write the gzip header even if nothing was ever written. 178 | if len(buf) > 0 { 179 | w.w = comp.comp.Get(w.ResponseWriter) 180 | w.enc = enc 181 | 182 | n, err := w.w.Write(buf) 183 | 184 | // This should never happen (per io.Writer docs), but if the write didn't 185 | // accept the entire buffer but returned no specific error, we have no clue 186 | // what's going on, so abort just to be safe. 187 | if err == nil && n < len(buf) { 188 | err = io.ErrShortWrite 189 | } 190 | return err 191 | } 192 | return nil 193 | } 194 | 195 | // startPlain writes to sent bytes and buffer the underlying ResponseWriter without gzip. 196 | func (w *compressWriter) startPlain(buf []byte) error { 197 | // See the comment about ranges in adapter.go; we need to do it even in this case 198 | // because adapter will strip the range header anyway. 199 | w.Header().Del(acceptRanges) 200 | 201 | if w.code != 0 { 202 | w.ResponseWriter.WriteHeader(w.code) 203 | // Ensure that no other WriteHeader's happen 204 | w.code = 0 205 | } 206 | w.w = w.ResponseWriter 207 | w.enc = "" 208 | // If Write was never called then don't call Write on the underlying ResponseWriter. 209 | if buf == nil { 210 | return nil 211 | } 212 | n, err := w.ResponseWriter.Write(buf) 213 | // This should never happen (per io.Writer docs), but if the write didn't 214 | // accept the entire buffer but returned no specific error, we have no clue 215 | // what's going on, so abort just to be safe. 216 | if err == nil && n < len(buf) { 217 | err = io.ErrShortWrite 218 | } 219 | w.recycleBuffer() 220 | return err 221 | } 222 | 223 | // WriteHeader sets the response code that will be returned in the response. 224 | func (w *compressWriter) WriteHeader(code int) { 225 | if w.code == 0 { 226 | w.code = code 227 | } 228 | } 229 | 230 | // Close closes the compression Writer. 231 | func (w *compressWriter) Close() error { 232 | if w.w != nil && w.enc == "" { 233 | return nil 234 | } 235 | if cw, ok := w.w.(io.Closer); ok { 236 | w.w = nil 237 | return cw.Close() 238 | } 239 | 240 | // compression not triggered yet, write out regular response. 241 | var buf []byte 242 | if w.buf != nil { 243 | buf = *w.buf 244 | } 245 | err := w.startPlain(buf) 246 | // Returns the error if any at write. 247 | if err != nil { 248 | err = fmt.Errorf("httpcompression: write to regular responseWriter at close gets error: %v", err) 249 | } 250 | return err 251 | } 252 | 253 | // Flush flushes the underlying compressor Writer and then the underlying 254 | // http.ResponseWriter if it is an http.Flusher. This makes compressWriter 255 | // an http.Flusher. 256 | // Flush is a no-op until enough data has been written to decide whether the 257 | // response should be compressed or not (e.g. less than MinSize bytes have 258 | // been written). 259 | func (w *compressWriter) Flush() { 260 | if w.w == nil { 261 | // Flush is thus a no-op until we're certain whether a plain 262 | // or compressed response will be served. 263 | return 264 | } 265 | 266 | // Flush the compressor, if supported. 267 | // note: http.ResponseWriter does not implement Flusher (http.Flusher does not return an error), 268 | // so we need to later call ResponseWriter.Flush anyway: 269 | // - in case we are bypassing compression, w.w is the parent ResponseWriter, and therefore we skip 270 | // this as the parent ResponseWriter does not implement Flusher. 271 | // - in case we are NOT bypassing compression, w.w is the compressor, and therefore we flush the 272 | // compressor and then we flush the parent ResponseWriter. 273 | if fw, ok := w.w.(Flusher); ok { 274 | _ = fw.Flush() 275 | } 276 | 277 | // Flush the ResponseWriter (the previous Flusher is not expected to flush the parent writer). 278 | if fw, ok := w.ResponseWriter.(http.Flusher); ok { 279 | fw.Flush() 280 | } 281 | } 282 | 283 | // Hijack implements http.Hijacker. If the underlying ResponseWriter is a 284 | // Hijacker, its Hijack method is returned. Otherwise an error is returned. 285 | func (w *compressWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 286 | if hj, ok := w.ResponseWriter.(http.Hijacker); ok { 287 | return hj.Hijack() 288 | } 289 | return nil, nil, fmt.Errorf("http.Hijacker interface is not supported") 290 | } 291 | 292 | func (w *compressWriter) getBuffer() *[]byte { 293 | b := w.pool.Get() 294 | if b == nil { 295 | var s []byte 296 | return &s 297 | } 298 | return b.(*[]byte) 299 | } 300 | 301 | func (w *compressWriter) recycleBuffer() { 302 | if w.buf == nil { 303 | return 304 | } 305 | buf := w.buf 306 | w.buf = nil 307 | if cap(*buf) > maxBuf { 308 | // If the buffer is too big, let's drop it to avoid 309 | // keeping huge buffers alive in the pool. In this case 310 | // we still recycle the pointer to the slice. 311 | *buf = nil 312 | } 313 | if len(*buf) > 0 { 314 | // Reset the buffer to zero length. 315 | *buf = (*buf)[:0] 316 | } 317 | w.pool.Put(buf) 318 | } 319 | -------------------------------------------------------------------------------- /results.md: -------------------------------------------------------------------------------- 1 | Results of running 2 | ``` 3 | benchstat -html <(go test -count=5 -benchtime=0.2s -bench=././././serial) >results.md 4 | ``` 5 | 6 | The benchmark names follow the format `Adapter/(body_size)/(encoder)/(level)/serial-*`. 7 | 8 | The first table lists time to compress the body with the specified encoder and level. 9 | 10 | The second table lists the size of the resulting compressed body. 11 | 12 |
time/op 18 | | |
---|---|
Adapter/100/stdlib-gzip/1/serial-4 | 815ns ± 5% 19 | |
Adapter/100/klauspost-gzip/1/serial-4 | 846ns ± 7% 20 | |
Adapter/100/andybalholm-brotli/1/serial-4 | 788ns ± 2% 21 | |
Adapter/100/google-cbrotli/1/serial-4 | 780ns ± 2% 22 | |
Adapter/100/klauspost-zstd/1/serial-4 | 800ns ± 7% 23 | |
Adapter/100/valyala-gozstd/1/serial-4 | 790ns ± 4% 24 | |
Adapter/1000/google-cbrotli/1/serial-4 | 10.5µs ± 1% 25 | |
Adapter/1000/google-cbrotli/2/serial-4 | 11.5µs ± 1% 26 | |
Adapter/1000/google-cbrotli/3/serial-4 | 20.3µs ±16% 27 | |
Adapter/1000/google-cbrotli/4/serial-4 | 29.9µs ± 2% 28 | |
Adapter/1000/google-cbrotli/5/serial-4 | 34.4µs ± 1% 29 | |
Adapter/1000/google-cbrotli/6/serial-4 | 35.3µs ± 3% 30 | |
Adapter/1000/google-cbrotli/7/serial-4 | 44.2µs ±10% 31 | |
Adapter/1000/google-cbrotli/8/serial-4 | 43.0µs ± 1% 32 | |
Adapter/1000/google-cbrotli/9/serial-4 | 2.25ms ± 4% 33 | |
Adapter/1000/google-cbrotli/10/serial-4 | 1.67ms ±11% 34 | |
Adapter/1000/google-cbrotli/11/serial-4 | 2.58ms ± 3% 35 | |
Adapter/1000/klauspost-zstd/1/serial-4 | 14.1µs ±30% 36 | |
Adapter/1000/klauspost-zstd/2/serial-4 | 16.2µs ± 9% 37 | |
Adapter/1000/klauspost-zstd/3/serial-4 | 25.8µs ± 9% 38 | |
Adapter/1000/klauspost-zstd/4/serial-4 | 242µs ± 6% 39 | |
Adapter/1000/valyala-gozstd/1/serial-4 | 10.3µs ± 5% 40 | |
Adapter/1000/valyala-gozstd/2/serial-4 | 14.8µs ± 4% 41 | |
Adapter/1000/valyala-gozstd/3/serial-4 | 32.2µs ±13% 42 | |
Adapter/1000/valyala-gozstd/4/serial-4 | 76.5µs ± 2% 43 | |
Adapter/1000/valyala-gozstd/5/serial-4 | 126µs ± 5% 44 | |
Adapter/1000/valyala-gozstd/6/serial-4 | 124µs ± 1% 45 | |
Adapter/1000/valyala-gozstd/7/serial-4 | 128µs ± 4% 46 | |
Adapter/1000/valyala-gozstd/8/serial-4 | 128µs ± 5% 47 | |
Adapter/1000/valyala-gozstd/9/serial-4 | 254µs ± 3% 48 | |
Adapter/1000/valyala-gozstd/10/serial-4 | 513µs ± 1% 49 | |
Adapter/1000/valyala-gozstd/11/serial-4 | 2.66ms ± 6% 50 | |
Adapter/1000/valyala-gozstd/12/serial-4 | 2.64ms ± 9% 51 | |
Adapter/1000/valyala-gozstd/13/serial-4 | 2.45ms ±11% 52 | |
Adapter/1000/valyala-gozstd/14/serial-4 | 7.17ms ± 2% 53 | |
Adapter/1000/valyala-gozstd/15/serial-4 | 9.35ms ± 1% 54 | |
Adapter/1000/valyala-gozstd/16/serial-4 | 4.52ms ±13% 55 | |
Adapter/1000/valyala-gozstd/17/serial-4 | 7.12ms ± 3% 56 | |
Adapter/1000/valyala-gozstd/18/serial-4 | 7.20ms ± 2% 57 | |
Adapter/1000/valyala-gozstd/19/serial-4 | 11.8ms ± 1% 58 | |
Adapter/1000/valyala-gozstd/20/serial-4 | 23.6ms ± 2% 59 | |
Adapter/1000/valyala-gozstd/21/serial-4 | 47.0ms ± 2% 60 | |
Adapter/1000/valyala-gozstd/22/serial-4 | 95.5ms ± 1% 61 | |
Adapter/1000/stdlib-gzip/1/serial-4 | 22.8µs ± 5% 62 | |
Adapter/1000/stdlib-gzip/2/serial-4 | 58.7µs ± 3% 63 | |
Adapter/1000/stdlib-gzip/3/serial-4 | 61.9µs ±18% 64 | |
Adapter/1000/stdlib-gzip/4/serial-4 | 59.4µs ± 1% 65 | |
Adapter/1000/stdlib-gzip/5/serial-4 | 62.4µs ± 4% 66 | |
Adapter/1000/stdlib-gzip/6/serial-4 | 60.6µs ± 2% 67 | |
Adapter/1000/stdlib-gzip/7/serial-4 | 61.8µs ± 2% 68 | |
Adapter/1000/stdlib-gzip/8/serial-4 | 63.2µs ± 4% 69 | |
Adapter/1000/stdlib-gzip/9/serial-4 | 63.3µs ± 7% 70 | |
Adapter/1000/klauspost-gzip/1/serial-4 | 18.3µs ±11% 71 | |
Adapter/1000/klauspost-gzip/2/serial-4 | 17.9µs ± 6% 72 | |
Adapter/1000/klauspost-gzip/3/serial-4 | 18.8µs ± 8% 73 | |
Adapter/1000/klauspost-gzip/4/serial-4 | 18.9µs ± 1% 74 | |
Adapter/1000/klauspost-gzip/5/serial-4 | 21.2µs ± 5% 75 | |
Adapter/1000/klauspost-gzip/6/serial-4 | 20.4µs ± 1% 76 | |
Adapter/1000/klauspost-gzip/7/serial-4 | 43.8µs ± 3% 77 | |
Adapter/1000/klauspost-gzip/8/serial-4 | 45.3µs ± 4% 78 | |
Adapter/1000/klauspost-gzip/9/serial-4 | 46.4µs ± 7% 79 | |
Adapter/1000/andybalholm-brotli/1/serial-4 | 23.0µs ± 1% 80 | |
Adapter/1000/andybalholm-brotli/2/serial-4 | 30.1µs ± 3% 81 | |
Adapter/1000/andybalholm-brotli/3/serial-4 | 51.0µs ± 2% 82 | |
Adapter/1000/andybalholm-brotli/4/serial-4 | 71.5µs ± 3% 83 | |
Adapter/1000/andybalholm-brotli/5/serial-4 | 73.7µs ± 3% 84 | |
Adapter/1000/andybalholm-brotli/6/serial-4 | 74.8µs ±12% 85 | |
Adapter/1000/andybalholm-brotli/7/serial-4 | 93.9µs ± 1% 86 | |
Adapter/1000/andybalholm-brotli/8/serial-4 | 94.8µs ± 6% 87 | |
Adapter/1000/andybalholm-brotli/9/serial-4 | 106µs ± 5% 88 | |
Adapter/1000/andybalholm-brotli/10/serial-4 | 2.09ms ± 2% 89 | |
Adapter/1000/andybalholm-brotli/11/serial-4 | 3.32ms ± 4% 90 | |
Adapter/10000/klauspost-zstd/1/serial-4 | 57.7µs ±22% 91 | |
Adapter/10000/klauspost-zstd/2/serial-4 | 85.0µs ± 5% 92 | |
Adapter/10000/klauspost-zstd/3/serial-4 | 125µs ± 1% 93 | |
Adapter/10000/klauspost-zstd/4/serial-4 | 1.00ms ± 7% 94 | |
Adapter/10000/valyala-gozstd/1/serial-4 | 30.1µs ±11% 95 | |
Adapter/10000/valyala-gozstd/2/serial-4 | 31.4µs ± 1% 96 | |
Adapter/10000/valyala-gozstd/3/serial-4 | 52.0µs ± 5% 97 | |
Adapter/10000/valyala-gozstd/4/serial-4 | 98.6µs ± 1% 98 | |
Adapter/10000/valyala-gozstd/5/serial-4 | 179µs ± 7% 99 | |
Adapter/10000/valyala-gozstd/6/serial-4 | 176µs ± 1% 100 | |
Adapter/10000/valyala-gozstd/7/serial-4 | 205µs ± 4% 101 | |
Adapter/10000/valyala-gozstd/8/serial-4 | 226µs ±15% 102 | |
Adapter/10000/valyala-gozstd/9/serial-4 | 349µs ± 2% 103 | |
Adapter/10000/valyala-gozstd/10/serial-4 | 616µs ± 4% 104 | |
Adapter/10000/valyala-gozstd/11/serial-4 | 2.32ms ±15% 105 | |
Adapter/10000/valyala-gozstd/12/serial-4 | 2.24ms ±13% 106 | |
Adapter/10000/valyala-gozstd/13/serial-4 | 2.44ms ±15% 107 | |
Adapter/10000/valyala-gozstd/14/serial-4 | 7.31ms ± 3% 108 | |
Adapter/10000/valyala-gozstd/15/serial-4 | 9.61ms ± 1% 109 | |
Adapter/10000/valyala-gozstd/16/serial-4 | 4.79ms ±11% 110 | |
Adapter/10000/valyala-gozstd/17/serial-4 | 8.02ms ± 1% 111 | |
Adapter/10000/valyala-gozstd/18/serial-4 | 8.79ms ± 3% 112 | |
Adapter/10000/valyala-gozstd/19/serial-4 | 16.0ms ±15% 113 | |
Adapter/10000/valyala-gozstd/20/serial-4 | 27.1ms ± 2% 114 | |
Adapter/10000/valyala-gozstd/21/serial-4 | 50.6ms ± 3% 115 | |
Adapter/10000/valyala-gozstd/22/serial-4 | 204ms ±87% 116 | |
Adapter/10000/stdlib-gzip/1/serial-4 | 97.7µs ± 2% 117 | |
Adapter/10000/stdlib-gzip/2/serial-4 | 148µs ±10% 118 | |
Adapter/10000/stdlib-gzip/3/serial-4 | 144µs ± 1% 119 | |
Adapter/10000/stdlib-gzip/4/serial-4 | 161µs ± 3% 120 | |
Adapter/10000/stdlib-gzip/5/serial-4 | 177µs ± 4% 121 | |
Adapter/10000/stdlib-gzip/6/serial-4 | 199µs ± 3% 122 | |
Adapter/10000/stdlib-gzip/7/serial-4 | 251µs ± 2% 123 | |
Adapter/10000/stdlib-gzip/8/serial-4 | 275µs ± 2% 124 | |
Adapter/10000/stdlib-gzip/9/serial-4 | 284µs ± 2% 125 | |
Adapter/10000/klauspost-gzip/1/serial-4 | 63.8µs ± 2% 126 | |
Adapter/10000/klauspost-gzip/2/serial-4 | 64.4µs ± 5% 127 | |
Adapter/10000/klauspost-gzip/3/serial-4 | 73.3µs ± 4% 128 | |
Adapter/10000/klauspost-gzip/4/serial-4 | 86.0µs ± 6% 129 | |
Adapter/10000/klauspost-gzip/5/serial-4 | 97.8µs ± 2% 130 | |
Adapter/10000/klauspost-gzip/6/serial-4 | 102µs ± 4% 131 | |
Adapter/10000/klauspost-gzip/7/serial-4 | 140µs ± 6% 132 | |
Adapter/10000/klauspost-gzip/8/serial-4 | 165µs ± 2% 133 | |
Adapter/10000/klauspost-gzip/9/serial-4 | 252µs ± 2% 134 | |
Adapter/10000/andybalholm-brotli/1/serial-4 | 117µs ± 6% 135 | |
Adapter/10000/andybalholm-brotli/2/serial-4 | 205µs ± 7% 136 | |
Adapter/10000/andybalholm-brotli/3/serial-4 | 272µs ± 2% 137 | |
Adapter/10000/andybalholm-brotli/4/serial-4 | 366µs ± 2% 138 | |
Adapter/10000/andybalholm-brotli/5/serial-4 | 419µs ± 2% 139 | |
Adapter/10000/andybalholm-brotli/6/serial-4 | 466µs ±13% 140 | |
Adapter/10000/andybalholm-brotli/7/serial-4 | 579µs ± 3% 141 | |
Adapter/10000/andybalholm-brotli/8/serial-4 | 616µs ± 2% 142 | |
Adapter/10000/andybalholm-brotli/9/serial-4 | 718µs ± 1% 143 | |
Adapter/10000/andybalholm-brotli/10/serial-4 | 8.20ms ± 5% 144 | |
Adapter/10000/andybalholm-brotli/11/serial-4 | 21.8ms ± 3% 145 | |
Adapter/10000/google-cbrotli/1/serial-4 | 36.6µs ±13% 146 | |
Adapter/10000/google-cbrotli/2/serial-4 | 74.3µs ± 3% 147 | |
Adapter/10000/google-cbrotli/3/serial-4 | 97.5µs ± 5% 148 | |
Adapter/10000/google-cbrotli/4/serial-4 | 157µs ± 3% 149 | |
Adapter/10000/google-cbrotli/5/serial-4 | 268µs ± 9% 150 | |
Adapter/10000/google-cbrotli/6/serial-4 | 268µs ± 4% 151 | |
Adapter/10000/google-cbrotli/7/serial-4 | 337µs ± 5% 152 | |
Adapter/10000/google-cbrotli/8/serial-4 | 358µs ± 8% 153 | |
Adapter/10000/google-cbrotli/9/serial-4 | 3.50ms ±140% 154 | |
Adapter/10000/google-cbrotli/10/serial-4 | 5.60ms ± 3% 155 | |
Adapter/10000/google-cbrotli/11/serial-4 | 15.1ms ± 2% 156 | |
Adapter/100000/klauspost-zstd/1/serial-4 | 655µs ± 1% 157 | |
Adapter/100000/klauspost-zstd/2/serial-4 | 819µs ± 8% 158 | |
Adapter/100000/klauspost-zstd/3/serial-4 | 1.09ms ± 6% 159 | |
Adapter/100000/klauspost-zstd/4/serial-4 | 6.69ms ±16% 160 | |
Adapter/100000/valyala-gozstd/1/serial-4 | 371µs ± 2% 161 | |
Adapter/100000/valyala-gozstd/2/serial-4 | 364µs ± 1% 162 | |
Adapter/100000/valyala-gozstd/3/serial-4 | 512µs ± 1% 163 | |
Adapter/100000/valyala-gozstd/4/serial-4 | 547µs ± 9% 164 | |
Adapter/100000/valyala-gozstd/5/serial-4 | 994µs ±12% 165 | |
Adapter/100000/valyala-gozstd/6/serial-4 | 982µs ± 6% 166 | |
Adapter/100000/valyala-gozstd/7/serial-4 | 1.27ms ± 2% 167 | |
Adapter/100000/valyala-gozstd/8/serial-4 | 1.44ms ± 5% 168 | |
Adapter/100000/valyala-gozstd/9/serial-4 | 1.72ms ± 3% 169 | |
Adapter/100000/valyala-gozstd/10/serial-4 | 2.26ms ±14% 170 | |
Adapter/100000/valyala-gozstd/11/serial-4 | 3.93ms ± 7% 171 | |
Adapter/100000/valyala-gozstd/12/serial-4 | 3.93ms ±12% 172 | |
Adapter/100000/valyala-gozstd/13/serial-4 | 7.13ms ± 3% 173 | |
Adapter/100000/valyala-gozstd/14/serial-4 | 11.9ms ± 2% 174 | |
Adapter/100000/valyala-gozstd/15/serial-4 | 15.1ms ± 2% 175 | |
Adapter/100000/valyala-gozstd/16/serial-4 | 19.4ms ± 2% 176 | |
Adapter/100000/valyala-gozstd/17/serial-4 | 23.9ms ± 6% 177 | |
Adapter/100000/valyala-gozstd/18/serial-4 | 31.0ms ± 1% 178 | |
Adapter/100000/valyala-gozstd/19/serial-4 | 59.5ms ± 7% 179 | |
Adapter/100000/valyala-gozstd/20/serial-4 | 74.0ms ± 9% 180 | |
Adapter/100000/valyala-gozstd/21/serial-4 | 93.0ms ± 1% 181 | |
Adapter/100000/valyala-gozstd/22/serial-4 | 145ms ± 6% 182 | |
Adapter/100000/stdlib-gzip/1/serial-4 | 871µs ± 2% 183 | |
Adapter/100000/stdlib-gzip/2/serial-4 | 1.04ms ± 4% 184 | |
Adapter/100000/stdlib-gzip/3/serial-4 | 1.16ms ± 6% 185 | |
Adapter/100000/stdlib-gzip/4/serial-4 | 1.30ms ± 2% 186 | |
Adapter/100000/stdlib-gzip/5/serial-4 | 1.50ms ± 1% 187 | |
Adapter/100000/stdlib-gzip/6/serial-4 | 1.71ms ± 3% 188 | |
Adapter/100000/stdlib-gzip/7/serial-4 | 2.92ms ± 3% 189 | |
Adapter/100000/stdlib-gzip/8/serial-4 | 7.20ms ± 7% 190 | |
Adapter/100000/stdlib-gzip/9/serial-4 | 7.06ms ± 2% 191 | |
Adapter/100000/klauspost-gzip/1/serial-4 | 617µs ± 3% 192 | |
Adapter/100000/klauspost-gzip/2/serial-4 | 604µs ± 5% 193 | |
Adapter/100000/klauspost-gzip/3/serial-4 | 792µs ± 5% 194 | |
Adapter/100000/klauspost-gzip/4/serial-4 | 907µs ± 5% 195 | |
Adapter/100000/klauspost-gzip/5/serial-4 | 1.06ms ± 2% 196 | |
Adapter/100000/klauspost-gzip/6/serial-4 | 1.15ms ± 4% 197 | |
Adapter/100000/klauspost-gzip/7/serial-4 | 1.30ms ± 3% 198 | |
Adapter/100000/klauspost-gzip/8/serial-4 | 1.62ms ± 2% 199 | |
Adapter/100000/klauspost-gzip/9/serial-4 | 6.78ms ± 5% 200 | |
Adapter/100000/andybalholm-brotli/1/serial-4 | 1.14ms ± 3% 201 | |
Adapter/100000/andybalholm-brotli/2/serial-4 | 2.06ms ±15% 202 | |
Adapter/100000/andybalholm-brotli/3/serial-4 | 2.45ms ± 2% 203 | |
Adapter/100000/andybalholm-brotli/4/serial-4 | 3.08ms ± 3% 204 | |
Adapter/100000/andybalholm-brotli/5/serial-4 | 3.95ms ± 4% 205 | |
Adapter/100000/andybalholm-brotli/6/serial-4 | 4.21ms ± 9% 206 | |
Adapter/100000/andybalholm-brotli/7/serial-4 | 5.36ms ± 5% 207 | |
Adapter/100000/andybalholm-brotli/8/serial-4 | 6.01ms ± 5% 208 | |
Adapter/100000/andybalholm-brotli/9/serial-4 | 7.70ms ± 2% 209 | |
Adapter/100000/andybalholm-brotli/10/serial-4 | 83.6ms ± 4% 210 | |
Adapter/100000/andybalholm-brotli/11/serial-4 | 233ms ± 1% 211 | |
Adapter/100000/google-cbrotli/1/serial-4 | 376µs ± 7% 212 | |
Adapter/100000/google-cbrotli/2/serial-4 | 767µs ±11% 213 | |
Adapter/100000/google-cbrotli/3/serial-4 | 834µs ± 0% 214 | |
Adapter/100000/google-cbrotli/4/serial-4 | 1.17ms ± 3% 215 | |
Adapter/100000/google-cbrotli/5/serial-4 | 2.39ms ± 3% 216 | |
Adapter/100000/google-cbrotli/6/serial-4 | 2.49ms ± 3% 217 | |
Adapter/100000/google-cbrotli/7/serial-4 | 3.09ms ± 2% 218 | |
Adapter/100000/google-cbrotli/8/serial-4 | 3.41ms ± 2% 219 | |
Adapter/100000/google-cbrotli/9/serial-4 | 8.66ms ±62% 220 | |
Adapter/100000/google-cbrotli/10/serial-4 | 55.3ms ± 5% 221 | |
Adapter/100000/google-cbrotli/11/serial-4 | 163ms ± 2% 222 | |
223 | | |
% 228 | | |
Adapter/100/stdlib-gzip/1/serial-4 | 100 ± 0% 229 | |
Adapter/100/klauspost-gzip/1/serial-4 | 100 ± 0% 230 | |
Adapter/100/andybalholm-brotli/1/serial-4 | 100 ± 0% 231 | |
Adapter/100/google-cbrotli/1/serial-4 | 100 ± 0% 232 | |
Adapter/100/klauspost-zstd/1/serial-4 | 100 ± 0% 233 | |
Adapter/100/valyala-gozstd/1/serial-4 | 100 ± 0% 234 | |
Adapter/1000/google-cbrotli/1/serial-4 | 45.2 ± 0% 235 | |
Adapter/1000/google-cbrotli/2/serial-4 | 42.1 ± 0% 236 | |
Adapter/1000/google-cbrotli/3/serial-4 | 39.9 ± 0% 237 | |
Adapter/1000/google-cbrotli/4/serial-4 | 39.3 ± 0% 238 | |
Adapter/1000/google-cbrotli/5/serial-4 | 36.6 ± 0% 239 | |
Adapter/1000/google-cbrotli/6/serial-4 | 36.8 ± 0% 240 | |
Adapter/1000/google-cbrotli/7/serial-4 | 36.7 ± 0% 241 | |
Adapter/1000/google-cbrotli/8/serial-4 | 36.7 ± 0% 242 | |
Adapter/1000/google-cbrotli/9/serial-4 | 36.7 ± 0% 243 | |
Adapter/1000/google-cbrotli/10/serial-4 | 37.4 ± 0% 244 | |
Adapter/1000/google-cbrotli/11/serial-4 | 37.2 ± 0% 245 | |
Adapter/1000/klauspost-zstd/1/serial-4 | 42.9 ± 0% 246 | |
Adapter/1000/klauspost-zstd/2/serial-4 | 42.2 ± 0% 247 | |
Adapter/1000/klauspost-zstd/3/serial-4 | 41.3 ± 0% 248 | |
Adapter/1000/klauspost-zstd/4/serial-4 | 40.5 ± 0% 249 | |
Adapter/1000/valyala-gozstd/1/serial-4 | 43.0 ± 0% 250 | |
Adapter/1000/valyala-gozstd/2/serial-4 | 42.6 ± 0% 251 | |
Adapter/1000/valyala-gozstd/3/serial-4 | 42.1 ± 0% 252 | |
Adapter/1000/valyala-gozstd/4/serial-4 | 42.1 ± 0% 253 | |
Adapter/1000/valyala-gozstd/5/serial-4 | 42.1 ± 0% 254 | |
Adapter/1000/valyala-gozstd/6/serial-4 | 41.3 ± 0% 255 | |
Adapter/1000/valyala-gozstd/7/serial-4 | 41.3 ± 0% 256 | |
Adapter/1000/valyala-gozstd/8/serial-4 | 41.3 ± 0% 257 | |
Adapter/1000/valyala-gozstd/9/serial-4 | 40.9 ± 0% 258 | |
Adapter/1000/valyala-gozstd/10/serial-4 | 40.9 ± 0% 259 | |
Adapter/1000/valyala-gozstd/11/serial-4 | 40.9 ± 0% 260 | |
Adapter/1000/valyala-gozstd/12/serial-4 | 40.9 ± 0% 261 | |
Adapter/1000/valyala-gozstd/13/serial-4 | 40.5 ± 0% 262 | |
Adapter/1000/valyala-gozstd/14/serial-4 | 40.5 ± 0% 263 | |
Adapter/1000/valyala-gozstd/15/serial-4 | 40.5 ± 0% 264 | |
Adapter/1000/valyala-gozstd/16/serial-4 | 40.4 ± 0% 265 | |
Adapter/1000/valyala-gozstd/17/serial-4 | 40.1 ± 0% 266 | |
Adapter/1000/valyala-gozstd/18/serial-4 | 40.0 ± 0% 267 | |
Adapter/1000/valyala-gozstd/19/serial-4 | 40.0 ± 0% 268 | |
Adapter/1000/valyala-gozstd/20/serial-4 | 40.0 ± 0% 269 | |
Adapter/1000/valyala-gozstd/21/serial-4 | 40.0 ± 0% 270 | |
Adapter/1000/valyala-gozstd/22/serial-4 | 40.0 ± 0% 271 | |
Adapter/1000/stdlib-gzip/1/serial-4 | 44.2 ± 0% 272 | |
Adapter/1000/stdlib-gzip/2/serial-4 | 41.9 ± 0% 273 | |
Adapter/1000/stdlib-gzip/3/serial-4 | 41.7 ± 0% 274 | |
Adapter/1000/stdlib-gzip/4/serial-4 | 41.7 ± 0% 275 | |
Adapter/1000/stdlib-gzip/5/serial-4 | 41.1 ± 0% 276 | |
Adapter/1000/stdlib-gzip/6/serial-4 | 41.1 ± 0% 277 | |
Adapter/1000/stdlib-gzip/7/serial-4 | 41.0 ± 0% 278 | |
Adapter/1000/stdlib-gzip/8/serial-4 | 41.0 ± 0% 279 | |
Adapter/1000/stdlib-gzip/9/serial-4 | 41.0 ± 0% 280 | |
Adapter/1000/klauspost-gzip/1/serial-4 | 45.3 ± 0% 281 | |
Adapter/1000/klauspost-gzip/2/serial-4 | 44.2 ± 0% 282 | |
Adapter/1000/klauspost-gzip/3/serial-4 | 42.2 ± 0% 283 | |
Adapter/1000/klauspost-gzip/4/serial-4 | 43.5 ± 0% 284 | |
Adapter/1000/klauspost-gzip/5/serial-4 | 42.2 ± 0% 285 | |
Adapter/1000/klauspost-gzip/6/serial-4 | 42.0 ± 0% 286 | |
Adapter/1000/klauspost-gzip/7/serial-4 | 41.0 ± 0% 287 | |
Adapter/1000/klauspost-gzip/8/serial-4 | 40.7 ± 0% 288 | |
Adapter/1000/klauspost-gzip/9/serial-4 | 40.7 ± 0% 289 | |
Adapter/1000/andybalholm-brotli/1/serial-4 | 45.2 ± 0% 290 | |
Adapter/1000/andybalholm-brotli/2/serial-4 | 42.1 ± 0% 291 | |
Adapter/1000/andybalholm-brotli/3/serial-4 | 39.9 ± 0% 292 | |
Adapter/1000/andybalholm-brotli/4/serial-4 | 39.3 ± 0% 293 | |
Adapter/1000/andybalholm-brotli/5/serial-4 | 36.6 ± 0% 294 | |
Adapter/1000/andybalholm-brotli/6/serial-4 | 36.8 ± 0% 295 | |
Adapter/1000/andybalholm-brotli/7/serial-4 | 36.7 ± 0% 296 | |
Adapter/1000/andybalholm-brotli/8/serial-4 | 36.7 ± 0% 297 | |
Adapter/1000/andybalholm-brotli/9/serial-4 | 36.7 ± 0% 298 | |
Adapter/1000/andybalholm-brotli/10/serial-4 | 37.4 ± 0% 299 | |
Adapter/1000/andybalholm-brotli/11/serial-4 | 37.2 ± 0% 300 | |
Adapter/10000/klauspost-zstd/1/serial-4 | 28.6 ± 0% 301 | |
Adapter/10000/klauspost-zstd/2/serial-4 | 28.1 ± 0% 302 | |
Adapter/10000/klauspost-zstd/3/serial-4 | 27.6 ± 0% 303 | |
Adapter/10000/klauspost-zstd/4/serial-4 | 27.5 ± 0% 304 | |
Adapter/10000/valyala-gozstd/1/serial-4 | 29.1 ± 0% 305 | |
Adapter/10000/valyala-gozstd/2/serial-4 | 28.5 ± 0% 306 | |
Adapter/10000/valyala-gozstd/3/serial-4 | 28.2 ± 0% 307 | |
Adapter/10000/valyala-gozstd/4/serial-4 | 28.2 ± 0% 308 | |
Adapter/10000/valyala-gozstd/5/serial-4 | 28.0 ± 0% 309 | |
Adapter/10000/valyala-gozstd/6/serial-4 | 27.6 ± 0% 310 | |
Adapter/10000/valyala-gozstd/7/serial-4 | 27.4 ± 0% 311 | |
Adapter/10000/valyala-gozstd/8/serial-4 | 27.3 ± 0% 312 | |
Adapter/10000/valyala-gozstd/9/serial-4 | 27.1 ± 0% 313 | |
Adapter/10000/valyala-gozstd/10/serial-4 | 27.1 ± 0% 314 | |
Adapter/10000/valyala-gozstd/11/serial-4 | 27.1 ± 0% 315 | |
Adapter/10000/valyala-gozstd/12/serial-4 | 27.0 ± 0% 316 | |
Adapter/10000/valyala-gozstd/13/serial-4 | 26.9 ± 0% 317 | |
Adapter/10000/valyala-gozstd/14/serial-4 | 26.9 ± 0% 318 | |
Adapter/10000/valyala-gozstd/15/serial-4 | 26.9 ± 0% 319 | |
Adapter/10000/valyala-gozstd/16/serial-4 | 26.8 ± 0% 320 | |
Adapter/10000/valyala-gozstd/17/serial-4 | 26.6 ± 0% 321 | |
Adapter/10000/valyala-gozstd/18/serial-4 | 26.5 ± 0% 322 | |
Adapter/10000/valyala-gozstd/19/serial-4 | 26.4 ± 0% 323 | |
Adapter/10000/valyala-gozstd/20/serial-4 | 26.4 ± 0% 324 | |
Adapter/10000/valyala-gozstd/21/serial-4 | 26.4 ± 0% 325 | |
Adapter/10000/valyala-gozstd/22/serial-4 | 26.4 ± 0% 326 | |
Adapter/10000/stdlib-gzip/1/serial-4 | 29.6 ± 0% 327 | |
Adapter/10000/stdlib-gzip/2/serial-4 | 28.5 ± 0% 328 | |
Adapter/10000/stdlib-gzip/3/serial-4 | 28.1 ± 0% 329 | |
Adapter/10000/stdlib-gzip/4/serial-4 | 28.2 ± 0% 330 | |
Adapter/10000/stdlib-gzip/5/serial-4 | 27.7 ± 0% 331 | |
Adapter/10000/stdlib-gzip/6/serial-4 | 27.6 ± 0% 332 | |
Adapter/10000/stdlib-gzip/7/serial-4 | 27.2 ± 0% 333 | |
Adapter/10000/stdlib-gzip/8/serial-4 | 27.2 ± 0% 334 | |
Adapter/10000/stdlib-gzip/9/serial-4 | 27.2 ± 0% 335 | |
Adapter/10000/klauspost-gzip/1/serial-4 | 31.0 ± 0% 336 | |
Adapter/10000/klauspost-gzip/2/serial-4 | 30.4 ± 0% 337 | |
Adapter/10000/klauspost-gzip/3/serial-4 | 29.1 ± 0% 338 | |
Adapter/10000/klauspost-gzip/4/serial-4 | 29.2 ± 0% 339 | |
Adapter/10000/klauspost-gzip/5/serial-4 | 28.3 ± 0% 340 | |
Adapter/10000/klauspost-gzip/6/serial-4 | 28.1 ± 0% 341 | |
Adapter/10000/klauspost-gzip/7/serial-4 | 27.9 ± 0% 342 | |
Adapter/10000/klauspost-gzip/8/serial-4 | 27.4 ± 0% 343 | |
Adapter/10000/klauspost-gzip/9/serial-4 | 27.2 ± 0% 344 | |
Adapter/10000/andybalholm-brotli/1/serial-4 | 29.9 ± 0% 345 | |
Adapter/10000/andybalholm-brotli/2/serial-4 | 28.1 ± 0% 346 | |
Adapter/10000/andybalholm-brotli/3/serial-4 | 27.9 ± 0% 347 | |
Adapter/10000/andybalholm-brotli/4/serial-4 | 27.5 ± 0% 348 | |
Adapter/10000/andybalholm-brotli/5/serial-4 | 26.0 ± 0% 349 | |
Adapter/10000/andybalholm-brotli/6/serial-4 | 25.9 ± 0% 350 | |
Adapter/10000/andybalholm-brotli/7/serial-4 | 25.8 ± 0% 351 | |
Adapter/10000/andybalholm-brotli/8/serial-4 | 25.8 ± 0% 352 | |
Adapter/10000/andybalholm-brotli/9/serial-4 | 25.8 ± 0% 353 | |
Adapter/10000/andybalholm-brotli/10/serial-4 | 23.4 ± 0% 354 | |
Adapter/10000/andybalholm-brotli/11/serial-4 | 23.1 ± 0% 355 | |
Adapter/10000/google-cbrotli/1/serial-4 | 29.9 ± 0% 356 | |
Adapter/10000/google-cbrotli/2/serial-4 | 28.1 ± 0% 357 | |
Adapter/10000/google-cbrotli/3/serial-4 | 27.9 ± 0% 358 | |
Adapter/10000/google-cbrotli/4/serial-4 | 27.5 ± 0% 359 | |
Adapter/10000/google-cbrotli/5/serial-4 | 26.0 ± 0% 360 | |
Adapter/10000/google-cbrotli/6/serial-4 | 25.9 ± 0% 361 | |
Adapter/10000/google-cbrotli/7/serial-4 | 25.8 ± 0% 362 | |
Adapter/10000/google-cbrotli/8/serial-4 | 25.8 ± 0% 363 | |
Adapter/10000/google-cbrotli/9/serial-4 | 25.8 ± 0% 364 | |
Adapter/10000/google-cbrotli/10/serial-4 | 23.4 ± 0% 365 | |
Adapter/10000/google-cbrotli/11/serial-4 | 23.1 ± 0% 366 | |
Adapter/100000/klauspost-zstd/1/serial-4 | 25.9 ± 0% 367 | |
Adapter/100000/klauspost-zstd/2/serial-4 | 25.8 ± 0% 368 | |
Adapter/100000/klauspost-zstd/3/serial-4 | 25.5 ± 0% 369 | |
Adapter/100000/klauspost-zstd/4/serial-4 | 24.7 ± 0% 370 | |
Adapter/100000/valyala-gozstd/1/serial-4 | 26.4 ± 0% 371 | |
Adapter/100000/valyala-gozstd/2/serial-4 | 25.8 ± 0% 372 | |
Adapter/100000/valyala-gozstd/3/serial-4 | 25.8 ± 0% 373 | |
Adapter/100000/valyala-gozstd/4/serial-4 | 25.8 ± 0% 374 | |
Adapter/100000/valyala-gozstd/5/serial-4 | 25.2 ± 0% 375 | |
Adapter/100000/valyala-gozstd/6/serial-4 | 25.0 ± 0% 376 | |
Adapter/100000/valyala-gozstd/7/serial-4 | 24.5 ± 0% 377 | |
Adapter/100000/valyala-gozstd/8/serial-4 | 24.2 ± 0% 378 | |
Adapter/100000/valyala-gozstd/9/serial-4 | 24.1 ± 0% 379 | |
Adapter/100000/valyala-gozstd/10/serial-4 | 24.1 ± 0% 380 | |
Adapter/100000/valyala-gozstd/11/serial-4 | 24.1 ± 0% 381 | |
Adapter/100000/valyala-gozstd/12/serial-4 | 24.0 ± 0% 382 | |
Adapter/100000/valyala-gozstd/13/serial-4 | 23.6 ± 0% 383 | |
Adapter/100000/valyala-gozstd/14/serial-4 | 23.6 ± 0% 384 | |
Adapter/100000/valyala-gozstd/15/serial-4 | 23.6 ± 0% 385 | |
Adapter/100000/valyala-gozstd/16/serial-4 | 23.2 ± 0% 386 | |
Adapter/100000/valyala-gozstd/17/serial-4 | 23.0 ± 0% 387 | |
Adapter/100000/valyala-gozstd/18/serial-4 | 22.9 ± 0% 388 | |
Adapter/100000/valyala-gozstd/19/serial-4 | 22.7 ± 0% 389 | |
Adapter/100000/valyala-gozstd/20/serial-4 | 22.7 ± 0% 390 | |
Adapter/100000/valyala-gozstd/21/serial-4 | 22.7 ± 0% 391 | |
Adapter/100000/valyala-gozstd/22/serial-4 | 22.7 ± 0% 392 | |
Adapter/100000/stdlib-gzip/1/serial-4 | 27.3 ± 0% 393 | |
Adapter/100000/stdlib-gzip/2/serial-4 | 26.2 ± 0% 394 | |
Adapter/100000/stdlib-gzip/3/serial-4 | 25.6 ± 0% 395 | |
Adapter/100000/stdlib-gzip/4/serial-4 | 25.8 ± 0% 396 | |
Adapter/100000/stdlib-gzip/5/serial-4 | 24.9 ± 0% 397 | |
Adapter/100000/stdlib-gzip/6/serial-4 | 24.8 ± 0% 398 | |
Adapter/100000/stdlib-gzip/7/serial-4 | 24.2 ± 0% 399 | |
Adapter/100000/stdlib-gzip/8/serial-4 | 24.1 ± 0% 400 | |
Adapter/100000/stdlib-gzip/9/serial-4 | 24.1 ± 0% 401 | |
Adapter/100000/klauspost-gzip/1/serial-4 | 28.5 ± 0% 402 | |
Adapter/100000/klauspost-gzip/2/serial-4 | 27.6 ± 0% 403 | |
Adapter/100000/klauspost-gzip/3/serial-4 | 26.9 ± 0% 404 | |
Adapter/100000/klauspost-gzip/4/serial-4 | 26.4 ± 0% 405 | |
Adapter/100000/klauspost-gzip/5/serial-4 | 25.8 ± 0% 406 | |
Adapter/100000/klauspost-gzip/6/serial-4 | 25.7 ± 0% 407 | |
Adapter/100000/klauspost-gzip/7/serial-4 | 25.4 ± 0% 408 | |
Adapter/100000/klauspost-gzip/8/serial-4 | 24.6 ± 0% 409 | |
Adapter/100000/klauspost-gzip/9/serial-4 | 24.1 ± 0% 410 | |
Adapter/100000/andybalholm-brotli/1/serial-4 | 27.6 ± 0% 411 | |
Adapter/100000/andybalholm-brotli/2/serial-4 | 25.4 ± 0% 412 | |
Adapter/100000/andybalholm-brotli/3/serial-4 | 25.3 ± 0% 413 | |
Adapter/100000/andybalholm-brotli/4/serial-4 | 24.9 ± 0% 414 | |
Adapter/100000/andybalholm-brotli/5/serial-4 | 23.6 ± 0% 415 | |
Adapter/100000/andybalholm-brotli/6/serial-4 | 23.4 ± 0% 416 | |
Adapter/100000/andybalholm-brotli/7/serial-4 | 23.3 ± 0% 417 | |
Adapter/100000/andybalholm-brotli/8/serial-4 | 23.2 ± 0% 418 | |
Adapter/100000/andybalholm-brotli/9/serial-4 | 23.2 ± 0% 419 | |
Adapter/100000/andybalholm-brotli/10/serial-4 | 20.2 ± 0% 420 | |
Adapter/100000/andybalholm-brotli/11/serial-4 | 19.9 ± 0% 421 | |
Adapter/100000/google-cbrotli/1/serial-4 | 27.6 ± 0% 422 | |
Adapter/100000/google-cbrotli/2/serial-4 | 25.4 ± 0% 423 | |
Adapter/100000/google-cbrotli/3/serial-4 | 25.3 ± 0% 424 | |
Adapter/100000/google-cbrotli/4/serial-4 | 24.9 ± 0% 425 | |
Adapter/100000/google-cbrotli/5/serial-4 | 23.6 ± 0% 426 | |
Adapter/100000/google-cbrotli/6/serial-4 | 23.4 ± 0% 427 | |
Adapter/100000/google-cbrotli/7/serial-4 | 23.3 ± 0% 428 | |
Adapter/100000/google-cbrotli/8/serial-4 | 23.2 ± 0% 429 | |
Adapter/100000/google-cbrotli/9/serial-4 | 23.2 ± 0% 430 | |
Adapter/100000/google-cbrotli/10/serial-4 | 20.2 ± 0% 431 | |
Adapter/100000/google-cbrotli/11/serial-4 | 19.9 ± 0% 432 | |
433 | |