├── .github └── workflows │ ├── golangci-lint.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── experimental ├── klauspost_snappy │ └── klauspost_snappy.go └── s2 │ └── s2.go ├── go.mod ├── go.sum ├── internal ├── deptest │ ├── deptest_test.go │ └── testlib │ │ └── testlib.go ├── klauspost_snappy │ ├── klauspost_snappy.go │ └── klauspost_snappy_test.go ├── lz4 │ ├── lz4.go │ └── lz4_test.go ├── s2 │ ├── s2.go │ └── s2_test.go ├── snappy │ ├── snappy.go │ └── snappy_test.go ├── testserver │ ├── testserver.go │ ├── testserver.pb.go │ └── testserver.proto └── zstd │ ├── zstd.go │ └── zstd_test.go ├── lz4 └── lz4.go ├── nonclobbering ├── experimental │ ├── klauspost_snappy │ │ └── klauspost_snappy.go │ └── s2 │ │ └── s2.go ├── lz4 │ └── lz4.go ├── snappy │ └── snappy.go └── zstd │ └── zstd.go ├── snappy └── snappy.go └── zstd └── zstd.go /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: golangci-lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v3 17 | with: 18 | version: latest 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: go test 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - main 9 | pull_request: 10 | 11 | jobs: 12 | linux: 13 | name: go test 14 | runs-on: ubuntu-20.04 15 | steps: 16 | - name: checkout code 17 | uses: actions/checkout@v3 18 | - name: test 19 | run: | 20 | set -euo pipefail 21 | go test ./... 22 | go test -race ./... 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OTEL_DOCKER_PROTOBUF ?= otel/build-protobuf:0.4.0 2 | PROTOC := docker run --rm -u ${shell id -u} -v${PWD}:${PWD} -w${PWD} ${OTEL_DOCKER_PROTOBUF} --proto_path=${PWD} 3 | 4 | .PHONY: genproto 5 | genproto: 6 | $(PROTOC) --go_out=plugins=grpc:. internal/testserver/testserver.proto 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-grpc-compression 2 | 3 | This respository contains go gRPC encoding wrappers for some useful compression 4 | algorithms that are not available in google.golang.org/grpc. 5 | 6 | * github.com/mostynb/go-grpc-compression/lz4 - using https://github.com/pierrec/lz4 7 | * github.com/mostynb/go-grpc-compression/snappy - using https://github.com/golang/snappy 8 | * github.com/mostynb/go-grpc-compression/zstd - using https://github.com/klauspost/compress/tree/master/zstd 9 | 10 | The following algorithms also have experimental implementations, which have 11 | not been tested as much as those above. These may be changed significantly, or 12 | even removed from this library at a future point. 13 | 14 | * github.com/mostynb/go-grpc-compression/experimental/klauspost_snappy - using https://github.com/klauspost/compress/tree/master/s2 15 | in snappy compatibility mode 16 | * github.com/mostynb/go-grpc-compression/experimental/s2 - using https://github.com/klauspost/compress/tree/master/s2 17 | 18 | Importing any of the packages above will override any previously registered 19 | encoders with the same name. If you would prefer imports to only register 20 | the encoder if there is no previously registered encoder with the same name, 21 | then you should instead import one of the following packages: 22 | 23 | * github.com/mostynb/go-grpc-compression/nonclobbering/lz4 24 | * github.com/mostynb/go-grpc-compression/nonclobbering/snappy 25 | * github.com/mostynb/go-grpc-compression/nonclobbering/zstd 26 | * github.com/mostynb/go-grpc-compression/nonclobbering/experimental/klauspost_snappy 27 | * github.com/mostynb/go-grpc-compression/nonclobbering/experimental/s2 28 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | At this time, only the most recent v1.* release is supported. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report any suspected security vulnerabilities via https://github.com/mostynb/go-grpc-compression/security/advisories/new 10 | Alternatively, you may email mostyn@antipode.se directly. 11 | 12 | ## Preferred Languages 13 | 14 | We prefer all communications to be in English. 15 | -------------------------------------------------------------------------------- /experimental/klauspost_snappy/klauspost_snappy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/experimental/klauspost_snappy 16 | // is a wrapper for using github.com/klauspost/compress/s2 in snappy 17 | // compatibility mode with gRPC. It might be more efficient than 18 | // github.com/mostynb/go-grpc-compression/snappy and 19 | // github.com/mostynb/go-grpc-compression/nonclobbering/snappy 20 | // packages which make use of github.com/golang/snappy. 21 | // 22 | // Note that this is registered under the name "snappy" with gRPC, so only 23 | // one of these packages should be used at a time. 24 | // 25 | // If you import this package, it will register itself as the encoder for 26 | // the "snappy" compressor, overriding any previously registered compressors 27 | // with this name. 28 | // 29 | // If you don't want to override previously registered "snappy" compressors, 30 | // then you should instead import 31 | // github.com/mostynb/go-grpc-compression/nonclobbering/experimental/klauspost_snappy 32 | package klauspost_snappy 33 | 34 | import ( 35 | internalsnappy "github.com/mostynb/go-grpc-compression/internal/klauspost_snappy" 36 | ) 37 | 38 | const Name = internalsnappy.Name 39 | 40 | func init() { 41 | clobbering := true 42 | internalsnappy.PretendInit(clobbering) 43 | } 44 | -------------------------------------------------------------------------------- /experimental/s2/s2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package s2 is an experimental wrapper for using 16 | // github.com/klauspost/compress/s2 stream compression with gRPC. 17 | 18 | // Package github.com/mostynb/go-grpc-compression/s2 is an experimental 19 | // wrapper for using github.com/klauspost/compress/s2 stream compression 20 | // with gRPC. 21 | // 22 | // If you import this package, it will register itself as the encoder for 23 | // the "s2" compressor, overriding any previously registered compressors 24 | // with this name. 25 | // 26 | // If you don't want to override previously registered "s2" compressors, 27 | // then you should instead import 28 | // github.com/mostynb/go-grpc-compression/nonclobbering/s2 29 | package s2 30 | 31 | import ( 32 | internals2 "github.com/mostynb/go-grpc-compression/internal/s2" 33 | ) 34 | 35 | const Name = internals2.Name 36 | 37 | func init() { 38 | clobbering := true 39 | internals2.PretendInit(clobbering) 40 | } 41 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mostynb/go-grpc-compression 2 | 3 | go 1.22 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/golang/snappy v0.0.4 9 | github.com/klauspost/compress v1.17.11 10 | github.com/pierrec/lz4/v4 v4.1.22 11 | github.com/stretchr/testify v1.9.0 12 | google.golang.org/grpc v1.69.2 13 | google.golang.org/protobuf v1.36.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/kr/pretty v0.3.0 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | golang.org/x/net v0.33.0 // indirect 21 | golang.org/x/sys v0.28.0 // indirect 22 | golang.org/x/text v0.21.0 // indirect 23 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect 24 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 25 | gopkg.in/yaml.v3 v3.0.1 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 5 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 6 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 7 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 8 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 9 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 10 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 11 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 12 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 13 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 14 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 15 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 16 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 17 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 18 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 19 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 20 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 21 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 22 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 23 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 24 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 25 | github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= 26 | github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 30 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 31 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 32 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 33 | go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= 34 | go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= 35 | go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= 36 | go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= 37 | go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= 38 | go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= 39 | go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= 40 | go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= 41 | go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= 42 | go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= 43 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 44 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 45 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 46 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 47 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 48 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 49 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI= 50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= 51 | google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= 52 | google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 53 | google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= 54 | google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 57 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 58 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 59 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 60 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 61 | -------------------------------------------------------------------------------- /internal/deptest/deptest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // package deptest ensures that the go-grpc-compression sub-packages will 16 | // not clobber an existing registration. 17 | package deptest 18 | 19 | import ( 20 | "testing" 21 | 22 | // This import happens first for the test, keep it ahead of 23 | // the three unnamed imports below. 24 | "github.com/mostynb/go-grpc-compression/internal/deptest/testlib" 25 | 26 | // If these were moved above, the test would fail because 27 | // testlib has the same no-clobber logic as the main packages. 28 | _ "github.com/mostynb/go-grpc-compression/nonclobbering/lz4" 29 | _ "github.com/mostynb/go-grpc-compression/nonclobbering/snappy" 30 | _ "github.com/mostynb/go-grpc-compression/nonclobbering/zstd" 31 | 32 | "github.com/stretchr/testify/require" 33 | "google.golang.org/grpc/encoding" 34 | ) 35 | 36 | func TestCompressorNotClobbered(t *testing.T) { 37 | // Because the deptest/lib imports first, it's init() function 38 | // registers dummy compressors. The following libraries do not 39 | // clobber, so we should find Dummy compressors. 40 | for _, name := range testlib.AllNames { 41 | _, ok := encoding.GetCompressor(name).(testlib.Dummy) 42 | require.True(t, ok) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/deptest/testlib/testlib.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testlib 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "google.golang.org/grpc/encoding" 22 | ) 23 | 24 | type Dummy string 25 | 26 | var _ encoding.Compressor = Dummy("") 27 | 28 | // AllNames lists every compressor in this repo, for testing. 29 | var AllNames = []string{"zstd", "snappy", "lz4"} 30 | 31 | func (d Dummy) Compress(io.Writer) (io.WriteCloser, error) { 32 | return nil, fmt.Errorf("not implemented") 33 | } 34 | 35 | func (d Dummy) Decompress(r io.Reader) (io.Reader, error) { 36 | return nil, fmt.Errorf("not implemented") 37 | } 38 | 39 | func (d Dummy) Name() string { 40 | return string(d) 41 | } 42 | 43 | func init() { 44 | for _, name := range AllNames { 45 | // This test will not register the dummies in case 46 | // of existing registrations, this ensures the import 47 | // order of the deptest actually tests no-clobbering. 48 | if encoding.GetCompressor(name) == nil { 49 | encoding.RegisterCompressor(Dummy(name)) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/klauspost_snappy/klauspost_snappy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2017 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | // Package experimental/klauspost_snappy is a wrapper for using 20 | // github.com/klauspost/compress/s2 in snappy compatibility mode with gRPC. 21 | // It might be more efficient than the top-level snappy package which makes 22 | // use of github.com/golang/snappy. 23 | // 24 | // Note that this is registered under the same "snappy" name with gRPC, so 25 | // only one of the two packages should be used at a time. 26 | package klauspost_snappy 27 | 28 | // This code is based upon the gzip wrapper in github.com/grpc/grpc-go: 29 | // https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go 30 | 31 | import ( 32 | "io" 33 | "sync" 34 | 35 | snappylib "github.com/klauspost/compress/s2" 36 | "google.golang.org/grpc/encoding" 37 | ) 38 | 39 | const Name = "snappy" 40 | 41 | type compressor struct { 42 | poolCompressor sync.Pool 43 | poolDecompressor sync.Pool 44 | } 45 | 46 | type writer struct { 47 | *snappylib.Writer 48 | pool *sync.Pool 49 | } 50 | 51 | type reader struct { 52 | *snappylib.Reader 53 | pool *sync.Pool 54 | } 55 | 56 | func PretendInit(clobbering bool) { 57 | if !clobbering && encoding.GetCompressor(Name) != nil { 58 | return 59 | } 60 | 61 | c := &compressor{} 62 | c.poolCompressor.New = func() interface{} { 63 | w := snappylib.NewWriter(io.Discard, snappylib.WriterSnappyCompat()) 64 | return &writer{Writer: w, pool: &c.poolCompressor} 65 | } 66 | encoding.RegisterCompressor(c) 67 | } 68 | 69 | func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) { 70 | z := c.poolCompressor.Get().(*writer) 71 | z.Writer.Reset(w) 72 | return z, nil 73 | } 74 | 75 | func (c *compressor) Decompress(r io.Reader) (io.Reader, error) { 76 | z, inPool := c.poolDecompressor.Get().(*reader) 77 | if !inPool { 78 | newR := snappylib.NewReader(r, snappylib.ReaderAllocBlock(64<<10)) 79 | return &reader{Reader: newR, pool: &c.poolDecompressor}, nil 80 | } 81 | z.Reset(r) 82 | return z, nil 83 | } 84 | 85 | func (c *compressor) Name() string { 86 | return Name 87 | } 88 | 89 | func (z *writer) Close() error { 90 | err := z.Writer.Close() 91 | z.pool.Put(z) 92 | return err 93 | } 94 | 95 | func (z *reader) Read(p []byte) (n int, err error) { 96 | n, err = z.Reader.Read(p) 97 | if err == io.EOF { 98 | z.pool.Put(z) 99 | } 100 | return n, err 101 | } 102 | -------------------------------------------------------------------------------- /internal/klauspost_snappy/klauspost_snappy_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package klauspost_snappy 20 | 21 | import ( 22 | "bytes" 23 | "context" 24 | "io" 25 | "net" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | "google.golang.org/grpc" 31 | "google.golang.org/grpc/credentials/insecure" 32 | "google.golang.org/grpc/encoding" 33 | "google.golang.org/grpc/test/bufconn" 34 | 35 | "github.com/mostynb/go-grpc-compression/internal/testserver" 36 | ) 37 | 38 | const ( 39 | bufSize = 1024 40 | message = "Message Request snappy" 41 | ) 42 | 43 | func TestRegisteredCompression(t *testing.T) { 44 | clobbering := true 45 | PretendInit(clobbering) 46 | 47 | comp := encoding.GetCompressor(Name) 48 | require.NotNil(t, comp) 49 | assert.Equal(t, Name, comp.Name()) 50 | 51 | buf := bytes.NewBuffer(make([]byte, 0, bufSize)) 52 | wc, err := comp.Compress(buf) 53 | require.NoError(t, err) 54 | 55 | _, err = wc.Write([]byte(message)) 56 | require.NoError(t, err) 57 | assert.NoError(t, wc.Close()) 58 | 59 | r, err := comp.Decompress(buf) 60 | require.NoError(t, err) 61 | expected, err := io.ReadAll(r) 62 | require.NoError(t, err) 63 | 64 | assert.Equal(t, message, string(expected)) 65 | } 66 | 67 | func TestRoundTrip(t *testing.T) { 68 | clobbering := true 69 | PretendInit(clobbering) 70 | 71 | lis := bufconn.Listen(bufSize) 72 | t.Cleanup(func() { 73 | assert.NoError(t, lis.Close()) 74 | }) 75 | 76 | done := make(chan struct{}, 1) 77 | 78 | s := grpc.NewServer() 79 | defer func() { 80 | s.GracefulStop() 81 | <-done 82 | }() 83 | testserver.RegisterTestServerServer(s, &testserver.EchoTestServer{}) 84 | go func() { 85 | if err := s.Serve(lis); err != nil && err != grpc.ErrServerStopped { 86 | t.Errorf("Server exited with error: %v", err) 87 | } 88 | done <- struct{}{} 89 | }() 90 | 91 | conn, err := grpc.NewClient("passthrough://bufnet", 92 | grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { 93 | return lis.Dial() 94 | }), 95 | grpc.WithDefaultCallOptions(grpc.UseCompressor(Name)), 96 | grpc.WithTransportCredentials(insecure.NewCredentials())) 97 | require.NoError(t, err) 98 | t.Cleanup(func() { 99 | assert.NoError(t, conn.Close()) 100 | }) 101 | 102 | client := testserver.NewTestServerClient(conn) 103 | resp, err := client.SendMessage(context.Background(), &testserver.MessageRequest{Request: message}) 104 | require.NoError(t, err) 105 | assert.Equal(t, message, resp.Response) 106 | } 107 | -------------------------------------------------------------------------------- /internal/lz4/lz4.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2017 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | // Package lz4 is a wrapper for using github.com/pierrec/lz4 with gRPC. 20 | package lz4 21 | 22 | // This code is based upon the gzip wrapper in github.com/grpc/grpc-go: 23 | // https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go 24 | 25 | import ( 26 | "io" 27 | "sync" 28 | 29 | lz4lib "github.com/pierrec/lz4/v4" 30 | "google.golang.org/grpc/encoding" 31 | ) 32 | 33 | const Name = "lz4" 34 | 35 | type compressor struct { 36 | poolCompressor sync.Pool 37 | poolDecompressor sync.Pool 38 | } 39 | 40 | type writer struct { 41 | *lz4lib.Writer 42 | pool *sync.Pool 43 | } 44 | 45 | type reader struct { 46 | *lz4lib.Reader 47 | pool *sync.Pool 48 | } 49 | 50 | func PretendInit(clobbering bool) { 51 | if !clobbering && encoding.GetCompressor(Name) != nil { 52 | return 53 | } 54 | 55 | c := &compressor{} 56 | c.poolCompressor.New = func() interface{} { 57 | w := lz4lib.NewWriter(io.Discard) 58 | return &writer{Writer: w, pool: &c.poolCompressor} 59 | } 60 | encoding.RegisterCompressor(c) 61 | } 62 | 63 | func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) { 64 | z := c.poolCompressor.Get().(*writer) 65 | z.Writer.Reset(w) 66 | return z, nil 67 | } 68 | 69 | func (c *compressor) Decompress(r io.Reader) (io.Reader, error) { 70 | z, inPool := c.poolDecompressor.Get().(*reader) 71 | if !inPool { 72 | newR := lz4lib.NewReader(r) 73 | return &reader{Reader: newR, pool: &c.poolDecompressor}, nil 74 | } 75 | z.Reset(r) 76 | return z, nil 77 | } 78 | 79 | func (c *compressor) Name() string { 80 | return Name 81 | } 82 | 83 | func (z *writer) Close() error { 84 | err := z.Writer.Close() 85 | z.pool.Put(z) 86 | return err 87 | } 88 | 89 | func (z *reader) Read(p []byte) (n int, err error) { 90 | n, err = z.Reader.Read(p) 91 | if err == io.EOF { 92 | z.pool.Put(z) 93 | } 94 | return n, err 95 | } 96 | -------------------------------------------------------------------------------- /internal/lz4/lz4_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package lz4 20 | 21 | import ( 22 | "bytes" 23 | "context" 24 | "io" 25 | "net" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | "google.golang.org/grpc" 31 | "google.golang.org/grpc/credentials/insecure" 32 | "google.golang.org/grpc/encoding" 33 | "google.golang.org/grpc/test/bufconn" 34 | 35 | "github.com/mostynb/go-grpc-compression/internal/testserver" 36 | ) 37 | 38 | const ( 39 | bufSize = 1024 40 | message = "Message Request LZ4" 41 | ) 42 | 43 | func TestRegisteredCompression(t *testing.T) { 44 | clobbering := true 45 | PretendInit(clobbering) 46 | 47 | comp := encoding.GetCompressor(Name) 48 | require.NotNil(t, comp) 49 | assert.Equal(t, Name, comp.Name()) 50 | 51 | buf := bytes.NewBuffer(make([]byte, 0, bufSize)) 52 | wc, err := comp.Compress(buf) 53 | require.NoError(t, err) 54 | 55 | _, err = wc.Write([]byte(message)) 56 | require.NoError(t, err) 57 | assert.NoError(t, wc.Close()) 58 | 59 | r, err := comp.Decompress(buf) 60 | require.NoError(t, err) 61 | expected, err := io.ReadAll(r) 62 | require.NoError(t, err) 63 | 64 | assert.Equal(t, message, string(expected)) 65 | } 66 | 67 | func TestRoundTrip(t *testing.T) { 68 | clobbering := true 69 | PretendInit(clobbering) 70 | 71 | lis := bufconn.Listen(bufSize) 72 | t.Cleanup(func() { 73 | assert.NoError(t, lis.Close()) 74 | }) 75 | 76 | done := make(chan struct{}, 1) 77 | 78 | s := grpc.NewServer() 79 | defer func() { 80 | s.GracefulStop() 81 | <-done 82 | }() 83 | testserver.RegisterTestServerServer(s, &testserver.EchoTestServer{}) 84 | go func() { 85 | if err := s.Serve(lis); err != nil && err != grpc.ErrServerStopped { 86 | t.Errorf("Server exited with error: %v", err) 87 | } 88 | done <- struct{}{} 89 | }() 90 | 91 | conn, err := grpc.NewClient("passthrough://bufnet", 92 | grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { 93 | return lis.Dial() 94 | }), 95 | grpc.WithDefaultCallOptions(grpc.UseCompressor(Name)), 96 | grpc.WithTransportCredentials(insecure.NewCredentials())) 97 | require.NoError(t, err) 98 | t.Cleanup(func() { 99 | assert.NoError(t, conn.Close()) 100 | }) 101 | 102 | client := testserver.NewTestServerClient(conn) 103 | resp, err := client.SendMessage(context.Background(), &testserver.MessageRequest{Request: message}) 104 | require.NoError(t, err) 105 | assert.Equal(t, message, resp.Response) 106 | } 107 | -------------------------------------------------------------------------------- /internal/s2/s2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package s2 is an experimental wrapper for using 16 | // github.com/klauspost/compress/s2 stream compression with gRPC. 17 | package s2 18 | 19 | import ( 20 | "io" 21 | "sync" 22 | 23 | "github.com/klauspost/compress/s2" 24 | "google.golang.org/grpc/encoding" 25 | ) 26 | 27 | const Name = "s2" 28 | 29 | type compressor struct { 30 | poolCompressor sync.Pool 31 | poolDecompressor sync.Pool 32 | } 33 | 34 | type writer struct { 35 | *s2.Writer 36 | pool *sync.Pool 37 | } 38 | 39 | type reader struct { 40 | *s2.Reader 41 | pool *sync.Pool 42 | } 43 | 44 | func PretendInit(clobbering bool) { 45 | if !clobbering && encoding.GetCompressor(Name) != nil { 46 | return 47 | } 48 | 49 | c := &compressor{} 50 | c.poolCompressor.New = func() interface{} { 51 | w := s2.NewWriter(io.Discard, s2.WriterConcurrency(1)) 52 | return &writer{Writer: w, pool: &c.poolCompressor} 53 | } 54 | encoding.RegisterCompressor(c) 55 | } 56 | 57 | func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) { 58 | s := c.poolCompressor.Get().(*writer) 59 | s.Writer.Reset(w) 60 | return s, nil 61 | } 62 | 63 | func (c *compressor) Decompress(r io.Reader) (io.Reader, error) { 64 | s, inPool := c.poolDecompressor.Get().(*reader) 65 | if !inPool { 66 | newR := s2.NewReader(r) 67 | return &reader{Reader: newR, pool: &c.poolDecompressor}, nil 68 | } 69 | s.Reset(r) 70 | return s, nil 71 | } 72 | 73 | func (c *compressor) Name() string { 74 | return Name 75 | } 76 | 77 | func (s *writer) Close() error { 78 | err := s.Writer.Close() 79 | s.pool.Put(s) 80 | return err 81 | } 82 | 83 | func (s *reader) Read(p []byte) (n int, err error) { 84 | n, err = s.Reader.Read(p) 85 | if err == io.EOF { 86 | s.pool.Put(s) 87 | } 88 | return n, err 89 | } 90 | -------------------------------------------------------------------------------- /internal/s2/s2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package s2 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "io" 21 | "net" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "github.com/stretchr/testify/require" 26 | "google.golang.org/grpc" 27 | "google.golang.org/grpc/credentials/insecure" 28 | "google.golang.org/grpc/encoding" 29 | "google.golang.org/grpc/test/bufconn" 30 | 31 | "github.com/mostynb/go-grpc-compression/internal/testserver" 32 | ) 33 | 34 | const ( 35 | bufSize = 1024 36 | message = "Message Request s2" 37 | ) 38 | 39 | func TestRegisteredCompression(t *testing.T) { 40 | clobbering := true 41 | PretendInit(clobbering) 42 | 43 | comp := encoding.GetCompressor(Name) 44 | require.NotNil(t, comp) 45 | assert.Equal(t, Name, comp.Name()) 46 | 47 | buf := bytes.NewBuffer(make([]byte, 0, bufSize)) 48 | wc, err := comp.Compress(buf) 49 | require.NoError(t, err) 50 | 51 | _, err = wc.Write([]byte(message)) 52 | require.NoError(t, err) 53 | assert.NoError(t, wc.Close()) 54 | 55 | r, err := comp.Decompress(buf) 56 | require.NoError(t, err) 57 | expected, err := io.ReadAll(r) 58 | require.NoError(t, err) 59 | 60 | assert.Equal(t, message, string(expected)) 61 | } 62 | 63 | func TestRoundTrip(t *testing.T) { 64 | clobbering := true 65 | PretendInit(clobbering) 66 | 67 | lis := bufconn.Listen(bufSize) 68 | t.Cleanup(func() { 69 | assert.NoError(t, lis.Close()) 70 | }) 71 | 72 | done := make(chan struct{}, 1) 73 | 74 | s := grpc.NewServer() 75 | defer func() { 76 | s.GracefulStop() 77 | <-done 78 | }() 79 | testserver.RegisterTestServerServer(s, &testserver.EchoTestServer{}) 80 | go func() { 81 | if err := s.Serve(lis); err != nil && err != grpc.ErrServerStopped { 82 | t.Errorf("Server exited with error: %v", err) 83 | } 84 | done <- struct{}{} 85 | }() 86 | 87 | conn, err := grpc.NewClient("passthrough://bufnet", 88 | grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { 89 | return lis.Dial() 90 | }), 91 | grpc.WithDefaultCallOptions(grpc.UseCompressor(Name)), 92 | grpc.WithTransportCredentials(insecure.NewCredentials())) 93 | require.NoError(t, err) 94 | t.Cleanup(func() { 95 | assert.NoError(t, conn.Close()) 96 | }) 97 | 98 | client := testserver.NewTestServerClient(conn) 99 | resp, err := client.SendMessage(context.Background(), &testserver.MessageRequest{Request: message}) 100 | require.NoError(t, err) 101 | assert.Equal(t, message, resp.Response) 102 | } 103 | -------------------------------------------------------------------------------- /internal/snappy/snappy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2017 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | // Package snappy is a wrapper for using github.com/golang/snappy with gRPC. 20 | package snappy 21 | 22 | // This code is based upon the gzip wrapper in github.com/grpc/grpc-go: 23 | // https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go 24 | 25 | import ( 26 | "io" 27 | "sync" 28 | 29 | snappylib "github.com/golang/snappy" 30 | "google.golang.org/grpc/encoding" 31 | ) 32 | 33 | const Name = "snappy" 34 | 35 | type compressor struct { 36 | poolCompressor sync.Pool 37 | poolDecompressor sync.Pool 38 | } 39 | 40 | type writer struct { 41 | *snappylib.Writer 42 | pool *sync.Pool 43 | } 44 | 45 | type reader struct { 46 | *snappylib.Reader 47 | pool *sync.Pool 48 | } 49 | 50 | func PretendInit(clobbering bool) { 51 | if !clobbering && encoding.GetCompressor(Name) != nil { 52 | return 53 | } 54 | 55 | c := &compressor{} 56 | c.poolCompressor.New = func() interface{} { 57 | w := snappylib.NewBufferedWriter(io.Discard) 58 | return &writer{Writer: w, pool: &c.poolCompressor} 59 | } 60 | encoding.RegisterCompressor(c) 61 | } 62 | 63 | func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) { 64 | z := c.poolCompressor.Get().(*writer) 65 | z.Writer.Reset(w) 66 | return z, nil 67 | } 68 | 69 | func (c *compressor) Decompress(r io.Reader) (io.Reader, error) { 70 | z, inPool := c.poolDecompressor.Get().(*reader) 71 | if !inPool { 72 | newR := snappylib.NewReader(r) 73 | return &reader{Reader: newR, pool: &c.poolDecompressor}, nil 74 | } 75 | z.Reset(r) 76 | return z, nil 77 | } 78 | 79 | func (c *compressor) Name() string { 80 | return Name 81 | } 82 | 83 | func (z *writer) Close() error { 84 | err := z.Writer.Close() 85 | z.pool.Put(z) 86 | return err 87 | } 88 | 89 | func (z *reader) Read(p []byte) (n int, err error) { 90 | n, err = z.Reader.Read(p) 91 | if err == io.EOF { 92 | z.pool.Put(z) 93 | } 94 | return n, err 95 | } 96 | -------------------------------------------------------------------------------- /internal/snappy/snappy_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package snappy 20 | 21 | import ( 22 | "bytes" 23 | "context" 24 | "io" 25 | "net" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | "google.golang.org/grpc" 31 | "google.golang.org/grpc/credentials/insecure" 32 | "google.golang.org/grpc/encoding" 33 | "google.golang.org/grpc/test/bufconn" 34 | 35 | "github.com/mostynb/go-grpc-compression/internal/testserver" 36 | ) 37 | 38 | const ( 39 | bufSize = 1024 40 | message = "Message Request LZ4" 41 | ) 42 | 43 | func TestRegisteredCompression(t *testing.T) { 44 | clobbering := true 45 | PretendInit(clobbering) 46 | 47 | comp := encoding.GetCompressor(Name) 48 | require.NotNil(t, comp) 49 | assert.Equal(t, Name, comp.Name()) 50 | 51 | buf := bytes.NewBuffer(make([]byte, 0, bufSize)) 52 | wc, err := comp.Compress(buf) 53 | require.NoError(t, err) 54 | 55 | _, err = wc.Write([]byte(message)) 56 | require.NoError(t, err) 57 | assert.NoError(t, wc.Close()) 58 | 59 | r, err := comp.Decompress(buf) 60 | require.NoError(t, err) 61 | expected, err := io.ReadAll(r) 62 | require.NoError(t, err) 63 | 64 | assert.Equal(t, message, string(expected)) 65 | } 66 | 67 | func TestRoundTrip(t *testing.T) { 68 | clobbering := true 69 | PretendInit(clobbering) 70 | 71 | lis := bufconn.Listen(bufSize) 72 | t.Cleanup(func() { 73 | assert.NoError(t, lis.Close()) 74 | }) 75 | 76 | done := make(chan struct{}, 1) 77 | 78 | s := grpc.NewServer() 79 | defer func() { 80 | s.GracefulStop() 81 | <-done 82 | }() 83 | testserver.RegisterTestServerServer(s, &testserver.EchoTestServer{}) 84 | go func() { 85 | if err := s.Serve(lis); err != nil && err != grpc.ErrServerStopped { 86 | t.Errorf("Server exited with error: %v", err) 87 | } 88 | done <- struct{}{} 89 | }() 90 | 91 | conn, err := grpc.NewClient("passthrough://bufnet", 92 | grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { 93 | return lis.Dial() 94 | }), 95 | grpc.WithDefaultCallOptions(grpc.UseCompressor(Name)), 96 | grpc.WithTransportCredentials(insecure.NewCredentials())) 97 | require.NoError(t, err) 98 | t.Cleanup(func() { 99 | assert.NoError(t, conn.Close()) 100 | }) 101 | 102 | client := testserver.NewTestServerClient(conn) 103 | resp, err := client.SendMessage(context.Background(), &testserver.MessageRequest{Request: message}) 104 | require.NoError(t, err) 105 | assert.Equal(t, message, resp.Response) 106 | } 107 | -------------------------------------------------------------------------------- /internal/testserver/testserver.go: -------------------------------------------------------------------------------- 1 | package testserver 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | var _ TestServerServer = (*EchoTestServer)(nil) 8 | 9 | type EchoTestServer struct{} 10 | 11 | func (f EchoTestServer) SendMessage(ctx context.Context, request *MessageRequest) (*MessageReply, error) { 12 | return &MessageReply{Response: request.Request}, nil 13 | } 14 | -------------------------------------------------------------------------------- /internal/testserver/testserver.pb.go: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // Copyright 2021 gRPC authors. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | // Code generated by protoc-gen-go. DO NOT EDIT. 19 | // versions: 20 | // protoc-gen-go v1.26.0 21 | // protoc v3.14.0 22 | // source: internal/testserver/testserver.proto 23 | 24 | package testserver 25 | 26 | import ( 27 | context "context" 28 | grpc "google.golang.org/grpc" 29 | codes "google.golang.org/grpc/codes" 30 | status "google.golang.org/grpc/status" 31 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 32 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 33 | reflect "reflect" 34 | sync "sync" 35 | ) 36 | 37 | const ( 38 | // Verify that this generated code is sufficiently up-to-date. 39 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 40 | // Verify that runtime/protoimpl is sufficiently up-to-date. 41 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 42 | ) 43 | 44 | // The request message containing the user's name. 45 | type MessageRequest struct { 46 | state protoimpl.MessageState 47 | sizeCache protoimpl.SizeCache 48 | unknownFields protoimpl.UnknownFields 49 | 50 | Request string `protobuf:"bytes,1,opt,name=request,proto3" json:"request,omitempty"` 51 | } 52 | 53 | func (x *MessageRequest) Reset() { 54 | *x = MessageRequest{} 55 | if protoimpl.UnsafeEnabled { 56 | mi := &file_internal_testserver_testserver_proto_msgTypes[0] 57 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 58 | ms.StoreMessageInfo(mi) 59 | } 60 | } 61 | 62 | func (x *MessageRequest) String() string { 63 | return protoimpl.X.MessageStringOf(x) 64 | } 65 | 66 | func (*MessageRequest) ProtoMessage() {} 67 | 68 | func (x *MessageRequest) ProtoReflect() protoreflect.Message { 69 | mi := &file_internal_testserver_testserver_proto_msgTypes[0] 70 | if protoimpl.UnsafeEnabled && x != nil { 71 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 72 | if ms.LoadMessageInfo() == nil { 73 | ms.StoreMessageInfo(mi) 74 | } 75 | return ms 76 | } 77 | return mi.MessageOf(x) 78 | } 79 | 80 | // Deprecated: Use MessageRequest.ProtoReflect.Descriptor instead. 81 | func (*MessageRequest) Descriptor() ([]byte, []int) { 82 | return file_internal_testserver_testserver_proto_rawDescGZIP(), []int{0} 83 | } 84 | 85 | func (x *MessageRequest) GetRequest() string { 86 | if x != nil { 87 | return x.Request 88 | } 89 | return "" 90 | } 91 | 92 | // The response message containing the greetings 93 | type MessageReply struct { 94 | state protoimpl.MessageState 95 | sizeCache protoimpl.SizeCache 96 | unknownFields protoimpl.UnknownFields 97 | 98 | Response string `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"` 99 | } 100 | 101 | func (x *MessageReply) Reset() { 102 | *x = MessageReply{} 103 | if protoimpl.UnsafeEnabled { 104 | mi := &file_internal_testserver_testserver_proto_msgTypes[1] 105 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 106 | ms.StoreMessageInfo(mi) 107 | } 108 | } 109 | 110 | func (x *MessageReply) String() string { 111 | return protoimpl.X.MessageStringOf(x) 112 | } 113 | 114 | func (*MessageReply) ProtoMessage() {} 115 | 116 | func (x *MessageReply) ProtoReflect() protoreflect.Message { 117 | mi := &file_internal_testserver_testserver_proto_msgTypes[1] 118 | if protoimpl.UnsafeEnabled && x != nil { 119 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 120 | if ms.LoadMessageInfo() == nil { 121 | ms.StoreMessageInfo(mi) 122 | } 123 | return ms 124 | } 125 | return mi.MessageOf(x) 126 | } 127 | 128 | // Deprecated: Use MessageReply.ProtoReflect.Descriptor instead. 129 | func (*MessageReply) Descriptor() ([]byte, []int) { 130 | return file_internal_testserver_testserver_proto_rawDescGZIP(), []int{1} 131 | } 132 | 133 | func (x *MessageReply) GetResponse() string { 134 | if x != nil { 135 | return x.Response 136 | } 137 | return "" 138 | } 139 | 140 | var File_internal_testserver_testserver_proto protoreflect.FileDescriptor 141 | 142 | var file_internal_testserver_testserver_proto_rawDesc = []byte{ 143 | 0x0a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 144 | 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 145 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 146 | 0x6c, 0x64, 0x22, 0x2a, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 147 | 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 148 | 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2a, 149 | 0x0a, 0x0c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a, 150 | 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 151 | 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x53, 0x0a, 0x0a, 0x54, 0x65, 152 | 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 153 | 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 154 | 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 155 | 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 156 | 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 157 | 0x15, 0x5a, 0x13, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 158 | 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 159 | } 160 | 161 | var ( 162 | file_internal_testserver_testserver_proto_rawDescOnce sync.Once 163 | file_internal_testserver_testserver_proto_rawDescData = file_internal_testserver_testserver_proto_rawDesc 164 | ) 165 | 166 | func file_internal_testserver_testserver_proto_rawDescGZIP() []byte { 167 | file_internal_testserver_testserver_proto_rawDescOnce.Do(func() { 168 | file_internal_testserver_testserver_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_testserver_testserver_proto_rawDescData) 169 | }) 170 | return file_internal_testserver_testserver_proto_rawDescData 171 | } 172 | 173 | var file_internal_testserver_testserver_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 174 | var file_internal_testserver_testserver_proto_goTypes = []interface{}{ 175 | (*MessageRequest)(nil), // 0: helloworld.MessageRequest 176 | (*MessageReply)(nil), // 1: helloworld.MessageReply 177 | } 178 | var file_internal_testserver_testserver_proto_depIdxs = []int32{ 179 | 0, // 0: helloworld.TestServer.SendMessage:input_type -> helloworld.MessageRequest 180 | 1, // 1: helloworld.TestServer.SendMessage:output_type -> helloworld.MessageReply 181 | 1, // [1:2] is the sub-list for method output_type 182 | 0, // [0:1] is the sub-list for method input_type 183 | 0, // [0:0] is the sub-list for extension type_name 184 | 0, // [0:0] is the sub-list for extension extendee 185 | 0, // [0:0] is the sub-list for field type_name 186 | } 187 | 188 | func init() { file_internal_testserver_testserver_proto_init() } 189 | func file_internal_testserver_testserver_proto_init() { 190 | if File_internal_testserver_testserver_proto != nil { 191 | return 192 | } 193 | if !protoimpl.UnsafeEnabled { 194 | file_internal_testserver_testserver_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 195 | switch v := v.(*MessageRequest); i { 196 | case 0: 197 | return &v.state 198 | case 1: 199 | return &v.sizeCache 200 | case 2: 201 | return &v.unknownFields 202 | default: 203 | return nil 204 | } 205 | } 206 | file_internal_testserver_testserver_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 207 | switch v := v.(*MessageReply); i { 208 | case 0: 209 | return &v.state 210 | case 1: 211 | return &v.sizeCache 212 | case 2: 213 | return &v.unknownFields 214 | default: 215 | return nil 216 | } 217 | } 218 | } 219 | type x struct{} 220 | out := protoimpl.TypeBuilder{ 221 | File: protoimpl.DescBuilder{ 222 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 223 | RawDescriptor: file_internal_testserver_testserver_proto_rawDesc, 224 | NumEnums: 0, 225 | NumMessages: 2, 226 | NumExtensions: 0, 227 | NumServices: 1, 228 | }, 229 | GoTypes: file_internal_testserver_testserver_proto_goTypes, 230 | DependencyIndexes: file_internal_testserver_testserver_proto_depIdxs, 231 | MessageInfos: file_internal_testserver_testserver_proto_msgTypes, 232 | }.Build() 233 | File_internal_testserver_testserver_proto = out.File 234 | file_internal_testserver_testserver_proto_rawDesc = nil 235 | file_internal_testserver_testserver_proto_goTypes = nil 236 | file_internal_testserver_testserver_proto_depIdxs = nil 237 | } 238 | 239 | // Reference imports to suppress errors if they are not otherwise used. 240 | var _ context.Context 241 | var _ grpc.ClientConnInterface 242 | 243 | // This is a compile-time assertion to ensure that this generated file 244 | // is compatible with the grpc package it is being compiled against. 245 | const _ = grpc.SupportPackageIsVersion6 246 | 247 | // TestServerClient is the client API for TestServer service. 248 | // 249 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 250 | type TestServerClient interface { 251 | // Sends a greeting 252 | SendMessage(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageReply, error) 253 | } 254 | 255 | type testServerClient struct { 256 | cc grpc.ClientConnInterface 257 | } 258 | 259 | func NewTestServerClient(cc grpc.ClientConnInterface) TestServerClient { 260 | return &testServerClient{cc} 261 | } 262 | 263 | func (c *testServerClient) SendMessage(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageReply, error) { 264 | out := new(MessageReply) 265 | err := c.cc.Invoke(ctx, "/helloworld.TestServer/SendMessage", in, out, opts...) 266 | if err != nil { 267 | return nil, err 268 | } 269 | return out, nil 270 | } 271 | 272 | // TestServerServer is the server API for TestServer service. 273 | type TestServerServer interface { 274 | // Sends a greeting 275 | SendMessage(context.Context, *MessageRequest) (*MessageReply, error) 276 | } 277 | 278 | // UnimplementedTestServerServer can be embedded to have forward compatible implementations. 279 | type UnimplementedTestServerServer struct { 280 | } 281 | 282 | func (*UnimplementedTestServerServer) SendMessage(context.Context, *MessageRequest) (*MessageReply, error) { 283 | return nil, status.Errorf(codes.Unimplemented, "method SendMessage not implemented") 284 | } 285 | 286 | func RegisterTestServerServer(s *grpc.Server, srv TestServerServer) { 287 | s.RegisterService(&_TestServer_serviceDesc, srv) 288 | } 289 | 290 | func _TestServer_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 291 | in := new(MessageRequest) 292 | if err := dec(in); err != nil { 293 | return nil, err 294 | } 295 | if interceptor == nil { 296 | return srv.(TestServerServer).SendMessage(ctx, in) 297 | } 298 | info := &grpc.UnaryServerInfo{ 299 | Server: srv, 300 | FullMethod: "/helloworld.TestServer/SendMessage", 301 | } 302 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 303 | return srv.(TestServerServer).SendMessage(ctx, req.(*MessageRequest)) 304 | } 305 | return interceptor(ctx, in, info, handler) 306 | } 307 | 308 | var _TestServer_serviceDesc = grpc.ServiceDesc{ 309 | ServiceName: "helloworld.TestServer", 310 | HandlerType: (*TestServerServer)(nil), 311 | Methods: []grpc.MethodDesc{ 312 | { 313 | MethodName: "SendMessage", 314 | Handler: _TestServer_SendMessage_Handler, 315 | }, 316 | }, 317 | Streams: []grpc.StreamDesc{}, 318 | Metadata: "internal/testserver/testserver.proto", 319 | } 320 | -------------------------------------------------------------------------------- /internal/testserver/testserver.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | syntax = "proto3"; 20 | 21 | option go_package = "internal/testserver"; 22 | 23 | package helloworld; 24 | 25 | // The greeting service definition. 26 | service TestServer { 27 | // Sends a greeting 28 | rpc SendMessage (MessageRequest) returns (MessageReply) {} 29 | } 30 | 31 | // The request message containing the user's name. 32 | message MessageRequest { 33 | string request = 1; 34 | } 35 | 36 | // The response message containing the greetings 37 | message MessageReply { 38 | string response = 1; 39 | } 40 | -------------------------------------------------------------------------------- /internal/zstd/zstd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package zstd is a wrapper for using github.com/klauspost/compress/zstd 16 | // with gRPC. 17 | package zstd 18 | 19 | import ( 20 | "bytes" 21 | "errors" 22 | "io" 23 | "runtime" 24 | "sync" 25 | 26 | "github.com/klauspost/compress/zstd" 27 | "google.golang.org/grpc/encoding" 28 | ) 29 | 30 | const Name = "zstd" 31 | 32 | var encoderOptions = []zstd.EOption{ 33 | // The default zstd window size is 8MB, which is much larger than the 34 | // typical RPC message and wastes a bunch of memory. 35 | zstd.WithWindowSize(512 * 1024), 36 | } 37 | 38 | var decoderOptions = []zstd.DOption{ 39 | // If the decoder concurrency level is not 1, we would need to call 40 | // Close() to avoid leaking resources when the object is released 41 | // from compressor.decoderPool. 42 | zstd.WithDecoderConcurrency(1), 43 | } 44 | 45 | // We will set a finalizer on these objects, so when the go-grpc code is 46 | // finished with them, they will be added back to compressor.decoderPool. 47 | type decoderWrapper struct { 48 | *zstd.Decoder 49 | } 50 | 51 | type compressor struct { 52 | encoder *zstd.Encoder 53 | decoderPool sync.Pool // To hold *zstd.Decoder's. 54 | } 55 | 56 | func PretendInit(clobbering bool) { 57 | if !clobbering && encoding.GetCompressor(Name) != nil { 58 | return 59 | } 60 | 61 | enc, _ := zstd.NewWriter(nil, encoderOptions...) 62 | c := &compressor{ 63 | encoder: enc, 64 | } 65 | encoding.RegisterCompressor(c) 66 | } 67 | 68 | var ErrNotInUse = errors.New("SetLevel ineffective because another zstd compressor has been registered") 69 | 70 | // SetLevel updates the registered compressor to use a particular compression 71 | // level. NOTE: this function must only be called from an init function, and 72 | // is not threadsafe. 73 | func SetLevel(level zstd.EncoderLevel) error { 74 | c, ok := encoding.GetCompressor(Name).(*compressor) 75 | if !ok { 76 | return ErrNotInUse 77 | } 78 | 79 | enc, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(level)) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | c.encoder = enc 85 | return nil 86 | } 87 | 88 | func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) { 89 | return &zstdWriteCloser{ 90 | enc: c.encoder, 91 | writer: w, 92 | }, nil 93 | } 94 | 95 | type zstdWriteCloser struct { 96 | enc *zstd.Encoder 97 | writer io.Writer // Compressed data will be written here. 98 | buf bytes.Buffer // Buffer uncompressed data here, compress on Close. 99 | } 100 | 101 | func (z *zstdWriteCloser) Write(p []byte) (int, error) { 102 | return z.buf.Write(p) 103 | } 104 | 105 | func (z *zstdWriteCloser) Close() error { 106 | compressed := z.enc.EncodeAll(z.buf.Bytes(), nil) 107 | _, err := io.Copy(z.writer, bytes.NewReader(compressed)) 108 | return err 109 | } 110 | 111 | func (c *compressor) Decompress(r io.Reader) (io.Reader, error) { 112 | var err error 113 | var found bool 114 | var decoder *zstd.Decoder 115 | 116 | // Note: avoid the use of zstd.Decoder.DecodeAll here, since 117 | // malicious payloads could DoS us with a decompression bomb. 118 | 119 | decoder, found = c.decoderPool.Get().(*zstd.Decoder) 120 | if !found { 121 | decoder, err = zstd.NewReader(r, decoderOptions...) 122 | if err != nil { 123 | return nil, err 124 | } 125 | } else { 126 | err = decoder.Reset(r) 127 | if err != nil { 128 | c.decoderPool.Put(decoder) 129 | return nil, err 130 | } 131 | } 132 | 133 | wrapper := &decoderWrapper{Decoder: decoder} 134 | runtime.SetFinalizer(wrapper, func(dw *decoderWrapper) { 135 | err := dw.Reset(nil) 136 | if err == nil { 137 | c.decoderPool.Put(dw.Decoder) 138 | } 139 | }) 140 | 141 | return wrapper, nil 142 | } 143 | 144 | func (c *compressor) Name() string { 145 | return Name 146 | } 147 | -------------------------------------------------------------------------------- /internal/zstd/zstd_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package zstd 20 | 21 | import ( 22 | "bytes" 23 | "context" 24 | "io" 25 | "net" 26 | "testing" 27 | 28 | "github.com/klauspost/compress/zstd" 29 | "github.com/stretchr/testify/assert" 30 | "github.com/stretchr/testify/require" 31 | 32 | "google.golang.org/grpc" 33 | "google.golang.org/grpc/credentials/insecure" 34 | "google.golang.org/grpc/encoding" 35 | "google.golang.org/grpc/test/bufconn" 36 | 37 | "github.com/mostynb/go-grpc-compression/internal/testserver" 38 | ) 39 | 40 | const ( 41 | bufSize = 1024 42 | message = "Message Request zSTD" 43 | ) 44 | 45 | func TestRegisteredCompression(t *testing.T) { 46 | clobbering := true 47 | PretendInit(clobbering) 48 | 49 | for _, lvl := range []zstd.EncoderLevel{zstd.SpeedFastest, zstd.SpeedDefault, zstd.SpeedBetterCompression, zstd.SpeedBestCompression} { 50 | require.NoError(t, SetLevel(lvl)) 51 | 52 | comp := encoding.GetCompressor(Name) 53 | require.NotNil(t, comp) 54 | assert.Equal(t, Name, comp.Name()) 55 | 56 | buf := bytes.NewBuffer(make([]byte, 0, bufSize)) 57 | wc, err := comp.Compress(buf) 58 | require.NoError(t, err) 59 | 60 | _, err = wc.Write([]byte(message)) 61 | require.NoError(t, err) 62 | assert.NoError(t, wc.Close()) 63 | 64 | r, err := comp.Decompress(buf) 65 | require.NoError(t, err) 66 | expected, err := io.ReadAll(r) 67 | require.NoError(t, err) 68 | 69 | assert.Equal(t, message, string(expected)) 70 | } 71 | } 72 | 73 | func TestRoundTrip(t *testing.T) { 74 | clobbering := true 75 | PretendInit(clobbering) 76 | 77 | lis := bufconn.Listen(bufSize) 78 | t.Cleanup(func() { 79 | assert.NoError(t, lis.Close()) 80 | }) 81 | 82 | done := make(chan struct{}, 1) 83 | 84 | s := grpc.NewServer() 85 | defer func() { 86 | s.GracefulStop() 87 | <-done 88 | }() 89 | testserver.RegisterTestServerServer(s, &testserver.EchoTestServer{}) 90 | go func() { 91 | if err := s.Serve(lis); err != nil && err != grpc.ErrServerStopped { 92 | t.Errorf("Server exited with error: %v", err) 93 | } 94 | done <- struct{}{} 95 | }() 96 | 97 | conn, err := grpc.NewClient("passthrough://bufnet", 98 | grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { 99 | return lis.Dial() 100 | }), 101 | grpc.WithDefaultCallOptions(grpc.UseCompressor(Name)), 102 | grpc.WithTransportCredentials(insecure.NewCredentials())) 103 | require.NoError(t, err) 104 | t.Cleanup(func() { 105 | assert.NoError(t, conn.Close()) 106 | }) 107 | 108 | client := testserver.NewTestServerClient(conn) 109 | resp, err := client.SendMessage(context.Background(), &testserver.MessageRequest{Request: message}) 110 | require.NoError(t, err) 111 | assert.Equal(t, message, resp.Response) 112 | } 113 | -------------------------------------------------------------------------------- /lz4/lz4.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/lz4 is a wrapper for 16 | // using github.com/pierrec/lz4 with gRPC. 17 | // 18 | // If you import this package, it will register itself as the encoder for 19 | // the "lz4" compressor, overriding any previously registered compressors 20 | // with this name. 21 | // 22 | // If you don't want to override previously registered "lz4" compressors, 23 | // then you should instead import 24 | // github.com/mostynb/go-grpc-compression/nonclobbering/lz4 25 | package lz4 26 | 27 | import ( 28 | internallz4 "github.com/mostynb/go-grpc-compression/internal/lz4" 29 | ) 30 | 31 | const Name = internallz4.Name 32 | 33 | func init() { 34 | clobbering := true 35 | internallz4.PretendInit(clobbering) 36 | } 37 | -------------------------------------------------------------------------------- /nonclobbering/experimental/klauspost_snappy/klauspost_snappy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/nonclobbering/klauspost_snappy 16 | // is a wrapper for using github.com/klauspost/compress/s2 in snappy 17 | // compatibility mode with gRPC. It might be more efficient than 18 | // github.com/mostynb/go-grpc-compression/snappy and 19 | // github.com/mostynb/go-grpc-compression/nonclobbering/snappy 20 | // packages which make use of github.com/golang/snappy. 21 | // 22 | // Note that this is registered under the name "snappy" with gRPC, so only 23 | // one of these packages should be used at a time. 24 | // 25 | // If you import this package, it will only register itself as the encoder 26 | // for the "snappy" compressor if no other compressors have already been 27 | // registered with that name. 28 | // 29 | // If you do want to override previously registered "snappy" compressors, 30 | // then you should instead import 31 | // github.com/mostynb/go-grpc-compression/klauspost_snappy 32 | package klauspost_snappy 33 | 34 | import ( 35 | internalsnappy "github.com/mostynb/go-grpc-compression/internal/klauspost_snappy" 36 | ) 37 | 38 | const Name = internalsnappy.Name 39 | 40 | func init() { 41 | clobbering := false 42 | internalsnappy.PretendInit(clobbering) 43 | } 44 | -------------------------------------------------------------------------------- /nonclobbering/experimental/s2/s2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/nonclobbering/experimental/s2 16 | // is a wrapper for using github.com/klauspost/compress/s2 stream compression 17 | // with gRPC. 18 | // 19 | // If you import this package, it will only register itself as the encoder 20 | // for the "s2" compressor if no other compressors have already been 21 | // registered with that name. 22 | // 23 | // If you do want to override previously registered "s2" compressors, 24 | // then you should instead import 25 | // github.com/mostynb/go-grpc-compression/experimental/s2 26 | package s2 27 | 28 | import ( 29 | internals2 "github.com/mostynb/go-grpc-compression/internal/s2" 30 | ) 31 | 32 | const Name = internals2.Name 33 | 34 | func init() { 35 | clobbering := false 36 | internals2.PretendInit(clobbering) 37 | } 38 | -------------------------------------------------------------------------------- /nonclobbering/lz4/lz4.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/nonclobbering/lz4 is a 16 | // wrapper for using github.com/pierrec/lz4 with gRPC. 17 | // 18 | // If you import this package, it will only register itself as the encoder 19 | // for the "lz4" compressor if no other compressors have already been 20 | // registered with that name. 21 | // 22 | // If you do want to override previously registered "lz4" compressors, 23 | // then you should instead import 24 | // github.com/mostynb/go-grpc-compression/lz4 25 | package lz4 26 | 27 | import ( 28 | internallz4 "github.com/mostynb/go-grpc-compression/internal/lz4" 29 | ) 30 | 31 | const Name = internallz4.Name 32 | 33 | func init() { 34 | clobbering := false 35 | internallz4.PretendInit(clobbering) 36 | } 37 | -------------------------------------------------------------------------------- /nonclobbering/snappy/snappy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/nonclobbering/snappy is 16 | // a wrapper for using github.com/golang/snappy with gRPC. 17 | // 18 | // If you import this package, it will only register itself as the encoder 19 | // for the "snappy" compressor if no other compressors have already been 20 | // registered with that name. 21 | // 22 | // If you do want to override previously registered "snappy" compressors, 23 | // then you should instead import 24 | // github.com/mostynb/go-grpc-compression/snappy 25 | package snappy 26 | 27 | import ( 28 | internalsnappy "github.com/mostynb/go-grpc-compression/internal/snappy" 29 | ) 30 | 31 | const Name = internalsnappy.Name 32 | 33 | func init() { 34 | clobbering := false 35 | internalsnappy.PretendInit(clobbering) 36 | } 37 | -------------------------------------------------------------------------------- /nonclobbering/zstd/zstd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/nonclobbering/zstd is a 16 | // wrapper for using github.com/klauspost/compress/zstd with gRPC. 17 | // 18 | // If you import this package, it will only register itself as the encoder 19 | // for the "zstd" compressor if no other compressors have already been 20 | // registered with that name. 21 | // 22 | // If you do want to override previously registered "zstd" compressors, 23 | // then you should instead import 24 | // github.com/mostynb/go-grpc-compression/zstd 25 | package zstd 26 | 27 | import ( 28 | internalzstd "github.com/mostynb/go-grpc-compression/internal/zstd" 29 | 30 | "github.com/klauspost/compress/zstd" 31 | ) 32 | 33 | const Name = internalzstd.Name 34 | 35 | func init() { 36 | clobbering := false 37 | internalzstd.PretendInit(clobbering) 38 | } 39 | 40 | var ErrNotInUse = internalzstd.ErrNotInUse 41 | 42 | // SetLevel updates the registered compressor to use a particular compression 43 | // level. Returns ErrNotInUse if this module isn't registered (because it has 44 | // been overridden by another encoder with the same name), or any error 45 | // returned by zstd.NewWriter(nil, zstd.WithEncoderLevel(level). 46 | // 47 | // NOTE: this function is not threadsafe and must only be called from an init 48 | // function or from the main goroutine before any other goroutines have been 49 | // created. 50 | func SetLevel(level zstd.EncoderLevel) error { 51 | return internalzstd.SetLevel(level) 52 | } 53 | -------------------------------------------------------------------------------- /snappy/snappy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/snappy is a wrapper for 16 | // using github.com/golang/snappy with gRPC. 17 | // 18 | // If you import this package, it will register itself as the encoder for 19 | // the "snappy" compressor, overriding any previously registered compressors 20 | // with this name. 21 | // 22 | // If you don't want to override previously registered "snappy" compressors, 23 | // then you should instead import 24 | // github.com/mostynb/go-grpc-compression/nonclobbering/snappy 25 | package snappy 26 | 27 | import ( 28 | internalsnappy "github.com/mostynb/go-grpc-compression/internal/snappy" 29 | ) 30 | 31 | const Name = internalsnappy.Name 32 | 33 | func init() { 34 | clobbering := true 35 | internalsnappy.PretendInit(clobbering) 36 | } 37 | -------------------------------------------------------------------------------- /zstd/zstd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Mostyn Bramley-Moore. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package github.com/mostynb/go-grpc-compression/zstd is a wrapper for 16 | // using github.com/klauspost/compress/zstd with gRPC. 17 | // 18 | // If you import this package, it will register itself as the encoder for 19 | // the "zstd" compressor, overriding any previously registered compressors 20 | // with this name. 21 | // 22 | // If you don't want to override previously registered "zstd" compressors, 23 | // then you should instead import 24 | // github.com/mostynb/go-grpc-compression/nonclobbering/zstd 25 | package zstd 26 | 27 | import ( 28 | internalzstd "github.com/mostynb/go-grpc-compression/internal/zstd" 29 | 30 | "github.com/klauspost/compress/zstd" 31 | ) 32 | 33 | const Name = internalzstd.Name 34 | 35 | func init() { 36 | clobbering := true 37 | internalzstd.PretendInit(clobbering) 38 | } 39 | 40 | var ErrNotInUse = internalzstd.ErrNotInUse 41 | 42 | // SetLevel updates the registered compressor to use a particular compression 43 | // level. Returns ErrNotInUse if this module isn't registered (because it has 44 | // been overridden by another encoder with the same name), or any error 45 | // returned by zstd.NewWriter(nil, zstd.WithEncoderLevel(level). 46 | // 47 | // NOTE: this function is not threadsafe and must only be called from an init 48 | // function or from the main goroutine before any other goroutines have been 49 | // created. 50 | func SetLevel(level zstd.EncoderLevel) error { 51 | return internalzstd.SetLevel(level) 52 | } 53 | --------------------------------------------------------------------------------