├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── dependabot.yml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── appveyor.yml ├── images └── project-icon.png └── src ├── Directory.Build.props ├── NuGet.Config ├── ServiceBus.CompressionPlugin.Tests ├── ApiApprovals.cs ├── ApprovalFiles │ ├── ApiApprovals.CompressionPlugin.approved.txt │ └── Approver.cs ├── ServiceBus.CompressionPlugin.Tests.csproj ├── When_compression_plugin_is_misconfigured.cs ├── When_configuring_plugin.cs ├── When_receiving_message.cs └── When_sending_message.cs ├── ServiceBus.CompressionPlugin.sln ├── ServiceBus.CompressionPlugin.sln.DotSettings └── ServiceBus.CompressionPlugin ├── CompressionConfiguration.cs ├── CompressionPlugin.cs ├── CompressionPluginExtensions.cs ├── ExposeInternals.cs ├── Guard.cs ├── GzipCompressionConfiguration.cs ├── Headers.cs └── ServiceBus.CompressionPlugin.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | --- 11 | name: Bug report 12 | about: Create a report to help us improve 13 | 14 | --- 15 | 16 | --- 17 | name: Bug report 18 | about: Create a report to help us improve 19 | 20 | --- 21 | 22 | **Describe the bug** 23 | A clear and concise description of what the bug is. 24 | 25 | **To Reproduce** 26 | Steps to reproduce the behavior: 27 | 1. Using this version of the library '...' 28 | 2. Run this code '....' 29 | 3. With these arguments '....' 30 | 4. See error 31 | 32 | **Expected behavior** 33 | A clear and concise description of what you expected to happen. 34 | 35 | **Screenshots** 36 | If applicable, add screenshots to help explain your problem. 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "00:00" 8 | timezone: Etc/UCT 9 | open-pull-requests-limit: 10 10 | ignore: 11 | - dependency-name: Microsoft.Azure.ServiceBus 12 | versions: 13 | - ">= 3.a, < 4" 14 | - dependency-name: Microsoft.Azure.ServiceBus 15 | versions: 16 | - ">= 4.a, < 5" 17 | - dependency-name: Microsoft.Azure.ServiceBus 18 | versions: 19 | - ">= 5.a, < 6" 20 | - dependency-name: Microsoft.NET.Test.Sdk 21 | versions: 22 | - "> 16.8.0, < 16.9" 23 | - dependency-name: PublicApiGenerator 24 | versions: 25 | - "> 10.0.0, < 10.1" 26 | - dependency-name: Microsoft.NET.Test.Sdk 27 | versions: 28 | - 16.9.1 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | /binaries 290 | /**/ApiApprovals.CompressionPlugin.received.txt 291 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @seanfeldman 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sean Feldman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon](https://github.com/SeanFeldman/ServiceBus.CompressionPlugin/blob/master/images/project-icon.png) 2 | 3 | ### This is a plugin for [Microsoft.Azure.ServiceBus client](https://github.com/Azure/azure-service-bus-dotnet/) 4 | 5 | Allows sending and receiving compressed messages. 6 | 7 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/SeanFeldman/ServiceBus.CompressionPlugin/blob/master/LICENSE) 8 | [![develop](https://img.shields.io/appveyor/ci/seanfeldman/ServiceBus-CompressionPlugin/develop.svg?style=flat-square&branch=develop)](https://ci.appveyor.com/project/seanfeldman/ServiceBus-CompressionPlugin) 9 | [![opened issues](https://img.shields.io/github/issues-raw/badges/shields/website.svg)](https://github.com/SeanFeldman/ServiceBus.CompressionPlugin/issues) 10 | 11 | ### NuGet package 12 | 13 | [![NuGet Status](https://buildstats.info/nuget/ServiceBus.CompressionPlugin?includePreReleases=true)](https://www.nuget.org/packages/ServiceBus.CompressionPlugin/) 14 | 15 | Available here http://nuget.org/packages/ServiceBus.CompressionPlugin 16 | 17 | To Install from the NuGet Package Manager Console 18 | 19 | PM> Install-Package ServiceBus.CompressionPlugin 20 | 21 | ## Examples 22 | 23 | ### Using default compression (GZip, body of at least 1500 bytes) 24 | 25 | Configuration and registration 26 | 27 | ```c# 28 | var sender = new MessageSender(connectionString, queueName); 29 | sender.RegisterCompressionPlugin(); 30 | ``` 31 | 32 | Sending 33 | 34 | ```c# 35 | var payload = new MyMessage { ... }; 36 | var serialized = JsonConvert.SerializeObject(payload); 37 | var payloadAsBytes = Encoding.UTF8.GetBytes(serialized); 38 | var message = new Message(payloadAsBytes); 39 | ``` 40 | 41 | Receiving 42 | 43 | ```c# 44 | var receiver = new MessageReceiver(connectionString, entityPath, ReceiveMode.ReceiveAndDelete); 45 | receiver.RegisterCompressionPlugin(); 46 | var msg = await receiver.ReceiveAsync().ConfigureAwait(false); 47 | // msg will contain the original payload 48 | ``` 49 | 50 | ### Overriding minimum body size 51 | 52 | ```c# 53 | var sender = new MessageSender(connectionString, queueName); 54 | sender.RegisterCompressionPlugin(1024); // compress messages using GZip with at least 1024 bytes 55 | ``` 56 | 57 | ### Custom compressions 58 | 59 | Configuration and registration 60 | 61 | ```c# 62 | var configuration = new CompressionConfiguration(compressionMethodName: "noop", compressor: bytes => Task.FromResult, decompressor: bytes => Task.FromResult, minimumSize: 1); 63 | 64 | var sender = new MessageSender(connectionString, queueName); 65 | sender.RegisterCompressionPlugin(configuration); 66 | ``` 67 | 68 | Sending 69 | 70 | ```c# 71 | var payload = new MyMessage { ... }; 72 | var serialized = JsonConvert.SerializeObject(payload); 73 | var payloadAsBytes = Encoding.UTF8.GetBytes(serialized); 74 | var message = new Message(payloadAsBytes); 75 | ``` 76 | 77 | Receiving 78 | 79 | ```c# 80 | var receiver = new MessageReceiver(connectionString, entityPath, ReceiveMode.ReceiveAndDelete); 81 | receiver.RegisterCompressionPlugin(configuration); 82 | var msg = await receiver.ReceiveAsync().ConfigureAwait(false); 83 | // msg will contain the original payload 84 | ``` 85 | 86 | ### Transitioning to a different compression or receiving a different compression 87 | 88 | To transition to a different compression or process messages compressed used a different method, additional decompressors can be registered to ensure messages in flight compressed using older/other methods are handled properly. 89 | 90 | ```c# 91 | configuration = new CompressionConfiguration(/* new version of compression */); 92 | configuration.AddDecompressor(compressionMethodName: "old compression method name", decompressor: bytes => Task.FromResult); 93 | configuration.AddDecompressor(compressionMethodName: "other compression method name", decompressor: bytes => Task.FromResult); 94 | ``` 95 | 96 | 97 | ## Who's trusting this plugin in production 98 | 99 | 103 | 104 | Proudly list your company here if use this plugin in production 105 | 106 | ## Icon 107 | 108 | Created by Eucalyp from the Noun Project. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | skip_tags: true 2 | skip_commits: 3 | files: 4 | - '**/*.md' 5 | - images/* 6 | matrix: 7 | fast_finish: true 8 | image: Visual Studio 2019 9 | environment: 10 | matrix: 11 | # First build 12 | - DotNetRunTime: net461 13 | # Second build 14 | - DotNetRunTime: netcoreapp3.0 15 | 16 | #---------------------------------# 17 | # restore nuget packages # 18 | #---------------------------------# 19 | before_build: 20 | - cmd: dotnet restore src\ServiceBus.CompressionPlugin.sln 21 | - cmd: echo %appveyor_build_version% 22 | 23 | #---------------------------------# 24 | # build configuration # 25 | #---------------------------------# 26 | 27 | # build Configuration, i.e. Debug, Release, etc. 28 | configuration: Release 29 | 30 | build: 31 | parallel: true # enable MSBuild parallel builds 32 | project: src\ServiceBus.CompressionPlugin.sln # path to Visual Studio project 33 | 34 | # MSBuild verbosity level 35 | verbosity: normal # quiet|minimal|normal|detailed 36 | 37 | test: 38 | assemblies: 39 | only: 40 | - '**\*.tests.dll' 41 | 42 | after_build: 43 | ## 7z a ServiceBus.CompressionPlugin-%GitVersion_MajorMinorPatch%.zip %APPVEYOR_BUILD_FOLDER%\src\ServiceBus.CompressionPlugin\bin\Release\*.* 44 | 45 | #---------------------------------# 46 | # artifacts configuration # 47 | #---------------------------------# 48 | 49 | artifacts: 50 | - path: '**\*.nupkg' 51 | name: 'ServiceBus.CompressionPlugin.%GitVersion_MajorMinorPatch%.nupkg' 52 | 53 | #---------------------------------# 54 | # GitHub PR notifications # 55 | #---------------------------------# 56 | 57 | notifications: 58 | - provider: GitHubPullRequest 59 | auth_token: 60 | secure: oQQNBFHTaM9998aRO2Y2Gnvz1hSdU44cQACuLaANlAusEEA2QqlWt5hw907b/IIC # encrypted token from GitHub 61 | template: "{{#passed}}:white_check_mark:{{/passed}}{{#failed}}:x:{{/failed}} [Build {{&projectName}} {{buildVersion}} {{status}}]({{buildUrl}}) (commit {{commitUrl}} by @{{&commitAuthorUsername}})" 62 | 63 | #---------------------------------# 64 | # deployment configuration # 65 | #---------------------------------# 66 | 67 | deploy: 68 | provider: NuGet 69 | api_key: 70 | secure: 1/gMnCpZKbZykJEuSO1iNNX+IcxKNhI3zliomM2g/SlEGl2LCdoUETDaTqjbBCuo # encrypted ServiceBus.CompressionPlugin API key 71 | artifact: /.*\.nupkg/ 72 | on: 73 | branch: master 74 | -------------------------------------------------------------------------------- /images/project-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeanFeldman/ServiceBus.CompressionPlugin/61d13b4f981fc886ab727dbd4eca9eea1a5f0796/images/project-icon.png -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | latest 6 | 7 | -------------------------------------------------------------------------------- /src/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/ApiApprovals.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin.Tests 2 | { 3 | using PublicApiGenerator; 4 | using ServiceBus.CompressionPlugin; 5 | using Xunit; 6 | 7 | public class ApiApprovals 8 | { 9 | [Fact] 10 | public void CompressionPlugin() 11 | { 12 | var publicApi = typeof(CompressionPlugin).Assembly.GeneratePublicApi( 13 | new ApiGeneratorOptions 14 | { 15 | WhitelistedNamespacePrefixes = new[] {"Microsoft.Azure.ServiceBus."}, 16 | ExcludeAttributes = new[] 17 | { 18 | "System.Runtime.Versioning.TargetFrameworkAttribute", 19 | "System.Reflection.AssemblyMetadataAttribute" 20 | } 21 | }); 22 | 23 | Approver.Verify(publicApi); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/ApprovalFiles/ApiApprovals.CompressionPlugin.approved.txt: -------------------------------------------------------------------------------- 1 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ServiceBus.CompressionPlugin.Tests")] 2 | namespace Microsoft.Azure.ServiceBus 3 | { 4 | public class CompressionConfiguration 5 | { 6 | public CompressionConfiguration(string compressionMethodName, System.Func compressor, int minimumSize, System.Collections.Generic.Dictionary> decompressors) { } 7 | public CompressionConfiguration(string compressionMethodName, System.Func compressor, int minimumSize, System.Func decompressor) { } 8 | public void AddDecompressor(string compressionMethodName, System.Func decompressor) { } 9 | } 10 | public static class CompressionPluginExtensions 11 | { 12 | public static Microsoft.Azure.ServiceBus.Core.ServiceBusPlugin RegisterCompressionPlugin(this Microsoft.Azure.ServiceBus.ClientEntity client, Microsoft.Azure.ServiceBus.CompressionConfiguration compressionConfiguration) { } 13 | public static Microsoft.Azure.ServiceBus.Core.ServiceBusPlugin RegisterCompressionPlugin(this Microsoft.Azure.ServiceBus.ClientEntity client, int minimumSizeToApplyCompression = 1500) { } 14 | } 15 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/ApprovalFiles/Approver.cs: -------------------------------------------------------------------------------- 1 | // Adopted from https://github.com/Particular/Particular.Approvals/blob/master/src/Particular.Approvals/Approver.cs 2 | 3 | namespace Xunit 4 | { 5 | using System; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Reflection; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Converters; 11 | 12 | /// 13 | /// Verifies that values contain approved content. 14 | /// 15 | static class Approver 16 | { 17 | static string TestDirectory 18 | { 19 | get 20 | { 21 | var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase); 22 | var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath); 23 | var dirPath = Path.GetDirectoryName(codeBasePath); 24 | return Path.Combine(dirPath, "..", "..", "..", "ApprovalFiles"); 25 | } 26 | } 27 | 28 | static readonly string approvalFilesPath = TestDirectory; 29 | static readonly JsonSerializerSettings jsonSerializerSettings; 30 | 31 | static Approver() 32 | { 33 | jsonSerializerSettings = new JsonSerializerSettings 34 | { 35 | Formatting = Formatting.Indented 36 | }; 37 | 38 | jsonSerializerSettings.Converters.Add(new StringEnumConverter()); 39 | } 40 | 41 | /// 42 | /// Verifies that the received string matches the contents of the corresponding approval file. 43 | /// 44 | /// The string to verify. 45 | /// A delegate that modifies the received string before comparing it to the approval file. 46 | /// A value that will be added to the name of the approval file. 47 | public static void Verify(string value, Func scrubber = null, string scenario = null) 48 | { 49 | var st = new StackTrace(); 50 | var sf = st.GetFrame(1); 51 | var currentMethodName = sf.GetMethod(); 52 | 53 | var className = currentMethodName.DeclaringType.Name; 54 | var methodName = currentMethodName.Name; 55 | var scenarioName = string.IsNullOrEmpty(scenario) ? "" : scenario + "."; 56 | 57 | if (scrubber != null) 58 | { 59 | value = scrubber(value); 60 | } 61 | 62 | var receivedFile = Path.Combine(approvalFilesPath, $"{className}.{methodName}.{scenarioName}received.txt"); 63 | File.WriteAllText(receivedFile, value); 64 | 65 | var approvedFile = Path.Combine(approvalFilesPath, $"{className}.{methodName}.{scenarioName}approved.txt"); 66 | var approvedText = File.ReadAllText(approvedFile); 67 | 68 | var normalizedApprovedText = approvedText.Replace("\r\n", "\n"); 69 | var normalizedReceivedText = value.Replace("\r\n", "\n"); 70 | 71 | Assert.Equal(normalizedApprovedText, normalizedReceivedText); 72 | 73 | File.Delete(receivedFile); 74 | } 75 | 76 | /// 77 | /// Verifies that the received object, after it has been serialized, matches the contents of the corresponding approval file. 78 | /// 79 | /// The object to verify. 80 | /// A delegate that modifies the received object, after it has been serialized, before comparing it to the approval file. 81 | /// A value that will be added to the name of the approval file. 82 | public static void Verify(object value, Func scrubber = null, string scenario = null) 83 | { 84 | var json = JsonConvert.SerializeObject(value, jsonSerializerSettings); 85 | 86 | Verify(json, scrubber, scenario); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/ServiceBus.CompressionPlugin.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0;net461 5 | ServiceBus.CompressionPlugin.Tests 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/When_compression_plugin_is_misconfigured.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.ServiceBus; 7 | using ServiceBus.CompressionPlugin; 8 | using Xunit; 9 | 10 | public class When_compression_plugin_is_misconfigured 11 | { 12 | [Fact] 13 | public async Task Should_throw_meaningful_exception_for_erred_compression_func() 14 | { 15 | var configuration = new CompressionConfiguration("test", bytes => throw new Exception("compressor threw"), 1, 16 | new Dictionary> { { "test", bytes => null } }); 17 | 18 | var plugin = new CompressionPlugin(configuration); 19 | 20 | var message = new Message(new byte[] { 1, 2, 3 }); 21 | 22 | var exception = await Assert.ThrowsAsync(() => plugin.BeforeMessageSend(message)); 23 | 24 | Assert.StartsWith("User provided compression delegate threw an exception.", exception.Message); 25 | } 26 | 27 | [Fact] 28 | public async Task Should_throw_meaningful_exception_for_erred_decompression_func() 29 | { 30 | var configuration = new CompressionConfiguration("test", bytes => null, 1, 31 | new Dictionary> { { "test", bytes => throw new Exception("decompressor threw") } }); 32 | 33 | var plugin = new CompressionPlugin(configuration); 34 | 35 | var message = new Message(new byte[] { 1, 2, 3 }); 36 | message.UserProperties[Headers.CompressionMethodName] = configuration.CompressionMethodName; 37 | 38 | var exception = await Assert.ThrowsAsync(() => plugin.AfterMessageReceive(message)); 39 | 40 | Assert.StartsWith($"User provided decompression delegate for '{configuration.CompressionMethodName}' compression method threw an exception.", exception.Message); 41 | } 42 | 43 | [Fact] 44 | public async Task Should_throw_when_cannot_decompress_due_to_missing_decompressor_func() 45 | { 46 | var configuration = new CompressionConfiguration("test", bytes => null, 1, 47 | new Dictionary> { { "test", bytes => throw new Exception("decompressor threw") } }); 48 | 49 | var plugin = new CompressionPlugin(configuration); 50 | 51 | var message = new Message(new byte[] { 1, 2, 3 }); 52 | message.UserProperties[Headers.CompressionMethodName] = "unknown-compression"; 53 | 54 | var exception = await Assert.ThrowsAsync(() => plugin.AfterMessageReceive(message)); 55 | 56 | Assert.StartsWith($"{nameof(CompressionPlugin)} has not been configured to handle messages compressed using", exception.Message); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/When_configuring_plugin.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.ServiceBus; 7 | using Microsoft.Azure.ServiceBus.Core; 8 | using Xunit; 9 | 10 | public class When_configuring_plugin 11 | { 12 | [Fact] 13 | public async Task Should_be_able_to_override_minimu_size_for_compression() 14 | { 15 | var client = new FakeClient("fake-client", string.Empty, RetryPolicy.NoRetry); 16 | 17 | var plugin = CompressionPluginExtensions.RegisterCompressionPlugin(client, 2); 18 | 19 | var message = new Message(new byte[] { 1, 2, 3 }); 20 | 21 | await plugin.BeforeMessageSend(message); 22 | 23 | Assert.Equal(2, client.RegisteredPluginMinimumCompressionSize); 24 | } 25 | 26 | class FakeClient : ClientEntity 27 | { 28 | public int RegisteredPluginMinimumCompressionSize; 29 | 30 | public FakeClient(string clientTypeName, string postfix, RetryPolicy retryPolicy) : base(clientTypeName, postfix, retryPolicy) 31 | { 32 | } 33 | 34 | public override void RegisterPlugin(ServiceBusPlugin serviceBusPlugin) 35 | { 36 | var plugin = serviceBusPlugin as CompressionPlugin; 37 | RegisteredPluginMinimumCompressionSize = plugin.configuration.MinimumSize; 38 | } 39 | 40 | public override void UnregisterPlugin(string serviceBusPluginName) 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | public override string Path { get; } 46 | public override TimeSpan OperationTimeout { get; set; } 47 | 48 | protected override Task OnClosingAsync() 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | 53 | public override ServiceBusConnection ServiceBusConnection { get; } 54 | public override IList RegisteredPlugins { get; } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/When_receiving_message.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.IO.Compression; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.ServiceBus; 10 | using ServiceBus.CompressionPlugin; 11 | using Xunit; 12 | 13 | public class When_receiving_message 14 | { 15 | [Fact] 16 | public async Task Should_skip_decompression_if_no_compression_header_is_found() 17 | { 18 | var body = new byte[] { 1, 2, 3 }; 19 | var message = new Message(body); 20 | 21 | var decompressorExecuted = false; 22 | 23 | var receivePlugin = new CompressionPlugin( 24 | new CompressionConfiguration("noop", bytes => bytes, 1, 25 | new Dictionary> 26 | { 27 | { 28 | "compression-x", bytes => 29 | { 30 | decompressorExecuted = true; 31 | return bytes; 32 | } 33 | } 34 | })); 35 | 36 | var receivedMessage = await receivePlugin.AfterMessageReceive(message); 37 | 38 | Assert.Equal(body, receivedMessage.Body); 39 | 40 | Assert.False(decompressorExecuted, "Decompressing function should not have executed, but it did."); 41 | } 42 | 43 | [Fact] 44 | public async Task Should_throw_for_not_configured_compression_method() 45 | { 46 | var bytes = new byte[] {1, 2, 3}; 47 | var message = new Message(bytes); 48 | 49 | var plugin = new CompressionPlugin(new GzipCompressionConfiguration()); 50 | var sentMessage = await plugin.BeforeMessageSend(message); 51 | sentMessage.UserProperties[Headers.CompressionMethodName] = "Unknown"; 52 | 53 | await Assert.ThrowsAsync(() => plugin.AfterMessageReceive(sentMessage)); 54 | } 55 | 56 | [Fact] 57 | public async Task Should_be_able_to_decompress_messages_sent_with_different_compression_other_than_default() 58 | { 59 | var deflateCompressionConfiguration = new DeflateCompressionConfiguration(); 60 | 61 | var gzipCompressionConfiguration = new GzipCompressionConfiguration(1); 62 | gzipCompressionConfiguration.AddDecompressor(deflateCompressionConfiguration.CompressionMethodName, DeflateDecompressor); 63 | 64 | var bytes = Enumerable.Range(1, 1024).Select(x => (byte)x).ToArray(); 65 | var message = new Message(bytes); 66 | message.UserProperties[Headers.CompressionMethodName] = deflateCompressionConfiguration.CompressionMethodName; 67 | 68 | var sendPlugin = new CompressionPlugin(deflateCompressionConfiguration); 69 | var sentMessage = await sendPlugin.BeforeMessageSend(message); 70 | 71 | var receivePlugin = new CompressionPlugin(gzipCompressionConfiguration); 72 | var receivedMessage = await receivePlugin.AfterMessageReceive(sentMessage); 73 | 74 | Assert.Equal(bytes, receivedMessage.Body); 75 | } 76 | 77 | class DeflateCompressionConfiguration : CompressionConfiguration 78 | { 79 | public DeflateCompressionConfiguration() : base("Deflate", DeflateCompressor, 1, DeflateDecompressor) { } 80 | } 81 | 82 | static byte[] DeflateCompressor(byte[] bytes) 83 | { 84 | using (var compressStream = new MemoryStream()) 85 | using (var compressor = new DeflateStream(compressStream, CompressionMode.Compress)) 86 | { 87 | compressor.Write(bytes, 0, bytes.Length); 88 | compressor.Flush(); 89 | compressor.Close(); 90 | return compressStream.GetBuffer(); 91 | } 92 | } 93 | 94 | static byte[] DeflateDecompressor(byte[] bytes) 95 | { 96 | using (var memoryStream = new MemoryStream(bytes)) 97 | using (var decompressStream = new DeflateStream(memoryStream, CompressionMode.Decompress)) 98 | { 99 | const int size = 4096; 100 | var buffer = new byte[size]; 101 | using (var memory = new MemoryStream()) 102 | { 103 | int count; 104 | do 105 | { 106 | count = decompressStream.Read(buffer, 0, size); 107 | if (count > 0) 108 | { 109 | memory.Write(buffer, 0, count); 110 | } 111 | } 112 | while (count > 0); 113 | 114 | return memory.ToArray(); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.Tests/When_sending_message.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin.Tests 2 | { 3 | using System; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.ServiceBus; 7 | using ServiceBus.CompressionPlugin; 8 | using Xunit; 9 | 10 | public class When_sending_message 11 | { 12 | [Fact] 13 | public async Task Should_not_compress_null_body() 14 | { 15 | var message = new Message(null); 16 | 17 | var plugin = new CompressionPlugin(new GzipCompressionConfiguration()); 18 | var result = await plugin.BeforeMessageSend(message); 19 | 20 | Assert.Null(result.Body); 21 | Assert.False(message.UserProperties.ContainsKey(Headers.CompressionMethodName), "Header should not be found for a message with null body."); 22 | } 23 | 24 | [Fact] 25 | public async Task Should_not_compress_empty_body() 26 | { 27 | var message = new Message(new byte[] {}); 28 | 29 | var plugin = new CompressionPlugin(new GzipCompressionConfiguration()); 30 | var result = await plugin.BeforeMessageSend(message); 31 | 32 | Assert.Equal(Array.Empty(), result.Body); 33 | Assert.False(message.UserProperties.ContainsKey(Headers.CompressionMethodName), "Header should not be found for a message with empty body."); 34 | } 35 | 36 | [Fact] 37 | public async Task Should_not_compress_body_smaller_than_configured_size() 38 | { 39 | var bytes = new byte[] { 1, 2, 3 }; 40 | var message = new Message(bytes); 41 | 42 | var plugin = new CompressionPlugin(new GzipCompressionConfiguration()); 43 | var result = await plugin.BeforeMessageSend(message); 44 | 45 | Assert.Equal(bytes, result.Body); 46 | Assert.False(message.UserProperties.ContainsKey(Headers.CompressionMethodName), "Header should not be found for a message with empty body."); 47 | } 48 | 49 | 50 | [Fact] 51 | public async Task Should_compress_normal_body() 52 | { 53 | var payload = new string('A', 1500); 54 | var bytes = Encoding.UTF8.GetBytes(payload); 55 | var message = new Message(bytes); 56 | 57 | var plugin = new CompressionPlugin(new GzipCompressionConfiguration()); 58 | var result = await plugin.BeforeMessageSend(message); 59 | 60 | var bodyAsBase64String = Convert.ToBase64String(result.Body); 61 | 62 | #if NETFRAMEWORK 63 | Assert.Equal("H4sIAAAAAAAEAHN0HAWjYBSMglEw3AAATW9s69wFAAA=", bodyAsBase64String); 64 | Assert.Equal(32, message.UserProperties[Headers.CompressedBodySize]); 65 | #elif NETCOREAPP2_0 66 | Assert.Equal("H4sIAAAAAAAAC3N0HAWjITAaAqMhMBoCjsMMAABNb2zr3AUAAA==", bodyAsBase64String); 67 | Assert.Equal(37, message.UserProperties[Headers.CompressedBodySize]); 68 | #else // NETCOREAPP3_0 69 | Assert.Equal("H4sIAAAAAAAACnN0HAWjYBSMglEw3AAATW9s69wFAAA=", bodyAsBase64String); 70 | Assert.Equal(32, message.UserProperties[Headers.CompressedBodySize]); 71 | #endif 72 | Assert.Equal("GZip", message.UserProperties[Headers.CompressionMethodName]); 73 | Assert.Equal(payload.Length, message.UserProperties[Headers.OriginalBodySize]); 74 | } 75 | 76 | [Fact] 77 | public async Task Should_receive_it() 78 | { 79 | var payload = new string('A', 1500); 80 | var bytes = Encoding.UTF8.GetBytes(payload); 81 | var message = new Message(bytes); 82 | 83 | var plugin = new CompressionPlugin(new GzipCompressionConfiguration()); 84 | var result = await plugin.BeforeMessageSend(message); 85 | 86 | var receivedMessage = await plugin.AfterMessageReceive(result); 87 | 88 | Assert.Equal(payload, Encoding.UTF8.GetString(receivedMessage.Body)); 89 | Assert.Equal("GZip", message.UserProperties[Headers.CompressionMethodName]); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceBus.CompressionPlugin", "ServiceBus.CompressionPlugin\ServiceBus.CompressionPlugin.csproj", "{5B09D75C-3554-4A8B-8DE2-C593CEF79656}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceBus.CompressionPlugin.Tests", "ServiceBus.CompressionPlugin.Tests\ServiceBus.CompressionPlugin.Tests.csproj", "{D52B8E45-D641-4E74-A0C9-222D408D3032}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{2295CF61-70EF-43B4-8747-7C01493D607C}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\appveyor.yml = ..\appveyor.yml 13 | ..\README.md = ..\README.md 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {5B09D75C-3554-4A8B-8DE2-C593CEF79656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {5B09D75C-3554-4A8B-8DE2-C593CEF79656}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {5B09D75C-3554-4A8B-8DE2-C593CEF79656}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {5B09D75C-3554-4A8B-8DE2-C593CEF79656}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {D52B8E45-D641-4E74-A0C9-222D408D3032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {D52B8E45-D641-4E74-A0C9-222D408D3032}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {D52B8E45-D641-4E74-A0C9-222D408D3032}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {D52B8E45-D641-4E74-A0C9-222D408D3032}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {A34E9317-BBE1-43A0-879E-BACF4FDD302B} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | CSharp71 4 | True 5 | True 6 | True 7 | False 8 | 9 | True 10 | ExplicitlyExcluded 11 | SOLUTION 12 | True 13 | SUGGESTION 14 | SUGGESTION 15 | DO_NOT_SHOW 16 | ERROR 17 | DO_NOT_SHOW 18 | DO_NOT_SHOW 19 | DO_NOT_SHOW 20 | DO_NOT_SHOW 21 | WARNING 22 | ERROR 23 | ERROR 24 | ERROR 25 | ERROR 26 | ERROR 27 | DO_NOT_SHOW 28 | DO_NOT_SHOW 29 | DO_NOT_SHOW 30 | ERROR 31 | ERROR 32 | ERROR 33 | ERROR 34 | ERROR 35 | ERROR 36 | ERROR 37 | ERROR 38 | ERROR 39 | ERROR 40 | ERROR 41 | ERROR 42 | ERROR 43 | ERROR 44 | ERROR 45 | ERROR 46 | ERROR 47 | ERROR 48 | ERROR 49 | ERROR 50 | ERROR 51 | DO_NOT_SHOW 52 | DO_NOT_SHOW 53 | ERROR 54 | DO_NOT_SHOW 55 | DO_NOT_SHOW 56 | ERROR 57 | ERROR 58 | ERROR 59 | ERROR 60 | ERROR 61 | ERROR 62 | ERROR 63 | ERROR 64 | ERROR 65 | WARNING 66 | ERROR 67 | ERROR 68 | ERROR 69 | ERROR 70 | ERROR 71 | ERROR 72 | ERROR 73 | SUGGESTION 74 | SUGGESTION 75 | ERROR 76 | ERROR 77 | ERROR 78 | ERROR 79 | ERROR 80 | ERROR 81 | ERROR 82 | SUGGESTION 83 | ERROR 84 | ERROR 85 | ERROR 86 | ERROR 87 | ERROR 88 | ERROR 89 | ERROR 90 | ERROR 91 | ERROR 92 | ERROR 93 | ERROR 94 | WARNING 95 | ERROR 96 | ERROR 97 | ERROR 98 | DoHide 99 | DoHide 100 | DoHide 101 | DoHide 102 | DoHide 103 | DoHide 104 | DoHide 105 | DoHide 106 | DoHide 107 | DoHide 108 | DoHide 109 | DoHide 110 | DoHide 111 | DoHide 112 | DoHide 113 | DoHide 114 | DoHide 115 | DoHide 116 | ERROR 117 | ERROR 118 | ERROR 119 | ERROR 120 | ERROR 121 | ERROR 122 | ERROR 123 | ERROR 124 | ERROR 125 | ERROR 126 | ERROR 127 | ERROR 128 | ERROR 129 | ERROR 130 | DO_NOT_SHOW 131 | SUGGESTION 132 | WARNING 133 | WARNING 134 | ERROR 135 | HINT 136 | WARNING 137 | ERROR 138 | ERROR 139 | ERROR 140 | <?xml version="1.0" encoding="utf-16"?><Profile name="Format My Code Using &quot;Particular&quot; conventions"><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssReformatCode>True</CssReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><HtmlReformatCode>True</HtmlReformatCode><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties></Profile> 141 | Default: Reformat Code 142 | Format My Code Using "Particular" conventions 143 | Implicit 144 | Implicit 145 | False 146 | False 147 | ALWAYS_ADD 148 | ALWAYS_ADD 149 | ALWAYS_ADD 150 | ALWAYS_ADD 151 | ALWAYS_ADD 152 | False 153 | False 154 | False 155 | CHOP_ALWAYS 156 | False 157 | CHOP_ALWAYS 158 | CHOP_ALWAYS 159 | True 160 | True 161 | ZeroIndent 162 | ZeroIndent 163 | <?xml version="1.0" encoding="utf-16"?> 164 | <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> 165 | <TypePattern DisplayName="COM interfaces or structs"> 166 | <TypePattern.Match> 167 | <Or> 168 | <And> 169 | <Kind Is="Interface" /> 170 | <Or> 171 | <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> 172 | <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> 173 | </Or> 174 | </And> 175 | <Kind Is="Struct" /> 176 | </Or> 177 | </TypePattern.Match> 178 | </TypePattern> 179 | <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> 180 | <TypePattern.Match> 181 | <And> 182 | <Kind Is="Class" /> 183 | <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> 184 | <HasAttribute Name="NUnit.Framework.TestCaseFixtureAttribute" Inherited="True" /> 185 | </And> 186 | </TypePattern.Match> 187 | <Entry DisplayName="Setup/Teardown Methods"> 188 | <Entry.Match> 189 | <And> 190 | <Kind Is="Method" /> 191 | <Or> 192 | <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> 193 | <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> 194 | <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> 195 | <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> 196 | </Or> 197 | </And> 198 | </Entry.Match> 199 | </Entry> 200 | <Entry DisplayName="All other members" /> 201 | <Entry DisplayName="Test Methods" Priority="100"> 202 | <Entry.Match> 203 | <And> 204 | <Kind Is="Method" /> 205 | <HasAttribute Name="NUnit.Framework.TestAttribute" /> 206 | </And> 207 | </Entry.Match> 208 | <Entry.SortBy> 209 | <Name /> 210 | </Entry.SortBy> 211 | </Entry> 212 | </TypePattern> 213 | <TypePattern DisplayName="Default Pattern"> 214 | <Entry DisplayName="Public Delegates" Priority="100"> 215 | <Entry.Match> 216 | <And> 217 | <Access Is="Public" /> 218 | <Kind Is="Delegate" /> 219 | </And> 220 | </Entry.Match> 221 | <Entry.SortBy> 222 | <Name /> 223 | </Entry.SortBy> 224 | </Entry> 225 | <Entry DisplayName="Public Enums" Priority="100"> 226 | <Entry.Match> 227 | <And> 228 | <Access Is="Public" /> 229 | <Kind Is="Enum" /> 230 | </And> 231 | </Entry.Match> 232 | <Entry.SortBy> 233 | <Name /> 234 | </Entry.SortBy> 235 | </Entry> 236 | <Entry DisplayName="Constructors"> 237 | <Entry.Match> 238 | <Kind Is="Constructor" /> 239 | </Entry.Match> 240 | <Entry.SortBy> 241 | <Static /> 242 | </Entry.SortBy> 243 | </Entry> 244 | <Entry DisplayName="Properties, Indexers"> 245 | <Entry.Match> 246 | <Or> 247 | <Kind Is="Property" /> 248 | <Kind Is="Indexer" /> 249 | </Or> 250 | </Entry.Match> 251 | </Entry> 252 | <Entry DisplayName="Interface Implementations" Priority="100"> 253 | <Entry.Match> 254 | <And> 255 | <Kind Is="Member" /> 256 | <ImplementsInterface /> 257 | </And> 258 | </Entry.Match> 259 | <Entry.SortBy> 260 | <ImplementsInterface Immediate="True" /> 261 | </Entry.SortBy> 262 | </Entry> 263 | <Entry DisplayName="All other members" /> 264 | <Entry DisplayName="Fields"> 265 | <Entry.Match> 266 | <And> 267 | <Kind Is="Field" /> 268 | <Not> 269 | <Static /> 270 | </Not> 271 | </And> 272 | </Entry.Match> 273 | <Entry.SortBy> 274 | <Access /> 275 | <Readonly /> 276 | </Entry.SortBy> 277 | </Entry> 278 | <Entry DisplayName="Static Fields and Constants"> 279 | <Entry.Match> 280 | <Or> 281 | <Kind Is="Constant" /> 282 | <And> 283 | <Kind Is="Field" /> 284 | <Static /> 285 | </And> 286 | </Or> 287 | </Entry.Match> 288 | <Entry.SortBy> 289 | <Kind Order="Constant Field" /> 290 | </Entry.SortBy> 291 | </Entry> 292 | <Entry DisplayName="Nested Types"> 293 | <Entry.Match> 294 | <Kind Is="Type" /> 295 | </Entry.Match> 296 | </Entry> 297 | </TypePattern> 298 | </Patterns> 299 | <?xml version="1.0" encoding="utf-8" ?> 300 | 301 | <!-- 302 | I. Overall 303 | 304 | I.1 Each pattern can have <Match>....</Match> element. For the given type declaration, the pattern with the match, evaluated to 'true' with the largest weight, will be used 305 | I.2 Each pattern consists of the sequence of <Entry>...</Entry> elements. Type member declarations are distributed between entries 306 | I.3 If pattern has RemoveAllRegions="true" attribute, then all regions will be cleared prior to reordering. Otherwise, only auto-generated regions will be cleared 307 | I.4 The contents of each entry is sorted by given keys (First key is primary, next key is secondary, etc). Then the declarations are grouped and en-regioned by given property 308 | 309 | II. Available match operands 310 | 311 | Each operand may have Weight="..." attribute. This weight will be added to the match weight if the operand is evaluated to 'true'. 312 | The default weight is 1 313 | 314 | II.1 Boolean functions: 315 | II.1.1 <And>....</And> 316 | II.1.2 <Or>....</Or> 317 | II.1.3 <Not>....</Not> 318 | 319 | II.2 Operands 320 | II.2.1 <Kind Is="..."/>. Kinds are: class, struct, interface, enum, delegate, type, constructor, destructor, property, indexer, method, operator, field, constant, event, member 321 | II.2.2 <Name Is="..." [IgnoreCase="true/false"] />. The 'Is' attribute contains regular expression 322 | II.2.3 <HasAttribute CLRName="..." [Inherit="true/false"] />. The 'CLRName' attribute contains regular expression 323 | II.2.4 <Access Is="..."/>. The 'Is' values are: public, protected, internal, protected internal, private 324 | II.2.5 <Static/> 325 | II.2.6 <Abstract/> 326 | II.2.7 <Virtual/> 327 | II.2.8 <Override/> 328 | II.2.9 <Sealed/> 329 | II.2.10 <Readonly/> 330 | II.2.11 <ImplementsInterface CLRName="..."/>. The 'CLRName' attribute contains regular expression 331 | II.2.12 <HandlesEvent /> 332 | --> 333 | 334 | <Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> 335 | 336 | <!--Do not reorder COM interfaces and structs marked by StructLayout attribute--> 337 | <Pattern> 338 | <Match> 339 | <Or Weight="100"> 340 | <And> 341 | <Kind Is="interface"/> 342 | <Or> 343 | <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> 344 | <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> 345 | </Or> 346 | </And> 347 | <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> 348 | </Or> 349 | </Match> 350 | </Pattern> 351 | 352 | <!--Special formatting of NUnit test fixture--> 353 | <Pattern RemoveAllRegions="true"> 354 | <Match> 355 | <And Weight="100"> 356 | <Kind Is="class"/> 357 | <HasAttribute CLRName="NUnit.Framework.TestFixtureAttribute" Inherit="true"/> 358 | </And> 359 | </Match> 360 | 361 | <!--Setup/Teardow--> 362 | <Entry> 363 | <Match> 364 | <And> 365 | <Kind Is="method"/> 366 | <Or> 367 | <HasAttribute CLRName="NUnit.Framework.SetUpAttribute" Inherit="true"/> 368 | <HasAttribute CLRName="NUnit.Framework.TearDownAttribute" Inherit="true"/> 369 | <HasAttribute CLRName="NUnit.Framework.FixtureSetUpAttribute" Inherit="true"/> 370 | <HasAttribute CLRName="NUnit.Framework.FixtureTearDownAttribute" Inherit="true"/> 371 | </Or> 372 | </And> 373 | </Match> 374 | </Entry> 375 | 376 | <!--All other members--> 377 | <Entry/> 378 | 379 | <!--Test methods--> 380 | <Entry> 381 | <Match> 382 | <And Weight="100"> 383 | <Kind Is="method"/> 384 | <HasAttribute CLRName="NUnit.Framework.TestAttribute" Inherit="false"/> 385 | </And> 386 | </Match> 387 | <Sort> 388 | <Name/> 389 | </Sort> 390 | </Entry> 391 | </Pattern> 392 | 393 | <!--Default pattern--> 394 | <Pattern> 395 | 396 | <!--public delegate--> 397 | <Entry> 398 | <Match> 399 | <And Weight="100"> 400 | <Access Is="public"/> 401 | <Kind Is="delegate"/> 402 | </And> 403 | </Match> 404 | <Sort> 405 | <Name/> 406 | </Sort> 407 | </Entry> 408 | 409 | <!--public enum--> 410 | <Entry> 411 | <Match> 412 | <And Weight="100"> 413 | <Access Is="public"/> 414 | <Kind Is="enum"/> 415 | </And> 416 | </Match> 417 | <Sort> 418 | <Name/> 419 | </Sort> 420 | </Entry> 421 | 422 | <!--Constructors. Place static one first--> 423 | <Entry> 424 | <Match> 425 | <Kind Is="constructor"/> 426 | </Match> 427 | <Sort> 428 | <Static/> 429 | </Sort> 430 | </Entry> 431 | 432 | <!--properties, indexers--> 433 | <Entry> 434 | <Match> 435 | <Or> 436 | <Kind Is="property"/> 437 | <Kind Is="indexer"/> 438 | </Or> 439 | </Match> 440 | </Entry> 441 | 442 | <!--interface implementations--> 443 | <Entry> 444 | <Match> 445 | <And Weight="100"> 446 | <Kind Is="member"/> 447 | <ImplementsInterface/> 448 | </And> 449 | </Match> 450 | <Sort> 451 | <ImplementsInterface Immediate="true"/> 452 | </Sort> 453 | </Entry> 454 | 455 | <!--all other members--> 456 | <Entry/> 457 | 458 | <!--static fields and constants--> 459 | <Entry> 460 | <Match> 461 | <Or> 462 | <Kind Is="constant"/> 463 | <And> 464 | <Kind Is="field"/> 465 | <Static/> 466 | </And> 467 | </Or> 468 | </Match> 469 | <Sort> 470 | <Kind Order="constant field"/> 471 | </Sort> 472 | </Entry> 473 | 474 | <!--instance fields--> 475 | <Entry> 476 | <Match> 477 | <And> 478 | <Kind Is="field"/> 479 | <Not> 480 | <Static/> 481 | </Not> 482 | </And> 483 | </Match> 484 | <Sort> 485 | <Readonly/> 486 | <Name/> 487 | </Sort> 488 | </Entry> 489 | 490 | <!--nested types--> 491 | <Entry> 492 | <Match> 493 | <Kind Is="type"/> 494 | </Match> 495 | <Sort> 496 | <Name/> 497 | </Sort> 498 | </Entry> 499 | </Pattern> 500 | 501 | </Patterns> 502 | 503 | CustomLayout 504 | True 505 | False 506 | True 507 | False 508 | True 509 | False 510 | False 511 | False 512 | True 513 | Automatic property 514 | True 515 | False 516 | False 517 | DB 518 | DTC 519 | ID 520 | NSB 521 | SLA 522 | $object$_On$event$ 523 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 524 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 525 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 526 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 527 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 528 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 529 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 530 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 531 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 532 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 533 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 534 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 535 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 536 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 537 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 538 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 539 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 540 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 541 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 542 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 543 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 544 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 545 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 546 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 547 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 548 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 549 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 550 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 551 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 552 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 553 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 554 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 555 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 556 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 557 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 558 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 559 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 560 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 561 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 562 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 563 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 564 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 565 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 566 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 567 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 568 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 569 | $object$_On$event$ 570 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 571 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 572 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 573 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 574 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 575 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 576 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 577 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 578 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 579 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 580 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 581 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 582 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 583 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 584 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 585 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 586 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 587 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 588 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 589 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 590 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 591 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 592 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 593 | True 594 | True 595 | True 596 | True 597 | True 598 | True 599 | True 600 | True 601 | True 602 | True 603 | True 604 | 605 | 606 | 607 | <data /> 608 | <data><IncludeFilters /><ExcludeFilters /></data> -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/CompressionConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Azure.ServiceBus 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using global::ServiceBus.CompressionPlugin; 6 | 7 | /// Runtime configuration for Compression plugin. 8 | public class CompressionConfiguration 9 | { 10 | Dictionary> mutableDecompressors; 11 | 12 | /// 13 | /// Compression configuration object to customize Compression plugin. 14 | /// Additional decompressors can be registered using AddDecompressor(name, function) API. 15 | /// 16 | /// Compression method name stored as a custom header. 17 | /// Function to compress an array of bytes. 18 | /// Minimum size of array for compression to be applied. 19 | /// Single function to decompressor and array of bytes. 20 | public CompressionConfiguration(string compressionMethodName, Func compressor, int minimumSize, Func decompressor) 21 | : this(compressionMethodName, compressor, minimumSize, new Dictionary> { {compressionMethodName, decompressor} }) 22 | { 23 | } 24 | 25 | /// 26 | /// Compression configuration object to customize Compression plugin. 27 | /// 28 | /// Compression method name stored as a custom header. 29 | /// Function to compress an array of bytes. 30 | /// Minimum size of array for compression to be applied. 31 | /// Dictionary of function to de-compress an array of bytes where keys are compression names. 32 | public CompressionConfiguration(string compressionMethodName, Func compressor, int minimumSize, Dictionary> decompressors) 33 | { 34 | Guard.AgainstEmpty(nameof(compressionMethodName), compressionMethodName); 35 | Guard.AgainstNull(nameof(compressor), compressor); 36 | Guard.AgainstNull(nameof(decompressors), decompressors); 37 | Guard.GuardAgainstEmptyCollection(nameof(decompressors), decompressors); 38 | Guard.AgainstNegativeOrZero(nameof(minimumSize), minimumSize); 39 | 40 | CompressionMethodName = compressionMethodName; 41 | MinimumSize = minimumSize; 42 | Compressor = GetSafeCompressor(compressor); 43 | mutableDecompressors = decompressors; 44 | Decompressors = GetSafeDecompressor(decompressors); 45 | } 46 | 47 | internal string CompressionMethodName { get; } 48 | internal int MinimumSize { get; } 49 | internal Func Compressor { get; } 50 | internal Func Decompressors { get; } 51 | 52 | Func GetSafeCompressor(Func userProvidedCompressor) 53 | { 54 | return bytes => 55 | { 56 | try 57 | { 58 | return userProvidedCompressor(bytes); 59 | } 60 | catch (Exception e) 61 | { 62 | throw new Exception("User provided compression delegate threw an exception.", e); 63 | } 64 | }; 65 | } 66 | 67 | Func GetSafeDecompressor(Dictionary> userProvidedDecompressors) 68 | { 69 | return (compressionMethodName, bytes) => 70 | { 71 | if (!userProvidedDecompressors.TryGetValue(compressionMethodName, out var decompressor)) 72 | { 73 | throw new Exception($"{nameof(CompressionPlugin)} has not been configured to handle messages compressed using '{compressionMethodName}', but a message with this compression has been received. Re-configure plugin to handle '{compressionMethodName}' for decompression."); 74 | } 75 | 76 | try 77 | { 78 | return decompressor(bytes); 79 | } 80 | catch (Exception e) 81 | { 82 | throw new Exception($"User provided decompression delegate for '{compressionMethodName}' compression method threw an exception.", e); 83 | } 84 | }; 85 | } 86 | 87 | /// 88 | /// Add additional decompression method. 89 | /// 90 | /// 91 | /// 92 | public void AddDecompressor(string compressionMethodName, Func decompressor) 93 | { 94 | mutableDecompressors.Add(compressionMethodName, decompressor); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/CompressionPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin 2 | { 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.ServiceBus; 5 | using Microsoft.Azure.ServiceBus.Core; 6 | 7 | class CompressionPlugin : ServiceBusPlugin 8 | { 9 | internal CompressionConfiguration configuration; 10 | 11 | public CompressionPlugin(CompressionConfiguration configuration) 12 | { 13 | Guard.AgainstNull(nameof(configuration), configuration); 14 | 15 | this.configuration = configuration; 16 | } 17 | 18 | public override string Name => nameof(CompressionPlugin); 19 | 20 | public override bool ShouldContinueOnException { get; } = false; 21 | 22 | public override Task BeforeMessageSend(Message message) 23 | { 24 | if (message.Body == null || message.Body.Length == 0) 25 | { 26 | return Task.FromResult(message); 27 | } 28 | 29 | // min size should be configurable 30 | if (message.Body.Length < configuration.MinimumSize) 31 | { 32 | return Task.FromResult(message); 33 | } 34 | 35 | message.UserProperties[Headers.OriginalBodySize] = message.Body.Length; 36 | 37 | message.Body = configuration.Compressor(message.Body); 38 | 39 | message.UserProperties[Headers.CompressionMethodName] = configuration.CompressionMethodName; 40 | message.UserProperties[Headers.CompressedBodySize] = message.Body.Length; 41 | 42 | return Task.FromResult(message); 43 | } 44 | 45 | public override Task AfterMessageReceive(Message message) 46 | { 47 | if (message.Body == null || message.Body.Length == 0) 48 | { 49 | return Task.FromResult(message); 50 | } 51 | 52 | if (!message.UserProperties.TryGetValue(Headers.CompressionMethodName, out var methodName)) 53 | { 54 | return Task.FromResult(message); 55 | } 56 | 57 | message.Body = configuration.Decompressors((string) methodName, message.Body); 58 | 59 | return Task.FromResult(message); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/CompressionPluginExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Azure.ServiceBus 2 | { 3 | using Core; 4 | using global::ServiceBus.CompressionPlugin; 5 | 6 | /// 7 | /// Compression plugin registration options. 8 | /// 9 | public static class CompressionPluginExtensions 10 | { 11 | /// 12 | /// Register compression plugin. 13 | /// GZip compression will be used. 14 | /// 15 | /// Minimum size of message body for compression to be applied. 16 | /// 17 | public static ServiceBusPlugin RegisterCompressionPlugin(this ClientEntity client, int minimumSizeToApplyCompression = GzipCompressionConfiguration.MinimumCompressionSize) 18 | { 19 | return client.RegisterCompressionPlugin(new GzipCompressionConfiguration(minimumSizeToApplyCompression)); 20 | } 21 | 22 | /// 23 | /// Register compression plugin with customizations. 24 | /// 25 | /// Compression to be used. 26 | /// 27 | public static ServiceBusPlugin RegisterCompressionPlugin(this ClientEntity client, CompressionConfiguration compressionConfiguration) 28 | { 29 | ServiceBusPlugin plugin = new CompressionPlugin(compressionConfiguration); 30 | 31 | client.RegisterPlugin(plugin); 32 | 33 | return plugin; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/ExposeInternals.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: InternalsVisibleTo("ServiceBus.CompressionPlugin.Tests")] -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/Guard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | 4 | static class Guard 5 | { 6 | public static void AgainstEmpty(string argumentName, string value) 7 | { 8 | if (value == null || string.IsNullOrWhiteSpace(value)) 9 | { 10 | throw new ArgumentNullException(argumentName); 11 | } 12 | } 13 | 14 | public static void AgainstNull(string argumentName, object value) 15 | { 16 | if (value == null) 17 | { 18 | throw new ArgumentNullException(argumentName); 19 | } 20 | } 21 | 22 | public static void AgainstNegativeOrZero(string argumentName, int value) 23 | { 24 | if (value <= 0) 25 | { 26 | throw new ArgumentException($"Value cannot be negative or zero. Value was: {value}.", argumentName); 27 | } 28 | } 29 | 30 | public static void GuardAgainstEmptyCollection(string argumentName, ICollection collection) 31 | { 32 | if (collection != null && collection.Count == 0) 33 | { 34 | throw new ArgumentException("Collection should not be empty.", argumentName); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/GzipCompressionConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.IO.Compression; 7 | using Microsoft.Azure.ServiceBus; 8 | 9 | class GzipCompressionConfiguration : CompressionConfiguration 10 | { 11 | public const int MinimumCompressionSize = 1500; 12 | 13 | public GzipCompressionConfiguration(int minimumSizeToApplyCompression = MinimumCompressionSize) 14 | : base("GZip", GzipCompressor, minimumSizeToApplyCompression, new Dictionary> { {"GZip", GzipDecompressor} }) 15 | { 16 | } 17 | 18 | static byte[] GzipCompressor(byte[] bytes) 19 | { 20 | using (var memoryStream = new MemoryStream()) 21 | { 22 | using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) 23 | { 24 | gzipStream.Write(bytes, 0, bytes.Length); 25 | } 26 | 27 | return memoryStream.ToArray(); 28 | } 29 | } 30 | 31 | static byte[] GzipDecompressor(byte[] bytes) 32 | { 33 | using (var memoryStream = new MemoryStream(bytes)) 34 | using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) 35 | { 36 | const int size = 4096; 37 | var buffer = new byte[size]; 38 | using (var memory = new MemoryStream()) 39 | { 40 | int count; 41 | do 42 | { 43 | count = gzipStream.Read(buffer, 0, size); 44 | if (count > 0) 45 | { 46 | memory.Write(buffer, 0, count); 47 | } 48 | } 49 | while (count > 0); 50 | 51 | return memory.ToArray(); 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/Headers.cs: -------------------------------------------------------------------------------- 1 | namespace ServiceBus.CompressionPlugin 2 | { 3 | static class Headers 4 | { 5 | public const string CompressionMethodName = "compression-method"; 6 | public const string OriginalBodySize = "compression-original-size"; 7 | public const string CompressedBodySize = "compression-compressed-size"; 8 | } 9 | } -------------------------------------------------------------------------------- /src/ServiceBus.CompressionPlugin/ServiceBus.CompressionPlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Microsoft Azure ServiceBus compression plugin 5 | 1.0.0 6 | Sean Feldman 7 | netstandard2.0;net461 8 | Azure;Service Bus;ServiceBus;.NET;AMQP;IoT;Queue;Topic;Compression;Plugin 9 | https://raw.githubusercontent.com/SeanFeldman/ServiceBus.CompressionPlugin/master/images/project-icon.png 10 | https://raw.githubusercontent.com/SeanFeldman/ServiceBus.CompressionPlugin/master/LICENSE 11 | https://github.com/SeanFeldman/ServiceBus.CompressionPlugin 12 | true 13 | true 14 | false 15 | false 16 | false 17 | true 18 | True 19 | ..\..\nugets 20 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 21 | ServiceBus.CompressionPlugin 22 | ServiceBus.CompressionPlugin 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------