pax_global_header00006660000000000000000000000064146244437000014516gustar00rootroot0000000000000052 comment=4910ceaddf701482cabd28db9a30568601bc139a golang-github-rabbitmq-amqp091-go-1.10.0/000077500000000000000000000000001462444370000177145ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/.ci/000077500000000000000000000000001462444370000203655ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/.ci/install.ps1000066400000000000000000000147271462444370000224730ustar00rootroot00000000000000$ProgressPreference = 'Continue' $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 'Tls12' $versions_path = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.ci' | Join-Path -ChildPath 'versions.json' $versions = Get-Content $versions_path | ConvertFrom-Json Write-Host "[INFO] versions: $versions" $erlang_ver = $versions.erlang $rabbitmq_ver = $versions.rabbitmq $base_installers_dir = Join-Path -Path $HOME -ChildPath 'installers' if (-Not (Test-Path $base_installers_dir)) { New-Item -Verbose -ItemType Directory $base_installers_dir } $erlang_download_url = "https://github.com/erlang/otp/releases/download/OTP-$erlang_ver/otp_win64_$erlang_ver.exe" $erlang_installer_path = Join-Path -Path $base_installers_dir -ChildPath "otp_win64_$erlang_ver.exe" $erlang_install_dir = Join-Path -Path $HOME -ChildPath 'erlang' Write-Host '[INFO] Downloading Erlang...' if (-Not (Test-Path $erlang_installer_path)) { Invoke-WebRequest -UseBasicParsing -Uri $erlang_download_url -OutFile $erlang_installer_path } else { Write-Host "[INFO] Found '$erlang_installer_path' in cache!" } Write-Host "[INFO] Installing Erlang to $erlang_install_dir..." & $erlang_installer_path '/S' "/D=$erlang_install_dir" | Out-Null $rabbitmq_installer_download_url = "https://github.com/rabbitmq/rabbitmq-server/releases/download/v$rabbitmq_ver/rabbitmq-server-$rabbitmq_ver.exe" $rabbitmq_installer_path = Join-Path -Path $base_installers_dir -ChildPath "rabbitmq-server-$rabbitmq_ver.exe" Write-Host "[INFO] rabbitmq installer path $rabbitmq_installer_path" $erlang_reg_path = 'HKLM:\SOFTWARE\Ericsson\Erlang' if (Test-Path 'HKLM:\SOFTWARE\WOW6432Node\') { $erlang_reg_path = 'HKLM:\SOFTWARE\WOW6432Node\Ericsson\Erlang' } $erlang_erts_version = Get-ChildItem -Path $erlang_reg_path -Name $erlang_home = (Get-ItemProperty -LiteralPath $erlang_reg_path\$erlang_erts_version).'(default)' Write-Host "[INFO] Setting ERLANG_HOME to '$erlang_home'..." $env:ERLANG_HOME = $erlang_home [Environment]::SetEnvironmentVariable('ERLANG_HOME', $erlang_home, 'Machine') Write-Host "[INFO] Setting RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS..." $env:RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS = '-rabbitmq_stream advertised_host localhost' [Environment]::SetEnvironmentVariable('RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS', '-rabbitmq_stream advertised_host localhost', 'Machine') Write-Host '[INFO] Downloading RabbitMQ...' if (-Not (Test-Path $rabbitmq_installer_path)) { Invoke-WebRequest -UseBasicParsing -Uri $rabbitmq_installer_download_url -OutFile $rabbitmq_installer_path } else { Write-Host "[INFO] Found '$rabbitmq_installer_path' in cache!" } Write-Host "[INFO] Installer dir '$base_installers_dir' contents:" Get-ChildItem -Verbose -Path $base_installers_dir Write-Host '[INFO] Creating Erlang cookie files...' function Set-ErlangCookie { Param($Path, $Value = 'RABBITMQ-COOKIE') Remove-Item -Force $Path -ErrorAction SilentlyContinue [System.IO.File]::WriteAllText($Path, $Value, [System.Text.Encoding]::ASCII) } $erlang_cookie_user = Join-Path -Path $HOME -ChildPath '.erlang.cookie' $erlang_cookie_system = Join-Path -Path $env:SystemRoot -ChildPath 'System32\config\systemprofile\.erlang.cookie' Set-ErlangCookie -Path $erlang_cookie_user Set-ErlangCookie -Path $erlang_cookie_system Write-Host '[INFO] Installing and starting RabbitMQ with default config...' & $rabbitmq_installer_path '/S' | Out-Null (Get-Service -Name RabbitMQ).Status $rabbitmq_base_path = (Get-ItemProperty -Name Install_Dir -Path 'HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.\RabbitMQ Server').Install_Dir $regPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\RabbitMQ' if (Test-Path 'HKLM:\SOFTWARE\WOW6432Node\') { $regPath = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\RabbitMQ' } $rabbitmq_version = (Get-ItemProperty $regPath 'DisplayVersion').DisplayVersion Write-Host "[INFO] RabbitMQ version path: $rabbitmq_base_path and version: $rabbitmq_version" $rabbitmq_home = Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" Write-Host "[INFO] Setting RABBITMQ_HOME to '$rabbitmq_home'..." [Environment]::SetEnvironmentVariable('RABBITMQ_HOME', $rabbitmq_home, 'Machine') $env:RABBITMQ_HOME = $rabbitmq_home $rabbitmqctl_path = Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" | Join-Path -ChildPath 'sbin' | Join-Path -ChildPath 'rabbitmqctl.bat' $rabbitmq_plugins_path = Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" | Join-Path -ChildPath 'sbin' | Join-Path -ChildPath 'rabbitmq-plugins.bat' Write-Host "[INFO] Setting RABBITMQ_RABBITMQCTL_PATH to '$rabbitmqctl_path'..." $env:RABBITMQ_RABBITMQCTL_PATH = $rabbitmqctl_path [Environment]::SetEnvironmentVariable('RABBITMQ_RABBITMQCTL_PATH', $rabbitmqctl_path, 'Machine') $epmd_running = $false [int]$count = 1 $epmd_exe = Join-Path -Path $erlang_home -ChildPath "erts-$erlang_erts_version" | Join-Path -ChildPath 'bin' | Join-Path -ChildPath 'epmd.exe' Write-Host "[INFO] Waiting for epmd ($epmd_exe) to report that RabbitMQ has started..." Do { $epmd_running = & $epmd_exe -names | Select-String -CaseSensitive -SimpleMatch -Quiet -Pattern 'name rabbit at port' if ($epmd_running -eq $true) { Write-Host '[INFO] epmd reports that RabbitMQ is running!' break } if ($count -gt 60) { throw '[ERROR] too many tries waiting for epmd to report RabbitMQ running!' } Write-Host "[INFO] epmd NOT reporting yet that RabbitMQ is running, count: '$count'..." $count = $count + 1 Start-Sleep -Seconds 5 } While ($true) [int]$count = 1 Do { $proc_id = (Get-Process -Name erl).Id if (-Not ($proc_id -is [array])) { & $rabbitmqctl_path wait -t 300000 -P $proc_id if ($LASTEXITCODE -ne 0) { throw "[ERROR] rabbitmqctl wait returned error: $LASTEXITCODE" } break } if ($count -gt 120) { throw '[ERROR] too many tries waiting for just one erl process to be running!' } Write-Host '[INFO] multiple erl instances running still...' $count = $count + 1 Start-Sleep -Seconds 5 } While ($true) $ErrorActionPreference = 'Continue' Write-Host '[INFO] Getting RabbitMQ status...' & $rabbitmqctl_path status $ErrorActionPreference = 'Continue' Write-Host '[INFO] Enabling plugins...' & $rabbitmq_plugins_path enable rabbitmq_management rabbitmq_stream rabbitmq_stream_management rabbitmq_amqp1_0 golang-github-rabbitmq-amqp091-go-1.10.0/.ci/versions.json000066400000000000000000000000611462444370000231250ustar00rootroot00000000000000{ "erlang": "26.2.2", "rabbitmq": "3.13.0" } golang-github-rabbitmq-amqp091-go-1.10.0/.github/000077500000000000000000000000001462444370000212545ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001462444370000234375ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015021462444370000261270ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. golang-github-rabbitmq-amqp091-go-1.10.0/.github/ISSUE_TEMPLATE/release.md000066400000000000000000000004621462444370000254030ustar00rootroot00000000000000--- name: Release about: Do not use this template. This template is meant to be used by RabbitMQ maintainers. title: Release x.y.z labels: '' assignees: '' --- - [ ] Update change log using `github_changelog_generator` - [ ] Update version in `connection.go` using `change_version.sh` - [ ] Create a tag golang-github-rabbitmq-amqp091-go-1.10.0/.github/dependabot.yml000066400000000000000000000001441462444370000241030ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" golang-github-rabbitmq-amqp091-go-1.10.0/.github/workflows/000077500000000000000000000000001462444370000233115ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/.github/workflows/codeql-analysis.yml000066400000000000000000000044651462444370000271350ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ main ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] schedule: - cron: '43 5 * * 4' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 golang-github-rabbitmq-amqp091-go-1.10.0/.github/workflows/golangci-lint.yml000066400000000000000000000007211462444370000265630ustar00rootroot00000000000000name: golangci-lint on: push: branches: - main pull_request: branches: - "*" permissions: contents: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/setup-go@v5 with: go-version: 'stable' check-latest: true - uses: actions/checkout@v4 - uses: golangci/golangci-lint-action@v4 with: version: latest only-new-issues: false golang-github-rabbitmq-amqp091-go-1.10.0/.github/workflows/tests.yml000066400000000000000000000027621462444370000252050ustar00rootroot00000000000000name: amqp091-go on: push: branches: ["*"] pull_request: branches: [main] jobs: build-win32: name: build/test on windows-latest runs-on: windows-latest strategy: fail-fast: true matrix: go-version: ['oldstable', 'stable'] steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Cache installers uses: actions/cache@v3 with: # Note: the cache path is relative to the workspace directory # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action path: ~/installers key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }} - name: Install and start RabbitMQ run: ./.ci/install.ps1 - name: Tests run: go test -cpu 1,2 -race -v -tags integration build-linux: name: build/test on ubuntu-latest runs-on: ubuntu-latest strategy: matrix: go-version: ['oldstable', 'stable'] services: rabbitmq: image: rabbitmq ports: - 5672:5672 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Tests env: RABBITMQ_RABBITMQCTL_PATH: DOCKER:${{ job.services.rabbitmq.id }} run: make check-fmt tests golang-github-rabbitmq-amqp091-go-1.10.0/.gitignore000066400000000000000000000001541462444370000217040ustar00rootroot00000000000000certs/* spec/spec examples/simple-consumer/simple-consumer examples/simple-producer/simple-producer .idea/ golang-github-rabbitmq-amqp091-go-1.10.0/.golangci.yml000066400000000000000000000000451462444370000222770ustar00rootroot00000000000000run: build-tags: - integration golang-github-rabbitmq-amqp091-go-1.10.0/CHANGELOG.md000066400000000000000000000624451462444370000215400ustar00rootroot00000000000000# Changelog ## [v1.10.0](https://github.com/rabbitmq/amqp091-go/tree/v1.10.0) (2024-05-08) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.9.0...v1.10.0) **Implemented enhancements:** - Undeprecate non-context publish functions [\#259](https://github.com/rabbitmq/amqp091-go/pull/259) ([Zerpet](https://github.com/Zerpet)) - Update Go directive [\#257](https://github.com/rabbitmq/amqp091-go/pull/257) ([Zerpet](https://github.com/Zerpet)) **Fixed bugs:** - republishing on reconnect bug in the example [\#249](https://github.com/rabbitmq/amqp091-go/issues/249) - Channel Notify Close not receive event when connection is closed by RMQ server. [\#241](https://github.com/rabbitmq/amqp091-go/issues/241) - Inconsistent documentation [\#231](https://github.com/rabbitmq/amqp091-go/issues/231) - Data race in the client example [\#72](https://github.com/rabbitmq/amqp091-go/issues/72) - Fix string function of URI [\#258](https://github.com/rabbitmq/amqp091-go/pull/258) ([Zerpet](https://github.com/Zerpet)) **Closed issues:** - Documentation needed \(`PublishWithContext` does not use context\) [\#195](https://github.com/rabbitmq/amqp091-go/issues/195) - concurrent dispatch data race [\#226](https://github.com/rabbitmq/amqp091-go/issues/226) **Merged pull requests:** - Fix data race in example [\#260](https://github.com/rabbitmq/amqp091-go/pull/260) ([Zerpet](https://github.com/Zerpet)) - Address CodeQL warning [\#252](https://github.com/rabbitmq/amqp091-go/pull/252) ([lukebakken](https://github.com/lukebakken)) - Add support for additional AMQP URI query parameters [\#251](https://github.com/rabbitmq/amqp091-go/pull/251) ([vilius-g](https://github.com/vilius-g)) - Example fix [\#250](https://github.com/rabbitmq/amqp091-go/pull/250) ([Boris-Plato](https://github.com/Boris-Plato)) - Increasing the code coverage [\#248](https://github.com/rabbitmq/amqp091-go/pull/248) ([edercarloscosta](https://github.com/edercarloscosta)) - Use correct mutex to guard confirms.published [\#240](https://github.com/rabbitmq/amqp091-go/pull/240) ([hjr265](https://github.com/hjr265)) - Documenting Publishing.Expiration usage [\#232](https://github.com/rabbitmq/amqp091-go/pull/232) ([niksteff](https://github.com/niksteff)) - fix comment typo in example\_client\_test.go [\#228](https://github.com/rabbitmq/amqp091-go/pull/228) ([wisaTong](https://github.com/wisaTong)) - Bump go.uber.org/goleak from 1.2.1 to 1.3.0 [\#227](https://github.com/rabbitmq/amqp091-go/pull/227) ([dependabot[bot]](https://github.com/apps/dependabot)) ## [v1.9.0](https://github.com/rabbitmq/amqp091-go/tree/v1.9.0) (2023-10-02) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.8.1...v1.9.0) **Implemented enhancements:** - Use of buffered delivery channels when prefetch\_count is not null [\#200](https://github.com/rabbitmq/amqp091-go/issues/200) **Fixed bugs:** - connection block when write connection reset by peer [\#222](https://github.com/rabbitmq/amqp091-go/issues/222) - Test failure on 32bit architectures [\#202](https://github.com/rabbitmq/amqp091-go/issues/202) **Closed issues:** - Add a constant to set consumer timeout as queue argument [\#201](https://github.com/rabbitmq/amqp091-go/issues/201) - Add a constant for CQ version [\#199](https://github.com/rabbitmq/amqp091-go/issues/199) - Examples may need to be updated after \#140 [\#153](https://github.com/rabbitmq/amqp091-go/issues/153) **Merged pull requests:** - Update spec091.go [\#224](https://github.com/rabbitmq/amqp091-go/pull/224) ([pinkfish](https://github.com/pinkfish)) - Closes 222 [\#223](https://github.com/rabbitmq/amqp091-go/pull/223) ([yywing](https://github.com/yywing)) - Update write.go [\#221](https://github.com/rabbitmq/amqp091-go/pull/221) ([pinkfish](https://github.com/pinkfish)) - Bump versions [\#219](https://github.com/rabbitmq/amqp091-go/pull/219) ([lukebakken](https://github.com/lukebakken)) - remove extra word 'accept' from ExchangeDeclare description [\#217](https://github.com/rabbitmq/amqp091-go/pull/217) ([a-sabzian](https://github.com/a-sabzian)) - Misc Windows CI updates [\#216](https://github.com/rabbitmq/amqp091-go/pull/216) ([lukebakken](https://github.com/lukebakken)) - Stop using deprecated Publish function [\#207](https://github.com/rabbitmq/amqp091-go/pull/207) ([Zerpet](https://github.com/Zerpet)) - Constant for consumer timeout queue argument [\#206](https://github.com/rabbitmq/amqp091-go/pull/206) ([Zerpet](https://github.com/Zerpet)) - Add a constant for CQ v2 queue argument [\#205](https://github.com/rabbitmq/amqp091-go/pull/205) ([Zerpet](https://github.com/Zerpet)) - Fix example for 32-bit compatibility [\#204](https://github.com/rabbitmq/amqp091-go/pull/204) ([Zerpet](https://github.com/Zerpet)) - Fix to increase timeout milliseconds since it's too tight [\#203](https://github.com/rabbitmq/amqp091-go/pull/203) ([t2y](https://github.com/t2y)) - Add Channel.ConsumeWithContext to be able to cancel delivering [\#192](https://github.com/rabbitmq/amqp091-go/pull/192) ([t2y](https://github.com/t2y)) ## [v1.8.1](https://github.com/rabbitmq/amqp091-go/tree/v1.8.1) (2023-05-04) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.8.0...v1.8.1) **Fixed bugs:** - Fixed incorrect version reported in client properties [52ce2efd03c53dcf77d5496977da46840e9abd24](https://github.com/rabbitmq/amqp091-go/commit/52ce2efd03c53dcf77d5496977da46840e9abd24) **Merged pull requests:** - Fix Example Client not reconnecting [\#186](https://github.com/rabbitmq/amqp091-go/pull/186) ([frankfil](https://github.com/frankfil)) ## [v1.8.0](https://github.com/rabbitmq/amqp091-go/tree/v1.8.0) (2023-03-21) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.7.0...v1.8.0) **Closed issues:** - memory leak [\#179](https://github.com/rabbitmq/amqp091-go/issues/179) - the publishWithContext interface will not return when it times out [\#178](https://github.com/rabbitmq/amqp091-go/issues/178) **Merged pull requests:** - Fix race condition on confirms [\#183](https://github.com/rabbitmq/amqp091-go/pull/183) ([calloway-jacob](https://github.com/calloway-jacob)) - Add a CloseDeadline function to Connection [\#181](https://github.com/rabbitmq/amqp091-go/pull/181) ([Zerpet](https://github.com/Zerpet)) - Fix memory leaks [\#180](https://github.com/rabbitmq/amqp091-go/pull/180) ([GXKe](https://github.com/GXKe)) - Bump go.uber.org/goleak from 1.2.0 to 1.2.1 [\#177](https://github.com/rabbitmq/amqp091-go/pull/177) ([dependabot[bot]](https://github.com/apps/dependabot)) ## [v1.7.0](https://github.com/rabbitmq/amqp091-go/tree/v1.7.0) (2023-02-09) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.6.1...v1.7.0) **Closed issues:** - \#31 resurfacing \(?\) [\#170](https://github.com/rabbitmq/amqp091-go/issues/170) - Deprecate QueueInspect [\#167](https://github.com/rabbitmq/amqp091-go/issues/167) - v1.6.0 causing rabbit connection errors [\#160](https://github.com/rabbitmq/amqp091-go/issues/160) **Merged pull requests:** - Set channels and allocator to nil in shutdown [\#172](https://github.com/rabbitmq/amqp091-go/pull/172) ([lukebakken](https://github.com/lukebakken)) - Fix racing in Open [\#171](https://github.com/rabbitmq/amqp091-go/pull/171) ([Zerpet](https://github.com/Zerpet)) - adding go 1.20 to tests [\#169](https://github.com/rabbitmq/amqp091-go/pull/169) ([halilylm](https://github.com/halilylm)) - Deprecate the QueueInspect function [\#168](https://github.com/rabbitmq/amqp091-go/pull/168) ([lukebakken](https://github.com/lukebakken)) - Check if channel is nil before updating it [\#150](https://github.com/rabbitmq/amqp091-go/pull/150) ([julienschmidt](https://github.com/julienschmidt)) ## [v1.6.1](https://github.com/rabbitmq/amqp091-go/tree/v1.6.1) (2023-02-01) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.6.1-rc.2...v1.6.1) **Merged pull requests:** - Update Makefile targets related to RabbitMQ [\#163](https://github.com/rabbitmq/amqp091-go/pull/163) ([Zerpet](https://github.com/Zerpet)) ## [v1.6.1-rc.2](https://github.com/rabbitmq/amqp091-go/tree/v1.6.1-rc.2) (2023-01-31) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.6.1-rc.1...v1.6.1-rc.2) **Merged pull requests:** - Do not overly protect writes [\#162](https://github.com/rabbitmq/amqp091-go/pull/162) ([lukebakken](https://github.com/lukebakken)) ## [v1.6.1-rc.1](https://github.com/rabbitmq/amqp091-go/tree/v1.6.1-rc.1) (2023-01-31) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.6.0...v1.6.1-rc.1) **Closed issues:** - Calling Channel\(\) on an empty connection panics [\#148](https://github.com/rabbitmq/amqp091-go/issues/148) **Merged pull requests:** - Ensure flush happens and correctly lock connection for a series of unflushed writes [\#161](https://github.com/rabbitmq/amqp091-go/pull/161) ([lukebakken](https://github.com/lukebakken)) ## [v1.6.0](https://github.com/rabbitmq/amqp091-go/tree/v1.6.0) (2023-01-20) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.5.0...v1.6.0) **Implemented enhancements:** - Add constants for Queue arguments [\#145](https://github.com/rabbitmq/amqp091-go/pull/145) ([Zerpet](https://github.com/Zerpet)) **Closed issues:** - README not up to date [\#154](https://github.com/rabbitmq/amqp091-go/issues/154) - Allow re-using default connection config \(custom properties\) [\#152](https://github.com/rabbitmq/amqp091-go/issues/152) - Rename package name to amqp in V2 [\#151](https://github.com/rabbitmq/amqp091-go/issues/151) - Helper types to declare quorum queues [\#144](https://github.com/rabbitmq/amqp091-go/issues/144) - Inefficient use of buffers reduces potential throughput for basicPublish with small messages. [\#141](https://github.com/rabbitmq/amqp091-go/issues/141) - bug, close cause panic [\#130](https://github.com/rabbitmq/amqp091-go/issues/130) - Publishing Headers are unable to store Table with slice values [\#125](https://github.com/rabbitmq/amqp091-go/issues/125) - Example client can deadlock in Close due to unconsumed confirmations [\#122](https://github.com/rabbitmq/amqp091-go/issues/122) - SAC not working properly [\#106](https://github.com/rabbitmq/amqp091-go/issues/106) **Merged pull requests:** - Add automatic CHANGELOG.md generation [\#158](https://github.com/rabbitmq/amqp091-go/pull/158) ([lukebakken](https://github.com/lukebakken)) - Supply library-defined props with NewConnectionProperties [\#157](https://github.com/rabbitmq/amqp091-go/pull/157) ([slagiewka](https://github.com/slagiewka)) - Fix linter warnings [\#156](https://github.com/rabbitmq/amqp091-go/pull/156) ([Zerpet](https://github.com/Zerpet)) - Remove outdated information from README [\#155](https://github.com/rabbitmq/amqp091-go/pull/155) ([scriptcoded](https://github.com/scriptcoded)) - Add example producer using DeferredConfirm [\#149](https://github.com/rabbitmq/amqp091-go/pull/149) ([Zerpet](https://github.com/Zerpet)) - Ensure code is formatted [\#147](https://github.com/rabbitmq/amqp091-go/pull/147) ([lukebakken](https://github.com/lukebakken)) - Fix inefficient use of buffers that reduces the potential throughput of basicPublish [\#142](https://github.com/rabbitmq/amqp091-go/pull/142) ([fadams](https://github.com/fadams)) - Do not embed context in DeferredConfirmation [\#140](https://github.com/rabbitmq/amqp091-go/pull/140) ([tie](https://github.com/tie)) - Add constant for default exchange [\#139](https://github.com/rabbitmq/amqp091-go/pull/139) ([marlongerson](https://github.com/marlongerson)) - Fix indentation and remove unnecessary instructions [\#138](https://github.com/rabbitmq/amqp091-go/pull/138) ([alraujo](https://github.com/alraujo)) - Remove unnecessary instruction [\#135](https://github.com/rabbitmq/amqp091-go/pull/135) ([alraujo](https://github.com/alraujo)) - Fix example client to avoid deadlock in Close [\#123](https://github.com/rabbitmq/amqp091-go/pull/123) ([Zerpet](https://github.com/Zerpet)) - Bump go.uber.org/goleak from 1.1.12 to 1.2.0 [\#116](https://github.com/rabbitmq/amqp091-go/pull/116) ([dependabot[bot]](https://github.com/apps/dependabot)) ## [v1.5.0](https://github.com/rabbitmq/amqp091-go/tree/v1.5.0) (2022-09-07) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.4.0...v1.5.0) **Implemented enhancements:** - Provide a friendly way to set connection name [\#105](https://github.com/rabbitmq/amqp091-go/issues/105) **Closed issues:** - Support connection.update-secret [\#107](https://github.com/rabbitmq/amqp091-go/issues/107) - Example Client: Implementation of a Consumer with reconnection support [\#40](https://github.com/rabbitmq/amqp091-go/issues/40) **Merged pull requests:** - use PublishWithContext instead of Publish [\#115](https://github.com/rabbitmq/amqp091-go/pull/115) ([Gsantomaggio](https://github.com/Gsantomaggio)) - Add support for connection.update-secret [\#114](https://github.com/rabbitmq/amqp091-go/pull/114) ([Zerpet](https://github.com/Zerpet)) - Remove warning on RabbitMQ tutorials in go [\#113](https://github.com/rabbitmq/amqp091-go/pull/113) ([ChunyiLyu](https://github.com/ChunyiLyu)) - Update AMQP Spec [\#110](https://github.com/rabbitmq/amqp091-go/pull/110) ([Zerpet](https://github.com/Zerpet)) - Add an example of reliable consumer [\#109](https://github.com/rabbitmq/amqp091-go/pull/109) ([Zerpet](https://github.com/Zerpet)) - Add convenience function to set connection name [\#108](https://github.com/rabbitmq/amqp091-go/pull/108) ([Zerpet](https://github.com/Zerpet)) ## [v1.4.0](https://github.com/rabbitmq/amqp091-go/tree/v1.4.0) (2022-07-19) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.3.4...v1.4.0) **Closed issues:** - target machine actively refused connection [\#99](https://github.com/rabbitmq/amqp091-go/issues/99) - 504 channel/connection is not open error occurred in multiple connection with same rabbitmq service [\#97](https://github.com/rabbitmq/amqp091-go/issues/97) - Add possible cancel of DeferredConfirmation [\#92](https://github.com/rabbitmq/amqp091-go/issues/92) - Documentation [\#89](https://github.com/rabbitmq/amqp091-go/issues/89) - Channel Close gets stuck after closing a connection \(via management UI\) [\#88](https://github.com/rabbitmq/amqp091-go/issues/88) - this library has same issue [\#83](https://github.com/rabbitmq/amqp091-go/issues/83) - Provide a logging interface [\#81](https://github.com/rabbitmq/amqp091-go/issues/81) - 1.4.0 release checklist [\#77](https://github.com/rabbitmq/amqp091-go/issues/77) - Data race in the client example [\#72](https://github.com/rabbitmq/amqp091-go/issues/72) - reader go routine hangs and leaks when Connection.Close\(\) is called multiple times [\#69](https://github.com/rabbitmq/amqp091-go/issues/69) - Support auto-reconnect and cluster [\#65](https://github.com/rabbitmq/amqp091-go/issues/65) - Connection/Channel Deadlock [\#32](https://github.com/rabbitmq/amqp091-go/issues/32) - Closing connection and/or channel hangs NotifyPublish is used [\#21](https://github.com/rabbitmq/amqp091-go/issues/21) - Consumer channel isn't closed in the event of unexpected disconnection [\#18](https://github.com/rabbitmq/amqp091-go/issues/18) **Merged pull requests:** - fix race condition with context close and confirm at the same time on DeferredConfirmation. [\#101](https://github.com/rabbitmq/amqp091-go/pull/101) ([sapk](https://github.com/sapk)) - Add build TLS config from URI [\#98](https://github.com/rabbitmq/amqp091-go/pull/98) ([reddec](https://github.com/reddec)) - Use context for Publish methods [\#96](https://github.com/rabbitmq/amqp091-go/pull/96) ([sapk](https://github.com/sapk)) - Added function to get the remote peer's IP address \(conn.RemoteAddr\(\)\) [\#95](https://github.com/rabbitmq/amqp091-go/pull/95) ([rabb1t](https://github.com/rabb1t)) - Update connection documentation [\#90](https://github.com/rabbitmq/amqp091-go/pull/90) ([Zerpet](https://github.com/Zerpet)) - Revert test to demonstrate actual bug [\#87](https://github.com/rabbitmq/amqp091-go/pull/87) ([lukebakken](https://github.com/lukebakken)) - Minor improvements to examples [\#86](https://github.com/rabbitmq/amqp091-go/pull/86) ([lukebakken](https://github.com/lukebakken)) - Do not skip flaky test in CI [\#85](https://github.com/rabbitmq/amqp091-go/pull/85) ([lukebakken](https://github.com/lukebakken)) - Add logging [\#84](https://github.com/rabbitmq/amqp091-go/pull/84) ([lukebakken](https://github.com/lukebakken)) - Add a win32 build [\#82](https://github.com/rabbitmq/amqp091-go/pull/82) ([lukebakken](https://github.com/lukebakken)) - channel: return nothing instead of always a nil-error in receive methods [\#80](https://github.com/rabbitmq/amqp091-go/pull/80) ([fho](https://github.com/fho)) - update the contributing & readme files, improve makefile [\#79](https://github.com/rabbitmq/amqp091-go/pull/79) ([fho](https://github.com/fho)) - Fix lint errors [\#78](https://github.com/rabbitmq/amqp091-go/pull/78) ([lukebakken](https://github.com/lukebakken)) - ci: run golangci-lint [\#76](https://github.com/rabbitmq/amqp091-go/pull/76) ([fho](https://github.com/fho)) - ci: run test via make & remove travis CI config [\#75](https://github.com/rabbitmq/amqp091-go/pull/75) ([fho](https://github.com/fho)) - ci: run tests with race detector [\#74](https://github.com/rabbitmq/amqp091-go/pull/74) ([fho](https://github.com/fho)) - Detect go routine leaks in integration testcases [\#73](https://github.com/rabbitmq/amqp091-go/pull/73) ([fho](https://github.com/fho)) - connection: fix: reader go-routine is leaked on connection close [\#70](https://github.com/rabbitmq/amqp091-go/pull/70) ([fho](https://github.com/fho)) - adding best practises for NotifyPublish for issue\_21 scenario [\#68](https://github.com/rabbitmq/amqp091-go/pull/68) ([DanielePalaia](https://github.com/DanielePalaia)) - Update Go version [\#67](https://github.com/rabbitmq/amqp091-go/pull/67) ([Zerpet](https://github.com/Zerpet)) - Regenerate certs with SHA256 to fix test with Go 1.18+ [\#66](https://github.com/rabbitmq/amqp091-go/pull/66) ([anthonyfok](https://github.com/anthonyfok)) ## [v1.3.4](https://github.com/rabbitmq/amqp091-go/tree/v1.3.4) (2022-04-01) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.3.3...v1.3.4) **Merged pull requests:** - bump version to 1.3.4 [\#63](https://github.com/rabbitmq/amqp091-go/pull/63) ([DanielePalaia](https://github.com/DanielePalaia)) - updating doc [\#62](https://github.com/rabbitmq/amqp091-go/pull/62) ([DanielePalaia](https://github.com/DanielePalaia)) ## [v1.3.3](https://github.com/rabbitmq/amqp091-go/tree/v1.3.3) (2022-04-01) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.3.2...v1.3.3) **Closed issues:** - Add Client Version [\#49](https://github.com/rabbitmq/amqp091-go/issues/49) - OpenTelemetry Propagation [\#22](https://github.com/rabbitmq/amqp091-go/issues/22) **Merged pull requests:** - bump buildVersion for release [\#61](https://github.com/rabbitmq/amqp091-go/pull/61) ([DanielePalaia](https://github.com/DanielePalaia)) - adding documentation for notifyClose best pratices [\#60](https://github.com/rabbitmq/amqp091-go/pull/60) ([DanielePalaia](https://github.com/DanielePalaia)) - adding documentation on NotifyClose of connection and channel to enfo… [\#59](https://github.com/rabbitmq/amqp091-go/pull/59) ([DanielePalaia](https://github.com/DanielePalaia)) ## [v1.3.2](https://github.com/rabbitmq/amqp091-go/tree/v1.3.2) (2022-03-28) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.3.1...v1.3.2) **Closed issues:** - Potential race condition in Connection module [\#31](https://github.com/rabbitmq/amqp091-go/issues/31) **Merged pull requests:** - bump versioning to 1.3.2 [\#58](https://github.com/rabbitmq/amqp091-go/pull/58) ([DanielePalaia](https://github.com/DanielePalaia)) ## [v1.3.1](https://github.com/rabbitmq/amqp091-go/tree/v1.3.1) (2022-03-25) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.3.0...v1.3.1) **Closed issues:** - Possible deadlock on DeferredConfirmation.Wait\(\) [\#46](https://github.com/rabbitmq/amqp091-go/issues/46) - Call to Delivery.Ack blocks indefinitely in case of disconnection [\#19](https://github.com/rabbitmq/amqp091-go/issues/19) - Unexpacted behavor of channel.IsClosed\(\) [\#14](https://github.com/rabbitmq/amqp091-go/issues/14) - A possible dead lock in connection close notification Go channel [\#11](https://github.com/rabbitmq/amqp091-go/issues/11) **Merged pull requests:** - These ones were the ones testing Open scenarios. The issue is that Op… [\#57](https://github.com/rabbitmq/amqp091-go/pull/57) ([DanielePalaia](https://github.com/DanielePalaia)) - changing defaultVersion to buildVersion and create a simple change\_ve… [\#54](https://github.com/rabbitmq/amqp091-go/pull/54) ([DanielePalaia](https://github.com/DanielePalaia)) - adding integration test for issue 11 [\#50](https://github.com/rabbitmq/amqp091-go/pull/50) ([DanielePalaia](https://github.com/DanielePalaia)) - Remove the old link product [\#48](https://github.com/rabbitmq/amqp091-go/pull/48) ([Gsantomaggio](https://github.com/Gsantomaggio)) - Fix deadlock on DeferredConfirmations [\#47](https://github.com/rabbitmq/amqp091-go/pull/47) ([SpencerTorres](https://github.com/SpencerTorres)) - Example client: Rename Stream\(\) to Consume\(\) to avoid confusion with RabbitMQ streams [\#39](https://github.com/rabbitmq/amqp091-go/pull/39) ([andygrunwald](https://github.com/andygrunwald)) - Example client: Rename `name` to `queueName` to make the usage clear and explicit [\#38](https://github.com/rabbitmq/amqp091-go/pull/38) ([andygrunwald](https://github.com/andygrunwald)) - Client example: Renamed concept "Session" to "Client" [\#37](https://github.com/rabbitmq/amqp091-go/pull/37) ([andygrunwald](https://github.com/andygrunwald)) - delete unuseful code [\#36](https://github.com/rabbitmq/amqp091-go/pull/36) ([liutaot](https://github.com/liutaot)) - Client Example: Fix closing order [\#35](https://github.com/rabbitmq/amqp091-go/pull/35) ([andygrunwald](https://github.com/andygrunwald)) - Client example: Use instance logger instead of global logger [\#34](https://github.com/rabbitmq/amqp091-go/pull/34) ([andygrunwald](https://github.com/andygrunwald)) ## [v1.3.0](https://github.com/rabbitmq/amqp091-go/tree/v1.3.0) (2022-01-13) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.2.0...v1.3.0) **Closed issues:** - documentation of changes triggering version updates [\#29](https://github.com/rabbitmq/amqp091-go/issues/29) - Persistent messages folder [\#27](https://github.com/rabbitmq/amqp091-go/issues/27) **Merged pull requests:** - Expose a method to enable out-of-order Publisher Confirms [\#33](https://github.com/rabbitmq/amqp091-go/pull/33) ([benmoss](https://github.com/benmoss)) - Fix Signed 8-bit headers being treated as unsigned [\#26](https://github.com/rabbitmq/amqp091-go/pull/26) ([alex-goodisman](https://github.com/alex-goodisman)) ## [v1.2.0](https://github.com/rabbitmq/amqp091-go/tree/v1.2.0) (2021-11-17) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/v1.1.0...v1.2.0) **Closed issues:** - No access to this vhost [\#24](https://github.com/rabbitmq/amqp091-go/issues/24) - copyright issue? [\#12](https://github.com/rabbitmq/amqp091-go/issues/12) - A possible dead lock when publishing message with confirmation [\#10](https://github.com/rabbitmq/amqp091-go/issues/10) - Semver release [\#7](https://github.com/rabbitmq/amqp091-go/issues/7) **Merged pull requests:** - Fix deadlock between publishing and receiving confirms [\#25](https://github.com/rabbitmq/amqp091-go/pull/25) ([benmoss](https://github.com/benmoss)) - Add GetNextPublishSeqNo for channel in confirm mode [\#23](https://github.com/rabbitmq/amqp091-go/pull/23) ([kamal-github](https://github.com/kamal-github)) - Added support for cert-only login without user and password [\#20](https://github.com/rabbitmq/amqp091-go/pull/20) ([mihaitodor](https://github.com/mihaitodor)) ## [v1.1.0](https://github.com/rabbitmq/amqp091-go/tree/v1.1.0) (2021-09-21) [Full Changelog](https://github.com/rabbitmq/amqp091-go/compare/ebd83429aa8cb06fa569473f623e87675f96d3a9...v1.1.0) **Closed issues:** - AMQPLAIN authentication does not work [\#15](https://github.com/rabbitmq/amqp091-go/issues/15) **Merged pull requests:** - Fix AMQPLAIN authentication mechanism [\#16](https://github.com/rabbitmq/amqp091-go/pull/16) ([hodbn](https://github.com/hodbn)) - connection: clarify documented behavior of NotifyClose [\#13](https://github.com/rabbitmq/amqp091-go/pull/13) ([pabigot](https://github.com/pabigot)) - Add a link to pkg.go.dev API docs [\#9](https://github.com/rabbitmq/amqp091-go/pull/9) ([benmoss](https://github.com/benmoss)) - add test go version 1.16.x and 1.17.x [\#8](https://github.com/rabbitmq/amqp091-go/pull/8) ([k4n4ry](https://github.com/k4n4ry)) - fix typos [\#6](https://github.com/rabbitmq/amqp091-go/pull/6) ([h44z](https://github.com/h44z)) - Heartbeat interval should be timeout/2 [\#5](https://github.com/rabbitmq/amqp091-go/pull/5) ([ifo20](https://github.com/ifo20)) - Exporting Channel State [\#4](https://github.com/rabbitmq/amqp091-go/pull/4) ([eibrunorodrigues](https://github.com/eibrunorodrigues)) - Add codeql analysis [\#3](https://github.com/rabbitmq/amqp091-go/pull/3) ([MirahImage](https://github.com/MirahImage)) - Add PR github action. [\#2](https://github.com/rabbitmq/amqp091-go/pull/2) ([MirahImage](https://github.com/MirahImage)) - Update Copyright Statement [\#1](https://github.com/rabbitmq/amqp091-go/pull/1) ([rlewis24](https://github.com/rlewis24)) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* golang-github-rabbitmq-amqp091-go-1.10.0/CODE_OF_CONDUCT.md000066400000000000000000000064501462444370000225200ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in RabbitMQ Operator project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oss-coc@vmware.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq golang-github-rabbitmq-amqp091-go-1.10.0/CONTRIBUTING.md000066400000000000000000000033651462444370000221540ustar00rootroot00000000000000# Contributing ## Workflow Here is the recommended workflow: 1. Fork this repository, **github.com/rabbitmq/amqp091-go** 1. Create your feature branch (`git checkout -b my-new-feature`) 1. Run Static Checks 1. Run integration tests (see below) 1. **Implement tests** 1. Implement fixes 1. Commit your changes. Use a [good, descriptive, commit message][good-commit]. 1. Push to a branch (`git push -u origin my-new-feature`) 1. Submit a pull request [good-commit]: https://cbea.ms/git-commit/ ## Running Static Checks golangci-lint must be installed to run the static checks. See [installation docs](https://golangci-lint.run/usage/install/) for more information. The static checks can be run via: ```shell make checks ``` ## Running Tests ### Integration Tests Running the Integration tests require: * A running RabbitMQ node with all defaults: [https://www.rabbitmq.com/download.html](https://www.rabbitmq.com/download.html) * That the server is either reachable via `amqp://guest:guest@127.0.0.1:5672/` or the environment variable `AMQP_URL` set to it's URL (e.g.: `export AMQP_URL="amqp://guest:verysecretpasswd@rabbitmq-host:5772/`) The integration tests can be run via: ```shell make tests ``` Some tests require access to `rabbitmqctl` CLI. Use the environment variable `RABBITMQ_RABBITMQCTL_PATH=/some/path/to/rabbitmqctl` to run those tests. If you have Docker available in your machine, you can run: ```shell make tests-docker ``` This target will start a RabbitMQ container, run the test suite with the environment variable setup, and stop RabbitMQ container after a successful run. All integration tests should use the `integrationConnection(...)` test helpers defined in `integration_test.go` to setup the integration environment and logging. golang-github-rabbitmq-amqp091-go-1.10.0/LICENSE000066400000000000000000000025451462444370000207270ustar00rootroot00000000000000AMQP 0-9-1 Go Client Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-github-rabbitmq-amqp091-go-1.10.0/Makefile000066400000000000000000000040261462444370000213560ustar00rootroot00000000000000.DEFAULT_GOAL := list # Insert a comment starting with '##' after a target, and it will be printed by 'make' and 'make list' .PHONY: list list: ## list Makefile targets @echo "The most used targets: \n" @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' .PHONY: check-fmt check-fmt: ## Ensure code is formatted gofmt -l -d . # For the sake of debugging test -z "$$(gofmt -l .)" .PHONY: fmt fmt: ## Run go fmt against code go fmt ./... .PHONY: tests tests: ## Run all tests and requires a running rabbitmq-server. Use GO_TEST_FLAGS to add extra flags to go test go test -race -v -tags integration $(GO_TEST_FLAGS) .PHONY: tests-docker tests-docker: rabbitmq-server RABBITMQ_RABBITMQCTL_PATH="DOCKER:$(CONTAINER_NAME)" go test -race -v -tags integration $(GO_TEST_FLAGS) $(MAKE) stop-rabbitmq-server .PHONY: check check: golangci-lint run ./... CONTAINER_NAME ?= amqp091-go-rabbitmq .PHONY: rabbitmq-server rabbitmq-server: ## Start a RabbitMQ server using Docker. Container name can be customised with CONTAINER_NAME=some-rabbit docker run --detach --rm --name $(CONTAINER_NAME) \ --publish 5672:5672 --publish 15672:15672 \ --pull always rabbitmq:3-management .PHONY: stop-rabbitmq-server stop-rabbitmq-server: ## Stop a RabbitMQ server using Docker. Container name can be customised with CONTAINER_NAME=some-rabbit docker stop $(CONTAINER_NAME) certs: ./certs.sh .PHONY: certs-rm certs-rm: rm -r ./certs/ .PHONY: rabbitmq-server-tls rabbitmq-server-tls: | certs ## Start a RabbitMQ server using Docker. Container name can be customised with CONTAINER_NAME=some-rabbit docker run --detach --rm --name $(CONTAINER_NAME) \ --publish 5672:5672 --publish 5671:5671 --publish 15672:15672 \ --mount type=bind,src=./certs/server,dst=/certs \ --mount type=bind,src=./certs/ca/cacert.pem,dst=/certs/cacert.pem,readonly \ --mount type=bind,src=./rabbitmq-confs/tls/90-tls.conf,dst=/etc/rabbitmq/conf.d/90-tls.conf \ --pull always rabbitmq:3-management golang-github-rabbitmq-amqp091-go-1.10.0/README.md000066400000000000000000000104051462444370000211730ustar00rootroot00000000000000# Go RabbitMQ Client Library [![amqp091-go](https://github.com/rabbitmq/amqp091-go/actions/workflows/tests.yml/badge.svg)](https://github.com/rabbitmq/amqp091-go/actions/workflows/tests.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/rabbitmq/amqp091-go.svg)](https://pkg.go.dev/github.com/rabbitmq/amqp091-go) [![Go Report Card](https://goreportcard.com/badge/github.com/rabbitmq/amqp091-go)](https://goreportcard.com/report/github.com/rabbitmq/amqp091-go) This is a Go AMQP 0.9.1 client maintained by the [RabbitMQ core team](https://github.com/rabbitmq). It was [originally developed by Sean Treadway](https://github.com/streadway/amqp). ## Differences from streadway/amqp Some things are different compared to the original client, others haven't changed. ### Package Name This library uses a different package name. If moving from `streadway/amqp`, using an alias may reduce the number of changes needed: ``` go amqp "github.com/rabbitmq/amqp091-go" ``` ### License This client uses the same 2-clause BSD license as the original project. ### Public API Evolution This client retains key API elements as practically possible. It is, however, open to reasonable breaking public API changes suggested by the community. We don't have the "no breaking public API changes ever" rule and fully recognize that a good client API evolves over time. ## Project Maturity This project is based on a mature Go client that's been around for over a decade. ## Supported Go Versions This client supports two most recent Go release series. ## Supported RabbitMQ Versions This project supports RabbitMQ versions starting with `2.0` but primarily tested against [currently supported RabbitMQ release series](https://www.rabbitmq.com/versions.html). Some features and behaviours may be server version-specific. ## Goals Provide a functional interface that closely represents the AMQP 0.9.1 model targeted to RabbitMQ as a server. This includes the minimum necessary to interact the semantics of the protocol. ## Non-goals Things not intended to be supported. * Auto reconnect and re-synchronization of client and server topologies. * Reconnection would require understanding the error paths when the topology cannot be declared on reconnect. This would require a new set of types and code paths that are best suited at the call-site of this package. AMQP has a dynamic topology that needs all peers to agree. If this doesn't happen, the behavior is undefined. Instead of producing a possible interface with undefined behavior, this package is designed to be simple for the caller to implement the necessary connection-time topology declaration so that reconnection is trivial and encapsulated in the caller's application code. * AMQP Protocol negotiation for forward or backward compatibility. * 0.9.1 is stable and widely deployed. AMQP 1.0 is a divergent specification (a different protocol) and belongs to a different library. * Anything other than PLAIN and EXTERNAL authentication mechanisms. * Keeping the mechanisms interface modular makes it possible to extend outside of this package. If other mechanisms prove to be popular, then we would accept patches to include them in this package. * Support for [`basic.return` and `basic.ack` frame ordering](https://www.rabbitmq.com/confirms.html#when-publishes-are-confirmed). This client uses Go channels for certain protocol events and ordering between events sent to two different channels generally cannot be guaranteed. ## Usage See the [_examples](_examples) subdirectory for simple producers and consumers executables. If you have a use-case in mind which isn't well-represented by the examples, please file an issue. ## Documentation * [Godoc API reference](http://godoc.org/github.com/rabbitmq/amqp091-go) * [RabbitMQ tutorials in Go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) ## Contributing Pull requests are very much welcomed. Create your pull request on a non-main branch, make sure a test or example is included that covers your change, and your commits represent coherent changes that include a reason for the change. See [CONTRIBUTING.md](CONTRIBUTING.md) for more information. ## License BSD 2 clause, see LICENSE for more details. golang-github-rabbitmq-amqp091-go-1.10.0/RELEASE.md000066400000000000000000000021561462444370000213220ustar00rootroot00000000000000# Guide to release a new version 1. Update the `buildVersion` constant in [connection.go](https://github.com/rabbitmq/amqp091-go/blob/4886c35d10b273bd374e3ed2356144ad41d27940/connection.go#L31) 2. Commit and push. Include the version in the commit message e.g. [this commit](https://github.com/rabbitmq/amqp091-go/commit/52ce2efd03c53dcf77d5496977da46840e9abd24) 3. Create a new [GitHub Release](https://github.com/rabbitmq/amqp091-go/releases). Create a new tag as `v..` 1. Use auto-generate release notes feature in GitHub 4. Generate the change log, see [Changelog Generation](#changelog-generation) 5. Review the changelog. Watch out for issues closed as "not-fixed" or without a PR 6. Commit and Push. Pro-tip: include `[skip ci]` in the commit message to skip the CI run, since it's only documentation 7. Send an announcement to the mailing list. Take inspiration from [this message](https://groups.google.com/g/rabbitmq-users/c/EBGYGOWiSgs/m/0sSFuAGICwAJ) ## Changelog Generation ``` github_changelog_generator --token GITHUB-TOKEN -u rabbitmq -p amqp091-go --no-unreleased --release-branch main ``` golang-github-rabbitmq-amqp091-go-1.10.0/_examples/000077500000000000000000000000001462444370000216715ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/_examples/client/000077500000000000000000000000001462444370000231475ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/_examples/client/client.go000066400000000000000000000255741462444370000247710ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "context" "errors" "log" "os" "sync" "time" amqp "github.com/rabbitmq/amqp091-go" ) // This exports a Client object that wraps this library. It // automatically reconnects when the connection fails, and // blocks all pushes until the connection succeeds. It also // confirms every outgoing message, so none are lost. // It doesn't automatically ack each message, but leaves that // to the parent process, since it is usage-dependent. // // Try running this in one terminal, and rabbitmq-server in another. // // Stop & restart RabbitMQ to see how the queue reacts. func publish(done chan struct{}) { queueName := "job_queue" addr := "amqp://guest:guest@localhost:5672/" queue := New(queueName, addr) message := []byte("message") ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*20)) defer cancel() loop: for { select { // Attempt to push a message every 2 seconds case <-time.After(time.Second * 2): if err := queue.Push(message); err != nil { queue.errlog.Printf("push failed: %s\n", err) } else { queue.infolog.Println("push succeeded") } case <-ctx.Done(): if err := queue.Close(); err != nil { queue.errlog.Printf("close failed: %s\n", err) } break loop } } close(done) } func consume(done chan struct{}) { queueName := "job_queue" addr := "amqp://guest:guest@localhost:5672/" queue := New(queueName, addr) // Give the connection sometime to set up <-time.After(time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second*25) defer cancel() deliveries, err := queue.Consume() if err != nil { queue.errlog.Printf("could not start consuming: %s\n", err) return } // This channel will receive a notification when a channel closed event // happens. This must be different from Client.notifyChanClose because the // library sends only one notification and Client.notifyChanClose already has // a receiver in handleReconnect(). // Recommended to make it buffered to avoid deadlocks chClosedCh := make(chan *amqp.Error, 1) queue.channel.NotifyClose(chClosedCh) loop: for { select { case <-ctx.Done(): err := queue.Close() if err != nil { queue.errlog.Printf("close failed: %s\n", err) } break loop case amqErr := <-chClosedCh: // This case handles the event of closed channel e.g. abnormal shutdown queue.errlog.Printf("AMQP Channel closed due to: %s\n", amqErr) deliveries, err = queue.Consume() if err != nil { // If the AMQP channel is not ready, it will continue the loop. Next // iteration will enter this case because chClosedCh is closed by the // library queue.errlog.Println("error trying to consume, will try again") continue } // Re-set channel to receive notifications // The library closes this channel after abnormal shutdown chClosedCh = make(chan *amqp.Error, 1) queue.channel.NotifyClose(chClosedCh) case delivery := <-deliveries: // Ack a message every 2 seconds queue.infolog.Printf("received message: %s\n", delivery.Body) if err := delivery.Ack(false); err != nil { queue.errlog.Printf("error acknowledging message: %s\n", err) } <-time.After(time.Second * 2) } } close(done) } // Client is the base struct for handling connection recovery, consumption and // publishing. Note that this struct has an internal mutex to safeguard against // data races. As you develop and iterate over this example, you may need to add // further locks, or safeguards, to keep your application safe from data races type Client struct { m *sync.Mutex queueName string infolog *log.Logger errlog *log.Logger connection *amqp.Connection channel *amqp.Channel done chan bool notifyConnClose chan *amqp.Error notifyChanClose chan *amqp.Error notifyConfirm chan amqp.Confirmation isReady bool } const ( // When reconnecting to the server after connection failure reconnectDelay = 5 * time.Second // When setting up the channel after a channel exception reInitDelay = 2 * time.Second // When resending messages the server didn't confirm resendDelay = 5 * time.Second ) var ( errNotConnected = errors.New("not connected to a server") errAlreadyClosed = errors.New("already closed: not connected to the server") errShutdown = errors.New("client is shutting down") ) // New creates a new consumer state instance, and automatically // attempts to connect to the server. func New(queueName, addr string) *Client { client := Client{ m: &sync.Mutex{}, infolog: log.New(os.Stdout, "[INFO] ", log.LstdFlags|log.Lmsgprefix), errlog: log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lmsgprefix), queueName: queueName, done: make(chan bool), } go client.handleReconnect(addr) return &client } // handleReconnect will wait for a connection error on // notifyConnClose, and then continuously attempt to reconnect. func (client *Client) handleReconnect(addr string) { for { client.m.Lock() client.isReady = false client.m.Unlock() client.infolog.Println("attempting to connect") conn, err := client.connect(addr) if err != nil { client.errlog.Println("failed to connect. Retrying...") select { case <-client.done: return case <-time.After(reconnectDelay): } continue } if done := client.handleReInit(conn); done { break } } } // connect will create a new AMQP connection func (client *Client) connect(addr string) (*amqp.Connection, error) { conn, err := amqp.Dial(addr) if err != nil { return nil, err } client.changeConnection(conn) client.infolog.Println("connected") return conn, nil } // handleReInit will wait for a channel error // and then continuously attempt to re-initialize both channels func (client *Client) handleReInit(conn *amqp.Connection) bool { for { client.m.Lock() client.isReady = false client.m.Unlock() err := client.init(conn) if err != nil { client.errlog.Println("failed to initialize channel, retrying...") select { case <-client.done: return true case <-client.notifyConnClose: client.infolog.Println("connection closed, reconnecting...") return false case <-time.After(reInitDelay): } continue } select { case <-client.done: return true case <-client.notifyConnClose: client.infolog.Println("connection closed, reconnecting...") return false case <-client.notifyChanClose: client.infolog.Println("channel closed, re-running init...") } } } // init will initialize channel & declare queue func (client *Client) init(conn *amqp.Connection) error { ch, err := conn.Channel() if err != nil { return err } err = ch.Confirm(false) if err != nil { return err } _, err = ch.QueueDeclare( client.queueName, false, // Durable false, // Delete when unused false, // Exclusive false, // No-wait nil, // Arguments ) if err != nil { return err } client.changeChannel(ch) client.m.Lock() client.isReady = true client.m.Unlock() client.infolog.Println("client init done") return nil } // changeConnection takes a new connection to the queue, // and updates the close listener to reflect this. func (client *Client) changeConnection(connection *amqp.Connection) { client.connection = connection client.notifyConnClose = make(chan *amqp.Error, 1) client.connection.NotifyClose(client.notifyConnClose) } // changeChannel takes a new channel to the queue, // and updates the channel listeners to reflect this. func (client *Client) changeChannel(channel *amqp.Channel) { client.channel = channel client.notifyChanClose = make(chan *amqp.Error, 1) client.notifyConfirm = make(chan amqp.Confirmation, 1) client.channel.NotifyClose(client.notifyChanClose) client.channel.NotifyPublish(client.notifyConfirm) } // Push will push data onto the queue, and wait for a confirmation. // This will block until the server sends a confirmation. Errors are // only returned if the push action itself fails, see UnsafePush. func (client *Client) Push(data []byte) error { client.m.Lock() if !client.isReady { client.m.Unlock() return errors.New("failed to push: not connected") } client.m.Unlock() for { err := client.UnsafePush(data) if err != nil { client.errlog.Println("push failed. Retrying...") select { case <-client.done: return errShutdown case <-time.After(resendDelay): } continue } confirm := <-client.notifyConfirm if confirm.Ack { client.infolog.Printf("push confirmed [%d]", confirm.DeliveryTag) return nil } } } // UnsafePush will push to the queue without checking for // confirmation. It returns an error if it fails to connect. // No guarantees are provided for whether the server will // receive the message. func (client *Client) UnsafePush(data []byte) error { client.m.Lock() if !client.isReady { client.m.Unlock() return errNotConnected } client.m.Unlock() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return client.channel.PublishWithContext( ctx, "", // Exchange client.queueName, // Routing key false, // Mandatory false, // Immediate amqp.Publishing{ ContentType: "text/plain", Body: data, }, ) } // Consume will continuously put queue items on the channel. // It is required to call delivery.Ack when it has been // successfully processed, or delivery.Nack when it fails. // Ignoring this will cause data to build up on the server. func (client *Client) Consume() (<-chan amqp.Delivery, error) { client.m.Lock() if !client.isReady { client.m.Unlock() return nil, errNotConnected } client.m.Unlock() if err := client.channel.Qos( 1, // prefetchCount 0, // prefetchSize false, // global ); err != nil { return nil, err } return client.channel.Consume( client.queueName, "", // Consumer false, // Auto-Ack false, // Exclusive false, // No-local false, // No-Wait nil, // Args ) } // Close will cleanly shut down the channel and connection. func (client *Client) Close() error { client.m.Lock() // we read and write isReady in two locations, so we grab the lock and hold onto // it until we are finished defer client.m.Unlock() if !client.isReady { return errAlreadyClosed } close(client.done) err := client.channel.Close() if err != nil { return err } err = client.connection.Close() if err != nil { return err } client.isReady = false return nil } func main() { publishDone := make(chan struct{}) consumeDone := make(chan struct{}) go publish(publishDone) go consume(consumeDone) select { case <-publishDone: log.Println("publishing is done") } select { case <-consumeDone: log.Println("consuming is done") } log.Println("exiting") } golang-github-rabbitmq-amqp091-go-1.10.0/_examples/consumer/000077500000000000000000000000001462444370000235245ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/_examples/consumer/consumer.go000066400000000000000000000131071462444370000257100ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // This example declares a durable Exchange, an ephemeral (auto-delete) Queue, // binds the Queue to the Exchange with a binding key, and consumes every // message published to that Exchange with that routing key. package main import ( "flag" "fmt" "log" "os" "os/signal" "syscall" "time" amqp "github.com/rabbitmq/amqp091-go" ) var ( uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") exchange = flag.String("exchange", "test-exchange", "Durable, non-auto-deleted AMQP exchange name") exchangeType = flag.String("exchange-type", "direct", "Exchange type - direct|fanout|topic|x-custom") queue = flag.String("queue", "test-queue", "Ephemeral AMQP queue name") bindingKey = flag.String("key", "test-key", "AMQP binding key") consumerTag = flag.String("consumer-tag", "simple-consumer", "AMQP consumer tag (should not be blank)") lifetime = flag.Duration("lifetime", 5*time.Second, "lifetime of process before shutdown (0s=infinite)") verbose = flag.Bool("verbose", true, "enable verbose output of message data") autoAck = flag.Bool("auto_ack", false, "enable message auto-ack") ErrLog = log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lmsgprefix) Log = log.New(os.Stdout, "[INFO] ", log.LstdFlags|log.Lmsgprefix) deliveryCount int = 0 ) func init() { flag.Parse() } func main() { c, err := NewConsumer(*uri, *exchange, *exchangeType, *queue, *bindingKey, *consumerTag) if err != nil { ErrLog.Fatalf("%s", err) } SetupCloseHandler(c) if *lifetime > 0 { Log.Printf("running for %s", *lifetime) time.Sleep(*lifetime) } else { Log.Printf("running until Consumer is done") <-c.done } Log.Printf("shutting down") if err := c.Shutdown(); err != nil { ErrLog.Fatalf("error during shutdown: %s", err) } } type Consumer struct { conn *amqp.Connection channel *amqp.Channel tag string done chan error } func SetupCloseHandler(consumer *Consumer) { c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c Log.Printf("Ctrl+C pressed in Terminal") if err := consumer.Shutdown(); err != nil { ErrLog.Fatalf("error during shutdown: %s", err) } os.Exit(0) }() } func NewConsumer(amqpURI, exchange, exchangeType, queueName, key, ctag string) (*Consumer, error) { c := &Consumer{ conn: nil, channel: nil, tag: ctag, done: make(chan error), } var err error config := amqp.Config{Properties: amqp.NewConnectionProperties()} config.Properties.SetClientConnectionName("sample-consumer") Log.Printf("dialing %q", amqpURI) c.conn, err = amqp.DialConfig(amqpURI, config) if err != nil { return nil, fmt.Errorf("Dial: %s", err) } go func() { Log.Printf("closing: %s", <-c.conn.NotifyClose(make(chan *amqp.Error))) }() Log.Printf("got Connection, getting Channel") c.channel, err = c.conn.Channel() if err != nil { return nil, fmt.Errorf("Channel: %s", err) } Log.Printf("got Channel, declaring Exchange (%q)", exchange) if err = c.channel.ExchangeDeclare( exchange, // name of the exchange exchangeType, // type true, // durable false, // delete when complete false, // internal false, // noWait nil, // arguments ); err != nil { return nil, fmt.Errorf("Exchange Declare: %s", err) } Log.Printf("declared Exchange, declaring Queue %q", queueName) queue, err := c.channel.QueueDeclare( queueName, // name of the queue true, // durable false, // delete when unused false, // exclusive false, // noWait nil, // arguments ) if err != nil { return nil, fmt.Errorf("Queue Declare: %s", err) } Log.Printf("declared Queue (%q %d messages, %d consumers), binding to Exchange (key %q)", queue.Name, queue.Messages, queue.Consumers, key) if err = c.channel.QueueBind( queue.Name, // name of the queue key, // bindingKey exchange, // sourceExchange false, // noWait nil, // arguments ); err != nil { return nil, fmt.Errorf("Queue Bind: %s", err) } Log.Printf("Queue bound to Exchange, starting Consume (consumer tag %q)", c.tag) deliveries, err := c.channel.Consume( queue.Name, // name c.tag, // consumerTag, *autoAck, // autoAck false, // exclusive false, // noLocal false, // noWait nil, // arguments ) if err != nil { return nil, fmt.Errorf("Queue Consume: %s", err) } go handle(deliveries, c.done) return c, nil } func (c *Consumer) Shutdown() error { // will close() the deliveries channel if err := c.channel.Cancel(c.tag, true); err != nil { return fmt.Errorf("Consumer cancel failed: %s", err) } if err := c.conn.Close(); err != nil { return fmt.Errorf("AMQP connection close error: %s", err) } defer Log.Printf("AMQP shutdown OK") // wait for handle() to exit return <-c.done } func handle(deliveries <-chan amqp.Delivery, done chan error) { cleanup := func() { Log.Printf("handle: deliveries channel closed") done <- nil } defer cleanup() for d := range deliveries { deliveryCount++ if *verbose == true { Log.Printf( "got %dB delivery: [%v] %q", len(d.Body), d.DeliveryTag, d.Body, ) } else { if deliveryCount%65536 == 0 { Log.Printf("delivery count %d", deliveryCount) } } if *autoAck == false { d.Ack(false) } } } golang-github-rabbitmq-amqp091-go-1.10.0/_examples/producer/000077500000000000000000000000001462444370000235145ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/_examples/producer/producer.go000066400000000000000000000175521462444370000257000ustar00rootroot00000000000000// This example declares a durable exchange, and publishes one messages to that // exchange. This example allows up to 8 outstanding publisher confirmations // before blocking publishing. package main import ( "flag" amqp "github.com/rabbitmq/amqp091-go" "log" "os" "os/signal" "syscall" "time" ) var ( uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") exchange = flag.String("exchange", "test-exchange", "Durable AMQP exchange name") exchangeType = flag.String("exchange-type", "direct", "Exchange type - direct|fanout|topic|x-custom") queue = flag.String("queue", "test-queue", "Ephemeral AMQP queue name") routingKey = flag.String("key", "test-key", "AMQP routing key") body = flag.String("body", "foobar", "Body of message") continuous = flag.Bool("continuous", false, "Keep publishing messages at a 1msg/sec rate") WarnLog = log.New(os.Stderr, "[WARNING] ", log.LstdFlags|log.Lmsgprefix) ErrLog = log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lmsgprefix) Log = log.New(os.Stdout, "[INFO] ", log.LstdFlags|log.Lmsgprefix) ) func init() { flag.Parse() } func main() { exitCh := make(chan struct{}) confirmsCh := make(chan *amqp.DeferredConfirmation) confirmsDoneCh := make(chan struct{}) // Note: this is a buffered channel so that indicating OK to // publish does not block the confirm handler publishOkCh := make(chan struct{}, 1) setupCloseHandler(exitCh) startConfirmHandler(publishOkCh, confirmsCh, confirmsDoneCh, exitCh) publish(publishOkCh, confirmsCh, confirmsDoneCh, exitCh) } func setupCloseHandler(exitCh chan struct{}) { c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c Log.Printf("close handler: Ctrl+C pressed in Terminal") close(exitCh) }() } func publish(publishOkCh <-chan struct{}, confirmsCh chan<- *amqp.DeferredConfirmation, confirmsDoneCh <-chan struct{}, exitCh chan struct{}) { config := amqp.Config{ Vhost: "/", Properties: amqp.NewConnectionProperties(), } config.Properties.SetClientConnectionName("producer-with-confirms") Log.Printf("producer: dialing %s", *uri) conn, err := amqp.DialConfig(*uri, config) if err != nil { ErrLog.Fatalf("producer: error in dial: %s", err) } defer conn.Close() Log.Println("producer: got Connection, getting Channel") channel, err := conn.Channel() if err != nil { ErrLog.Fatalf("error getting a channel: %s", err) } defer channel.Close() Log.Printf("producer: declaring exchange") if err := channel.ExchangeDeclare( *exchange, // name *exchangeType, // type true, // durable false, // auto-delete false, // internal false, // noWait nil, // arguments ); err != nil { ErrLog.Fatalf("producer: Exchange Declare: %s", err) } Log.Printf("producer: declaring queue '%s'", *queue) queue, err := channel.QueueDeclare( *queue, // name of the queue true, // durable false, // delete when unused false, // exclusive false, // noWait nil, // arguments ) if err == nil { Log.Printf("producer: declared queue (%q %d messages, %d consumers), binding to Exchange (key %q)", queue.Name, queue.Messages, queue.Consumers, *routingKey) } else { ErrLog.Fatalf("producer: Queue Declare: %s", err) } Log.Printf("producer: declaring binding") if err := channel.QueueBind(queue.Name, *routingKey, *exchange, false, nil); err != nil { ErrLog.Fatalf("producer: Queue Bind: %s", err) } // Reliable publisher confirms require confirm.select support from the // connection. Log.Printf("producer: enabling publisher confirms.") if err := channel.Confirm(false); err != nil { ErrLog.Fatalf("producer: channel could not be put into confirm mode: %s", err) } for { canPublish := false Log.Println("producer: waiting on the OK to publish...") for { select { case <-confirmsDoneCh: Log.Println("producer: stopping, all confirms seen") return case <-publishOkCh: Log.Println("producer: got the OK to publish") canPublish = true break case <-time.After(time.Second): WarnLog.Println("producer: still waiting on the OK to publish...") continue } if canPublish { break } } Log.Printf("producer: publishing %dB body (%q)", len(*body), *body) dConfirmation, err := channel.PublishWithDeferredConfirm( *exchange, *routingKey, true, false, amqp.Publishing{ Headers: amqp.Table{}, ContentType: "text/plain", ContentEncoding: "", DeliveryMode: amqp.Persistent, Priority: 0, AppId: "sequential-producer", Body: []byte(*body), }, ) if err != nil { ErrLog.Fatalf("producer: error in publish: %s", err) } select { case <-confirmsDoneCh: Log.Println("producer: stopping, all confirms seen") return case confirmsCh <- dConfirmation: Log.Println("producer: delivered deferred confirm to handler") break } select { case <-confirmsDoneCh: Log.Println("producer: stopping, all confirms seen") return case <-time.After(time.Millisecond * 250): if *continuous { continue } else { Log.Println("producer: initiating stop") close(exitCh) select { case <-confirmsDoneCh: Log.Println("producer: stopping, all confirms seen") return case <-time.After(time.Second * 10): WarnLog.Println("producer: may be stopping with outstanding confirmations") return } } } } } func startConfirmHandler(publishOkCh chan<- struct{}, confirmsCh <-chan *amqp.DeferredConfirmation, confirmsDoneCh chan struct{}, exitCh <-chan struct{}) { go func() { confirms := make(map[uint64]*amqp.DeferredConfirmation) for { select { case <-exitCh: exitConfirmHandler(confirms, confirmsDoneCh) return default: break } outstandingConfirmationCount := len(confirms) // Note: 8 is arbitrary, you may wish to allow more outstanding confirms before blocking publish if outstandingConfirmationCount <= 8 { select { case publishOkCh <- struct{}{}: Log.Println("confirm handler: sent OK to publish") case <-time.After(time.Second * 5): WarnLog.Println("confirm handler: timeout indicating OK to publish (this should never happen!)") } } else { WarnLog.Printf("confirm handler: waiting on %d outstanding confirmations, blocking publish", outstandingConfirmationCount) } select { case confirmation := <-confirmsCh: dtag := confirmation.DeliveryTag confirms[dtag] = confirmation case <-exitCh: exitConfirmHandler(confirms, confirmsDoneCh) return } checkConfirmations(confirms) } }() } func exitConfirmHandler(confirms map[uint64]*amqp.DeferredConfirmation, confirmsDoneCh chan struct{}) { Log.Println("confirm handler: exit requested") waitConfirmations(confirms) close(confirmsDoneCh) Log.Println("confirm handler: exiting") } func checkConfirmations(confirms map[uint64]*amqp.DeferredConfirmation) { Log.Printf("confirm handler: checking %d outstanding confirmations", len(confirms)) for k, v := range confirms { if v.Acked() { Log.Printf("confirm handler: confirmed delivery with tag: %d", k) delete(confirms, k) } } } func waitConfirmations(confirms map[uint64]*amqp.DeferredConfirmation) { Log.Printf("confirm handler: waiting on %d outstanding confirmations", len(confirms)) checkConfirmations(confirms) for k, v := range confirms { select { case <-v.Done(): Log.Printf("confirm handler: confirmed delivery with tag: %d", k) delete(confirms, k) case <-time.After(time.Second): WarnLog.Printf("confirm handler: did not receive confirmation for tag %d", k) } } outstandingConfirmationCount := len(confirms) if outstandingConfirmationCount > 0 { ErrLog.Printf("confirm handler: exiting with %d outstanding confirmations", outstandingConfirmationCount) } else { Log.Println("confirm handler: done waiting on outstanding confirmations") } } golang-github-rabbitmq-amqp091-go-1.10.0/_examples/pubsub/000077500000000000000000000000001462444370000231715ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/_examples/pubsub/pubsub.go000066400000000000000000000127651462444370000250330ustar00rootroot00000000000000// Command pubsub is an example of a fanout exchange with dynamic reliable // membership, reading from stdin, writing to stdout. // // This example shows how to implement reconnect logic independent from a // publish/subscribe loop with bridges to application types. package main import ( "bufio" "context" "crypto/sha1" "flag" "fmt" amqp "github.com/rabbitmq/amqp091-go" "io" "log" "os" ) var url = flag.String("url", "amqp:///", "AMQP url for both the publisher and subscriber") // exchange binds the publishers to the subscribers const exchange = "pubsub" // message is the application type for a message. This can contain identity, // or a reference to the receiver chan for further demuxing. type message []byte // session composes an amqp.Connection with an amqp.Channel type session struct { *amqp.Connection *amqp.Channel } // Close tears the connection down, taking the channel with it. func (s session) Close() error { if s.Connection == nil { return nil } return s.Connection.Close() } // redial continually connects to the URL, exiting the program when no longer possible func redial(ctx context.Context, url string) chan chan session { sessions := make(chan chan session) go func() { sess := make(chan session) defer close(sessions) for { select { case sessions <- sess: case <-ctx.Done(): log.Println("shutting down session factory") return } conn, err := amqp.Dial(url) if err != nil { log.Fatalf("cannot (re)dial: %v: %q", err, url) } ch, err := conn.Channel() if err != nil { log.Fatalf("cannot create channel: %v", err) } if err := ch.ExchangeDeclare(exchange, "fanout", false, true, false, false, nil); err != nil { log.Fatalf("cannot declare fanout exchange: %v", err) } select { case sess <- session{conn, ch}: case <-ctx.Done(): log.Println("shutting down new session") return } } }() return sessions } // publish publishes messages to a reconnecting session to a fanout exchange. // It receives from the application specific source of messages. func publish(sessions chan chan session, messages <-chan message) { pending := make(chan message, 1) for session := range sessions { var ( running bool reading = messages confirm = make(chan amqp.Confirmation, 1) ) pub := <-session // publisher confirms for this channel/connection if err := pub.Confirm(false); err != nil { log.Printf("publisher confirms not supported") close(confirm) // confirms not supported, simulate by always nacking } else { pub.NotifyPublish(confirm) } log.Printf("publishing...") Publish: for { var body message select { case confirmed, ok := <-confirm: if !ok { pub.Close() break Publish } if !confirmed.Ack { log.Printf("nack message %d, body: %q", confirmed.DeliveryTag, string(body)) } reading = messages case body = <-pending: routingKey := "ignored for fanout exchanges, application dependent for other exchanges" err := pub.Publish(exchange, routingKey, false, false, amqp.Publishing{ Body: body, }) // Retry failed delivery on the next session if err != nil { pending <- body pub.Close() break Publish } case body, running = <-reading: // all messages consumed if !running { return } // work on pending delivery until ack'd pending <- body reading = nil } } } } // identity returns the same host/process unique string for the lifetime of // this process so that subscriber reconnections reuse the same queue name. func identity() string { hostname, err := os.Hostname() h := sha1.New() fmt.Fprint(h, hostname) fmt.Fprint(h, err) fmt.Fprint(h, os.Getpid()) return fmt.Sprintf("%x", h.Sum(nil)) } // subscribe consumes deliveries from an exclusive queue from a fanout exchange and sends to the application specific messages chan. func subscribe(sessions chan chan session, messages chan<- message) { queue := identity() for session := range sessions { sub := <-session if _, err := sub.QueueDeclare(queue, false, true, true, false, nil); err != nil { log.Printf("cannot consume from exclusive queue: %q, %v", queue, err) return } routingKey := "application specific routing key for fancy topologies" if err := sub.QueueBind(queue, routingKey, exchange, false, nil); err != nil { log.Printf("cannot consume without a binding to exchange: %q, %v", exchange, err) return } deliveries, err := sub.Consume(queue, "", false, true, false, false, nil) if err != nil { log.Printf("cannot consume from: %q, %v", queue, err) return } log.Printf("subscribed...") for msg := range deliveries { messages <- msg.Body sub.Ack(msg.DeliveryTag, false) } sub.Close() } } // read is this application's translation to the message format, scanning from // stdin. func read(r io.Reader) <-chan message { lines := make(chan message) go func() { defer close(lines) scan := bufio.NewScanner(r) for scan.Scan() { lines <- scan.Bytes() } }() return lines } // write is this application's subscriber of application messages, printing to // stdout. func write(w io.Writer) chan<- message { lines := make(chan message) go func() { for line := range lines { fmt.Fprintln(w, string(line)) } }() return lines } func main() { flag.Parse() ctx, done := context.WithCancel(context.Background()) go func() { publish(redial(ctx, *url), read(os.Stdin)) done() }() go func() { subscribe(redial(ctx, *url), write(os.Stdout)) done() }() <-ctx.Done() } golang-github-rabbitmq-amqp091-go-1.10.0/allocator.go000066400000000000000000000050631462444370000222270ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "bytes" "fmt" "math/big" ) const ( free = 0 allocated = 1 ) // allocator maintains a bitset of allocated numbers. type allocator struct { pool *big.Int follow int low int high int } // NewAllocator reserves and frees integers out of a range between low and // high. // // O(N) worst case space used, where N is maximum allocated, divided by // sizeof(big.Word) func newAllocator(low, high int) *allocator { return &allocator{ pool: big.NewInt(0), follow: low, low: low, high: high, } } // String returns a string describing the contents of the allocator like // "allocator[low..high] reserved..until" // // O(N) where N is high-low func (a allocator) String() string { b := &bytes.Buffer{} fmt.Fprintf(b, "allocator[%d..%d]", a.low, a.high) for low := a.low; low <= a.high; low++ { high := low for a.reserved(high) && high <= a.high { high++ } if high > low+1 { fmt.Fprintf(b, " %d..%d", low, high-1) } else if high > low { fmt.Fprintf(b, " %d", high-1) } low = high } return b.String() } // Next reserves and returns the next available number out of the range between // low and high. If no number is available, false is returned. // // O(N) worst case runtime where N is allocated, but usually O(1) due to a // rolling index into the oldest allocation. func (a *allocator) next() (int, bool) { wrapped := a.follow defer func() { // make a.follow point to next value if a.follow == a.high { a.follow = a.low } else { a.follow += 1 } }() // Find trailing bit for ; a.follow <= a.high; a.follow++ { if a.reserve(a.follow) { return a.follow, true } } // Find preceding free'd pool a.follow = a.low for ; a.follow < wrapped; a.follow++ { if a.reserve(a.follow) { return a.follow, true } } return 0, false } // reserve claims the bit if it is not already claimed, returning true if // successfully claimed. func (a *allocator) reserve(n int) bool { if a.reserved(n) { return false } a.pool.SetBit(a.pool, n-a.low, allocated) return true } // reserved returns true if the integer has been allocated func (a *allocator) reserved(n int) bool { return a.pool.Bit(n-a.low) == allocated } // release frees the use of the number for another allocation func (a *allocator) release(n int) { a.pool.SetBit(a.pool, n-a.low, free) } golang-github-rabbitmq-amqp091-go-1.10.0/allocator_test.go000066400000000000000000000055241462444370000232700ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "math/rand" "testing" ) func TestAllocatorFirstShouldBeTheLow(t *testing.T) { n, ok := newAllocator(1, 2).next() if !ok { t.Fatalf("expected to allocate between 1 and 2") } if want, got := 1, n; want != got { t.Fatalf("expected to first allocation to be 1") } } func TestAllocatorShouldBeBoundByHigh(t *testing.T) { a := newAllocator(1, 2) if n, ok := a.next(); n != 1 || !ok { t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok) } if n, ok := a.next(); n != 2 || !ok { t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok) } if _, ok := a.next(); ok { t.Fatalf("expected not to allocate outside of 1 and 2") } } func TestAllocatorStringShouldIncludeAllocatedRanges(t *testing.T) { a := newAllocator(1, 10) a.reserve(1) a.reserve(2) a.reserve(3) a.reserve(5) a.reserve(6) a.reserve(8) a.reserve(10) if want, got := "allocator[1..10] 1..3 5..6 8 10", a.String(); want != got { t.Fatalf("expected String of %q, got %q", want, got) } } func TestAllocatorShouldReuseReleased(t *testing.T) { a := newAllocator(1, 2) first, _ := a.next() if want, got := 1, first; want != got { t.Fatalf("expected allocation to be %d, got: %d", want, got) } second, _ := a.next() if want, got := 2, second; want != got { t.Fatalf("expected allocation to be %d, got: %d", want, got) } a.release(first) third, _ := a.next() if want, got := first, third; want != got { t.Fatalf("expected third allocation to be %d, got: %d", want, got) } _, ok := a.next() if want, got := false, ok; want != got { t.Fatalf("expected fourth allocation to saturate the pool") } } func TestAllocatorShouldNotReuseEarly(t *testing.T) { a := newAllocator(1, 2) first, _ := a.next() if want, got := 1, first; want != got { t.Fatalf("expected allocation to be %d, got: %d", want, got) } a.release(first) second, _ := a.next() if want, got := 2, second; want != got { t.Fatalf("expected second allocation to be %d, got: %d", want, got) } third, _ := a.next() if want, got := first, third; want != got { t.Fatalf("expected third allocation to be %d, got: %d", want, got) } } func TestAllocatorReleasesKeepUpWithAllocationsForAllSizes(t *testing.T) { if testing.Short() { t.Skip() } const runs = 5 const max = 13 for lim := 1; lim < 2<= lim { // fills the allocator a.release(int(rand.Int63n(int64(lim)))) } if _, ok := a.next(); !ok { t.Fatalf("expected %d runs of random release of size %d not to fail on allocation %d", runs, lim, i) } } } } golang-github-rabbitmq-amqp091-go-1.10.0/auth.go000066400000000000000000000040261462444370000212060ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "bytes" "fmt" ) // Authentication interface provides a means for different SASL authentication // mechanisms to be used during connection tuning. type Authentication interface { Mechanism() string Response() string } // PlainAuth is a similar to Basic Auth in HTTP. type PlainAuth struct { Username string Password string } // Mechanism returns "PLAIN" func (auth *PlainAuth) Mechanism() string { return "PLAIN" } // Response returns the null character delimited encoding for the SASL PLAIN Mechanism. func (auth *PlainAuth) Response() string { return fmt.Sprintf("\000%s\000%s", auth.Username, auth.Password) } // AMQPlainAuth is similar to PlainAuth type AMQPlainAuth struct { Username string Password string } // Mechanism returns "AMQPLAIN" func (auth *AMQPlainAuth) Mechanism() string { return "AMQPLAIN" } // Response returns an AMQP encoded credentials table, without the field table size. func (auth *AMQPlainAuth) Response() string { var buf bytes.Buffer table := Table{"LOGIN": auth.Username, "PASSWORD": auth.Password} if err := writeTable(&buf, table); err != nil { return "" } return buf.String()[4:] } // ExternalAuth for RabbitMQ-auth-mechanism-ssl. type ExternalAuth struct { } // Mechanism returns "EXTERNAL" func (*ExternalAuth) Mechanism() string { return "EXTERNAL" } // Response returns an AMQP encoded credentials table, without the field table size. func (*ExternalAuth) Response() string { return "\000*\000*" } // Finds the first mechanism preferred by the client that the server supports. func pickSASLMechanism(client []Authentication, serverMechanisms []string) (auth Authentication, ok bool) { for _, auth = range client { for _, mech := range serverMechanisms { if auth.Mechanism() == mech { return auth, true } } } return } golang-github-rabbitmq-amqp091-go-1.10.0/auth_test.go000066400000000000000000000012121462444370000222370ustar00rootroot00000000000000package amqp091 import "testing" func TestPlainAuth(t *testing.T) { auth := &PlainAuth{ Username: "user", Password: "pass", } if auth.Mechanism() != "PLAIN" { t.Errorf("Expected PLAIN, got %s", auth.Mechanism()) } expectedResponse := "\000user\000pass" if auth.Response() != expectedResponse { t.Errorf("Expected %s, got %s", expectedResponse, auth.Response()) } } func TestExternalAuth(t *testing.T) { auth := &ExternalAuth{} if auth.Mechanism() != "EXTERNAL" { t.Errorf("Expected EXTERNAL, got %s", auth.Mechanism()) } if auth.Response() != "\000*\000*" { t.Errorf("Expected \000*\000*, got %s", auth.Response()) } } golang-github-rabbitmq-amqp091-go-1.10.0/certs.sh000077500000000000000000000057201462444370000213770ustar00rootroot00000000000000#!/bin/sh # # Creates the CA, server and client certs to be used by tls_test.go # http://www.rabbitmq.com/ssl.html # # Copy stdout into the const section of tls_test.go or use for RabbitMQ # root=$PWD/certs if [ -f $root/ca/serial ]; then echo >&2 "Previous installation found" echo >&2 "Remove $root/ca and rerun to overwrite" exit 1 fi mkdir -p $root/ca/private mkdir -p $root/ca/certs mkdir -p $root/server mkdir -p $root/client cd $root/ca chmod 700 private touch index.txt echo 'unique_subject = no' > index.txt.attr echo '01' > serial echo >openssl.cnf ' [ ca ] default_ca = testca [ testca ] dir = . certificate = $dir/cacert.pem database = $dir/index.txt new_certs_dir = $dir/certs private_key = $dir/private/cakey.pem serial = $dir/serial default_crl_days = 7 default_days = 3650 default_md = sha256 policy = testca_policy x509_extensions = certificate_extensions [ testca_policy ] commonName = supplied stateOrProvinceName = optional countryName = optional emailAddress = optional organizationName = optional organizationalUnitName = optional [ certificate_extensions ] basicConstraints = CA:false [ req ] default_bits = 2048 default_keyfile = ./private/cakey.pem default_md = sha256 prompt = yes distinguished_name = root_ca_distinguished_name x509_extensions = root_ca_extensions [ root_ca_distinguished_name ] commonName = hostname [ root_ca_extensions ] basicConstraints = CA:true keyUsage = keyCertSign, cRLSign [ client_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment,digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.2 [ server_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment,digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.1 subjectAltName = @alt_names [ alt_names ] IP.1 = 127.0.0.1 ' openssl req \ -x509 \ -nodes \ -config openssl.cnf \ -newkey rsa:2048 \ -days 3650 \ -subj "/CN=MyTestCA/" \ -out cacert.pem \ -outform PEM openssl x509 \ -in cacert.pem \ -out cacert.cer \ -outform DER openssl genrsa -out $root/server/key.pem 2048 openssl genrsa -out $root/client/key.pem 2048 openssl req \ -new \ -nodes \ -config openssl.cnf \ -subj "/CN=localhost/O=server/" \ -key $root/server/key.pem \ -out $root/server/req.pem \ -outform PEM openssl req \ -new \ -nodes \ -config openssl.cnf \ -subj "/CN=localhost/O=client/" \ -key $root/client/key.pem \ -out $root/client/req.pem \ -outform PEM openssl ca \ -config openssl.cnf \ -in $root/server/req.pem \ -out $root/server/cert.pem \ -notext \ -batch \ -extensions server_ca_extensions openssl ca \ -config openssl.cnf \ -in $root/client/req.pem \ -out $root/client/cert.pem \ -notext \ -batch \ -extensions client_ca_extensions cat <<-END const caCert = \` `cat $root/ca/cacert.pem` \` const serverCert = \` `cat $root/server/cert.pem` \` const serverKey = \` `cat $root/server/key.pem` \` const clientCert = \` `cat $root/client/cert.pem` \` const clientKey = \` `cat $root/client/key.pem` \` END golang-github-rabbitmq-amqp091-go-1.10.0/change_version.sh000077500000000000000000000002201462444370000232370ustar00rootroot00000000000000#!/bin/bash echo $1 > VERSION sed -i -e "s/.*buildVersion = \"*.*/buildVersion = \"$1\"/" ./connection.go go fmt ./... golang-github-rabbitmq-amqp091-go-1.10.0/channel.go000066400000000000000000001631121462444370000216570ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "context" "reflect" "sync" "sync/atomic" ) // 0 1 3 7 size+7 size+8 // +------+---------+-------------+ +------------+ +-----------+ // | type | channel | size | | payload | | frame-end | // +------+---------+-------------+ +------------+ +-----------+ // // octet short long size octets octet const frameHeaderSize = 1 + 2 + 4 + 1 /* Channel represents an AMQP channel. Used as a context for valid message exchange. Errors on methods with this Channel as a receiver means this channel should be discarded and a new channel established. */ type Channel struct { destructor sync.Once m sync.Mutex // struct field mutex confirmM sync.Mutex // publisher confirms state mutex notifyM sync.RWMutex connection *Connection rpc chan message consumers *consumers id uint16 // closed is set to 1 when the channel has been closed - see Channel.send() closed int32 close chan struct{} // true when we will never notify again noNotify bool // Channel and Connection exceptions will be broadcast on these listeners. closes []chan *Error // Listeners for active=true flow control. When true is sent to a listener, // publishing should pause until false is sent to listeners. flows []chan bool // Listeners for returned publishings for unroutable messages on mandatory // publishings or undeliverable messages on immediate publishings. returns []chan Return // Listeners for when the server notifies the client that // a consumer has been cancelled. cancels []chan string // Allocated when in confirm mode in order to track publish counter and order confirms confirms *confirms confirming bool // Selects on any errors from shutdown during RPC errors chan *Error // State machine that manages frame order, must only be mutated by the connection recv func(*Channel, frame) // Current state for frame re-assembly, only mutated from recv message messageWithContent header *headerFrame body []byte } // Constructs a new channel with the given framing rules func newChannel(c *Connection, id uint16) *Channel { return &Channel{ connection: c, id: id, rpc: make(chan message), consumers: makeConsumers(), confirms: newConfirms(), recv: (*Channel).recvMethod, errors: make(chan *Error, 1), close: make(chan struct{}), } } // Signal that from now on, Channel.send() should call Channel.sendClosed() func (ch *Channel) setClosed() { atomic.StoreInt32(&ch.closed, 1) } // shutdown is called by Connection after the channel has been removed from the // connection registry. func (ch *Channel) shutdown(e *Error) { ch.setClosed() ch.destructor.Do(func() { ch.m.Lock() defer ch.m.Unlock() // Grab an exclusive lock for the notify channels ch.notifyM.Lock() defer ch.notifyM.Unlock() // Broadcast abnormal shutdown if e != nil { for _, c := range ch.closes { c <- e } // Notify RPC if we're selecting ch.errors <- e } ch.consumers.close() for _, c := range ch.closes { close(c) } for _, c := range ch.flows { close(c) } for _, c := range ch.returns { close(c) } for _, c := range ch.cancels { close(c) } // Set the slices to nil to prevent the dispatch() range from sending on // the now closed channels after we release the notifyM mutex ch.flows = nil ch.closes = nil ch.returns = nil ch.cancels = nil if ch.confirms != nil { ch.confirms.Close() } close(ch.errors) close(ch.close) ch.noNotify = true }) } // send calls Channel.sendOpen() during normal operation. // // After the channel has been closed, send calls Channel.sendClosed(), ensuring // only 'channel.close' is sent to the server. func (ch *Channel) send(msg message) (err error) { // If the channel is closed, use Channel.sendClosed() if ch.IsClosed() { return ch.sendClosed(msg) } return ch.sendOpen(msg) } func (ch *Channel) open() error { return ch.call(&channelOpen{}, &channelOpenOk{}) } // Performs a request/response call for when the message is not NoWait and is // specified as Synchronous. func (ch *Channel) call(req message, res ...message) error { if err := ch.send(req); err != nil { return err } if req.wait() { select { case e, ok := <-ch.errors: if ok { return e } return ErrClosed case msg := <-ch.rpc: if msg != nil { for _, try := range res { if reflect.TypeOf(msg) == reflect.TypeOf(try) { // *res = *msg vres := reflect.ValueOf(try).Elem() vmsg := reflect.ValueOf(msg).Elem() vres.Set(vmsg) return nil } } return ErrCommandInvalid } // RPC channel has been closed without an error, likely due to a hard // error on the Connection. This indicates we have already been // shutdown and if were waiting, will have returned from the errors chan. return ErrClosed } } return nil } func (ch *Channel) sendClosed(msg message) (err error) { // After a 'channel.close' is sent or received the only valid response is // channel.close-ok if _, ok := msg.(*channelCloseOk); ok { return ch.connection.send(&methodFrame{ ChannelId: ch.id, Method: msg, }) } return ErrClosed } func (ch *Channel) sendOpen(msg message) (err error) { if content, ok := msg.(messageWithContent); ok { props, body := content.getContent() class, _ := content.id() // catch client max frame size==0 and server max frame size==0 // set size to length of what we're trying to publish var size int if ch.connection.Config.FrameSize > 0 { size = ch.connection.Config.FrameSize - frameHeaderSize } else { size = len(body) } // If the channel is closed, use Channel.sendClosed() if ch.IsClosed() { return ch.sendClosed(msg) } // Flush the buffer only after all the Frames that comprise the Message // have been written to maximise benefits of using a buffered writer. defer func() { if endError := ch.connection.endSendUnflushed(); endError != nil { if err == nil { err = endError } } }() // We use sendUnflushed() in this method as sending the message requires // sending multiple Frames (methodFrame, headerFrame, N x bodyFrame). // Flushing after each Frame is inefficient, as it negates much of the // benefit of using a buffered writer and results in more syscalls than // necessary. Flushing buffers after every frame can have a significant // performance impact when sending (e.g. basicPublish) small messages, // so sendUnflushed() performs an *Unflushed* write, but is otherwise // equivalent to the send() method. We later use the separate flush // method to explicitly flush the buffer after all Frames are written. if err = ch.connection.sendUnflushed(&methodFrame{ ChannelId: ch.id, Method: content, }); err != nil { return } if err = ch.connection.sendUnflushed(&headerFrame{ ChannelId: ch.id, ClassId: class, Size: uint64(len(body)), Properties: props, }); err != nil { return } // chunk body into size (max frame size - frame header size) for i, j := 0, size; i < len(body); i, j = j, j+size { if j > len(body) { j = len(body) } if err = ch.connection.sendUnflushed(&bodyFrame{ ChannelId: ch.id, Body: body[i:j], }); err != nil { return } } } else { // If the channel is closed, use Channel.sendClosed() if ch.IsClosed() { return ch.sendClosed(msg) } err = ch.connection.send(&methodFrame{ ChannelId: ch.id, Method: msg, }) } return } // Eventually called via the state machine from the connection's reader // goroutine, so assumes serialized access. func (ch *Channel) dispatch(msg message) { switch m := msg.(type) { case *channelClose: // Note: channel state is set to closed immedately after the message is // decoded by the Connection // lock before sending connection.close-ok // to avoid unexpected interleaving with basic.publish frames if // publishing is happening concurrently ch.m.Lock() if err := ch.send(&channelCloseOk{}); err != nil { Logger.Printf("error sending channelCloseOk, channel id: %d error: %+v", ch.id, err) } ch.m.Unlock() ch.connection.closeChannel(ch, newError(m.ReplyCode, m.ReplyText)) case *channelFlow: ch.notifyM.RLock() for _, c := range ch.flows { c <- m.Active } ch.notifyM.RUnlock() if err := ch.send(&channelFlowOk{Active: m.Active}); err != nil { Logger.Printf("error sending channelFlowOk, channel id: %d error: %+v", ch.id, err) } case *basicCancel: ch.notifyM.RLock() for _, c := range ch.cancels { c <- m.ConsumerTag } ch.notifyM.RUnlock() ch.consumers.cancel(m.ConsumerTag) case *basicReturn: ret := newReturn(*m) ch.notifyM.RLock() for _, c := range ch.returns { c <- *ret } ch.notifyM.RUnlock() case *basicAck: if ch.confirming { if m.Multiple { ch.confirms.Multiple(Confirmation{m.DeliveryTag, true}) } else { ch.confirms.One(Confirmation{m.DeliveryTag, true}) } } case *basicNack: if ch.confirming { if m.Multiple { ch.confirms.Multiple(Confirmation{m.DeliveryTag, false}) } else { ch.confirms.One(Confirmation{m.DeliveryTag, false}) } } case *basicDeliver: ch.consumers.send(m.ConsumerTag, newDelivery(ch, m)) // TODO log failed consumer and close channel, this can happen when // deliveries are in flight and a no-wait cancel has happened default: select { case <-ch.close: return case ch.rpc <- msg: } } } func (ch *Channel) transition(f func(*Channel, frame)) { ch.recv = f } func (ch *Channel) recvMethod(f frame) { switch frame := f.(type) { case *methodFrame: if msg, ok := frame.Method.(messageWithContent); ok { ch.body = make([]byte, 0) ch.message = msg ch.transition((*Channel).recvHeader) return } ch.dispatch(frame.Method) // termination state ch.transition((*Channel).recvMethod) case *headerFrame: // drop ch.transition((*Channel).recvMethod) case *bodyFrame: // drop ch.transition((*Channel).recvMethod) default: panic("unexpected frame type") } } func (ch *Channel) recvHeader(f frame) { switch frame := f.(type) { case *methodFrame: // interrupt content and handle method ch.recvMethod(f) case *headerFrame: // start collecting if we expect body frames ch.header = frame if frame.Size == 0 { ch.message.setContent(ch.header.Properties, ch.body) ch.dispatch(ch.message) // termination state ch.transition((*Channel).recvMethod) return } ch.transition((*Channel).recvContent) case *bodyFrame: // drop and reset ch.transition((*Channel).recvMethod) default: panic("unexpected frame type") } } // state after method + header and before the length // defined by the header has been reached func (ch *Channel) recvContent(f frame) { switch frame := f.(type) { case *methodFrame: // interrupt content and handle method ch.recvMethod(f) case *headerFrame: // drop and reset ch.transition((*Channel).recvMethod) case *bodyFrame: if cap(ch.body) == 0 { ch.body = make([]byte, 0, ch.header.Size) } ch.body = append(ch.body, frame.Body...) if uint64(len(ch.body)) >= ch.header.Size { ch.message.setContent(ch.header.Properties, ch.body) ch.dispatch(ch.message) // termination state ch.transition((*Channel).recvMethod) return } ch.transition((*Channel).recvContent) default: panic("unexpected frame type") } } /* Close initiate a clean channel closure by sending a close message with the error code set to '200'. It is safe to call this method multiple times. */ func (ch *Channel) Close() error { if ch.IsClosed() { return nil } defer ch.connection.closeChannel(ch, nil) return ch.call( &channelClose{ReplyCode: replySuccess}, &channelCloseOk{}, ) } // IsClosed returns true if the channel is marked as closed, otherwise false // is returned. func (ch *Channel) IsClosed() bool { return atomic.LoadInt32(&ch.closed) == 1 } /* NotifyClose registers a listener for when the server sends a channel or connection exception in the form of a Connection.Close or Channel.Close method. Connection exceptions will be broadcast to all open channels and all channels will be closed, where channel exceptions will only be broadcast to listeners to this channel. The chan provided will be closed when the Channel is closed and on a graceful close, no error will be sent. In case of a non graceful close the error will be notified synchronously by the library so that it will be necessary to consume the Channel from the caller in order to avoid deadlocks */ func (ch *Channel) NotifyClose(c chan *Error) chan *Error { ch.notifyM.Lock() defer ch.notifyM.Unlock() if ch.noNotify { close(c) } else { ch.closes = append(ch.closes, c) } return c } /* NotifyFlow registers a listener for basic.flow methods sent by the server. When `false` is sent on one of the listener channels, all publishers should pause until a `true` is sent. The server may ask the producer to pause or restart the flow of Publishings sent by on a channel. This is a simple flow-control mechanism that a server can use to avoid overflowing its queues or otherwise finding itself receiving more messages than it can process. Note that this method is not intended for window control. It does not affect contents returned by basic.get-ok methods. When a new channel is opened, it is active (flow is active). Some applications assume that channels are inactive until started. To emulate this behavior a client MAY open the channel, then pause it. Publishers should respond to a flow messages as rapidly as possible and the server may disconnect over producing channels that do not respect these messages. basic.flow-ok methods will always be returned to the server regardless of the number of listeners there are. To control the flow of deliveries from the server, use the Channel.Flow() method instead. Note: RabbitMQ will rather use TCP pushback on the network connection instead of sending basic.flow. This means that if a single channel is producing too much on the same connection, all channels using that connection will suffer, including acknowledgments from deliveries. Use different Connections if you desire to interleave consumers and producers in the same process to avoid your basic.ack messages from getting rate limited with your basic.publish messages. */ func (ch *Channel) NotifyFlow(c chan bool) chan bool { ch.notifyM.Lock() defer ch.notifyM.Unlock() if ch.noNotify { close(c) } else { ch.flows = append(ch.flows, c) } return c } /* NotifyReturn registers a listener for basic.return methods. These can be sent from the server when a publish is undeliverable either from the mandatory or immediate flags. A return struct has a copy of the Publishing along with some error information about why the publishing failed. */ func (ch *Channel) NotifyReturn(c chan Return) chan Return { ch.notifyM.Lock() defer ch.notifyM.Unlock() if ch.noNotify { close(c) } else { ch.returns = append(ch.returns, c) } return c } /* NotifyCancel registers a listener for basic.cancel methods. These can be sent from the server when a queue is deleted or when consuming from a mirrored queue where the master has just failed (and was moved to another node). The subscription tag is returned to the listener. */ func (ch *Channel) NotifyCancel(c chan string) chan string { ch.notifyM.Lock() defer ch.notifyM.Unlock() if ch.noNotify { close(c) } else { ch.cancels = append(ch.cancels, c) } return c } /* NotifyConfirm calls NotifyPublish and starts a goroutine sending ordered Ack and Nack DeliveryTag to the respective channels. For strict ordering, use NotifyPublish instead. */ func (ch *Channel) NotifyConfirm(ack, nack chan uint64) (chan uint64, chan uint64) { confirms := ch.NotifyPublish(make(chan Confirmation, cap(ack)+cap(nack))) go func() { for c := range confirms { if c.Ack { ack <- c.DeliveryTag } else { nack <- c.DeliveryTag } } close(ack) if nack != ack { close(nack) } }() return ack, nack } /* NotifyPublish registers a listener for reliable publishing. Receives from this chan for every publish after Channel.Confirm will be in order starting with DeliveryTag 1. There will be one and only one Confirmation Publishing starting with the delivery tag of 1 and progressing sequentially until the total number of Publishings have been seen by the server. Acknowledgments will be received in the order of delivery from the NotifyPublish channels even if the server acknowledges them out of order. The listener chan will be closed when the Channel is closed. The capacity of the chan Confirmation must be at least as large as the number of outstanding publishings. Not having enough buffered chans will create a deadlock if you attempt to perform other operations on the Connection or Channel while confirms are in-flight. It's advisable to wait for all Confirmations to arrive before calling Channel.Close() or Connection.Close(). It is also advisable for the caller to consume from the channel returned till it is closed to avoid possible deadlocks */ func (ch *Channel) NotifyPublish(confirm chan Confirmation) chan Confirmation { ch.notifyM.Lock() defer ch.notifyM.Unlock() if ch.noNotify { close(confirm) } else { ch.confirms.Listen(confirm) } return confirm } /* Qos controls how many messages or how many bytes the server will try to keep on the network for consumers before receiving delivery acks. The intent of Qos is to make sure the network buffers stay full between the server and client. With a prefetch count greater than zero, the server will deliver that many messages to consumers before acknowledgments are received. The server ignores this option when consumers are started with noAck because no acknowledgments are expected or sent. With a prefetch size greater than zero, the server will try to keep at least that many bytes of deliveries flushed to the network before receiving acknowledgments from the consumers. This option is ignored when consumers are started with noAck. When global is true, these Qos settings apply to all existing and future consumers on all channels on the same connection. When false, the Channel.Qos settings will apply to all existing and future consumers on this channel. Please see the RabbitMQ Consumer Prefetch documentation for an explanation of how the global flag is implemented in RabbitMQ, as it differs from the AMQP 0.9.1 specification in that global Qos settings are limited in scope to channels, not connections (https://www.rabbitmq.com/consumer-prefetch.html). To get round-robin behavior between consumers consuming from the same queue on different connections, set the prefetch count to 1, and the next available message on the server will be delivered to the next available consumer. If your consumer work time is reasonably consistent and not much greater than two times your network round trip time, you will see significant throughput improvements starting with a prefetch count of 2 or slightly greater as described by benchmarks on RabbitMQ. http://www.rabbitmq.com/blog/2012/04/25/rabbitmq-performance-measurements-part-2/ */ func (ch *Channel) Qos(prefetchCount, prefetchSize int, global bool) error { return ch.call( &basicQos{ PrefetchCount: uint16(prefetchCount), PrefetchSize: uint32(prefetchSize), Global: global, }, &basicQosOk{}, ) } /* Cancel stops deliveries to the consumer chan established in Channel.Consume and identified by consumer. Only use this method to cleanly stop receiving deliveries from the server and cleanly shut down the consumer chan identified by this tag. Using this method and waiting for remaining messages to flush from the consumer chan will ensure all messages received on the network will be delivered to the receiver of your consumer chan. Continue consuming from the chan Delivery provided by Channel.Consume until the chan closes. When noWait is true, do not wait for the server to acknowledge the cancel. Only use this when you are certain there are no deliveries in flight that require an acknowledgment, otherwise they will arrive and be dropped in the client without an ack, and will not be redelivered to other consumers. */ func (ch *Channel) Cancel(consumer string, noWait bool) error { req := &basicCancel{ ConsumerTag: consumer, NoWait: noWait, } res := &basicCancelOk{} if err := ch.call(req, res); err != nil { return err } if req.wait() { ch.consumers.cancel(res.ConsumerTag) } else { // Potentially could drop deliveries in flight ch.consumers.cancel(consumer) } return nil } /* QueueDeclare declares a queue to hold messages and deliver to consumers. Declaring creates a queue if it doesn't already exist, or ensures that an existing queue matches the same parameters. Every queue declared gets a default binding to the empty exchange "" which has the type "direct" with the routing key matching the queue's name. With this default binding, it is possible to publish messages that route directly to this queue by publishing to "" with the routing key of the queue name. QueueDeclare("alerts", true, false, false, false, nil) Publish("", "alerts", false, false, Publishing{Body: []byte("...")}) Delivery Exchange Key Queue ----------------------------------------------- key: alerts -> "" -> alerts -> alerts The queue name may be empty, in which case the server will generate a unique name which will be returned in the Name field of Queue struct. Durable and Non-Auto-Deleted queues will survive server restarts and remain when there are no remaining consumers or bindings. Persistent publishings will be restored in this queue on server restart. These queues are only able to be bound to durable exchanges. Non-Durable and Auto-Deleted queues will not be redeclared on server restart and will be deleted by the server after a short time when the last consumer is canceled or the last consumer's channel is closed. Queues with this lifetime can also be deleted normally with QueueDelete. These durable queues can only be bound to non-durable exchanges. Non-Durable and Non-Auto-Deleted queues will remain declared as long as the server is running regardless of how many consumers. This lifetime is useful for temporary topologies that may have long delays between consumer activity. These queues can only be bound to non-durable exchanges. Durable and Auto-Deleted queues will be restored on server restart, but without active consumers will not survive and be removed. This Lifetime is unlikely to be useful. Exclusive queues are only accessible by the connection that declares them and will be deleted when the connection closes. Channels on other connections will receive an error when attempting to declare, bind, consume, purge or delete a queue with the same name. When noWait is true, the queue will assume to be declared on the server. A channel exception will arrive if the conditions are met for existing queues or attempting to modify an existing queue from a different connection. When the error return value is not nil, you can assume the queue could not be declared with these parameters, and the channel will be closed. */ func (ch *Channel) QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args Table) (Queue, error) { if err := args.Validate(); err != nil { return Queue{}, err } req := &queueDeclare{ Queue: name, Passive: false, Durable: durable, AutoDelete: autoDelete, Exclusive: exclusive, NoWait: noWait, Arguments: args, } res := &queueDeclareOk{} if err := ch.call(req, res); err != nil { return Queue{}, err } if req.wait() { return Queue{ Name: res.Queue, Messages: int(res.MessageCount), Consumers: int(res.ConsumerCount), }, nil } return Queue{Name: name}, nil } /* QueueDeclarePassive is functionally and parametrically equivalent to QueueDeclare, except that it sets the "passive" attribute to true. A passive queue is assumed by RabbitMQ to already exist, and attempting to connect to a non-existent queue will cause RabbitMQ to throw an exception. This function can be used to test for the existence of a queue. */ func (ch *Channel) QueueDeclarePassive(name string, durable, autoDelete, exclusive, noWait bool, args Table) (Queue, error) { if err := args.Validate(); err != nil { return Queue{}, err } req := &queueDeclare{ Queue: name, Passive: true, Durable: durable, AutoDelete: autoDelete, Exclusive: exclusive, NoWait: noWait, Arguments: args, } res := &queueDeclareOk{} if err := ch.call(req, res); err != nil { return Queue{}, err } if req.wait() { return Queue{ Name: res.Queue, Messages: int(res.MessageCount), Consumers: int(res.ConsumerCount), }, nil } return Queue{Name: name}, nil } /* QueueInspect passively declares a queue by name to inspect the current message count and consumer count. Use this method to check how many messages ready for delivery reside in the queue, how many consumers are receiving deliveries, and whether a queue by this name already exists. If the queue by this name exists, use Channel.QueueDeclare check if it is declared with specific parameters. If a queue by this name does not exist, an error will be returned and the channel will be closed. Deprecated: Use QueueDeclare with "Passive: true" instead. */ func (ch *Channel) QueueInspect(name string) (Queue, error) { req := &queueDeclare{ Queue: name, Passive: true, } res := &queueDeclareOk{} err := ch.call(req, res) state := Queue{ Name: name, Messages: int(res.MessageCount), Consumers: int(res.ConsumerCount), } return state, err } /* QueueBind binds an exchange to a queue so that publishings to the exchange will be routed to the queue when the publishing routing key matches the binding routing key. QueueBind("pagers", "alert", "log", false, nil) QueueBind("emails", "info", "log", false, nil) Delivery Exchange Key Queue ----------------------------------------------- key: alert --> log ----> alert --> pagers key: info ---> log ----> info ---> emails key: debug --> log (none) (dropped) If a binding with the same key and arguments already exists between the exchange and queue, the attempt to rebind will be ignored and the existing binding will be retained. In the case that multiple bindings may cause the message to be routed to the same queue, the server will only route the publishing once. This is possible with topic exchanges. QueueBind("pagers", "alert", "amq.topic", false, nil) QueueBind("emails", "info", "amq.topic", false, nil) QueueBind("emails", "#", "amq.topic", false, nil) // match everything Delivery Exchange Key Queue ----------------------------------------------- key: alert --> amq.topic ----> alert --> pagers key: info ---> amq.topic ----> # ------> emails \---> info ---/ key: debug --> amq.topic ----> # ------> emails It is only possible to bind a durable queue to a durable exchange regardless of whether the queue or exchange is auto-deleted. Bindings between durable queues and exchanges will also be restored on server restart. If the binding could not complete, an error will be returned and the channel will be closed. When noWait is false and the queue could not be bound, the channel will be closed with an error. */ func (ch *Channel) QueueBind(name, key, exchange string, noWait bool, args Table) error { if err := args.Validate(); err != nil { return err } return ch.call( &queueBind{ Queue: name, Exchange: exchange, RoutingKey: key, NoWait: noWait, Arguments: args, }, &queueBindOk{}, ) } /* QueueUnbind removes a binding between an exchange and queue matching the key and arguments. */ func (ch *Channel) QueueUnbind(name, key, exchange string, args Table) error { if err := args.Validate(); err != nil { return err } return ch.call( &queueUnbind{ Queue: name, Exchange: exchange, RoutingKey: key, Arguments: args, }, &queueUnbindOk{}, ) } /* QueuePurge removes all messages from the named queue which are not waiting to be acknowledged. Messages that have been delivered but have not yet been acknowledged will not be removed. When successful, returns the number of messages purged. If noWait is true, do not wait for the server response and the number of messages purged will not be meaningful. */ func (ch *Channel) QueuePurge(name string, noWait bool) (int, error) { req := &queuePurge{ Queue: name, NoWait: noWait, } res := &queuePurgeOk{} err := ch.call(req, res) return int(res.MessageCount), err } /* QueueDelete removes the queue from the server including all bindings then purges the messages based on server configuration, returning the number of messages purged. When ifUnused is true, the queue will not be deleted if there are any consumers on the queue. If there are consumers, an error will be returned and the channel will be closed. When ifEmpty is true, the queue will not be deleted if there are any messages remaining on the queue. If there are messages, an error will be returned and the channel will be closed. When noWait is true, the queue will be deleted without waiting for a response from the server. The purged message count will not be meaningful. If the queue could not be deleted, a channel exception will be raised and the channel will be closed. */ func (ch *Channel) QueueDelete(name string, ifUnused, ifEmpty, noWait bool) (int, error) { req := &queueDelete{ Queue: name, IfUnused: ifUnused, IfEmpty: ifEmpty, NoWait: noWait, } res := &queueDeleteOk{} err := ch.call(req, res) return int(res.MessageCount), err } /* Consume immediately starts delivering queued messages. Begin receiving on the returned chan Delivery before any other operation on the Connection or Channel. Continues deliveries to the returned chan Delivery until Channel.Cancel, Connection.Close, Channel.Close, or an AMQP exception occurs. Consumers must range over the chan to ensure all deliveries are received. Unreceived deliveries will block all methods on the same connection. All deliveries in AMQP must be acknowledged. It is expected of the consumer to call Delivery.Ack after it has successfully processed the delivery. If the consumer is cancelled or the channel or connection is closed any unacknowledged deliveries will be requeued at the end of the same queue. The consumer is identified by a string that is unique and scoped for all consumers on this channel. If you wish to eventually cancel the consumer, use the same non-empty identifier in Channel.Cancel. An empty string will cause the library to generate a unique identity. The consumer identity will be included in every Delivery in the ConsumerTag field When autoAck (also known as noAck) is true, the server will acknowledge deliveries to this consumer prior to writing the delivery to the network. When autoAck is true, the consumer should not call Delivery.Ack. Automatically acknowledging deliveries means that some deliveries may get lost if the consumer is unable to process them after the server delivers them. See http://www.rabbitmq.com/confirms.html for more details. When exclusive is true, the server will ensure that this is the sole consumer from this queue. When exclusive is false, the server will fairly distribute deliveries across multiple consumers. The noLocal flag is not supported by RabbitMQ. It's advisable to use separate connections for Channel.Publish and Channel.Consume so not to have TCP pushback on publishing affect the ability to consume messages, so this parameter is here mostly for completeness. When noWait is true, do not wait for the server to confirm the request and immediately begin deliveries. If it is not possible to consume, a channel exception will be raised and the channel will be closed. Optional arguments can be provided that have specific semantics for the queue or server. Inflight messages, limited by Channel.Qos will be buffered until received from the returned chan. When the Channel or Connection is closed, all buffered and inflight messages will be dropped. RabbitMQ will requeue messages not acknowledged. In other words, dropped messages in this way won't be lost. When the consumer tag is cancelled, all inflight messages will be delivered until the returned chan is closed. */ func (ch *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error) { // When we return from ch.call, there may be a delivery already for the // consumer that hasn't been added to the consumer hash yet. Because of // this, we never rely on the server picking a consumer tag for us. if err := args.Validate(); err != nil { return nil, err } if consumer == "" { consumer = uniqueConsumerTag() } req := &basicConsume{ Queue: queue, ConsumerTag: consumer, NoLocal: noLocal, NoAck: autoAck, Exclusive: exclusive, NoWait: noWait, Arguments: args, } res := &basicConsumeOk{} deliveries := make(chan Delivery) ch.consumers.add(consumer, deliveries) if err := ch.call(req, res); err != nil { ch.consumers.cancel(consumer) return nil, err } return deliveries, nil } /* ConsumeWithContext immediately starts delivering queued messages. This function is similar to Channel.Consume, and accepts a context to control consumer lifecycle. When the context passed to this function is canceled, the consumer associated with the deliveries channel will be canceled too. When the context passed to this function is cancelled, the deliveries channel will be closed. An application is advised to keep on receiving messages from the delivery channel until the channel is empty. This is specially important to avoid memory leaks from unconsumed messages from the delivery channel. Begin receiving on the returned chan Delivery before any other operation on the Connection or Channel. Continues deliveries to the returned chan Delivery until Channel.Cancel, Connection.Close, Channel.Close, context is cancelled, or an AMQP exception occurs. Consumers must range over the chan to ensure all deliveries are received. Unreceived deliveries will block all methods on the same connection. All deliveries in AMQP must be acknowledged. It is expected of the consumer to call Delivery.Ack after it has successfully processed the delivery. If the consumer is cancelled or the channel or connection is closed any unacknowledged deliveries will be requeued at the end of the same queue. The consumer is identified by a string that is unique and scoped for all consumers on this channel. If you wish to eventually cancel the consumer, use the same non-empty identifier in Channel.Cancel. An empty string will cause the library to generate a unique identity. The consumer identity will be included in every Delivery in the ConsumerTag field When autoAck (also known as noAck) is true, the server will acknowledge deliveries to this consumer prior to writing the delivery to the network. When autoAck is true, the consumer should not call Delivery.Ack. Automatically acknowledging deliveries means that some deliveries may get lost if the consumer is unable to process them after the server delivers them. See http://www.rabbitmq.com/confirms.html for more details. When exclusive is true, the server will ensure that this is the sole consumer from this queue. When exclusive is false, the server will fairly distribute deliveries across multiple consumers. The noLocal flag is not supported by RabbitMQ. It's advisable to use separate connections for Channel.Publish and Channel.Consume so not to have TCP pushback on publishing affect the ability to consume messages, so this parameter is here mostly for completeness. When noWait is true, do not wait for the server to confirm the request and immediately begin deliveries. If it is not possible to consume, a channel exception will be raised and the channel will be closed. Optional arguments can be provided that have specific semantics for the queue or server. Inflight messages, limited by Channel.Qos will be buffered until received from the returned chan. When the Channel or Connection is closed, all buffered and inflight messages will be dropped. RabbitMQ will requeue messages not acknowledged. In other words, dropped messages in this way won't be lost. */ func (ch *Channel) ConsumeWithContext(ctx context.Context, queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error) { // When we return from ch.call, there may be a delivery already for the // consumer that hasn't been added to the consumer hash yet. Because of // this, we never rely on the server picking a consumer tag for us. if err := args.Validate(); err != nil { return nil, err } if consumer == "" { consumer = uniqueConsumerTag() } req := &basicConsume{ Queue: queue, ConsumerTag: consumer, NoLocal: noLocal, NoAck: autoAck, Exclusive: exclusive, NoWait: noWait, Arguments: args, } res := &basicConsumeOk{} select { default: case <-ctx.Done(): return nil, ctx.Err() } deliveries := make(chan Delivery) ch.consumers.add(consumer, deliveries) if err := ch.call(req, res); err != nil { ch.consumers.cancel(consumer) return nil, err } go func() { select { case <-ch.consumers.closed: return case <-ctx.Done(): if ch != nil { _ = ch.Cancel(consumer, false) } } }() return deliveries, nil } /* ExchangeDeclare declares an exchange on the server. If the exchange does not already exist, the server will create it. If the exchange exists, the server verifies that it is of the provided type, durability and auto-delete flags. Errors returned from this method will close the channel. Exchange names starting with "amq." are reserved for pre-declared and standardized exchanges. The client MAY declare an exchange starting with "amq." if the passive option is set, or the exchange already exists. Names can consist of a non-empty sequence of letters, digits, hyphen, underscore, period, or colon. Each exchange belongs to one of a set of exchange kinds/types implemented by the server. The exchange types define the functionality of the exchange - i.e. how messages are routed through it. Once an exchange is declared, its type cannot be changed. The common types are "direct", "fanout", "topic" and "headers". Durable and Non-Auto-Deleted exchanges will survive server restarts and remain declared when there are no remaining bindings. This is the best lifetime for long-lived exchange configurations like stable routes and default exchanges. Non-Durable and Auto-Deleted exchanges will be deleted when there are no remaining bindings and not restored on server restart. This lifetime is useful for temporary topologies that should not pollute the virtual host on failure or after the consumers have completed. Non-Durable and Non-Auto-deleted exchanges will remain as long as the server is running including when there are no remaining bindings. This is useful for temporary topologies that may have long delays between bindings. Durable and Auto-Deleted exchanges will survive server restarts and will be removed before and after server restarts when there are no remaining bindings. These exchanges are useful for robust temporary topologies or when you require binding durable queues to auto-deleted exchanges. Note: RabbitMQ declares the default exchange types like 'amq.fanout' as durable, so queues that bind to these pre-declared exchanges must also be durable. Exchanges declared as `internal` do not accept publishings. Internal exchanges are useful when you wish to implement inter-exchange topologies that should not be exposed to users of the broker. When noWait is true, declare without waiting for a confirmation from the server. The channel may be closed as a result of an error. Add a NotifyClose listener to respond to any exceptions. Optional amqp.Table of arguments that are specific to the server's implementation of the exchange can be sent for exchange types that require extra parameters. */ func (ch *Channel) ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args Table) error { if err := args.Validate(); err != nil { return err } return ch.call( &exchangeDeclare{ Exchange: name, Type: kind, Passive: false, Durable: durable, AutoDelete: autoDelete, Internal: internal, NoWait: noWait, Arguments: args, }, &exchangeDeclareOk{}, ) } /* ExchangeDeclarePassive is functionally and parametrically equivalent to ExchangeDeclare, except that it sets the "passive" attribute to true. A passive exchange is assumed by RabbitMQ to already exist, and attempting to connect to a non-existent exchange will cause RabbitMQ to throw an exception. This function can be used to detect the existence of an exchange. */ func (ch *Channel) ExchangeDeclarePassive(name, kind string, durable, autoDelete, internal, noWait bool, args Table) error { if err := args.Validate(); err != nil { return err } return ch.call( &exchangeDeclare{ Exchange: name, Type: kind, Passive: true, Durable: durable, AutoDelete: autoDelete, Internal: internal, NoWait: noWait, Arguments: args, }, &exchangeDeclareOk{}, ) } /* ExchangeDelete removes the named exchange from the server. When an exchange is deleted all queue bindings on the exchange are also deleted. If this exchange does not exist, the channel will be closed with an error. When ifUnused is true, the server will only delete the exchange if it has no queue bindings. If the exchange has queue bindings the server does not delete it but close the channel with an exception instead. Set this to true if you are not the sole owner of the exchange. When noWait is true, do not wait for a server confirmation that the exchange has been deleted. Failing to delete the channel could close the channel. Add a NotifyClose listener to respond to these channel exceptions. */ func (ch *Channel) ExchangeDelete(name string, ifUnused, noWait bool) error { return ch.call( &exchangeDelete{ Exchange: name, IfUnused: ifUnused, NoWait: noWait, }, &exchangeDeleteOk{}, ) } /* ExchangeBind binds an exchange to another exchange to create inter-exchange routing topologies on the server. This can decouple the private topology and routing exchanges from exchanges intended solely for publishing endpoints. Binding two exchanges with identical arguments will not create duplicate bindings. Binding one exchange to another with multiple bindings will only deliver a message once. For example if you bind your exchange to `amq.fanout` with two different binding keys, only a single message will be delivered to your exchange even though multiple bindings will match. Given a message delivered to the source exchange, the message will be forwarded to the destination exchange when the routing key is matched. ExchangeBind("sell", "MSFT", "trade", false, nil) ExchangeBind("buy", "AAPL", "trade", false, nil) Delivery Source Key Destination example exchange exchange ----------------------------------------------- key: AAPL --> trade ----> MSFT sell \---> AAPL --> buy When noWait is true, do not wait for the server to confirm the binding. If any error occurs the channel will be closed. Add a listener to NotifyClose to handle these errors. Optional arguments specific to the exchanges bound can also be specified. */ func (ch *Channel) ExchangeBind(destination, key, source string, noWait bool, args Table) error { if err := args.Validate(); err != nil { return err } return ch.call( &exchangeBind{ Destination: destination, Source: source, RoutingKey: key, NoWait: noWait, Arguments: args, }, &exchangeBindOk{}, ) } /* ExchangeUnbind unbinds the destination exchange from the source exchange on the server by removing the routing key between them. This is the inverse of ExchangeBind. If the binding does not currently exist, an error will be returned. When noWait is true, do not wait for the server to confirm the deletion of the binding. If any error occurs the channel will be closed. Add a listener to NotifyClose to handle these errors. Optional arguments that are specific to the type of exchanges bound can also be provided. These must match the same arguments specified in ExchangeBind to identify the binding. */ func (ch *Channel) ExchangeUnbind(destination, key, source string, noWait bool, args Table) error { if err := args.Validate(); err != nil { return err } return ch.call( &exchangeUnbind{ Destination: destination, Source: source, RoutingKey: key, NoWait: noWait, Arguments: args, }, &exchangeUnbindOk{}, ) } /* Publish sends a Publishing from the client to an exchange on the server. When you want a single message to be delivered to a single queue, you can publish to the default exchange with the routingKey of the queue name. This is because every declared queue gets an implicit route to the default exchange. Since publishings are asynchronous, any undeliverable message will get returned by the server. Add a listener with Channel.NotifyReturn to handle any undeliverable message when calling publish with either the mandatory or immediate parameters as true. Publishings can be undeliverable when the mandatory flag is true and no queue is bound that matches the routing key, or when the immediate flag is true and no consumer on the matched queue is ready to accept the delivery. This can return an error when the channel, connection or socket is closed. The error or lack of an error does not indicate whether the server has received this publishing. It is possible for publishing to not reach the broker if the underlying socket is shut down without pending publishing packets being flushed from the kernel buffers. The easy way of making it probable that all publishings reach the server is to always call Connection.Close before terminating your publishing application. The way to ensure that all publishings reach the server is to add a listener to Channel.NotifyPublish and put the channel in confirm mode with Channel.Confirm. Publishing delivery tags and their corresponding confirmations start at 1. Exit when all publishings are confirmed. When Publish does not return an error and the channel is in confirm mode, the internal counter for DeliveryTags with the first confirmation starts at 1. */ func (ch *Channel) Publish(exchange, key string, mandatory, immediate bool, msg Publishing) error { _, err := ch.PublishWithDeferredConfirm(exchange, key, mandatory, immediate, msg) return err } /* PublishWithContext sends a Publishing from the client to an exchange on the server. NOTE: this function is equivalent to [Channel.Publish]. Context is not honoured. When you want a single message to be delivered to a single queue, you can publish to the default exchange with the routingKey of the queue name. This is because every declared queue gets an implicit route to the default exchange. Since publishings are asynchronous, any undeliverable message will get returned by the server. Add a listener with Channel.NotifyReturn to handle any undeliverable message when calling publish with either the mandatory or immediate parameters as true. Publishings can be undeliverable when the mandatory flag is true and no queue is bound that matches the routing key, or when the immediate flag is true and no consumer on the matched queue is ready to accept the delivery. This can return an error when the channel, connection or socket is closed. The error or lack of an error does not indicate whether the server has received this publishing. It is possible for publishing to not reach the broker if the underlying socket is shut down without pending publishing packets being flushed from the kernel buffers. The easy way of making it probable that all publishings reach the server is to always call Connection.Close before terminating your publishing application. The way to ensure that all publishings reach the server is to add a listener to Channel.NotifyPublish and put the channel in confirm mode with Channel.Confirm. Publishing delivery tags and their corresponding confirmations start at 1. Exit when all publishings are confirmed. When Publish does not return an error and the channel is in confirm mode, the internal counter for DeliveryTags with the first confirmation starts at 1. */ func (ch *Channel) PublishWithContext(_ context.Context, exchange, key string, mandatory, immediate bool, msg Publishing) error { return ch.Publish(exchange, key, mandatory, immediate, msg) } /* PublishWithDeferredConfirm behaves identically to Publish, but additionally returns a DeferredConfirmation, allowing the caller to wait on the publisher confirmation for this message. If the channel has not been put into confirm mode, the DeferredConfirmation will be nil. */ func (ch *Channel) PublishWithDeferredConfirm(exchange, key string, mandatory, immediate bool, msg Publishing) (*DeferredConfirmation, error) { if err := msg.Headers.Validate(); err != nil { return nil, err } ch.m.Lock() defer ch.m.Unlock() var dc *DeferredConfirmation if ch.confirming { dc = ch.confirms.publish() } if err := ch.send(&basicPublish{ Exchange: exchange, RoutingKey: key, Mandatory: mandatory, Immediate: immediate, Body: msg.Body, Properties: properties{ Headers: msg.Headers, ContentType: msg.ContentType, ContentEncoding: msg.ContentEncoding, DeliveryMode: msg.DeliveryMode, Priority: msg.Priority, CorrelationId: msg.CorrelationId, ReplyTo: msg.ReplyTo, Expiration: msg.Expiration, MessageId: msg.MessageId, Timestamp: msg.Timestamp, Type: msg.Type, UserId: msg.UserId, AppId: msg.AppId, }, }); err != nil { if ch.confirming { ch.confirms.unpublish() } return nil, err } return dc, nil } /* PublishWithDeferredConfirmWithContext behaves identically to Publish but additionally returns a DeferredConfirmation, allowing the caller to wait on the publisher confirmation for this message. If the channel has not been put into confirm mode, the DeferredConfirmation will be nil. NOTE: PublishWithDeferredConfirmWithContext is equivalent to its non-context variant. The context passed to this function is not honoured. */ func (ch *Channel) PublishWithDeferredConfirmWithContext(_ context.Context, exchange, key string, mandatory, immediate bool, msg Publishing) (*DeferredConfirmation, error) { return ch.PublishWithDeferredConfirm(exchange, key, mandatory, immediate, msg) } /* Get synchronously receives a single Delivery from the head of a queue from the server to the client. In almost all cases, using Channel.Consume will be preferred. If there was a delivery waiting on the queue and that delivery was received, the second return value will be true. If there was no delivery waiting or an error occurred, the ok bool will be false. All deliveries must be acknowledged including those from Channel.Get. Call Delivery.Ack on the returned delivery when you have fully processed this delivery. When autoAck is true, the server will automatically acknowledge this message so you don't have to. But if you are unable to fully process this message before the channel or connection is closed, the message will not get requeued. */ func (ch *Channel) Get(queue string, autoAck bool) (msg Delivery, ok bool, err error) { req := &basicGet{Queue: queue, NoAck: autoAck} res := &basicGetOk{} empty := &basicGetEmpty{} if err := ch.call(req, res, empty); err != nil { return Delivery{}, false, err } if res.DeliveryTag > 0 { return *(newDelivery(ch, res)), true, nil } return Delivery{}, false, nil } /* Tx puts the channel into transaction mode on the server. All publishings and acknowledgments following this method will be atomically committed or rolled back for a single queue. Call either Channel.TxCommit or Channel.TxRollback to leave a this transaction and immediately start a new transaction. The atomicity across multiple queues is not defined as queue declarations and bindings are not included in the transaction. The behavior of publishings that are delivered as mandatory or immediate while the channel is in a transaction is not defined. Once a channel has been put into transaction mode, it cannot be taken out of transaction mode. Use a different channel for non-transactional semantics. */ func (ch *Channel) Tx() error { return ch.call( &txSelect{}, &txSelectOk{}, ) } /* TxCommit atomically commits all publishings and acknowledgments for a single queue and immediately start a new transaction. Calling this method without having called Channel.Tx is an error. */ func (ch *Channel) TxCommit() error { return ch.call( &txCommit{}, &txCommitOk{}, ) } /* TxRollback atomically rolls back all publishings and acknowledgments for a single queue and immediately start a new transaction. Calling this method without having called Channel.Tx is an error. */ func (ch *Channel) TxRollback() error { return ch.call( &txRollback{}, &txRollbackOk{}, ) } /* Flow pauses the delivery of messages to consumers on this channel. Channels are opened with flow control active, to open a channel with paused deliveries immediately call this method with `false` after calling Connection.Channel. When active is `false`, this method asks the server to temporarily pause deliveries until called again with active as `true`. Channel.Get methods will not be affected by flow control. This method is not intended to act as window control. Use Channel.Qos to limit the number of unacknowledged messages or bytes in flight instead. The server may also send us flow methods to throttle our publishings. A well behaving publishing client should add a listener with Channel.NotifyFlow and pause its publishings when `false` is sent on that channel. Note: RabbitMQ prefers to use TCP push back to control flow for all channels on a connection, so under high volume scenarios, it's wise to open separate Connections for publishings and deliveries. */ func (ch *Channel) Flow(active bool) error { return ch.call( &channelFlow{Active: active}, &channelFlowOk{}, ) } /* Confirm puts this channel into confirm mode so that the client can ensure all publishings have successfully been received by the server. After entering this mode, the server will send a basic.ack or basic.nack message with the deliver tag set to a 1 based incremental index corresponding to every publishing received after the this method returns. Add a listener to Channel.NotifyPublish to respond to the Confirmations. If Channel.NotifyPublish is not called, the Confirmations will be silently ignored. The order of acknowledgments is not bound to the order of deliveries. Ack and Nack confirmations will arrive at some point in the future. Unroutable mandatory or immediate messages are acknowledged immediately after any Channel.NotifyReturn listeners have been notified. Other messages are acknowledged when all queues that should have the message routed to them have either received acknowledgment of delivery or have enqueued the message, persisting the message if necessary. When noWait is true, the client will not wait for a response. A channel exception could occur if the server does not support this method. */ func (ch *Channel) Confirm(noWait bool) error { if err := ch.call( &confirmSelect{Nowait: noWait}, &confirmSelectOk{}, ); err != nil { return err } ch.confirmM.Lock() ch.confirming = true ch.confirmM.Unlock() return nil } /* Recover redelivers all unacknowledged deliveries on this channel. When requeue is false, messages will be redelivered to the original consumer. When requeue is true, messages will be redelivered to any available consumer, potentially including the original. If the deliveries cannot be recovered, an error will be returned and the channel will be closed. Note: this method is not implemented on RabbitMQ, use Delivery.Nack instead Deprecated: This method is deprecated in RabbitMQ. RabbitMQ used Recover(true) as a mechanism for consumers to tell the broker that they were ready for more deliveries, back in 2008-2009. Support for this will be removed from RabbitMQ in a future release. Use Nack() with requeue=true instead. */ func (ch *Channel) Recover(requeue bool) error { return ch.call( &basicRecover{Requeue: requeue}, &basicRecoverOk{}, ) } /* Ack acknowledges a delivery by its delivery tag when having been consumed with Channel.Consume or Channel.Get. Ack acknowledges all message received prior to the delivery tag when multiple is true. See also Delivery.Ack */ func (ch *Channel) Ack(tag uint64, multiple bool) error { ch.m.Lock() defer ch.m.Unlock() return ch.send(&basicAck{ DeliveryTag: tag, Multiple: multiple, }) } /* Nack negatively acknowledges a delivery by its delivery tag. Prefer this method to notify the server that you were not able to process this delivery and it must be redelivered or dropped. See also Delivery.Nack */ func (ch *Channel) Nack(tag uint64, multiple bool, requeue bool) error { ch.m.Lock() defer ch.m.Unlock() return ch.send(&basicNack{ DeliveryTag: tag, Multiple: multiple, Requeue: requeue, }) } /* Reject negatively acknowledges a delivery by its delivery tag. Prefer Nack over Reject when communicating with a RabbitMQ server because you can Nack multiple messages, reducing the amount of protocol messages to exchange. See also Delivery.Reject */ func (ch *Channel) Reject(tag uint64, requeue bool) error { ch.m.Lock() defer ch.m.Unlock() return ch.send(&basicReject{ DeliveryTag: tag, Requeue: requeue, }) } // GetNextPublishSeqNo returns the sequence number of the next message to be // published, when in confirm mode. func (ch *Channel) GetNextPublishSeqNo() uint64 { ch.confirms.publishedMut.Lock() defer ch.confirms.publishedMut.Unlock() return ch.confirms.published + 1 } golang-github-rabbitmq-amqp091-go-1.10.0/client_test.go000066400000000000000000000532351462444370000225700ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "bytes" "context" "io" "reflect" "testing" "time" ) type server struct { *testing.T r reader // framer <- client w writer // framer -> client S io.ReadWriteCloser // Server IO C io.ReadWriteCloser // Client IO // captured client frames start connectionStartOk tune connectionTuneOk } var defaultLogin = "guest" var defaultPassword = "guest" var defaultPlainAuth = &PlainAuth{defaultLogin, defaultPassword} var defaultAMQPlainAuth = &AMQPlainAuth{defaultLogin, defaultPassword} func defaultConfigWithAuth(auth Authentication) Config { return Config{ SASL: []Authentication{auth}, Vhost: "/", Locale: defaultLocale, } } func defaultConfig() Config { return defaultConfigWithAuth(defaultPlainAuth) } func amqplainConfig() Config { return defaultConfigWithAuth(defaultAMQPlainAuth) } func newServer(t *testing.T, serverIO, clientIO io.ReadWriteCloser) *server { return &server{ T: t, r: reader{serverIO}, w: writer{serverIO}, S: serverIO, C: clientIO, } } func newSession(t *testing.T) (io.ReadWriteCloser, *server) { rs, wc := io.Pipe() rc, ws := io.Pipe() rws := &logIO{t, "server", pipe{rs, ws}} rwc := &logIO{t, "client", pipe{rc, wc}} return rwc, newServer(t, rws, rwc) } func (t *server) expectBytes(b []byte) { in := make([]byte, len(b)) if _, err := io.ReadFull(t.S, in); err != nil { t.Fatalf("io error expecting bytes: %v", err) } if !bytes.Equal(b, in) { t.Fatalf("failed bytes: expected: %s got: %s", string(b), string(in)) } } func (t *server) send(channel int, m message) { defer time.AfterFunc(time.Second, func() { t.Fatalf("send deadlock") }).Stop() if msg, ok := m.(messageWithContent); ok { props, body := msg.getContent() class, _ := msg.id() if err := t.w.WriteFrame(&methodFrame{ ChannelId: uint16(channel), Method: msg, }); err != nil { t.Fatalf("WriteFrame error: %v", err) } if err := t.w.WriteFrame(&headerFrame{ ChannelId: uint16(channel), ClassId: class, Size: uint64(len(body)), Properties: props, }); err != nil { t.Fatalf("WriteFrame error: %v", err) } if err := t.w.WriteFrame(&bodyFrame{ ChannelId: uint16(channel), Body: body, }); err != nil { t.Fatalf("WriteFrame error: %v", err) } } else { if err := t.w.WriteFrame(&methodFrame{ ChannelId: uint16(channel), Method: m, }); err != nil { t.Fatalf("WriteFrame error: %v", err) } } } // drops all but method frames expected on the given channel func (t *server) recv(channel int, m message) message { defer time.AfterFunc(time.Second, func() { t.Fatalf("recv deadlock") }).Stop() var remaining int var header *headerFrame var body []byte for { frame, err := t.r.ReadFrame() if err != nil { t.Fatalf("frame err, read: %s", err) } if frame.channel() != uint16(channel) { t.Fatalf("expected frame on channel %d, got channel %d", channel, frame.channel()) } switch f := frame.(type) { case *heartbeatFrame: // drop case *headerFrame: // start content state header = f remaining = int(header.Size) if remaining == 0 { m.(messageWithContent).setContent(header.Properties, nil) return m } case *bodyFrame: // continue until terminated body = append(body, f.Body...) remaining -= len(f.Body) if remaining <= 0 { m.(messageWithContent).setContent(header.Properties, body) return m } case *methodFrame: if reflect.TypeOf(m) == reflect.TypeOf(f.Method) { wantv := reflect.ValueOf(m).Elem() havev := reflect.ValueOf(f.Method).Elem() wantv.Set(havev) if _, ok := m.(messageWithContent); !ok { return m } } else { t.Fatalf("expected method type: %T, got: %T", m, f.Method) } default: t.Fatalf("unexpected frame: %+v", f) } } } func (t *server) expectAMQP() { t.expectBytes([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1}) } func (t *server) connectionStartWithMechanisms(mechs string, recv bool) { t.send(0, &connectionStart{ VersionMajor: 0, VersionMinor: 9, Mechanisms: mechs, Locales: defaultLocale, }) if recv { t.recv(0, &t.start) } } func (t *server) connectionStart() { t.connectionStartWithMechanisms("PLAIN", true) } func (t *server) connectionTune() { t.send(0, &connectionTune{ ChannelMax: 11, FrameMax: 20000, Heartbeat: 10, }) t.recv(0, &t.tune) } func (t *server) connectionOpen() { t.expectAMQP() t.connectionStart() t.connectionTune() t.recv(0, &connectionOpen{}) t.send(0, &connectionOpenOk{}) } func (t *server) connectionClose() { t.recv(0, &connectionClose{}) t.send(0, &connectionCloseOk{}) } func (t *server) channelOpen(id int) { t.recv(id, &channelOpen{}) t.send(id, &channelOpenOk{}) } func TestDefaultClientProperties(t *testing.T) { rwc, srv := newSession(t) t.Cleanup(func() { rwc.Close() }) go func() { srv.connectionOpen() }() if c, err := Open(rwc, defaultConfig()); err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } if want, got := defaultProduct, srv.start.ClientProperties["product"]; want != got { t.Errorf("expected product %s got: %s", want, got) } if want, got := buildVersion, srv.start.ClientProperties["version"]; want != got { t.Errorf("expected version %s got: %s", want, got) } if want, got := defaultLocale, srv.start.Locale; want != got { t.Errorf("expected locale %s got: %s", want, got) } } func TestCustomClientProperties(t *testing.T) { rwc, srv := newSession(t) t.Cleanup(func() { rwc.Close() }) config := defaultConfig() config.Properties = Table{ "product": "foo", "version": "1.0", } go func() { srv.connectionOpen() }() if c, err := Open(rwc, config); err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } if want, got := config.Properties["product"], srv.start.ClientProperties["product"]; want != got { t.Errorf("expected product %s got: %s", want, got) } if want, got := config.Properties["version"], srv.start.ClientProperties["version"]; want != got { t.Errorf("expected version %s got: %s", want, got) } } func TestOpen(t *testing.T) { rwc, srv := newSession(t) t.Cleanup(func() { rwc.Close() }) go func() { srv.connectionOpen() }() if c, err := Open(rwc, defaultConfig()); err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } } func TestOpenClose_ShouldNotPanic(t *testing.T) { rwc, srv := newSession(t) t.Cleanup(func() { _ = rwc.Close() }) go func() { srv.connectionOpen() srv.connectionClose() }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } if err := c.Close(); err != nil { t.Fatalf("could not close connection: %s", err) } defer func() { if r := recover(); r != nil { t.Fatalf("creating a channel on a closed connection should not panic: %s", r) } }() ch, err := c.Channel() if ch != nil { t.Fatalf("creating a channel on a closed connection should not succeed: %v, (%s)", ch, err) } if err != ErrClosed { t.Fatalf("error should be closed: %s", err) } } func TestChannelOpen(t *testing.T) { rwc, srv := newSession(t) t.Cleanup(func() { rwc.Close() }) go func() { srv.connectionOpen() srv.channelOpen(1) }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } } func TestOpenFailedSASLUnsupportedMechanisms(t *testing.T) { rwc, srv := newSession(t) t.Cleanup(func() { rwc.Close() }) go func() { srv.expectAMQP() srv.connectionStartWithMechanisms("KERBEROS NTLM", false) }() c, err := Open(rwc, defaultConfig()) if err != ErrSASL { t.Fatalf("expected ErrSASL got: %+v on %+v", err, c) } } func TestOpenAMQPlainAuth(t *testing.T) { auth := make(chan Table) rwc, srv := newSession(t) t.Cleanup(func() { rwc.Close() }) go func() { srv.expectAMQP() srv.connectionStartWithMechanisms("AMQPLAIN", true) var authresp bytes.Buffer _ = writeLongstr(&authresp, srv.start.Response) table, _ := readTable(&authresp) srv.connectionTune() srv.recv(0, &connectionOpen{}) srv.send(0, &connectionOpenOk{}) auth <- table }() if c, err := Open(rwc, amqplainConfig()); err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } table := <-auth if table["LOGIN"] != defaultLogin { t.Fatalf("unexpected login: want: %s, got: %s", defaultLogin, table["LOGIN"]) } if table["PASSWORD"] != defaultPassword { t.Fatalf("unexpected password: want: %s, got: %s", defaultPassword, table["PASSWORD"]) } } func TestOpenFailedCredentials(t *testing.T) { rwc, srv := newSession(t) go func() { srv.expectAMQP() srv.connectionStart() // Now kill/timeout the connection indicating bad auth rwc.Close() }() c, err := Open(rwc, defaultConfig()) if err != ErrCredentials { t.Fatalf("expected ErrCredentials got: %+v on %+v", err, c) } } func TestOpenFailedVhost(t *testing.T) { rwc, srv := newSession(t) go func() { srv.expectAMQP() srv.connectionStart() srv.connectionTune() srv.recv(0, &connectionOpen{}) // Now kill/timeout the connection on bad Vhost rwc.Close() }() c, err := Open(rwc, defaultConfig()) if err != ErrVhost { t.Fatalf("expected ErrVhost got: %+v on %+v", err, c) } } func TestConfirmMultipleOrdersDeliveryTags(t *testing.T) { rwc, srv := newSession(t) defer rwc.Close() go func() { srv.connectionOpen() srv.channelOpen(1) srv.recv(1, &confirmSelect{}) srv.send(1, &confirmSelectOk{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) // Single tag, plus multiple, should produce // 2, 1, 3, 4 srv.send(1, &basicAck{DeliveryTag: 2}) srv.send(1, &basicAck{DeliveryTag: 1}) srv.send(1, &basicAck{DeliveryTag: 4, Multiple: true}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) // And some more, but in reverse order, multiple then one // 5, 6, 7, 8 srv.send(1, &basicAck{DeliveryTag: 6, Multiple: true}) srv.send(1, &basicAck{DeliveryTag: 8}) srv.send(1, &basicAck{DeliveryTag: 7}) }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } confirm := ch.NotifyPublish(make(chan Confirmation)) err = ch.Confirm(false) if err != nil { t.Fatalf("channel error setting confirm mode: %v (%s)", ch, err) } go func() { var e error if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 1")}); e != nil { t.Errorf("publish error: %v", err) } if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 2")}); e != nil { t.Errorf("publish error: %v", err) } if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 3")}); e != nil { t.Errorf("publish error: %v", err) } if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 4")}); e != nil { t.Errorf("publish error: %v", err) } }() // received out of order, consumed in order for i, tag := range []uint64{1, 2, 3, 4} { if ack := <-confirm; tag != ack.DeliveryTag { t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack.DeliveryTag) } } go func() { var e error if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 5")}); e != nil { t.Errorf("publish error: %v", err) } if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 6")}); e != nil { t.Errorf("publish error: %v", err) } if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 7")}); e != nil { t.Errorf("publish error: %v", err) } if e = ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub 8")}); e != nil { t.Errorf("publish error: %v", err) } }() for i, tag := range []uint64{5, 6, 7, 8} { if ack := <-confirm; tag != ack.DeliveryTag { t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack.DeliveryTag) } } } func TestDeferredConfirmations(t *testing.T) { rwc, srv := newSession(t) defer rwc.Close() go func() { srv.connectionOpen() srv.channelOpen(1) srv.recv(1, &confirmSelect{}) srv.send(1, &confirmSelectOk{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) srv.recv(1, &basicPublish{}) }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } err = ch.Confirm(false) if err != nil { t.Fatalf("channel error setting confirm mode: %v (%s)", ch, err) } var results []*DeferredConfirmation for i := 1; i < 5; i++ { dc, err := ch.PublishWithDeferredConfirmWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("pub")}) if err != nil { t.Fatalf("failed to PublishWithDeferredConfirm: %v", err) } results = append(results, dc) } acks := make(chan Confirmation, 4) for _, result := range results { go func(r *DeferredConfirmation) { acks <- Confirmation{Ack: r.Wait(), DeliveryTag: r.DeliveryTag} }(result) } // received out of order, consumed out of order assertReceive := func(ack Confirmation, tags ...uint64) { for _, tag := range tags { if tag == ack.DeliveryTag { return } } t.Fatalf("failed ack, expected ack to be in set %v, got %d", tags, ack.DeliveryTag) } srv.send(1, &basicAck{DeliveryTag: 2}) assertReceive(<-acks, 2) srv.send(1, &basicAck{DeliveryTag: 1}) assertReceive(<-acks, 1) srv.send(1, &basicAck{DeliveryTag: 4, Multiple: true}) assertReceive(<-acks, 3, 4) // 3 and 4 are non-determistic due to map ordering assertReceive(<-acks, 3, 4) } func TestNotifyClosesReusedPublisherConfirmChan(t *testing.T) { rwc, srv := newSession(t) go func() { srv.connectionOpen() srv.channelOpen(1) srv.recv(1, &confirmSelect{}) srv.send(1, &confirmSelectOk{}) srv.recv(0, &connectionClose{}) srv.send(0, &connectionCloseOk{}) }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } ackAndNack := make(chan uint64) ch.NotifyConfirm(ackAndNack, ackAndNack) if err := ch.Confirm(false); err != nil { t.Fatalf("expected to enter confirm mode: %v", err) } if err := c.Close(); err != nil { t.Fatalf("could not close connection: %v (%s)", c, err) } } func TestNotifyClosesAllChansAfterConnectionClose(t *testing.T) { rwc, srv := newSession(t) go func() { srv.connectionOpen() srv.channelOpen(1) srv.recv(0, &connectionClose{}) srv.send(0, &connectionCloseOk{}) }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } if err := c.Close(); err != nil { t.Fatalf("could not close connection: %v (%s)", c, err) } select { case <-c.NotifyClose(make(chan *Error)): case <-time.After(time.Millisecond): t.Errorf("expected to close NotifyClose chan after Connection.Close") } select { case <-ch.NotifyClose(make(chan *Error)): case <-time.After(time.Millisecond): t.Errorf("expected to close Connection.NotifyClose chan after Connection.Close") } select { case <-ch.NotifyFlow(make(chan bool)): case <-time.After(time.Millisecond): t.Errorf("expected to close Channel.NotifyFlow chan after Connection.Close") } select { case <-ch.NotifyCancel(make(chan string)): case <-time.After(time.Millisecond): t.Errorf("expected to close Channel.NofityCancel chan after Connection.Close") } select { case <-ch.NotifyReturn(make(chan Return)): case <-time.After(time.Millisecond): t.Errorf("expected to close Channel.NotifyReturn chan after Connection.Close") } confirms := ch.NotifyPublish(make(chan Confirmation)) select { case <-confirms: case <-time.After(time.Millisecond): t.Errorf("expected to close confirms on Channel.NotifyPublish chan after Connection.Close") } } // Should not panic when sending bodies split at different boundaries func TestPublishBodySliceIssue74(t *testing.T) { rwc, srv := newSession(t) defer rwc.Close() const frameSize = 100 const publishings = frameSize * 3 done := make(chan bool) base := make([]byte, publishings) go func() { srv.connectionOpen() srv.channelOpen(1) for i := 0; i < publishings; i++ { srv.recv(1, &basicPublish{}) } done <- true }() cfg := defaultConfig() cfg.FrameSize = frameSize c, err := Open(rwc, cfg) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } for i := 0; i < publishings; i++ { go func(ii int) { if err := ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: base[0:ii]}); err != nil { t.Errorf("publish error: %v", err) } }(i) } <-done } // Should not panic when server and client have frame_size of 0 func TestPublishZeroFrameSizeIssue161(t *testing.T) { rwc, srv := newSession(t) defer rwc.Close() const frameSize = 0 const publishings = 1 done := make(chan bool) go func() { srv.connectionOpen() srv.channelOpen(1) for i := 0; i < publishings; i++ { srv.recv(1, &basicPublish{}) } done <- true }() cfg := defaultConfig() cfg.FrameSize = frameSize c, err := Open(rwc, cfg) // override the tuned framesize with a hard 0, as would happen when rabbit is configured with 0 c.Config.FrameSize = frameSize if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } for i := 0; i < publishings; i++ { go func() { if err := ch.PublishWithContext(context.TODO(), "", "q", false, false, Publishing{Body: []byte("anything")}); err != nil { t.Errorf("publish error: %v", err) } }() } <-done } func TestPublishAndShutdownDeadlockIssue84(t *testing.T) { rwc, srv := newSession(t) defer rwc.Close() go func() { srv.connectionOpen() srv.channelOpen(1) srv.recv(1, &basicPublish{}) // Mimic a broken io pipe so that Publish catches the error and goes into shutdown srv.S.Close() }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("couldn't create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("couldn't open channel: %v (%s)", ch, err) } defer time.AfterFunc(500*time.Millisecond, func() { t.Fatalf("Publish deadlock") }).Stop() for { if err := ch.PublishWithContext(context.TODO(), "exchange", "q", false, false, Publishing{Body: []byte("test")}); err != nil { t.Log("successfully caught disconnect error", err) return } } } // TestChannelReturnsCloseRace ensures that receiving a basicReturn frame and // sending the notification to the bound channel does not race with // channel.shutdown() which closes all registered notification channels - checks // for a "send on closed channel" panic func TestChannelReturnsCloseRace(t *testing.T) { defer time.AfterFunc(5*time.Second, func() { t.Fatalf("Shutdown deadlock") }).Stop() ch := newChannel(&Connection{}, 1) // Register a channel to close in channel.shutdown() notify := make(chan Return, 1) ch.NotifyReturn(notify) go func() { for range notify { // Drain notifications } }() // Simulate receiving a load of returns (triggering a write to the above // channel) while we call shutdown concurrently go func() { for i := 0; i < 100; i++ { ch.dispatch(&basicReturn{}) } }() ch.shutdown(nil) } // TestLeakClosedConsumersIssue264 ensures that closing a consumer with // prefetched messages does not leak the buffering goroutine. func TestLeakClosedConsumersIssue264(t *testing.T) { const tag = "consumer-tag" rwc, srv := newSession(t) defer rwc.Close() go func() { srv.connectionOpen() srv.channelOpen(1) srv.recv(1, &basicQos{}) srv.send(1, &basicQosOk{}) srv.recv(1, &basicConsume{}) srv.send(1, &basicConsumeOk{ConsumerTag: tag}) // This delivery is intended to be consumed srv.send(1, &basicDeliver{ConsumerTag: tag, DeliveryTag: 1}) // This delivery is intended to be dropped srv.send(1, &basicDeliver{ConsumerTag: tag, DeliveryTag: 2}) srv.recv(0, &connectionClose{}) srv.send(0, &connectionCloseOk{}) srv.C.Close() }() c, err := Open(rwc, defaultConfig()) if err != nil { t.Fatalf("could not create connection: %v (%s)", c, err) } ch, err := c.Channel() if err != nil { t.Fatalf("could not open channel: %v (%s)", ch, err) } err = ch.Qos(2, 0, false) if err != nil { t.Fatalf("channel Qos error: %v (%s)", ch, err) } consumer, err := ch.Consume("queue", tag, false, false, false, false, nil) if err != nil { t.Fatalf("unexpected error during consumer: %v", err) } first := <-consumer if want, got := uint64(1), first.DeliveryTag; want != got { t.Fatalf("unexpected delivery tag: want: %d, got: %d", want, got) } if err := c.Close(); err != nil { t.Fatalf("unexpected error during connection close: %v", err) } if _, open := <-consumer; open { t.Fatalf("expected deliveries channel to be closed immediately when the connection is closed so not to leak the bufferDeliveries goroutine") } } golang-github-rabbitmq-amqp091-go-1.10.0/confirms.go000066400000000000000000000134611462444370000220700ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "context" "sync" ) // confirms resequences and notifies one or multiple publisher confirmation listeners type confirms struct { m sync.Mutex listeners []chan Confirmation sequencer map[uint64]Confirmation deferredConfirmations *deferredConfirmations published uint64 publishedMut sync.Mutex expecting uint64 } // newConfirms allocates a confirms func newConfirms() *confirms { return &confirms{ sequencer: map[uint64]Confirmation{}, deferredConfirmations: newDeferredConfirmations(), published: 0, expecting: 1, } } func (c *confirms) Listen(l chan Confirmation) { c.m.Lock() defer c.m.Unlock() c.listeners = append(c.listeners, l) } // Publish increments the publishing counter func (c *confirms) publish() *DeferredConfirmation { c.publishedMut.Lock() defer c.publishedMut.Unlock() c.published++ return c.deferredConfirmations.Add(c.published) } // unpublish decrements the publishing counter and removes the // DeferredConfirmation. It must be called immediately after a publish fails. func (c *confirms) unpublish() { c.publishedMut.Lock() defer c.publishedMut.Unlock() c.deferredConfirmations.remove(c.published) c.published-- } // confirm confirms one publishing, increments the expecting delivery tag, and // removes bookkeeping for that delivery tag. func (c *confirms) confirm(confirmation Confirmation) { delete(c.sequencer, c.expecting) c.expecting++ for _, l := range c.listeners { l <- confirmation } } // resequence confirms any out of order delivered confirmations func (c *confirms) resequence() { c.publishedMut.Lock() defer c.publishedMut.Unlock() for c.expecting <= c.published { sequenced, found := c.sequencer[c.expecting] if !found { return } c.confirm(sequenced) } } // One confirms one publishing and all following in the publishing sequence func (c *confirms) One(confirmed Confirmation) { c.m.Lock() defer c.m.Unlock() c.deferredConfirmations.Confirm(confirmed) if c.expecting == confirmed.DeliveryTag { c.confirm(confirmed) } else { c.sequencer[confirmed.DeliveryTag] = confirmed } c.resequence() } // Multiple confirms all publishings up until the delivery tag func (c *confirms) Multiple(confirmed Confirmation) { c.m.Lock() defer c.m.Unlock() c.deferredConfirmations.ConfirmMultiple(confirmed) for c.expecting <= confirmed.DeliveryTag { c.confirm(Confirmation{c.expecting, confirmed.Ack}) } c.resequence() } // Cleans up the confirms struct and its dependencies. // Closes all listeners, discarding any out of sequence confirmations func (c *confirms) Close() error { c.m.Lock() defer c.m.Unlock() c.deferredConfirmations.Close() for _, l := range c.listeners { close(l) } c.listeners = nil return nil } type deferredConfirmations struct { m sync.Mutex confirmations map[uint64]*DeferredConfirmation } func newDeferredConfirmations() *deferredConfirmations { return &deferredConfirmations{ confirmations: map[uint64]*DeferredConfirmation{}, } } func (d *deferredConfirmations) Add(tag uint64) *DeferredConfirmation { d.m.Lock() defer d.m.Unlock() dc := &DeferredConfirmation{DeliveryTag: tag} dc.done = make(chan struct{}) d.confirmations[tag] = dc return dc } // remove is only used to drop a tag whose publish failed func (d *deferredConfirmations) remove(tag uint64) { d.m.Lock() defer d.m.Unlock() dc, found := d.confirmations[tag] if !found { return } close(dc.done) delete(d.confirmations, tag) } func (d *deferredConfirmations) Confirm(confirmation Confirmation) { d.m.Lock() defer d.m.Unlock() dc, found := d.confirmations[confirmation.DeliveryTag] if !found { // We should never receive a confirmation for a tag that hasn't // been published, but a test causes this to happen. return } dc.setAck(confirmation.Ack) delete(d.confirmations, confirmation.DeliveryTag) } func (d *deferredConfirmations) ConfirmMultiple(confirmation Confirmation) { d.m.Lock() defer d.m.Unlock() for k, v := range d.confirmations { if k <= confirmation.DeliveryTag { v.setAck(confirmation.Ack) delete(d.confirmations, k) } } } // Close nacks all pending DeferredConfirmations being blocked by dc.Wait(). func (d *deferredConfirmations) Close() { d.m.Lock() defer d.m.Unlock() for k, v := range d.confirmations { v.setAck(false) delete(d.confirmations, k) } } // setAck sets the acknowledgement status of the confirmation. Note that it must // not be called more than once. func (d *DeferredConfirmation) setAck(ack bool) { d.ack = ack close(d.done) } // Done returns the channel that can be used to wait for the publisher // confirmation. func (d *DeferredConfirmation) Done() <-chan struct{} { return d.done } // Acked returns the publisher confirmation in a non-blocking manner. It returns // false if the confirmation was not acknowledged yet or received negative // acknowledgement. func (d *DeferredConfirmation) Acked() bool { select { case <-d.done: default: return false } return d.ack } // Wait blocks until the publisher confirmation. It returns true if the server // successfully received the publishing. func (d *DeferredConfirmation) Wait() bool { <-d.done return d.ack } // WaitContext waits until the publisher confirmation. It returns true if the // server successfully received the publishing. If the context expires before // that, ctx.Err() is returned. func (d *DeferredConfirmation) WaitContext(ctx context.Context) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() case <-d.done: } return d.ack, nil } golang-github-rabbitmq-amqp091-go-1.10.0/confirms_test.go000066400000000000000000000144451462444370000231320ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "context" "sync" "testing" "time" ) func TestConfirmOneResequences(t *testing.T) { var ( fixtures = []Confirmation{ {1, true}, {2, false}, {3, true}, } c = newConfirms() l = make(chan Confirmation, len(fixtures)) ) c.Listen(l) for i := range fixtures { if want, got := uint64(i+1), c.publish(); want != got.DeliveryTag { t.Fatalf("expected publish to return the 1 based delivery tag published, want: %d, got: %d", want, got.DeliveryTag) } } c.One(fixtures[1]) c.One(fixtures[2]) select { case confirm := <-l: t.Fatalf("expected to wait in order to properly resequence results, got: %+v", confirm) default: } c.One(fixtures[0]) for i, fix := range fixtures { if want, got := fix, <-l; want != got { t.Fatalf("expected to return confirmations in sequence for %d, want: %+v, got: %+v", i, want, got) } } } func TestConfirmAndPublishDoNotDeadlock(t *testing.T) { var ( c = newConfirms() l = make(chan Confirmation) iterations = 10 ) c.Listen(l) go func() { for i := 0; i < iterations; i++ { c.One(Confirmation{uint64(i + 1), true}) } }() for i := 0; i < iterations; i++ { c.publish() <-l } } func TestConfirmMixedResequences(t *testing.T) { var ( fixtures = []Confirmation{ {1, true}, {2, true}, {3, true}, } c = newConfirms() l = make(chan Confirmation, len(fixtures)) ) c.Listen(l) for range fixtures { c.publish() } c.One(fixtures[0]) c.One(fixtures[2]) c.Multiple(fixtures[1]) for i, fix := range fixtures { want := fix var got Confirmation select { case got = <-l: case <-time.After(1 * time.Second): t.Fatalf("timeout on reading confirmations") } if want != got { t.Fatalf("expected to confirm in sequence for %d, want: %+v, got: %+v", i, want, got) } } } func TestConfirmMultipleResequences(t *testing.T) { var ( fixtures = []Confirmation{ {1, true}, {2, true}, {3, true}, {4, true}, } c = newConfirms() l = make(chan Confirmation, len(fixtures)) ) c.Listen(l) for range fixtures { c.publish() } c.Multiple(fixtures[len(fixtures)-1]) for i, fix := range fixtures { if want, got := fix, <-l; want != got { t.Fatalf("expected to confirm multiple in sequence for %d, want: %+v, got: %+v", i, want, got) } } } func BenchmarkSequentialBufferedConfirms(t *testing.B) { var ( c = newConfirms() l = make(chan Confirmation, 10) ) c.Listen(l) for i := 0; i < t.N; i++ { if i > cap(l)-1 { <-l } c.One(Confirmation{c.publish().DeliveryTag, true}) } } func TestConfirmsIsThreadSafe(t *testing.T) { const count = 1000 const timeout = 5 * time.Second var ( c = newConfirms() l = make(chan Confirmation) pub = make(chan Confirmation) done = make(chan Confirmation) late = time.After(timeout) ) c.Listen(l) for i := 0; i < count; i++ { go func() { pub <- Confirmation{c.publish().DeliveryTag, true} }() } for i := 0; i < count; i++ { go func() { c.One(<-pub) }() } for i := 0; i < count; i++ { go func() { done <- <-l }() } for i := 0; i < count; i++ { select { case <-done: case <-late: t.Fatalf("expected all publish/confirms to finish after %s", timeout) } } } func TestDeferredConfirmationsConfirm(t *testing.T) { dcs := newDeferredConfirmations() var wg sync.WaitGroup for i, ack := range []bool{true, false} { var result bool deliveryTag := uint64(i + 1) dc := dcs.Add(deliveryTag) wg.Add(1) go func() { result = dc.Wait() wg.Done() }() dcs.Confirm(Confirmation{deliveryTag, ack}) wg.Wait() if result != ack { t.Fatalf("expected to receive matching ack got %v", result) } } } func TestDeferredConfirmationsConfirmMultiple(t *testing.T) { dcs := newDeferredConfirmations() var wg sync.WaitGroup var result bool dc1 := dcs.Add(1) dc2 := dcs.Add(2) dc3 := dcs.Add(3) wg.Add(1) go func() { result = dc1.Wait() && dc2.Wait() && dc3.Wait() wg.Done() }() dcs.ConfirmMultiple(Confirmation{4, true}) wg.Wait() if !result { t.Fatal("expected to receive true for result, received false") } } func TestDeferredConfirmationsClose(t *testing.T) { dcs := newDeferredConfirmations() var wg sync.WaitGroup var result bool dc1 := dcs.Add(1) dc2 := dcs.Add(2) dc3 := dcs.Add(3) wg.Add(1) go func() { result = !dc1.Wait() && !dc2.Wait() && !dc3.Wait() wg.Done() }() dcs.Close() wg.Wait() if !result { t.Fatal("expected to receive false for nacked confirmations, received true") } } func TestDeferredConfirmationsDoneAcked(t *testing.T) { dcs := newDeferredConfirmations() dc := dcs.Add(1) if dc.Acked() { t.Fatal("expected to receive false for pending confirmations, received true") } // Confirm twice to ensure that setAck is called once. for i := 0; i < 2; i++ { dcs.Confirm(Confirmation{dc.DeliveryTag, true}) } <-dc.Done() if !dc.Acked() { t.Fatal("expected to receive true for acked confirmations, received false") } } func TestDeferredConfirmationsWaitContextNack(t *testing.T) { dcs := newDeferredConfirmations() dc := dcs.Add(1) dcs.Confirm(Confirmation{dc.DeliveryTag, false}) ack, err := dc.WaitContext(context.Background()) if err != nil { t.Fatalf("expected to receive nil, got %v", err) } if ack { t.Fatal("expected to receive false for nacked confirmations, received true") } } func TestDeferredConfirmationsWaitContextCancel(t *testing.T) { dc := newDeferredConfirmations().Add(1) ctx, cancel := context.WithCancel(context.Background()) cancel() ack, err := dc.WaitContext(ctx) if err == nil { t.Fatal("expected to receive context error, got nil") } if ack { t.Fatal("expected to receive false for pending confirmations, received true") } } func TestDeferredConfirmationsConcurrency(t *testing.T) { dcs := newDeferredConfirmations() var wg sync.WaitGroup var result bool dc1 := dcs.Add(1) dc2 := dcs.Add(2) dc3 := dcs.Add(3) wg.Add(1) go func() { defer wg.Done() result = dc1.Wait() && dc2.Wait() && dc3.Wait() }() dcs.ConfirmMultiple(Confirmation{4, true}) wg.Wait() if !result { t.Fatal("expected to receive true for concurrent confirmations, received false") } } golang-github-rabbitmq-amqp091-go-1.10.0/connection.go000066400000000000000000001011071462444370000224020ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "bufio" "crypto/tls" "crypto/x509" "errors" "fmt" "io" "net" "os" "reflect" "strconv" "strings" "sync" "sync/atomic" "time" ) const ( maxChannelMax = (2 << 15) - 1 defaultHeartbeat = 10 * time.Second defaultConnectionTimeout = 30 * time.Second defaultProduct = "AMQP 0.9.1 Client" buildVersion = "1.10.0" platform = "golang" // Safer default that makes channel leaks a lot easier to spot // before they create operational headaches. See https://github.com/rabbitmq/rabbitmq-server/issues/1593. defaultChannelMax = uint16((2 << 10) - 1) defaultLocale = "en_US" ) // Config is used in DialConfig and Open to specify the desired tuning // parameters used during a connection open handshake. The negotiated tuning // will be stored in the returned connection's Config field. type Config struct { // The SASL mechanisms to try in the client request, and the successful // mechanism used on the Connection object. // If SASL is nil, PlainAuth from the URL is used. SASL []Authentication // Vhost specifies the namespace of permissions, exchanges, queues and // bindings on the server. Dial sets this to the path parsed from the URL. Vhost string ChannelMax uint16 // 0 max channels means 2^16 - 1 FrameSize int // 0 max bytes means unlimited Heartbeat time.Duration // less than 1s uses the server's interval // TLSClientConfig specifies the client configuration of the TLS connection // when establishing a tls transport. // If the URL uses an amqps scheme, then an empty tls.Config with the // ServerName from the URL is used. TLSClientConfig *tls.Config // Properties is table of properties that the client advertises to the server. // This is an optional setting - if the application does not set this, // the underlying library will use a generic set of client properties. Properties Table // Connection locale that we expect to always be en_US // Even though servers must return it as per the AMQP 0-9-1 spec, // we are not aware of it being used other than to satisfy the spec requirements Locale string // Dial returns a net.Conn prepared for a TLS handshake with TSLClientConfig, // then an AMQP connection handshake. // If Dial is nil, net.DialTimeout with a 30s connection and 30s deadline is // used during TLS and AMQP handshaking. Dial func(network, addr string) (net.Conn, error) } // NewConnectionProperties creates an amqp.Table to be used as amqp.Config.Properties. // // Defaults to library-defined values. For empty properties, use make(amqp.Table) instead. func NewConnectionProperties() Table { return Table{ "product": defaultProduct, "version": buildVersion, "platform": platform, } } // Connection manages the serialization and deserialization of frames from IO // and dispatches the frames to the appropriate channel. All RPC methods and // asynchronous Publishing, Delivery, Ack, Nack and Return messages are // multiplexed on this channel. There must always be active receivers for // every asynchronous message on this connection. type Connection struct { destructor sync.Once // shutdown once sendM sync.Mutex // conn writer mutex m sync.Mutex // struct field mutex conn io.ReadWriteCloser rpc chan message writer *writer sends chan time.Time // timestamps of each frame sent deadlines chan readDeadliner // heartbeater updates read deadlines allocator *allocator // id generator valid after openTune channels map[uint16]*Channel noNotify bool // true when we will never notify again closes []chan *Error blocks []chan Blocking errors chan *Error // if connection is closed should close this chan close chan struct{} Config Config // The negotiated Config after connection.open Major int // Server's major version Minor int // Server's minor version Properties Table // Server properties Locales []string // Server locales closed int32 // Will be 1 if the connection is closed, 0 otherwise. Should only be accessed as atomic } type readDeadliner interface { SetReadDeadline(time.Time) error } // DefaultDial establishes a connection when config.Dial is not provided func DefaultDial(connectionTimeout time.Duration) func(network, addr string) (net.Conn, error) { return func(network, addr string) (net.Conn, error) { conn, err := net.DialTimeout(network, addr, connectionTimeout) if err != nil { return nil, err } // Heartbeating hasn't started yet, don't stall forever on a dead server. // A deadline is set for TLS and AMQP handshaking. After AMQP is established, // the deadline is cleared in openComplete. if err := conn.SetDeadline(time.Now().Add(connectionTimeout)); err != nil { return nil, err } return conn, nil } } // Dial accepts a string in the AMQP URI format and returns a new Connection // over TCP using PlainAuth. Defaults to a server heartbeat interval of 10 // seconds and sets the handshake deadline to 30 seconds. After handshake, // deadlines are cleared. // // Dial uses the zero value of tls.Config when it encounters an amqps:// // scheme. It is equivalent to calling DialTLS(amqp, nil). func Dial(url string) (*Connection, error) { return DialConfig(url, Config{ Locale: defaultLocale, }) } // DialTLS accepts a string in the AMQP URI format and returns a new Connection // over TCP using PlainAuth. Defaults to a server heartbeat interval of 10 // seconds and sets the initial read deadline to 30 seconds. // // DialTLS uses the provided tls.Config when encountering an amqps:// scheme. func DialTLS(url string, amqps *tls.Config) (*Connection, error) { return DialConfig(url, Config{ TLSClientConfig: amqps, Locale: defaultLocale, }) } // DialTLS_ExternalAuth accepts a string in the AMQP URI format and returns a // new Connection over TCP using EXTERNAL auth. Defaults to a server heartbeat // interval of 10 seconds and sets the initial read deadline to 30 seconds. // // This mechanism is used, when RabbitMQ is configured for EXTERNAL auth with // ssl_cert_login plugin for userless/passwordless logons // // DialTLS_ExternalAuth uses the provided tls.Config when encountering an // amqps:// scheme. func DialTLS_ExternalAuth(url string, amqps *tls.Config) (*Connection, error) { return DialConfig(url, Config{ TLSClientConfig: amqps, SASL: []Authentication{&ExternalAuth{}}, }) } // DialConfig accepts a string in the AMQP URI format and a configuration for // the transport and connection setup, returning a new Connection. Defaults to // a server heartbeat interval of 10 seconds and sets the initial read deadline // to 30 seconds. The heartbeat interval specified in the AMQP URI takes precedence // over the value specified in the config. To disable heartbeats, you must use // the AMQP URI and set heartbeat=0 there. func DialConfig(url string, config Config) (*Connection, error) { var err error var conn net.Conn uri, err := ParseURI(url) if err != nil { return nil, err } if config.SASL == nil { if uri.AuthMechanism != nil { for _, identifier := range uri.AuthMechanism { switch strings.ToUpper(identifier) { case "PLAIN": config.SASL = append(config.SASL, uri.PlainAuth()) case "AMQPLAIN": config.SASL = append(config.SASL, uri.AMQPlainAuth()) case "EXTERNAL": config.SASL = append(config.SASL, &ExternalAuth{}) default: return nil, fmt.Errorf("unsupported auth_mechanism: %v", identifier) } } } else { config.SASL = []Authentication{uri.PlainAuth()} } } if config.Vhost == "" { config.Vhost = uri.Vhost } if uri.Heartbeat.hasValue { config.Heartbeat = uri.Heartbeat.value } else { if config.Heartbeat == 0 { config.Heartbeat = defaultHeartbeat } } if config.ChannelMax == 0 { config.ChannelMax = uri.ChannelMax } connectionTimeout := defaultConnectionTimeout if uri.ConnectionTimeout != 0 { connectionTimeout = time.Duration(uri.ConnectionTimeout) * time.Millisecond } addr := net.JoinHostPort(uri.Host, strconv.FormatInt(int64(uri.Port), 10)) dialer := config.Dial if dialer == nil { dialer = DefaultDial(connectionTimeout) } conn, err = dialer("tcp", addr) if err != nil { return nil, err } if uri.Scheme == "amqps" { if config.TLSClientConfig == nil { tlsConfig, err := tlsConfigFromURI(uri) if err != nil { return nil, fmt.Errorf("create TLS config from URI: %w", err) } config.TLSClientConfig = tlsConfig } // If ServerName has not been specified in TLSClientConfig, // set it to the URI host used for this connection. if config.TLSClientConfig.ServerName == "" { config.TLSClientConfig.ServerName = uri.Host } client := tls.Client(conn, config.TLSClientConfig) if err := client.Handshake(); err != nil { conn.Close() return nil, err } conn = client } return Open(conn, config) } /* Open accepts an already established connection, or other io.ReadWriteCloser as a transport. Use this method if you have established a TLS connection or wish to use your own custom transport. */ func Open(conn io.ReadWriteCloser, config Config) (*Connection, error) { c := &Connection{ conn: conn, writer: &writer{bufio.NewWriter(conn)}, channels: make(map[uint16]*Channel), rpc: make(chan message), sends: make(chan time.Time), errors: make(chan *Error, 1), close: make(chan struct{}), deadlines: make(chan readDeadliner, 1), } go c.reader(conn) return c, c.open(config) } /* UpdateSecret updates the secret used to authenticate this connection. It is used when secrets have an expiration date and need to be renewed, like OAuth 2 tokens. It returns an error if the operation is not successful, or if the connection is closed. */ func (c *Connection) UpdateSecret(newSecret, reason string) error { if c.IsClosed() { return ErrClosed } return c.call(&connectionUpdateSecret{ NewSecret: newSecret, Reason: reason, }, &connectionUpdateSecretOk{}) } /* LocalAddr returns the local TCP peer address, or ":0" (the zero value of net.TCPAddr) as a fallback default value if the underlying transport does not support LocalAddr(). */ func (c *Connection) LocalAddr() net.Addr { if conn, ok := c.conn.(interface { LocalAddr() net.Addr }); ok { return conn.LocalAddr() } return &net.TCPAddr{} } /* RemoteAddr returns the remote TCP peer address, if known. */ func (c *Connection) RemoteAddr() net.Addr { if conn, ok := c.conn.(interface { RemoteAddr() net.Addr }); ok { return conn.RemoteAddr() } return &net.TCPAddr{} } // ConnectionState returns basic TLS details of the underlying transport. // Returns a zero value when the underlying connection does not implement // ConnectionState() tls.ConnectionState. func (c *Connection) ConnectionState() tls.ConnectionState { if conn, ok := c.conn.(interface { ConnectionState() tls.ConnectionState }); ok { return conn.ConnectionState() } return tls.ConnectionState{} } /* NotifyClose registers a listener for close events either initiated by an error accompanying a connection.close method or by a normal shutdown. The chan provided will be closed when the Connection is closed and on a graceful close, no error will be sent. In case of a non graceful close the error will be notified synchronously by the library so that it will be necessary to consume the Channel from the caller in order to avoid deadlocks To reconnect after a transport or protocol error, register a listener here and re-run your setup process. */ func (c *Connection) NotifyClose(receiver chan *Error) chan *Error { c.m.Lock() defer c.m.Unlock() if c.noNotify { close(receiver) } else { c.closes = append(c.closes, receiver) } return receiver } /* NotifyBlocked registers a listener for RabbitMQ specific TCP flow control method extensions connection.blocked and connection.unblocked. Flow control is active with a reason when Blocking.Blocked is true. When a Connection is blocked, all methods will block across all connections until server resources become free again. This optional extension is supported by the server when the "connection.blocked" server capability key is true. */ func (c *Connection) NotifyBlocked(receiver chan Blocking) chan Blocking { c.m.Lock() defer c.m.Unlock() if c.noNotify { close(receiver) } else { c.blocks = append(c.blocks, receiver) } return receiver } /* Close requests and waits for the response to close the AMQP connection. It's advisable to use this message when publishing to ensure all kernel buffers have been flushed on the server and client before exiting. An error indicates that server may not have received this request to close but the connection should be treated as closed regardless. After returning from this call, all resources associated with this connection, including the underlying io, Channels, Notify listeners and Channel consumers will also be closed. */ func (c *Connection) Close() error { if c.IsClosed() { return ErrClosed } defer c.shutdown(nil) return c.call( &connectionClose{ ReplyCode: replySuccess, ReplyText: "kthxbai", }, &connectionCloseOk{}, ) } // CloseDeadline requests and waits for the response to close this AMQP connection. // // Accepts a deadline for waiting the server response. The deadline is passed // to the low-level connection i.e. network socket. // // Regardless of the error returned, the connection is considered closed, and it // should not be used after calling this function. // // In the event of an I/O timeout, connection-closed listeners are NOT informed. // // After returning from this call, all resources associated with this connection, // including the underlying io, Channels, Notify listeners and Channel consumers // will also be closed. func (c *Connection) CloseDeadline(deadline time.Time) error { if c.IsClosed() { return ErrClosed } defer c.shutdown(nil) err := c.setDeadline(deadline) if err != nil { return err } return c.call( &connectionClose{ ReplyCode: replySuccess, ReplyText: "kthxbai", }, &connectionCloseOk{}, ) } func (c *Connection) closeWith(err *Error) error { if c.IsClosed() { return ErrClosed } defer c.shutdown(err) return c.call( &connectionClose{ ReplyCode: uint16(err.Code), ReplyText: err.Reason, }, &connectionCloseOk{}, ) } // IsClosed returns true if the connection is marked as closed, otherwise false // is returned. func (c *Connection) IsClosed() bool { return atomic.LoadInt32(&c.closed) == 1 } // setDeadline is a wrapper to type assert Connection.conn and set an I/O // deadline in the underlying TCP connection socket, by calling // net.Conn.SetDeadline(). It returns an error, in case the type assertion fails, // although this should never happen. func (c *Connection) setDeadline(t time.Time) error { con, ok := c.conn.(net.Conn) if !ok { return errInvalidTypeAssertion } return con.SetDeadline(t) } func (c *Connection) send(f frame) error { if c.IsClosed() { return ErrClosed } c.sendM.Lock() err := c.writer.WriteFrame(f) c.sendM.Unlock() if err != nil { // shutdown could be re-entrant from signaling notify chans go c.shutdown(&Error{ Code: FrameError, Reason: err.Error(), }) } else { // Broadcast we sent a frame, reducing heartbeats, only // if there is something that can receive - like a non-reentrant // call or if the heartbeater isn't running select { case c.sends <- time.Now(): default: } } return err } // This method is intended to be used with sendUnflushed() to end a sequence // of sendUnflushed() calls and flush the connection func (c *Connection) endSendUnflushed() error { c.sendM.Lock() defer c.sendM.Unlock() return c.flush() } // sendUnflushed performs an *Unflushed* write. It is otherwise equivalent to // send(), and we provide a separate flush() function to explicitly flush the // buffer after all Frames are written. // // Why is this a thing? // // send() method uses writer.WriteFrame(), which will write the Frame then // flush the buffer. For cases like the sendOpen() method on Channel, which // sends multiple Frames (methodFrame, headerFrame, N x bodyFrame), flushing // after each Frame is inefficient as it negates much of the benefit of using a // buffered writer, and results in more syscalls than necessary. Flushing buffers // after every frame can have a significant performance impact when sending // (basicPublish) small messages, so this method performs an *Unflushed* write // but is otherwise equivalent to send() method, and we provide a separate // flush method to explicitly flush the buffer after all Frames are written. func (c *Connection) sendUnflushed(f frame) error { if c.IsClosed() { return ErrClosed } c.sendM.Lock() err := c.writer.WriteFrameNoFlush(f) c.sendM.Unlock() if err != nil { // shutdown could be re-entrant from signaling notify chans go c.shutdown(&Error{ Code: FrameError, Reason: err.Error(), }) } return err } // This method is intended to be used with sendUnflushed() to explicitly flush // the buffer after all required Frames have been written to the buffer. func (c *Connection) flush() (err error) { if buf, ok := c.writer.w.(*bufio.Writer); ok { err = buf.Flush() // Moving send notifier to flush increases basicPublish for the small message // case. As sendUnflushed + flush is used for the case of sending semantically // related Frames (e.g. a Message like basicPublish) there is no real advantage // to sending per Frame vice per "group of related Frames" and for the case of // small messages time.Now() is (relatively) expensive. if err == nil { // Broadcast we sent a frame, reducing heartbeats, only // if there is something that can receive - like a non-reentrant // call or if the heartbeater isn't running select { case c.sends <- time.Now(): default: } } } return } func (c *Connection) shutdown(err *Error) { atomic.StoreInt32(&c.closed, 1) c.destructor.Do(func() { c.m.Lock() defer c.m.Unlock() if err != nil { for _, c := range c.closes { c <- err } c.errors <- err } // Shutdown handler goroutine can still receive the result. close(c.errors) for _, c := range c.closes { close(c) } for _, c := range c.blocks { close(c) } // Shutdown the channel, but do not use closeChannel() as it calls // releaseChannel() which requires the connection lock. // // Ranging over c.channels and calling releaseChannel() that mutates // c.channels is racy - see commit 6063341 for an example. for _, ch := range c.channels { ch.shutdown(err) } c.conn.Close() // reader exit close(c.close) c.channels = nil c.allocator = nil c.noNotify = true }) } // All methods sent to the connection channel should be synchronous so we // can handle them directly without a framing component func (c *Connection) demux(f frame) { if f.channel() == 0 { c.dispatch0(f) } else { c.dispatchN(f) } } func (c *Connection) dispatch0(f frame) { switch mf := f.(type) { case *methodFrame: switch m := mf.Method.(type) { case *connectionClose: // Send immediately as shutdown will close our side of the writer. f := &methodFrame{ChannelId: 0, Method: &connectionCloseOk{}} if err := c.send(f); err != nil { Logger.Printf("error sending connectionCloseOk, error: %+v", err) } c.shutdown(newError(m.ReplyCode, m.ReplyText)) case *connectionBlocked: for _, c := range c.blocks { c <- Blocking{Active: true, Reason: m.Reason} } case *connectionUnblocked: for _, c := range c.blocks { c <- Blocking{Active: false} } default: select { case <-c.close: return case c.rpc <- m: } } case *heartbeatFrame: // kthx - all reads reset our deadline. so we can drop this default: // lolwat - channel0 only responds to methods and heartbeats // closeWith use call don't block reader go func() { if err := c.closeWith(ErrUnexpectedFrame); err != nil { Logger.Printf("error sending connectionCloseOk with ErrUnexpectedFrame, error: %+v", err) } }() } } func (c *Connection) dispatchN(f frame) { c.m.Lock() channel, ok := c.channels[f.channel()] if ok { updateChannel(f, channel) } else { Logger.Printf("[debug] dropping frame, channel %d does not exist", f.channel()) } c.m.Unlock() // Note: this could result in concurrent dispatch depending on // how channels are managed in an application if ok { channel.recv(channel, f) } else { c.dispatchClosed(f) } } // section 2.3.7: "When a peer decides to close a channel or connection, it // sends a Close method. The receiving peer MUST respond to a Close with a // Close-Ok, and then both parties can close their channel or connection. Note // that if peers ignore Close, deadlock can happen when both peers send Close // at the same time." // // When we don't have a channel, so we must respond with close-ok on a close // method. This can happen between a channel exception on an asynchronous // method like basic.publish and a synchronous close with channel.close. // In that case, we'll get both a channel.close and channel.close-ok in any // order. func (c *Connection) dispatchClosed(f frame) { // Only consider method frames, drop content/header frames if mf, ok := f.(*methodFrame); ok { switch mf.Method.(type) { case *channelClose: f := &methodFrame{ChannelId: f.channel(), Method: &channelCloseOk{}} if err := c.send(f); err != nil { Logger.Printf("error sending channelCloseOk, channel id: %d error: %+v", f.channel(), err) } case *channelCloseOk: // we are already closed, so do nothing default: // unexpected method on closed channel // closeWith use call don't block reader go func() { if err := c.closeWith(ErrClosed); err != nil { Logger.Printf("error sending connectionCloseOk with ErrClosed, error: %+v", err) } }() } } } // Reads each frame off the IO and hand off to the connection object that // will demux the streams and dispatch to one of the opened channels or // handle on channel 0 (the connection channel). func (c *Connection) reader(r io.Reader) { buf := bufio.NewReader(r) frames := &reader{buf} conn, haveDeadliner := r.(readDeadliner) defer close(c.rpc) for { frame, err := frames.ReadFrame() if err != nil { c.shutdown(&Error{Code: FrameError, Reason: err.Error()}) return } c.demux(frame) if haveDeadliner { select { case c.deadlines <- conn: default: // On c.Close() c.heartbeater() might exit just before c.deadlines <- conn is called. // Which results in this goroutine being stuck forever. } } } } // Ensures that at least one frame is being sent at the tuned interval with a // jitter tolerance of 1s func (c *Connection) heartbeater(interval time.Duration, done chan *Error) { const maxServerHeartbeatsInFlight = 3 var sendTicks <-chan time.Time if interval > 0 { ticker := time.NewTicker(interval) defer ticker.Stop() sendTicks = ticker.C } lastSent := time.Now() for { select { case at, stillSending := <-c.sends: // When actively sending, depend on sent frames to reset server timer if stillSending { lastSent = at } else { return } case at := <-sendTicks: // When idle, fill the space with a heartbeat frame if at.Sub(lastSent) > interval-time.Second { if err := c.send(&heartbeatFrame{}); err != nil { // send heartbeats even after close/closeOk so we // tick until the connection starts erroring return } } case conn := <-c.deadlines: // When reading, reset our side of the deadline, if we've negotiated one with // a deadline that covers at least 2 server heartbeats if interval > 0 { if err := conn.SetReadDeadline(time.Now().Add(maxServerHeartbeatsInFlight * interval)); err != nil { var opErr *net.OpError if !errors.As(err, &opErr) { Logger.Printf("error setting read deadline in heartbeater: %+v", err) return } } } case <-done: return } } } // Convenience method to inspect the Connection.Properties["capabilities"] // Table for server identified capabilities like "basic.ack" or // "confirm.select". func (c *Connection) isCapable(featureName string) bool { capabilities, _ := c.Properties["capabilities"].(Table) hasFeature, _ := capabilities[featureName].(bool) return hasFeature } // allocateChannel records but does not open a new channel with a unique id. // This method is the initial part of the channel lifecycle and paired with // releaseChannel func (c *Connection) allocateChannel() (*Channel, error) { c.m.Lock() defer c.m.Unlock() if c.IsClosed() { return nil, ErrClosed } id, ok := c.allocator.next() if !ok { return nil, ErrChannelMax } ch := newChannel(c, uint16(id)) c.channels[uint16(id)] = ch return ch, nil } // releaseChannel removes a channel from the registry as the final part of the // channel lifecycle func (c *Connection) releaseChannel(ch *Channel) { c.m.Lock() defer c.m.Unlock() if !c.IsClosed() { got, ok := c.channels[ch.id] if ok && got == ch { delete(c.channels, ch.id) c.allocator.release(int(ch.id)) } } } // openChannel allocates and opens a channel, must be paired with closeChannel func (c *Connection) openChannel() (*Channel, error) { ch, err := c.allocateChannel() if err != nil { return nil, err } if err := ch.open(); err != nil { c.releaseChannel(ch) return nil, err } return ch, nil } // closeChannel releases and initiates a shutdown of the channel. All channel // closures should be initiated here for proper channel lifecycle management on // this connection. func (c *Connection) closeChannel(ch *Channel, e *Error) { ch.shutdown(e) c.releaseChannel(ch) } /* Channel opens a unique, concurrent server channel to process the bulk of AMQP messages. Any error from methods on this receiver will render the receiver invalid and a new Channel should be opened. */ func (c *Connection) Channel() (*Channel, error) { return c.openChannel() } func (c *Connection) call(req message, res ...message) error { // Special case for when the protocol header frame is sent insted of a // request method if req != nil { if err := c.send(&methodFrame{ChannelId: 0, Method: req}); err != nil { return err } } var msg message select { case e, ok := <-c.errors: if ok { return e } return ErrClosed case msg = <-c.rpc: } // Try to match one of the result types for _, try := range res { if reflect.TypeOf(msg) == reflect.TypeOf(try) { // *res = *msg vres := reflect.ValueOf(try).Elem() vmsg := reflect.ValueOf(msg).Elem() vres.Set(vmsg) return nil } } return ErrCommandInvalid } // Communication flow to open, use and close a connection. 'C:' are // frames sent by the Client. 'S:' are frames sent by the Server. // // Connection = open-Connection *use-Connection close-Connection // // open-Connection = C:protocol-header // S:START C:START-OK // *challenge // S:TUNE C:TUNE-OK // C:OPEN S:OPEN-OK // // challenge = S:SECURE C:SECURE-OK // // use-Connection = *channel // // close-Connection = C:CLOSE S:CLOSE-OK // S:CLOSE C:CLOSE-OK func (c *Connection) open(config Config) error { if err := c.send(&protocolHeader{}); err != nil { return err } return c.openStart(config) } func (c *Connection) openStart(config Config) error { start := &connectionStart{} if err := c.call(nil, start); err != nil { return err } c.Major = int(start.VersionMajor) c.Minor = int(start.VersionMinor) c.Properties = start.ServerProperties c.Locales = strings.Split(start.Locales, " ") // eventually support challenge/response here by also responding to // connectionSecure. auth, ok := pickSASLMechanism(config.SASL, strings.Split(start.Mechanisms, " ")) if !ok { return ErrSASL } // Save this mechanism off as the one we chose c.Config.SASL = []Authentication{auth} // Set the connection locale to client locale c.Config.Locale = config.Locale return c.openTune(config, auth) } func (c *Connection) openTune(config Config, auth Authentication) error { if len(config.Properties) == 0 { config.Properties = NewConnectionProperties() } config.Properties["capabilities"] = Table{ "connection.blocked": true, "consumer_cancel_notify": true, "basic.nack": true, "publisher_confirms": true, } ok := &connectionStartOk{ ClientProperties: config.Properties, Mechanism: auth.Mechanism(), Response: auth.Response(), Locale: config.Locale, } tune := &connectionTune{} if err := c.call(ok, tune); err != nil { // per spec, a connection can only be closed when it has been opened // so at this point, we know it's an auth error, but the socket // was closed instead. Return a meaningful error. return ErrCredentials } // Edge case that may race with c.shutdown() // https://github.com/rabbitmq/amqp091-go/issues/170 c.m.Lock() // When the server and client both use default 0, then the max channel is // only limited by uint16. c.Config.ChannelMax = pickUInt16(config.ChannelMax, tune.ChannelMax) if c.Config.ChannelMax == 0 { c.Config.ChannelMax = defaultChannelMax } c.Config.ChannelMax = minUInt16(c.Config.ChannelMax, maxChannelMax) c.allocator = newAllocator(1, int(c.Config.ChannelMax)) c.m.Unlock() // Frame size includes headers and end byte (len(payload)+8), even if // this is less than FrameMinSize, use what the server sends because the // alternative is to stop the handshake here. c.Config.FrameSize = pick(config.FrameSize, int(tune.FrameMax)) // Save this off for resetDeadline() c.Config.Heartbeat = time.Second * time.Duration(pick( int(config.Heartbeat/time.Second), int(tune.Heartbeat))) // "The client should start sending heartbeats after receiving a // Connection.Tune method" go c.heartbeater(c.Config.Heartbeat/2, c.NotifyClose(make(chan *Error, 1))) if err := c.send(&methodFrame{ ChannelId: 0, Method: &connectionTuneOk{ ChannelMax: uint16(c.Config.ChannelMax), FrameMax: uint32(c.Config.FrameSize), Heartbeat: uint16(c.Config.Heartbeat / time.Second), }, }); err != nil { return err } return c.openVhost(config) } func (c *Connection) openVhost(config Config) error { req := &connectionOpen{VirtualHost: config.Vhost} res := &connectionOpenOk{} if err := c.call(req, res); err != nil { // Cannot be closed yet, but we know it's a vhost problem return ErrVhost } c.Config.Vhost = config.Vhost return c.openComplete() } // openComplete performs any final Connection initialization dependent on the // connection handshake and clears any state needed for TLS and AMQP handshaking. func (c *Connection) openComplete() error { // We clear the deadlines and let the heartbeater reset the read deadline if requested. // RabbitMQ uses TCP flow control at this point for pushback so Writes can // intentionally block. if deadliner, ok := c.conn.(interface { SetDeadline(time.Time) error }); ok { _ = deadliner.SetDeadline(time.Time{}) } return nil } // tlsConfigFromURI tries to create TLS configuration based on query parameters. // Returns default (empty) config in case no suitable client cert and/or client key not provided. // Returns error in case certificates can not be parsed. func tlsConfigFromURI(uri URI) (*tls.Config, error) { var certPool *x509.CertPool if uri.CACertFile != "" { data, err := os.ReadFile(uri.CACertFile) if err != nil { return nil, fmt.Errorf("read CA certificate: %w", err) } certPool = x509.NewCertPool() certPool.AppendCertsFromPEM(data) } else if sysPool, err := x509.SystemCertPool(); err != nil { return nil, fmt.Errorf("load system certificates: %w", err) } else { certPool = sysPool } if uri.CertFile == "" || uri.KeyFile == "" { // no client auth (mTLS), just server auth return &tls.Config{ RootCAs: certPool, ServerName: uri.ServerName, }, nil } certificate, err := tls.LoadX509KeyPair(uri.CertFile, uri.KeyFile) if err != nil { return nil, fmt.Errorf("load client certificate: %w", err) } return &tls.Config{ Certificates: []tls.Certificate{certificate}, RootCAs: certPool, ServerName: uri.ServerName, }, nil } func max(a, b int) int { if a > b { return a } return b } func maxUInt16(a, b uint16) uint16 { if a > b { return a } return b } func min(a, b int) int { if a < b { return a } return b } func minUInt16(a, b uint16) uint16 { if a < b { return a } return b } func pickUInt16(client, server uint16) uint16 { if client == 0 || server == 0 { return maxUInt16(client, server) } else { return minUInt16(client, server) } } func pick(client, server int) int { if client == 0 || server == 0 { return max(client, server) } return min(client, server) } golang-github-rabbitmq-amqp091-go-1.10.0/connection_test.go000066400000000000000000000246731462444370000234550ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build integration // +build integration package amqp091 import ( "context" "crypto/tls" "net" "os" "os/exec" "regexp" "strings" "sync" "testing" "time" ) const rabbitmqctlEnvKey = "RABBITMQ_RABBITMQCTL_PATH" func TestRequiredServerLocale(t *testing.T) { conn := integrationConnection(t, "AMQP 0-9-1 required server locale") t.Cleanup(func() { conn.Close() }) requiredServerLocale := defaultLocale for _, locale := range conn.Locales { if locale == requiredServerLocale { return } } t.Fatalf("AMQP 0-9-1 server must support at least the %s locale, server sent the following locales: %#v", requiredServerLocale, conn.Locales) } func TestDefaultConnectionLocale(t *testing.T) { conn := integrationConnection(t, "client default locale") t.Cleanup(func() { conn.Close() }) if conn.Config.Locale != defaultLocale { t.Fatalf("Expected default connection locale to be %s, is was: %s", defaultLocale, conn.Config.Locale) } } func TestChannelOpenOnAClosedConnectionFails(t *testing.T) { conn := integrationConnection(t, "channel on close") conn.Close() if _, err := conn.Channel(); err != ErrClosed { t.Fatalf("channel.open on a closed connection %#v is expected to fail", conn) } } // TestChannelOpenOnAClosedConnectionFails_ReleasesAllocatedChannel ensures the // channel allocated is released if opening the channel fails. func TestChannelOpenOnAClosedConnectionFails_ReleasesAllocatedChannel(t *testing.T) { conn := integrationConnection(t, "releases channel allocation") conn.Close() before := len(conn.channels) if _, err := conn.Channel(); err != ErrClosed { t.Fatalf("channel.open on a closed connection %#v is expected to fail", conn) } if len(conn.channels) != before { t.Fatalf("channel.open failed, but the allocated channel was not released") } } // TestRaceBetweenChannelAndConnectionClose ensures allocating a new channel // does not race with shutting the connection down. // // See https://github.com/streadway/amqp/issues/251 - thanks to jmalloc for the // test case. func TestRaceBetweenChannelAndConnectionClose(t *testing.T) { defer time.AfterFunc(10*time.Second, func() { t.Fatalf("Close deadlock") }).Stop() conn := integrationConnection(t, "allocation/shutdown race") go conn.Close() for i := 0; i < 10; i++ { go func() { ch, err := conn.Channel() if err == nil { ch.Close() } }() } } // TestRaceBetweenChannelShutdownAndSend ensures closing a channel // (channel.shutdown) does not race with calling channel.send() from any other // goroutines. // // See https://github.com/streadway/amqp/pull/253#issuecomment-292464811 for // more details - thanks to jmalloc again. func TestRaceBetweenChannelShutdownAndSend(t *testing.T) { const concurrency = 10 defer time.AfterFunc(10*time.Second, func() { t.Fatalf("Close deadlock") }).Stop() conn := integrationConnection(t, "channel close/send race") defer conn.Close() ch, _ := conn.Channel() go ch.Close() errs := make(chan error, concurrency) wg := sync.WaitGroup{} wg.Add(concurrency) for i := 0; i < concurrency; i++ { go func() { defer wg.Done() // ch.Ack calls ch.send() internally. if err := ch.Ack(42, false); err != nil { errs <- err } }() } wg.Wait() close(errs) for err := range errs { if err != nil { t.Logf("[INFO] %#v (%s) of type %T", err, err, err) } } } func TestQueueDeclareOnAClosedConnectionFails(t *testing.T) { conn := integrationConnection(t, "queue declare on close") ch, _ := conn.Channel() conn.Close() if _, err := ch.QueueDeclare("an example", false, false, false, false, nil); err != ErrClosed { t.Fatalf("queue.declare on a closed connection %#v is expected to return ErrClosed, returned: %#v", conn, err) } } func TestConcurrentClose(t *testing.T) { const concurrency = 32 conn := integrationConnection(t, "concurrent close") defer conn.Close() errs := make(chan error, concurrency) wg := sync.WaitGroup{} wg.Add(concurrency) for i := 0; i < concurrency; i++ { go func() { defer wg.Done() err := conn.Close() if err == nil { t.Log("first concurrent close was successful") return } if err == ErrClosed { t.Log("later concurrent close were successful and returned ErrClosed") return } // BUG(st) is this really acceptable? we got a net.OpError before the // connection was marked as closed means a race condition between the // network connection and handshake state. It should be a package error // returned. if _, neterr := err.(*net.OpError); neterr { t.Logf("unknown net.OpError during close, ignoring: %+v", err) return } // A different/protocol error occurred indicating a race or missed condition if _, other := err.(*Error); other { errs <- err } }() } wg.Wait() close(errs) for err := range errs { if err != nil { t.Fatalf("Expected no error, or ErrClosed, or a net.OpError from conn.Close(), got %#v (%s) of type %T", err, err, err) } } } // TestPlaintextDialTLS ensures amqp:// connections succeed when using DialTLS. func TestPlaintextDialTLS(t *testing.T) { uri, err := ParseURI(amqpURL) if err != nil { t.Fatalf("parse URI error: %s", err) } // We can only test when we have a plaintext listener if uri.Scheme != "amqp" { t.Skip("requires server listening for plaintext connections") } conn, err := DialTLS(uri.String(), &tls.Config{MinVersion: tls.VersionTLS12}) if err != nil { t.Fatalf("unexpected dial error, got %v", err) } conn.Close() } // TestIsClosed will test the public method IsClosed on a connection. func TestIsClosed(t *testing.T) { conn := integrationConnection(t, "public IsClosed()") if conn.IsClosed() { t.Fatalf("connection expected to not be marked as closed") } conn.Close() if !conn.IsClosed() { t.Fatal("connection expected to be marked as closed") } } // TestChannelIsClosed will test the public method IsClosed on a channel. func TestChannelIsClosed(t *testing.T) { conn := integrationConnection(t, "public channel.IsClosed()") t.Cleanup(func() { conn.Close() }) ch, _ := conn.Channel() if ch.IsClosed() { t.Fatalf("channel expected to not be marked as closed") } ch.Close() if !ch.IsClosed() { t.Fatal("channel expected to be marked as closed") } } // TestReaderGoRoutineTerminatesWhenMsgIsProcessedDuringClose tests the issue // described in https://github.com/rabbitmq/amqp091-go/issues/69. func TestReaderGoRoutineTerminatesWhenMsgIsProcessedDuringClose(t *testing.T) { const routines = 10 c := integrationConnection(t, t.Name()) var wg sync.WaitGroup startSigCh := make(chan interface{}) for i := 0; i < routines; i++ { wg.Add(1) go func(id int) { defer wg.Done() <-startSigCh err := c.Close() if err != nil { t.Logf("close failed in routine %d: %s", id, err.Error()) } }(i) } close(startSigCh) t.Log("waiting for go-routines to terminate") wg.Wait() } func TestConnectionConfigPropertiesWithClientProvidedConnectionName(t *testing.T) { const expectedConnectionName = "amqp091-go-test" connectionProperties := NewConnectionProperties() connectionProperties.SetClientConnectionName(expectedConnectionName) currentConnectionName, ok := connectionProperties["connection_name"] if !ok { t.Fatal("Connection name was not set by Table.SetClientConnectionName") } if currentConnectionName != expectedConnectionName { t.Fatalf("Connection name is set to: %s. Expected: %s", currentConnectionName, expectedConnectionName) } } func TestNewConnectionProperties_HasDefaultProperties(t *testing.T) { expectedProductName := defaultProduct expectedPlatform := platform props := NewConnectionProperties() productName, ok := props["product"] if !ok { t.Fatal("Product name was not set by NewConnectionProperties") } if productName != expectedProductName { t.Fatalf("Product name is set to: %s. Expected: %s", productName, expectedProductName, ) } platform, ok := props["platform"] if !ok { t.Fatal("Platform was not set by NewConnectionProperties") } if platform != expectedPlatform { t.Fatalf("Platform is set to: %s. Expected: %s", platform, expectedPlatform, ) } versionUntyped, ok := props["version"] if !ok { t.Fatal("Version was not set by NewConnectionProperties") } version, ok := versionUntyped.(string) if !ok { t.Fatalf("Version in NewConnectionProperties should be string. Type given was: %T", versionUntyped) } // semver regexp as specified by https://semver.org/ semverRegexp := regexp.MustCompile(`^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) if !semverRegexp.MatchString(version) { t.Fatalf("Version in NewConnectionProperties is not a valid semver value: %s", version) } } // Connection and channels should be closeable when a memory alarm is active. // https://github.com/rabbitmq/amqp091-go/issues/178 func TestConnection_Close_WhenMemoryAlarmIsActive(t *testing.T) { err := rabbitmqctl(t, "set_vm_memory_high_watermark", "0.0001") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = rabbitmqctl(t, "set_vm_memory_high_watermark", "0.4") conn, ch := integrationQueue(t, t.Name()) integrationQueueDelete(t, ch, t.Name()) _ = ch.Close() _ = conn.Close() }) conn, ch := integrationQueue(t, t.Name()) go func() { // simulate a producer // required to block the connection _ = ch.PublishWithContext(context.Background(), "", t.Name(), false, false, Publishing{ Body: []byte("this is a test"), }) }() <-time.After(time.Second * 1) err = conn.CloseDeadline(time.Now().Add(time.Second * 2)) if err == nil { t.Fatal("expected error, got nil") } if !conn.IsClosed() { t.Fatal("expected connection to be closed") } } func rabbitmqctl(t *testing.T, args ...string) error { rabbitmqctlPath, found := os.LookupEnv(rabbitmqctlEnvKey) if !found { t.Skipf("variable for %s for rabbitmqctl not found, skipping", rabbitmqctlEnvKey) } var cmd *exec.Cmd if strings.HasPrefix(rabbitmqctlPath, "DOCKER:") { containerName := strings.Split(rabbitmqctlPath, ":")[1] cmd = exec.Command("docker", "exec", containerName, "rabbitmqctl") cmd.Args = append(cmd.Args, args...) } else { cmd = exec.Command(rabbitmqctlPath, args...) } return cmd.Run() } golang-github-rabbitmq-amqp091-go-1.10.0/consumers.go000066400000000000000000000101011462444370000222520ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "os" "strconv" "sync" "sync/atomic" ) var consumerSeq uint64 const consumerTagLengthMax = 0xFF // see writeShortstr func uniqueConsumerTag() string { return commandNameBasedUniqueConsumerTag(os.Args[0]) } func commandNameBasedUniqueConsumerTag(commandName string) string { tagPrefix := "ctag-" tagInfix := commandName tagSuffix := "-" + strconv.FormatUint(atomic.AddUint64(&consumerSeq, 1), 10) if len(tagPrefix)+len(tagInfix)+len(tagSuffix) > consumerTagLengthMax { tagInfix = "streadway/amqp" } return tagPrefix + tagInfix + tagSuffix } type consumerBuffers map[string]chan *Delivery // Concurrent type that manages the consumerTag -> // ingress consumerBuffer mapping type consumers struct { sync.WaitGroup // one for buffer closed chan struct{} // signal buffer sync.Mutex // protects below chans consumerBuffers } func makeConsumers() *consumers { return &consumers{ closed: make(chan struct{}), chans: make(consumerBuffers), } } func (subs *consumers) buffer(in chan *Delivery, out chan Delivery) { defer close(out) defer subs.Done() var inflight = in var queue []*Delivery for delivery := range in { queue = append(queue, delivery) for len(queue) > 0 { select { case <-subs.closed: // closed before drained, drop in-flight return case delivery, consuming := <-inflight: if consuming { queue = append(queue, delivery) } else { inflight = nil } case out <- *queue[0]: /* * https://github.com/rabbitmq/amqp091-go/issues/179 * https://github.com/rabbitmq/amqp091-go/pull/180 * * Comment from @lars-t-hansen: * * Given Go's slice semantics, and barring any information * available to the compiler that proves that queue is the only * pointer to the memory it references, the only meaning that * queue = queue[1:] can have is basically queue += sizeof(queue * element), ie, it bumps a pointer. Looking at the generated * code for a simple example (on ARM64 in this case) bears this * out. So what we're left with is an array that we have a * pointer into the middle of. When the GC traces this pointer, * it too does not know whether the array has multiple * referents, and so its only sensible choice is to find the * beginning of the array, and if the array is not already * visited, mark every element in it, including the "dead" * pointer. * * (Depending on the program dynamics, an element may eventually * be appended to the queue when the queue is at capacity, and * in this case the live elements are copied into a new array * and the old array is left to be GC'd eventually, along with * the dead object. But that can take time.) */ queue[0] = nil queue = queue[1:] } } } } // On key conflict, close the previous channel. func (subs *consumers) add(tag string, consumer chan Delivery) { subs.Lock() defer subs.Unlock() if prev, found := subs.chans[tag]; found { close(prev) } in := make(chan *Delivery) subs.chans[tag] = in subs.Add(1) go subs.buffer(in, consumer) } func (subs *consumers) cancel(tag string) (found bool) { subs.Lock() defer subs.Unlock() ch, found := subs.chans[tag] if found { delete(subs.chans, tag) close(ch) } return found } func (subs *consumers) close() { subs.Lock() defer subs.Unlock() close(subs.closed) for tag, ch := range subs.chans { delete(subs.chans, tag) close(ch) } subs.Wait() } // Sends a delivery to a the consumer identified by `tag`. // If unbuffered channels are used for Consume this method // could block all deliveries until the consumer // receives on the other end of the channel. func (subs *consumers) send(tag string, msg *Delivery) bool { subs.Lock() defer subs.Unlock() buffer, found := subs.chans[tag] if found { buffer <- msg } return found } golang-github-rabbitmq-amqp091-go-1.10.0/consumers_test.go000066400000000000000000000013741462444370000233250ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "strings" "testing" ) func TestGeneratedUniqueConsumerTagDoesNotExceedMaxLength(t *testing.T) { assertCorrectLength := func(commandName string) { tag := commandNameBasedUniqueConsumerTag(commandName) if len(tag) > consumerTagLengthMax { t.Error("Generated unique consumer tag exceeds maximum length:", tag) } } assertCorrectLength("test") assertCorrectLength(strings.Repeat("z", 249)) assertCorrectLength(strings.Repeat("z", 256)) assertCorrectLength(strings.Repeat("z", 1024)) } golang-github-rabbitmq-amqp091-go-1.10.0/delivery.go000066400000000000000000000135351462444370000220750ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "errors" "time" ) var errDeliveryNotInitialized = errors.New("delivery not initialized") // Acknowledger notifies the server of successful or failed consumption of // deliveries via identifier found in the Delivery.DeliveryTag field. // // Applications can provide mock implementations in tests of Delivery handlers. type Acknowledger interface { Ack(tag uint64, multiple bool) error Nack(tag uint64, multiple bool, requeue bool) error Reject(tag uint64, requeue bool) error } // Delivery captures the fields for a previously delivered message resident in // a queue to be delivered by the server to a consumer from Channel.Consume or // Channel.Get. type Delivery struct { Acknowledger Acknowledger // the channel from which this delivery arrived Headers Table // Application or header exchange table // Properties ContentType string // MIME content type ContentEncoding string // MIME content encoding DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2) Priority uint8 // queue implementation use - 0 to 9 CorrelationId string // application use - correlation identifier ReplyTo string // application use - address to reply to (ex: RPC) Expiration string // implementation use - message expiration spec MessageId string // application use - message identifier Timestamp time.Time // application use - message timestamp Type string // application use - message type name UserId string // application use - creating user - should be authenticated user AppId string // application use - creating application id // Valid only with Channel.Consume ConsumerTag string // Valid only with Channel.Get MessageCount uint32 DeliveryTag uint64 Redelivered bool Exchange string // basic.publish exchange RoutingKey string // basic.publish routing key Body []byte } func newDelivery(channel *Channel, msg messageWithContent) *Delivery { props, body := msg.getContent() delivery := Delivery{ Acknowledger: channel, Headers: props.Headers, ContentType: props.ContentType, ContentEncoding: props.ContentEncoding, DeliveryMode: props.DeliveryMode, Priority: props.Priority, CorrelationId: props.CorrelationId, ReplyTo: props.ReplyTo, Expiration: props.Expiration, MessageId: props.MessageId, Timestamp: props.Timestamp, Type: props.Type, UserId: props.UserId, AppId: props.AppId, Body: body, } // Properties for the delivery types switch m := msg.(type) { case *basicDeliver: delivery.ConsumerTag = m.ConsumerTag delivery.DeliveryTag = m.DeliveryTag delivery.Redelivered = m.Redelivered delivery.Exchange = m.Exchange delivery.RoutingKey = m.RoutingKey case *basicGetOk: delivery.MessageCount = m.MessageCount delivery.DeliveryTag = m.DeliveryTag delivery.Redelivered = m.Redelivered delivery.Exchange = m.Exchange delivery.RoutingKey = m.RoutingKey } return &delivery } /* Ack delegates an acknowledgement through the Acknowledger interface that the client or server has finished work on a delivery. All deliveries in AMQP must be acknowledged. If you called Channel.Consume with autoAck true then the server will be automatically ack each message and this method should not be called. Otherwise, you must call Delivery.Ack after you have successfully processed this delivery. When multiple is true, this delivery and all prior unacknowledged deliveries on the same channel will be acknowledged. This is useful for batch processing of deliveries. An error will indicate that the acknowledge could not be delivered to the channel it was sent from. Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every delivery that is not automatically acknowledged. */ func (d Delivery) Ack(multiple bool) error { if d.Acknowledger == nil { return errDeliveryNotInitialized } return d.Acknowledger.Ack(d.DeliveryTag, multiple) } /* Reject delegates a negatively acknowledgement through the Acknowledger interface. When requeue is true, queue this message to be delivered to a consumer on a different channel. When requeue is false or the server is unable to queue this message, it will be dropped. If you are batch processing deliveries, and your server supports it, prefer Delivery.Nack. Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every delivery that is not automatically acknowledged. */ func (d Delivery) Reject(requeue bool) error { if d.Acknowledger == nil { return errDeliveryNotInitialized } return d.Acknowledger.Reject(d.DeliveryTag, requeue) } /* Nack negatively acknowledge the delivery of message(s) identified by the delivery tag from either the client or server. When multiple is true, nack messages up to and including delivered messages up until the delivery tag delivered on the same channel. When requeue is true, request the server to deliver this message to a different consumer. If it is not possible or requeue is false, the message will be dropped or delivered to a server configured dead-letter queue. This method must not be used to select or requeue messages the client wishes not to handle, rather it is to inform the server that the client is incapable of handling this message at this time. Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every delivery that is not automatically acknowledged. */ func (d Delivery) Nack(multiple, requeue bool) error { if d.Acknowledger == nil { return errDeliveryNotInitialized } return d.Acknowledger.Nack(d.DeliveryTag, multiple, requeue) } golang-github-rabbitmq-amqp091-go-1.10.0/delivery_test.go000066400000000000000000000022231462444370000231240ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import "testing" func shouldNotPanic(t *testing.T) { if err := recover(); err != nil { t.Fatalf("should not panic, got: %s", err) } } // A closed delivery chan could produce zero value. Ack/Nack/Reject on these // deliveries can produce a nil pointer panic. Instead return an error when // the method can never be successful. func TestAckZeroValueAcknowledgerDoesNotPanic(t *testing.T) { defer shouldNotPanic(t) if err := (Delivery{}).Ack(false); err == nil { t.Errorf("expected Delivery{}.Ack to error") } } func TestNackZeroValueAcknowledgerDoesNotPanic(t *testing.T) { defer shouldNotPanic(t) if err := (Delivery{}).Nack(false, false); err == nil { t.Errorf("expected Delivery{}.Ack to error") } } func TestRejectZeroValueAcknowledgerDoesNotPanic(t *testing.T) { defer shouldNotPanic(t) if err := (Delivery{}).Reject(false); err == nil { t.Errorf("expected Delivery{}.Ack to error") } } golang-github-rabbitmq-amqp091-go-1.10.0/doc.go000066400000000000000000000157771462444370000210310ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* Package amqp091 is an AMQP 0.9.1 client with RabbitMQ extensions Understand the AMQP 0.9.1 messaging model by reviewing these links first. Much of the terminology in this library directly relates to AMQP concepts. Resources http://www.rabbitmq.com/tutorials/amqp-concepts.html http://www.rabbitmq.com/getstarted.html http://www.rabbitmq.com/amqp-0-9-1-reference.html # Design Most other broker clients publish to queues, but in AMQP, clients publish Exchanges instead. AMQP is programmable, meaning that both the producers and consumers agree on the configuration of the broker, instead of requiring an operator or system configuration that declares the logical topology in the broker. The routing between producers and consumer queues is via Bindings. These bindings form the logical topology of the broker. In this library, a message sent from publisher is called a "Publishing" and a message received to a consumer is called a "Delivery". The fields of Publishings and Deliveries are close but not exact mappings to the underlying wire format to maintain stronger types. Many other libraries will combine message properties with message headers. In this library, the message well known properties are strongly typed fields on the Publishings and Deliveries, whereas the user defined headers are in the Headers field. The method naming closely matches the protocol's method name with positional parameters mapping to named protocol message fields. The motivation here is to present a comprehensive view over all possible interactions with the server. Generally, methods that map to protocol methods of the "basic" class will be elided in this interface, and "select" methods of various channel mode selectors will be elided for example Channel.Confirm and Channel.Tx. The library is intentionally designed to be synchronous, where responses for each protocol message are required to be received in an RPC manner. Some methods have a noWait parameter like Channel.QueueDeclare, and some methods are asynchronous like Channel.Publish. The error values should still be checked for these methods as they will indicate IO failures like when the underlying connection closes. # Asynchronous Events Clients of this library may be interested in receiving some of the protocol messages other than Deliveries like basic.ack methods while a channel is in confirm mode. The Notify* methods with Connection and Channel receivers model the pattern of asynchronous events like closes due to exceptions, or messages that are sent out of band from an RPC call like basic.ack or basic.flow. Any asynchronous events, including Deliveries and Publishings must always have a receiver until the corresponding chans are closed. Without asynchronous receivers, the synchronous methods will block. # Use Case It's important as a client to an AMQP topology to ensure the state of the broker matches your expectations. For both publish and consume use cases, make sure you declare the queues, exchanges and bindings you expect to exist prior to calling [Channel.PublishWithContext] or [Channel.Consume]. // Connections start with amqp.Dial() typically from a command line argument // or environment variable. connection, err := amqp.Dial(os.Getenv("AMQP_URL")) // To cleanly shutdown by flushing kernel buffers, make sure to close and // wait for the response. defer connection.Close() // Most operations happen on a channel. If any error is returned on a // channel, the channel will no longer be valid, throw it away and try with // a different channel. If you use many channels, it's useful for the // server to channel, err := connection.Channel() // Declare your topology here, if it doesn't exist, it will be created, if // it existed already and is not what you expect, then that's considered an // error. // Use your connection on this topology with either Publish or Consume, or // inspect your queues with QueueInspect. It's unwise to mix Publish and // Consume to let TCP do its job well. # SSL/TLS - Secure connections When Dial encounters an amqps:// scheme, it will use the zero value of a tls.Config. This will only perform server certificate and host verification. Use DialTLS when you wish to provide a client certificate (recommended), include a private certificate authority's certificate in the cert chain for server validity, or run insecure by not verifying the server certificate. DialTLS will use the provided tls.Config when it encounters an amqps:// scheme and will dial a plain connection when it encounters an amqp:// scheme. SSL/TLS in RabbitMQ is documented here: http://www.rabbitmq.com/ssl.html # Best practises for Connection and Channel notifications: In order to be notified when a connection or channel gets closed, both structures offer the possibility to register channels using [Channel.NotifyClose] and [Connection.NotifyClose] functions: notifyConnCloseCh := conn.NotifyClose(make(chan *amqp.Error, 1)) No errors will be sent in case of a graceful connection close. In case of a non-graceful closure due to e.g. network issue, or forced connection closure from the Management UI, the error will be notified synchronously by the library. The library sends to notification channels just once. After sending a notification to all channels, the library closes all registered notification channels. After receiving a notification, the application should create and register a new channel. To avoid deadlocks in the library, it is necessary to consume from the channels. This could be done inside a different goroutine with a select listening on the two channels inside a for loop like: go func() { for notifyConnClose != nil || notifyChanClose != nil { select { case err, ok := <-notifyConnClose: if !ok { notifyConnClose = nil } else { fmt.Printf("connection closed, error %s", err) } case err, ok := <-notifyChanClose: if !ok { notifyChanClose = nil } else { fmt.Printf("channel closed, error %s", err) } } } }() It is strongly recommended to use buffered channels to avoid deadlocks inside the library. # Best practises for NotifyPublish notifications: Using [Channel.NotifyPublish] allows the caller of the library to be notified, through a go channel, when a message has been received and confirmed by the broker. It's advisable to wait for all Confirmations to arrive before calling [Channel.Close] or [Connection.Close]. It is also necessary to consume from this channel until it gets closed. The library sends synchronously to the registered channel. It is advisable to use a buffered channel, with capacity set to the maximum acceptable number of unconfirmed messages. It is important to consume from the confirmation channel at all times, in order to avoid deadlocks in the library. */ package amqp091 golang-github-rabbitmq-amqp091-go-1.10.0/example_client_test.go000066400000000000000000000244721462444370000243040ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091_test import ( "context" "errors" "log" "os" "sync" "time" amqp "github.com/rabbitmq/amqp091-go" ) // This exports a Client object that wraps this library. It // automatically reconnects when the connection fails, and // blocks all pushes until the connection succeeds. It also // confirms every outgoing message, so none are lost. // It doesn't automatically ack each message, but leaves that // to the parent process, since it is usage-dependent. // // Try running this in one terminal, and rabbitmq-server in another. // // Stop & restart RabbitMQ to see how the queue reacts. func Example_publish() { queueName := "job_queue" addr := "amqp://guest:guest@localhost:5672/" queue := New(queueName, addr) message := []byte("message") ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*20)) defer cancel() loop: for { select { // Attempt to push a message every 2 seconds case <-time.After(time.Second * 2): if err := queue.Push(message); err != nil { log.Printf("Push failed: %s\n", err) } else { log.Println("Push succeeded!") } case <-ctx.Done(): if err := queue.Close(); err != nil { log.Printf("Close failed: %s\n", err) } break loop } } } func Example_consume() { queueName := "job_queue" addr := "amqp://guest:guest@localhost:5672/" queue := New(queueName, addr) // Give the connection sometime to set up <-time.After(time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() deliveries, err := queue.Consume() if err != nil { log.Printf("Could not start consuming: %s\n", err) return } // This channel will receive a notification when a channel closed event // happens. This must be different from Client.notifyChanClose because the // library sends only one notification and Client.notifyChanClose already has // a receiver in handleReconnect(). // Recommended to make it buffered to avoid deadlocks chClosedCh := make(chan *amqp.Error, 1) queue.channel.NotifyClose(chClosedCh) for { select { case <-ctx.Done(): err := queue.Close() if err != nil { log.Printf("Close failed: %s\n", err) } return case amqErr := <-chClosedCh: // This case handles the event of closed channel e.g. abnormal shutdown log.Printf("AMQP Channel closed due to: %s\n", amqErr) deliveries, err = queue.Consume() if err != nil { // If the AMQP channel is not ready, it will continue the loop. Next // iteration will enter this case because chClosedCh is closed by the // library log.Println("Error trying to consume, will try again") continue } // Re-set channel to receive notifications // The library closes this channel after abnormal shutdown chClosedCh = make(chan *amqp.Error, 1) queue.channel.NotifyClose(chClosedCh) case delivery := <-deliveries: // Ack a message every 2 seconds log.Printf("Received message: %s\n", delivery.Body) if err := delivery.Ack(false); err != nil { log.Printf("Error acknowledging message: %s\n", err) } <-time.After(time.Second * 2) } } } // Client is the base struct for handling connection recovery, consumption and // publishing. Note that this struct has an internal mutex to safeguard against // data races. As you develop and iterate over this example, you may need to add // further locks, or safeguards, to keep your application safe from data races type Client struct { m *sync.Mutex queueName string logger *log.Logger connection *amqp.Connection channel *amqp.Channel done chan bool notifyConnClose chan *amqp.Error notifyChanClose chan *amqp.Error notifyConfirm chan amqp.Confirmation isReady bool } const ( // When reconnecting to the server after connection failure reconnectDelay = 5 * time.Second // When setting up the channel after a channel exception reInitDelay = 2 * time.Second // When resending messages the server didn't confirm resendDelay = 5 * time.Second ) var ( errNotConnected = errors.New("not connected to a server") errAlreadyClosed = errors.New("already closed: not connected to the server") errShutdown = errors.New("client is shutting down") ) // New creates a new consumer state instance, and automatically // attempts to connect to the server. func New(queueName, addr string) *Client { client := Client{ m: &sync.Mutex{}, logger: log.New(os.Stdout, "", log.LstdFlags), queueName: queueName, done: make(chan bool), } go client.handleReconnect(addr) return &client } // handleReconnect will wait for a connection error on // notifyConnClose, and then continuously attempt to reconnect. func (client *Client) handleReconnect(addr string) { for { client.m.Lock() client.isReady = false client.m.Unlock() client.logger.Println("Attempting to connect") conn, err := client.connect(addr) if err != nil { client.logger.Println("Failed to connect. Retrying...") select { case <-client.done: return case <-time.After(reconnectDelay): } continue } if done := client.handleReInit(conn); done { break } } } // connect will create a new AMQP connection func (client *Client) connect(addr string) (*amqp.Connection, error) { conn, err := amqp.Dial(addr) if err != nil { return nil, err } client.changeConnection(conn) client.logger.Println("Connected!") return conn, nil } // handleReInit will wait for a channel error // and then continuously attempt to re-initialize both channels func (client *Client) handleReInit(conn *amqp.Connection) bool { for { client.m.Lock() client.isReady = false client.m.Unlock() err := client.init(conn) if err != nil { client.logger.Println("Failed to initialize channel. Retrying...") select { case <-client.done: return true case <-client.notifyConnClose: client.logger.Println("Connection closed. Reconnecting...") return false case <-time.After(reInitDelay): } continue } select { case <-client.done: return true case <-client.notifyConnClose: client.logger.Println("Connection closed. Reconnecting...") return false case <-client.notifyChanClose: client.logger.Println("Channel closed. Re-running init...") } } } // init will initialize channel & declare queue func (client *Client) init(conn *amqp.Connection) error { ch, err := conn.Channel() if err != nil { return err } err = ch.Confirm(false) if err != nil { return err } _, err = ch.QueueDeclare( client.queueName, false, // Durable false, // Delete when unused false, // Exclusive false, // No-wait nil, // Arguments ) if err != nil { return err } client.changeChannel(ch) client.m.Lock() client.isReady = true client.m.Unlock() client.logger.Println("Setup!") return nil } // changeConnection takes a new connection to the queue, // and updates the close listener to reflect this. func (client *Client) changeConnection(connection *amqp.Connection) { client.connection = connection client.notifyConnClose = make(chan *amqp.Error, 1) client.connection.NotifyClose(client.notifyConnClose) } // changeChannel takes a new channel to the queue, // and updates the channel listeners to reflect this. func (client *Client) changeChannel(channel *amqp.Channel) { client.channel = channel client.notifyChanClose = make(chan *amqp.Error, 1) client.notifyConfirm = make(chan amqp.Confirmation, 1) client.channel.NotifyClose(client.notifyChanClose) client.channel.NotifyPublish(client.notifyConfirm) } // Push will push data onto the queue, and wait for a confirmation. // This will block until the server sends a confirmation. Errors are // only returned if the push action itself fails, see UnsafePush. func (client *Client) Push(data []byte) error { client.m.Lock() if !client.isReady { client.m.Unlock() return errors.New("failed to push: not connected") } client.m.Unlock() for { err := client.UnsafePush(data) if err != nil { client.logger.Println("Push failed. Retrying...") select { case <-client.done: return errShutdown case <-time.After(resendDelay): } continue } confirm := <-client.notifyConfirm if confirm.Ack { client.logger.Printf("Push confirmed [%d]!", confirm.DeliveryTag) return nil } } } // UnsafePush will push to the queue without checking for // confirmation. It returns an error if it fails to connect. // No guarantees are provided for whether the server will // receive the message. func (client *Client) UnsafePush(data []byte) error { client.m.Lock() if !client.isReady { client.m.Unlock() return errNotConnected } client.m.Unlock() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return client.channel.PublishWithContext( ctx, "", // Exchange client.queueName, // Routing key false, // Mandatory false, // Immediate amqp.Publishing{ ContentType: "text/plain", Body: data, }, ) } // Consume will continuously put queue items on the channel. // It is required to call delivery.Ack when it has been // successfully processed, or delivery.Nack when it fails. // Ignoring this will cause data to build up on the server. func (client *Client) Consume() (<-chan amqp.Delivery, error) { client.m.Lock() if !client.isReady { client.m.Unlock() return nil, errNotConnected } client.m.Unlock() if err := client.channel.Qos( 1, // prefetchCount 0, // prefetchSize false, // global ); err != nil { return nil, err } return client.channel.Consume( client.queueName, "", // Consumer false, // Auto-Ack false, // Exclusive false, // No-local false, // No-Wait nil, // Args ) } // Close will cleanly shut down the channel and connection. func (client *Client) Close() error { client.m.Lock() // we read and write isReady in two locations, so we grab the lock and hold onto // it until we are finished defer client.m.Unlock() if !client.isReady { return errAlreadyClosed } close(client.done) err := client.channel.Close() if err != nil { return err } err = client.connection.Close() if err != nil { return err } client.isReady = false return nil } golang-github-rabbitmq-amqp091-go-1.10.0/examples_test.go000066400000000000000000000425001462444370000231210ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091_test import ( "context" "crypto/tls" "crypto/x509" "fmt" "log" "net" "os" "runtime" "time" amqp "github.com/rabbitmq/amqp091-go" ) func ExampleConfig_timeout() { // Provide your own anonymous Dial function that delegates to net.DialTimout // for custom timeouts conn, err := amqp.DialConfig("amqp:///", amqp.Config{ Dial: func(network, addr string) (net.Conn, error) { return net.DialTimeout(network, addr, 2*time.Second) }, }) log.Printf("conn: %v, err: %v", conn, err) } func ExampleDialTLS() { // This example assume you have a RabbitMQ node running on localhost // with TLS enabled. // // The easiest way to create the CA, certificates and keys required for these // examples is by using tls-gen: https://github.com/michaelklishin/tls-gen // // A comprehensive RabbitMQ TLS guide can be found at // http://www.rabbitmq.com/ssl.html // // Once you have the required TLS files in place, use the following // rabbitmq.config example for the RabbitMQ node that you will run on // localhost: // // [ // {rabbit, [ // {tcp_listeners, []}, % listens on 127.0.0.1:5672 // {ssl_listeners, [5671]}, % listens on 0.0.0.0:5671 // {ssl_options, [{cacertfile,"/path/to/your/testca/cacert.pem"}, // {certfile,"/path/to/your/server/cert.pem"}, // {keyfile,"/path/to/your/server/key.pem"}, // {verify,verify_peer}, // {fail_if_no_peer_cert,true}]} // ]} // ]. // // // In the above rabbitmq.config example, we are disabling the plain AMQP port // and verifying that clients and fail if no certificate is presented. // // The self-signing certificate authority's certificate (cacert.pem) must be // included in the RootCAs to be trusted, otherwise the server certificate // will fail certificate verification. // // Alternatively to adding it to the tls.Config. you can add the CA's cert to // your system's root CAs. The tls package will use the system roots // specific to each support OS. Under OS X, add (drag/drop) cacert.pem // file to the 'Certificates' section of KeyChain.app to add and always // trust. You can also add it via the command line: // // security add-certificate testca/cacert.pem // security add-trusted-cert testca/cacert.pem // // If you depend on the system root CAs, then use nil for the RootCAs field // so the system roots will be loaded instead. // // Server names are validated by the crypto/tls package, so the server // certificate must be made for the hostname in the URL. Find the commonName // (CN) and make sure the hostname in the URL matches this common name. Per // the RabbitMQ instructions (or tls-gen) for a self-signed cert, this defaults to the // current hostname. // // openssl x509 -noout -in /path/to/certificate.pem -subject // // If your server name in your certificate is different than the host you are // connecting to, set the hostname used for verification in // ServerName field of the tls.Config struct. cfg := new(tls.Config) // see at the top cfg.RootCAs = x509.NewCertPool() if ca, err := os.ReadFile("testca/cacert.pem"); err == nil { cfg.RootCAs.AppendCertsFromPEM(ca) } // Move the client cert and key to a location specific to your application // and load them here. if cert, err := tls.LoadX509KeyPair("client/cert.pem", "client/key.pem"); err == nil { cfg.Certificates = append(cfg.Certificates, cert) } // see a note about Common Name (CN) at the top conn, err := amqp.DialTLS("amqps://server-name-from-certificate/", cfg) log.Printf("conn: %v, err: %v", conn, err) } func ExampleChannel_Confirm_bridge() { // This example acts as a bridge, shoveling all messages sent from the source // exchange "log" to destination exchange "log". // Confirming publishes can help from overproduction and ensure every message // is delivered. // Setup the source of the store and forward source, err := amqp.Dial("amqp://source/") if err != nil { log.Fatalf("connection.open source: %s", err) } defer source.Close() chs, err := source.Channel() if err != nil { log.Fatalf("channel.open source: %s", err) } if err := chs.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil { log.Fatalf("exchange.declare destination: %s", err) } if _, err := chs.QueueDeclare("remote-tee", true, true, false, false, nil); err != nil { log.Fatalf("queue.declare source: %s", err) } if err := chs.QueueBind("remote-tee", "#", "logs", false, nil); err != nil { log.Fatalf("queue.bind source: %s", err) } shovel, err := chs.Consume("remote-tee", "shovel", false, false, false, false, nil) if err != nil { log.Fatalf("basic.consume source: %s", err) } // Setup the destination of the store and forward destination, err := amqp.Dial("amqp://destination/") if err != nil { log.Fatalf("connection.open destination: %s", err) } defer destination.Close() chd, err := destination.Channel() if err != nil { log.Fatalf("channel.open destination: %s", err) } if err := chd.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil { log.Fatalf("exchange.declare destination: %s", err) } // Buffer of 1 for our single outstanding publishing confirms := chd.NotifyPublish(make(chan amqp.Confirmation, 1)) if err := chd.Confirm(false); err != nil { log.Fatalf("confirm.select destination: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Now pump the messages, one by one, a smarter implementation // would batch the deliveries and use multiple ack/nacks for { msg, ok := <-shovel if !ok { log.Fatalf("source channel closed, see the reconnect example for handling this") } err = chd.PublishWithContext(ctx, "logs", msg.RoutingKey, false, false, amqp.Publishing{ // Copy all the properties ContentType: msg.ContentType, ContentEncoding: msg.ContentEncoding, DeliveryMode: msg.DeliveryMode, Priority: msg.Priority, CorrelationId: msg.CorrelationId, ReplyTo: msg.ReplyTo, Expiration: msg.Expiration, MessageId: msg.MessageId, Timestamp: msg.Timestamp, Type: msg.Type, UserId: msg.UserId, AppId: msg.AppId, // Custom headers Headers: msg.Headers, // And the body Body: msg.Body, }) if err != nil { if e := msg.Nack(false, false); e != nil { log.Printf("nack error: %+v", e) } log.Fatalf("basic.publish destination: %+v", msg) } // only ack the source delivery when the destination acks the publishing if confirmed := <-confirms; confirmed.Ack { if e := msg.Ack(false); e != nil { log.Printf("ack error: %+v", e) } } else { if e := msg.Nack(false, false); e != nil { log.Printf("nack error: %+v", e) } } } } func ExampleChannel_Consume() { // Connects opens an AMQP connection from the credentials in the URL. conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") if err != nil { log.Fatalf("connection.open: %s", err) } defer conn.Close() c, err := conn.Channel() if err != nil { log.Fatalf("channel.open: %s", err) } // We declare our topology on both the publisher and consumer to ensure they // are the same. This is part of AMQP being a programmable messaging model. // // See the Channel.Publish example for the complimentary declare. err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil) if err != nil { log.Fatalf("exchange.declare: %s", err) } // Establish our queue topologies that we are responsible for type bind struct { queue string key string } bindings := []bind{ {"page", "alert"}, {"email", "info"}, {"firehose", "#"}, } for _, b := range bindings { _, err = c.QueueDeclare(b.queue, true, false, false, false, nil) if err != nil { log.Fatalf("queue.declare: %v", err) } err = c.QueueBind(b.queue, b.key, "logs", false, nil) if err != nil { log.Fatalf("queue.bind: %v", err) } } // Set our quality of service. Since we're sharing 3 consumers on the same // channel, we want at least 3 messages in flight. err = c.Qos(3, 0, false) if err != nil { log.Fatalf("basic.qos: %v", err) } // Establish our consumers that have different responsibilities. Our first // two queues do not ack the messages on the server, so require to be acked // on the client. pages, err := c.Consume("page", "pager", false, false, false, false, nil) if err != nil { log.Fatalf("basic.consume: %v", err) } go func() { for page := range pages { // ... this consumer is responsible for sending pages per log if e := page.Ack(false); e != nil { log.Printf("ack error: %+v", e) } } }() // Notice how the concern for which messages arrive here are in the AMQP // topology and not in the queue. We let the server pick a consumer tag this // time. emails, err := c.Consume("email", "", false, false, false, false, nil) if err != nil { log.Fatalf("basic.consume: %v", err) } go func() { for email := range emails { // ... this consumer is responsible for sending emails per log if e := email.Ack(false); e != nil { log.Printf("ack error: %+v", e) } } }() // This consumer requests that every message is acknowledged as soon as it's // delivered. firehose, err := c.Consume("firehose", "", true, false, false, false, nil) if err != nil { log.Fatalf("basic.consume: %v", err) } // To show how to process the items in parallel, we'll use a work pool. for i := 0; i < runtime.NumCPU(); i++ { go func(work <-chan amqp.Delivery) { for range work { // ... this consumer pulls from the firehose and doesn't need to acknowledge } }(firehose) } // Wait until you're ready to finish, could be a signal handler here. time.Sleep(10 * time.Second) // Cancelling a consumer by name will finish the range and gracefully end the // goroutine err = c.Cancel("pager", false) if err != nil { log.Fatalf("basic.cancel: %v", err) } // deferred closing the Connection will also finish the consumer's ranges of // their delivery chans. If you need every delivery to be processed, make // sure to wait for all consumers goroutines to finish before exiting your // process. } func ExampleChannel_PublishWithContext() { // Connects opens an AMQP connection from the credentials in the URL. conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") if err != nil { log.Fatalf("connection.open: %s", err) } // This waits for a server acknowledgment which means the sockets will have // flushed all outbound publishings prior to returning. It's important to // block on Close to not lose any publishings. defer conn.Close() c, err := conn.Channel() if err != nil { log.Fatalf("channel.open: %s", err) } // We declare our topology on both the publisher and consumer to ensure they // are the same. This is part of AMQP being a programmable messaging model. // // See the Channel.Consume example for the complimentary declare. err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil) if err != nil { log.Fatalf("exchange.declare: %v", err) } // Prepare this message to be persistent. Your publishing requirements may // be different. msg := amqp.Publishing{ DeliveryMode: amqp.Persistent, Timestamp: time.Now(), ContentType: "text/plain", Body: []byte("Go Go AMQP!"), } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // This is not a mandatory delivery, so it will be dropped if there are no // queues bound to the logs exchange. err = c.PublishWithContext(ctx, "logs", "info", false, false, msg) if err != nil { // Since publish is asynchronous this can happen if the network connection // is reset or if the server has run out of resources. log.Fatalf("basic.publish: %v", err) } } func publishAllTheThings(conn *amqp.Connection) { // ... snarf snarf, barf barf } func ExampleConnection_NotifyBlocked() { // Simply logs when the server throttles the TCP connection for publishers // Test this by tuning your server to have a low memory watermark: // rabbitmqctl set_vm_memory_high_watermark 0.00000001 conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") if err != nil { log.Fatalf("connection.open: %s", err) } defer conn.Close() blockings := conn.NotifyBlocked(make(chan amqp.Blocking)) go func() { for b := range blockings { if b.Active { log.Printf("TCP blocked: %q", b.Reason) } else { log.Printf("TCP unblocked") } } }() // Your application domain channel setup publishings publishAllTheThings(conn) } func ExampleTable_SetClientConnectionName() { // Sets the well-known connection_name property in amqp.Config. The connection // name will be visible in RabbitMQ Management UI. config := amqp.Config{Properties: amqp.NewConnectionProperties()} config.Properties.SetClientConnectionName("my-client-app") conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") if err != nil { log.Fatalf("connection.open: %s", err) } defer conn.Close() } func ExampleConnection_UpdateSecret() { // In order to authenticate into RabbitMQ, the application must acquire a JWT token. // This may be different, depending on the library used to communicate with the OAuth2 // server. This examples assumes that it's possible to obtain tokens using username+password. // // The authentication is successful if RabbitMQ can validate the JWT with the OAuth2 server. // The permissions are determined from the scopes. Check the OAuth2 plugin readme for more details: // https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/rabbitmq_auth_backend_oauth2#scope-to-permission-translation // // Once the app has a JWT token, this can be used as credentials in the URI used in Connection.Dial() // // The app should have a long-running task that checks the validity of the JWT token, and renew it before // the refresher time expires. Once a new JWT token is obtained, it shall be used in Connection.UpdateSecret(). token, _ := getJWToken("username", "password") uri := fmt.Sprintf("amqp://%s:%s@localhost:5672", "client_id", token) c, _ := amqp.Dial(uri) defer c.Close() // It also calls Connection.UpdateSecret() tokenRefresherTask := func(conn *amqp.Connection, token string) { // if token is expired // then renewedToken, _ := refreshJWToken(token) _ = conn.UpdateSecret(renewedToken, "Token refreshed!") } go tokenRefresherTask(c, "my-JWT-token") ch, _ := c.Channel() defer ch.Close() _, _ = ch.QueueDeclare( "test-amqp", false, false, false, false, amqp.Table{}, ) _ = ch.PublishWithContext( context.Background(), "", "test-amqp", false, false, amqp.Publishing{ Headers: amqp.Table{}, ContentType: "text/plain", ContentEncoding: "", DeliveryMode: amqp.Persistent, Body: []byte("message"), }, ) } func getJWToken(username, password string) (string, error) { // do OAuth2 things return "a-token", nil } func refreshJWToken(token string) (string, error) { // do OAuth2 things to refresh tokens return "so fresh!", nil } func ExampleChannel_QueueDeclare_quorum() { conn, _ := amqp.Dial("amqp://localhost") ch, _ := conn.Channel() args := amqp.Table{ // queue args amqp.QueueTypeArg: amqp.QueueTypeQuorum, } q, _ := ch.QueueDeclare( "my-quorum-queue", // queue name true, // durable false, // auto-delete false, // exclusive false, // noWait args, ) log.Printf("Declared queue: %s with arguments: %v", q.Name, args) } func ExampleChannel_QueueDeclare_stream() { conn, _ := amqp.Dial("amqp://localhost") ch, _ := conn.Channel() q, _ := ch.QueueDeclare( "my-stream-queue", // queue name true, // durable false, // auto-delete false, // exclusive false, // noWait amqp.Table{ // queue args amqp.QueueTypeArg: amqp.QueueTypeStream, amqp.StreamMaxLenBytesArg: int64(5_000_000_000), // 5 Gb amqp.StreamMaxSegmentSizeBytesArg: 500_000_000, // 500 Mb amqp.StreamMaxAgeArg: "3D", // 3 days }, ) log.Printf("Declared queue: %s", q.Name) } func ExampleChannel_QueueDeclare_classicQueueV2() { conn, _ := amqp.Dial("amqp://localhost") ch, _ := conn.Channel() q, _ := ch.QueueDeclare( "my-classic-queue-v2", // queue name true, // durable false, // auto-delete false, // exclusive false, // noWait amqp.Table{ amqp.QueueTypeArg: amqp.QueueTypeClassic, amqp.QueueVersionArg: 2, }, ) log.Printf("Declared Classic Queue v2: %s", q.Name) } func ExampleChannel_QueueDeclare_consumerTimeout() { conn, _ := amqp.Dial("amqp://localhost") ch, _ := conn.Channel() // this works only with RabbitMQ 3.12+ q, _ := ch.QueueDeclare( "my-classic-queue-v2", // queue name true, // durable false, // auto-delete false, // exclusive false, // noWait amqp.Table{ amqp.QueueTypeArg: amqp.QueueTypeQuorum, // also works with classic queues amqp.ConsumerTimeoutArg: 600_000, // 10 minute consumer timeout }, ) log.Printf("Declared Classic Queue v2: %s", q.Name) } golang-github-rabbitmq-amqp091-go-1.10.0/fuzz.go000066400000000000000000000007621462444370000212460ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build gofuzz // +build gofuzz package amqp091 import "bytes" func Fuzz(data []byte) int { r := reader{bytes.NewReader(data)} frame, err := r.ReadFrame() if err != nil { if frame != nil { panic("frame is not nil") } return 0 } return 1 } golang-github-rabbitmq-amqp091-go-1.10.0/gen.ps1000066400000000000000000000012331462444370000211110ustar00rootroot00000000000000$DebugPreference = 'Continue' $ErrorActionPreference = 'Stop' Set-PSDebug -Off Set-StrictMode -Version 'Latest' -ErrorAction 'Stop' -Verbose New-Variable -Name curdir -Option Constant -Value $PSScriptRoot $specDir = Resolve-Path -LiteralPath (Join-Path -Path $curdir -ChildPath 'spec') $amqpSpecXml = Resolve-Path -LiteralPath (Join-Path -Path $specDir -ChildPath 'amqp0-9-1.stripped.extended.xml') $gen = Resolve-Path -LiteralPath (Join-Path -Path $specDir -ChildPath 'gen.go') $spec091 = Resolve-Path -LiteralPath (Join-Path -Path $curdir -ChildPath 'spec091.go') Get-Content -LiteralPath $amqpSpecXml | go run $gen | gofmt | Set-Content -Force -Path $spec091 golang-github-rabbitmq-amqp091-go-1.10.0/gen.sh000077500000000000000000000001311462444370000210170ustar00rootroot00000000000000#!/bin/sh go run spec/gen.go < spec/amqp0-9-1.stripped.extended.xml | gofmt > spec091.go golang-github-rabbitmq-amqp091-go-1.10.0/go.mod000066400000000000000000000001221462444370000210150ustar00rootroot00000000000000module github.com/rabbitmq/amqp091-go go 1.20 require go.uber.org/goleak v1.3.0 golang-github-rabbitmq-amqp091-go-1.10.0/go.sum000066400000000000000000000007351462444370000210540ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= golang-github-rabbitmq-amqp091-go-1.10.0/integration_test.go000066400000000000000000001563761462444370000236470ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build integration // +build integration package amqp091 import ( "bytes" "context" devrand "crypto/rand" "encoding/binary" "fmt" "hash/crc32" "io" "math/rand" "net" "os" "reflect" "strconv" "sync" "testing" "testing/quick" "time" "go.uber.org/goleak" ) const envAMQPURLName = "AMQP_URL" var amqpURL = "amqp://guest:guest@127.0.0.1:5672/" func init() { url := os.Getenv(envAMQPURLName) if url != "" { amqpURL = url return } fmt.Printf("environment variable envAMQPURLName undefined or empty, using default: %q\n", amqpURL) } func TestIntegrationOpenClose(t *testing.T) { if c := integrationConnection(t, "open-close"); c != nil { t.Logf("have connection, calling connection close") if err := c.Close(); err != nil { t.Fatalf("connection close: %s", err) } t.Logf("connection close OK") } } func TestIntegrationOpenCloseChannel(t *testing.T) { if c := integrationConnection(t, "channel"); c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("create channel 1: %s", err) } ch.Close() } } func TestIntegrationHighChannelChurnInTightLoop(t *testing.T) { if c := integrationConnection(t, "channel churn"); c != nil { defer c.Close() for i := 0; i < 1000; i++ { ch, err := c.Channel() if err != nil { t.Fatalf("create channel 1: %s", err) } ch.Close() } } } func TestIntegrationOpenConfig(t *testing.T) { config := Config{} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } if _, err := c.Channel(); err != nil { t.Fatalf("expected to open channel: %s", err) } if err := c.Close(); err != nil { t.Fatalf("expected to close the connection: %s", err) } } func TestIntegrationOpenConfigWithNetDial(t *testing.T) { config := Config{Dial: net.Dial} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } if _, err := c.Channel(); err != nil { t.Fatalf("expected to open channel: %s", err) } if err := c.Close(); err != nil { t.Fatalf("expected to close the connection: %s", err) } } func TestIntegrationLocalAddr(t *testing.T) { config := Config{} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } defer c.Close() a := c.LocalAddr() _, portString, err := net.SplitHostPort(a.String()) if err != nil { t.Fatalf("expected to get a local network address with config %+v integration server: %s", config, a.String()) } port, err := strconv.Atoi(portString) if err != nil { t.Fatalf("expected to get a TCP port number with config %+v integration server: %s", config, err) } t.Logf("Connected to port %d\n", port) } func TestIntegrationRemoteAddr(t *testing.T) { config := Config{} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } defer c.Close() a := c.RemoteAddr() _, portString, err := net.SplitHostPort(a.String()) if err != nil { t.Fatalf("expected to get a remote network address with config %+v integration server: %s", config, a.String()) } port, err := strconv.Atoi(portString) if err != nil { t.Fatalf("expected to get a TCP port number with config %+v integration server: %s", config, err) } t.Logf("Connected to port %d\n", port) } // https://github.com/streadway/amqp/issues/94 func TestExchangePassiveOnMissingExchangeShouldError(t *testing.T) { c := integrationConnection(t, "exch") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("create channel 1: %s", err) } defer ch.Close() if err := ch.ExchangeDeclarePassive( "test-integration-missing-passive-exchange", "direct", // type false, // duration (note: is durable) true, // auto-delete false, // internal false, // nowait nil, // args ); err == nil { t.Fatal("ExchangeDeclarePassive of a missing exchange should return error") } } } // https://github.com/streadway/amqp/issues/94 func TestIntegrationExchangeDeclarePassiveOnDeclaredShouldNotError(t *testing.T) { c := integrationConnection(t, "exch") if c != nil { defer c.Close() exchange := "test-integration-declared-passive-exchange" ch, err := c.Channel() if err != nil { t.Fatalf("create channel: %s", err) } defer ch.Close() if err := ch.ExchangeDeclare( exchange, // name "direct", // type false, // durable true, // auto-delete false, // internal false, // nowait nil, // args ); err != nil { t.Fatalf("declare exchange: %s", err) } if err := ch.ExchangeDeclarePassive( exchange, // name "direct", // type false, // durable true, // auto-delete false, // internal false, // nowait nil, // args ); err != nil { t.Fatalf("ExchangeDeclarePassive on a declared exchange should not error, got: %q", err) } } } func TestIntegrationExchange(t *testing.T) { c := integrationConnection(t, "exch") if c != nil { defer c.Close() channel, err := c.Channel() if err != nil { t.Fatalf("create channel: %s", err) } t.Logf("create channel OK") exchange := "test-integration-exchange" if err := channel.ExchangeDeclare( exchange, // name "direct", // type false, // duration true, // auto-delete false, // internal false, // nowait nil, // args ); err != nil { t.Fatalf("declare exchange: %s", err) } t.Logf("declare exchange OK") if err := channel.ExchangeDelete(exchange, false, false); err != nil { t.Fatalf("delete exchange: %s", err) } t.Logf("delete exchange OK") if err := channel.Close(); err != nil { t.Fatalf("close channel: %s", err) } t.Logf("close channel OK") } } // https://github.com/streadway/amqp/issues/94 func TestIntegrationQueueDeclarePassiveOnMissingExchangeShouldError(t *testing.T) { c := integrationConnection(t, "queue") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("create channel1: %s", err) } defer ch.Close() if _, err := ch.QueueDeclarePassive( "test-integration-missing-passive-queue", // name false, // duration (note: not durable) true, // auto-delete false, // exclusive false, // noWait nil, // arguments ); err == nil { t.Fatal("QueueDeclarePassive of a missing queue should error") } } } // https://github.com/streadway/amqp/issues/94 func TestIntegrationPassiveQueue(t *testing.T) { c := integrationConnection(t, "queue") if c != nil { defer c.Close() name := "test-integration-declared-passive-queue" ch, err := c.Channel() if err != nil { t.Fatalf("create channel1: %s", err) } defer ch.Close() if _, err := ch.QueueDeclare( name, // name false, // durable true, // auto-delete false, // exclusive false, // noWait nil, // arguments ); err != nil { t.Fatalf("queue declare: %s", err) } if _, err := ch.QueueDeclarePassive( name, // name false, // durable true, // auto-delete false, // exclusive false, // noWait nil, // arguments ); err != nil { t.Fatalf("QueueDeclarePassive on declared queue should not error, got: %q", err) } if _, err := ch.QueueDeclarePassive( name, // name true, // durable (note: differs) true, // auto-delete false, // exclusive false, // noWait nil, // arguments ); err != nil { t.Fatalf("QueueDeclarePassive on declared queue with different flags should error") } } } func TestIntegrationBasicQueueOperations(t *testing.T) { c := integrationConnection(t, "queue") if c != nil { defer c.Close() channel, err := c.Channel() if err != nil { t.Fatalf("create channel: %s", err) } t.Logf("create channel OK") exchangeName := "test-basic-ops-exchange" queueName := "test-basic-ops-queue" deleteQueueFirstOptions := []bool{true, false} for _, deleteQueueFirst := range deleteQueueFirstOptions { if err := channel.ExchangeDeclare( exchangeName, // name "direct", // type true, // duration (note: is durable) false, // auto-delete false, // internal false, // nowait nil, // args ); err != nil { t.Fatalf("declare exchange: %s", err) } t.Logf("declare exchange OK") if _, err := channel.QueueDeclare( queueName, // name true, // duration (note: durable) false, // auto-delete false, // exclusive false, // noWait nil, // arguments ); err != nil { t.Fatalf("queue declare: %s", err) } t.Logf("declare queue OK") if err := channel.QueueBind( queueName, // name "", // routingKey exchangeName, // sourceExchange false, // noWait nil, // arguments ); err != nil { t.Fatalf("queue bind: %s", err) } t.Logf("queue bind OK") if deleteQueueFirst { if _, err := channel.QueueDelete( queueName, // name false, // ifUnused (false=be aggressive) false, // ifEmpty (false=be aggressive) false, // noWait ); err != nil { t.Fatalf("delete queue (first): %s", err) } t.Logf("delete queue (first) OK") if err := channel.ExchangeDelete(exchangeName, false, false); err != nil { t.Fatalf("delete exchange (after delete queue): %s", err) } t.Logf("delete exchange (after delete queue) OK") } else { // deleteExchangeFirst if err := channel.ExchangeDelete(exchangeName, false, false); err != nil { t.Fatalf("delete exchange (first): %s", err) } t.Logf("delete exchange (first) OK") if _, err := channel.QueueDeclarePassive( queueName, true, false, false, false, nil, ); err != nil { t.Fatalf("inspect queue state after deleting exchange: %s", err) } t.Logf("queue properly remains after exchange is deleted") if _, err := channel.QueueDelete( queueName, false, // ifUnused false, // ifEmpty false, // noWait ); err != nil { t.Fatalf("delete queue (after delete exchange): %s", err) } t.Logf("delete queue (after delete exchange) OK") } } if err := channel.Close(); err != nil { t.Fatalf("close channel: %s", err) } t.Logf("close channel OK") } } func TestIntegrationConnectionNegotiatesMaxChannels(t *testing.T) { config := Config{ChannelMax: 0} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } defer c.Close() if want, got := defaultChannelMax, c.Config.ChannelMax; want != got { t.Fatalf("expected connection to negotiate uint16 (%d) channels, got: %d", want, got) } } func TestIntegrationConnectionNegotiatesClientMaxChannels(t *testing.T) { config := Config{ChannelMax: 16} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } defer c.Close() if want, got := config.ChannelMax, c.Config.ChannelMax; want != got { t.Fatalf("expected client specified channel limit after handshake %d, got: %d", want, got) } } func TestIntegrationChannelIDsExhausted(t *testing.T) { config := Config{ChannelMax: 16} c, err := DialConfig(amqpURL, config) if err != nil { t.Fatalf("expected to dial with config %+v integration server: %s", config, err) } defer c.Close() for i := uint16(1); i <= c.Config.ChannelMax; i++ { if _, err := c.Channel(); err != nil { t.Fatalf("expected allocating all channel ids to succed, failed on %d with %v", i, err) } } if _, err := c.Channel(); err != ErrChannelMax { t.Fatalf("expected allocating all channels to produce the client side error %#v, got: %#v", ErrChannelMax, err) } } func TestIntegrationChannelClosing(t *testing.T) { c := integrationConnection(t, "closings") if c != nil { defer c.Close() // open and close channel, err := c.Channel() if err != nil { t.Fatalf("basic create channel: %s", err) } t.Logf("basic create channel OK") if err := channel.Close(); err != nil { t.Fatalf("basic close channel: %s", err) } t.Logf("basic close channel OK") // deferred close signal := make(chan bool) err0 := make(chan error) err1 := make(chan error) go func() { channel, err := c.Channel() if err != nil { err0 <- err } <-signal // a bit of synchronization defer func() { if err := channel.Close(); err != nil { err1 <- err } signal <- true }() }() signal <- true select { case e0 := <-err0: t.Fatalf("second create channel: %s", e0) case e1 := <-err1: t.Fatalf("deferred close channel: %s", e1) case <-signal: t.Logf("(got close signal OK)") break case <-time.After(250 * time.Millisecond): t.Fatalf("deferred close: timeout") } // multiple channels for _, n := range []int{2, 4, 8, 16, 32, 64, 128, 256} { channels := make([]*Channel, n) for i := 0; i < n; i++ { var err error if channels[i], err = c.Channel(); err != nil { t.Fatalf("create channel %d/%d: %s", i+1, n, err) } } for i, channel := range channels { if err := channel.Close(); err != nil { t.Fatalf("close channel %d/%d: %s", i+1, n, err) } } t.Logf("created/closed %d channels OK", n) } } } func TestIntegrationMeaningfulChannelErrors(t *testing.T) { c := integrationConnection(t, "pub") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("Could not create channel") } queue := "test.integration.channel.error" _, err = ch.QueueDeclare(queue, false, true, false, false, nil) if err != nil { t.Fatalf("Could not declare") } _, err = ch.QueueDeclare(queue, true, false, false, false, nil) if err == nil { t.Fatalf("Expected error, got nil") } e, ok := err.(*Error) if !ok { t.Fatalf("Expected type Error response, got %T", err) } if e.Code != PreconditionFailed { t.Fatalf("Expected PreconditionFailed, got: %+v", e) } _, err = ch.QueueDeclare(queue, false, true, false, false, nil) if err != ErrClosed { t.Fatalf("Expected channel to be closed, got: %T", err) } } } // https://github.com/streadway/amqp/issues/6 func TestIntegrationNonBlockingClose(t *testing.T) { c := integrationConnection(t, "#6") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("Could not create channel") } queue := "test.integration.blocking.close" _, err = ch.QueueDeclare(queue, false, true, false, false, nil) if err != nil { t.Fatalf("Could not declare") } msgs, err := ch.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Could not consume") } // Simulate a consumer go func() { for range msgs { t.Logf("Oh my, received message on an empty queue") } }() succeed := make(chan bool) errs := make(chan error) go func() { if err = ch.Close(); err != nil { errs <- err } else { succeed <- true } }() select { case <-errs: t.Fatalf("Close produced an error when it shouldn't") case <-succeed: break case <-time.After(1 * time.Second): t.Fatalf("Close timed out after 1s") } } } func TestIntegrationPublishConsume(t *testing.T) { queue := "test.integration.publish.consume" c1 := integrationConnection(t, "pub") c2 := integrationConnection(t, "sub") if c1 != nil && c2 != nil { defer c1.Close() defer c2.Close() pub, _ := c1.Channel() sub, _ := c2.Channel() if _, e := pub.QueueDeclare(queue, false, true, false, false, nil); e != nil { t.Fatalf("error declaring queue %s: %v", queue, e) } if _, e := sub.QueueDeclare(queue, false, true, false, false, nil); e != nil { t.Fatalf("error declaring queue %s: %v", queue, e) } defer integrationQueueDelete(t, pub, queue) messages, _ := sub.Consume(queue, "", false, false, false, false, nil) if e := pub.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("pub 1")}); e != nil { t.Fatalf("publish error: %v", e) } if e := pub.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("pub 2")}); e != nil { t.Fatalf("publish error: %v", e) } if e := pub.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("pub 3")}); e != nil { t.Fatalf("publish error: %v", e) } assertConsumeBody(t, messages, []byte("pub 1")) assertConsumeBody(t, messages, []byte("pub 2")) assertConsumeBody(t, messages, []byte("pub 3")) } } func TestIntegrationConsumeFlow(t *testing.T) { queue := "test.integration.consumer-flow" c1 := integrationConnection(t, "pub-flow") c2 := integrationConnection(t, "sub-flow") if c1 != nil && c2 != nil { defer c1.Close() defer c2.Close() pub, _ := c1.Channel() sub, _ := c2.Channel() if _, e := pub.QueueDeclare(queue, false, true, false, false, nil); e != nil { t.Fatalf("error declaring queue %s: %v", queue, e) } if _, e := sub.QueueDeclare(queue, false, true, false, false, nil); e != nil { t.Fatalf("error declaring queue %s: %v", queue, e) } defer integrationQueueDelete(t, pub, queue) if err := sub.Qos(1, 0, false); err != nil { t.Fatalf("error setting QoS: %v", err) } var messages <-chan Delivery var err error if messages, err = sub.Consume(queue, "", false, false, false, false, nil); err != nil { t.Fatalf("error consuming: %v", err) } if e := pub.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("pub 1")}); e != nil { t.Fatalf("error publishing: %v", e) } if e := pub.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("pub 2")}); e != nil { t.Fatalf("error publishing: %v", e) } msg := assertConsumeBody(t, messages, []byte("pub 1")) if err := sub.Flow(false); err.(*Error).Code == NotImplemented { t.Log("flow control is not supported on this version of rabbitmq") return } if e := msg.Ack(false); e != nil { t.Fatalf("error acking: %v", e) } select { case <-messages: t.Fatalf("message was delivered when flow was not active") default: } if e := sub.Flow(true); e != nil { t.Fatalf("error flow: %v", e) } msg = assertConsumeBody(t, messages, []byte("pub 2")) if e := msg.Ack(false); e != nil { t.Fatalf("error acking: %v", e) } } } func TestIntegrationRecoverNotImplemented(t *testing.T) { // TODO: remove this when Channel.Recover is removed queue := "test.recover" if c, ch := integrationQueue(t, queue); c != nil { if product, ok := c.Properties["product"]; ok && product.(string) == "RabbitMQ" { defer c.Close() err := ch.Recover(false) if ex, ok := err.(*Error); !ok || ex.Code != 540 { t.Fatalf("Expected NOT IMPLEMENTED got: %v", ex) } } } } // This test is driven by a private API to simulate the server sending a channelFlow message func TestIntegrationPublishFlow(t *testing.T) { // TODO - no idea how to test without affecting the server or mucking internal APIs // i'd like to make sure the RW lock can be held by multiple publisher threads // and that multiple channelFlow messages do not block the dispatch thread } func TestIntegrationConsumeCancel(t *testing.T) { queue := "test.integration.consume-cancel" c := integrationConnection(t, "pub") if c != nil { defer c.Close() ch, _ := c.Channel() if _, e := ch.QueueDeclare(queue, false, true, false, false, nil); e != nil { t.Fatalf("error declaring queue %s: %v", queue, e) } defer integrationQueueDelete(t, ch, queue) messages, _ := ch.Consume(queue, "integration-tag", false, false, false, false, nil) if e := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("1")}); e != nil { t.Fatalf("error publishing: %v", e) } assertConsumeBody(t, messages, []byte("1")) err := ch.Cancel("integration-tag", false) if err != nil { t.Fatalf("error cancelling the consumer: %v", err) } if e := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("2")}); e != nil { t.Fatalf("error publishing: %v", e) } select { case <-time.After(100 * time.Millisecond): t.Fatalf("Timeout on Close") case _, ok := <-messages: if ok { t.Fatalf("Extra message on consumer when consumer should have been closed") } } } } func TestIntegrationConsumeCancelWithContext(t *testing.T) { queue := "test.integration.consume-cancel-with-context" c := integrationConnection(t, "pub") if c != nil { defer c.Close() ch, _ := c.Channel() if _, e := ch.QueueDeclare(queue, false, true, false, false, nil); e != nil { t.Fatalf("error declaring queue %s: %v", queue, e) } defer integrationQueueDelete(t, ch, queue) ctx, cancel := context.WithCancel(context.Background()) messages, _ := ch.ConsumeWithContext(ctx, queue, "integration-tag-with-context", false, false, false, false, nil) if e := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("1")}); e != nil { t.Fatalf("error publishing: %v", e) } assertConsumeBody(t, messages, []byte("1")) cancel() <-time.After(200 * time.Millisecond) // wait to call cancel asynchronously if e := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("2")}); e != nil { t.Fatalf("error publishing: %v", e) } select { case <-time.After(200 * time.Millisecond): t.Fatalf("Timeout on Close") case _, ok := <-messages: if ok { t.Fatalf("Extra message on consumer when consumer should have been closed") } } } } func (c *Connection) Generate(_ *rand.Rand, _ int) reflect.Value { urlStr := amqpURL conn, err := Dial(urlStr) if err != nil { return reflect.ValueOf(nil) } return reflect.ValueOf(conn) } func (c Publishing) Generate(r *rand.Rand, _ int) reflect.Value { var ok bool var t reflect.Value p := Publishing{} //p.DeliveryMode = uint8(r.Intn(3)) //p.Priority = uint8(r.Intn(8)) if r.Intn(2) > 0 { p.ContentType = "application/octet-stream" } if r.Intn(2) > 0 { p.ContentEncoding = "gzip" } if r.Intn(2) > 0 { p.CorrelationId = fmt.Sprintf("%d", r.Int()) } if r.Intn(2) > 0 { p.ReplyTo = fmt.Sprintf("%d", r.Int()) } if r.Intn(2) > 0 { p.MessageId = fmt.Sprintf("%d", r.Int()) } if r.Intn(2) > 0 { p.Type = fmt.Sprintf("%d", r.Int()) } if r.Intn(2) > 0 { p.AppId = fmt.Sprintf("%d", r.Int()) } if r.Intn(2) > 0 { p.Timestamp = time.Unix(r.Int63(), r.Int63()) } if t, ok = quick.Value(reflect.TypeOf(p.Body), r); ok { p.Body = t.Bytes() } return reflect.ValueOf(p) } func TestQuickPublishOnly(t *testing.T) { if c := integrationConnection(t, "quick"); c != nil { defer c.Close() pub, err := c.Channel() if err != nil { t.Fatalf("getting channel failed: %s", err) } queue := "test-publish" if _, err = pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) return } defer integrationQueueDelete(t, pub, queue) chk := func(msg Publishing) bool { return pub.PublishWithContext(context.TODO(), "", queue, false, false, msg) == nil } if err := quick.Check(chk, nil); err != nil { t.Fatalf("check error: %v", err) } } } func TestPublishEmptyBody(t *testing.T) { c := integrationConnection(t, "empty") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("Failed to create channel") } queue := "test-TestPublishEmptyBody" if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Could not declare") } defer integrationQueueDelete(t, ch, queue) messages, err := ch.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Could not consume") } err = ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{}) if err != nil { t.Fatalf("Could not publish") } select { case msg := <-messages: if len(msg.Body) != 0 { t.Fatalf("Received non empty body") } case <-time.After(200 * time.Millisecond): t.Fatalf("Timeout on receive") } } } func TestPublishEmptyBodyWithHeadersIssue67(t *testing.T) { c := integrationConnection(t, "issue67") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("Failed to create channel") return } queue := "test-TestPublishEmptyBodyWithHeaders" if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Could not declare") } defer integrationQueueDelete(t, ch, queue) messages, err := ch.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Could not consume") } headers := Table{ "ham": "spam", } err = ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Headers: headers}) if err != nil { t.Fatalf("Could not publish") } select { case msg := <-messages: if msg.Headers["ham"] == nil { t.Fatalf("Headers aren't sent") } if msg.Headers["ham"] != "spam" { t.Fatalf("Headers are wrong") } case <-time.After(200 * time.Millisecond): t.Fatalf("Timeout on receive") } } } func TestQuickPublishConsumeOnly(t *testing.T) { c1 := integrationConnection(t, "quick-pub") c2 := integrationConnection(t, "quick-sub") if c1 != nil && c2 != nil { defer c1.Close() defer c2.Close() pub, err := c1.Channel() if err != nil { t.Fatalf("getting channel1 failed: %s", err) } sub, err := c2.Channel() if err != nil { t.Fatalf("getting channel2 failed: %s", err) } queue := "TestPublishConsumeOnly" if _, err = pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) return } if _, err = sub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) return } defer integrationQueueDelete(t, sub, queue) ch, err := sub.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Could not sub: %s", err) } if chkerr := quick.CheckEqual( func(msg Publishing) []byte { empty := Publishing{Body: msg.Body} if pub.PublishWithContext(context.TODO(), "", queue, false, false, empty) != nil { return []byte{'X'} } return msg.Body }, func(_ Publishing) []byte { out := <-ch if out.Ack(false) != nil { return []byte{'X'} } return out.Body }, nil); chkerr != nil { t.Fatalf("check error: %v", chkerr) } } } func TestQuickPublishConsumeBigBody(t *testing.T) { c1 := integrationConnection(t, "big-pub") c2 := integrationConnection(t, "big-sub") if c1 != nil && c2 != nil { defer c1.Close() defer c2.Close() pub, err := c1.Channel() if err != nil { t.Fatalf("getting channel 1 failed: %s", err) } sub, err := c2.Channel() if err != nil { t.Fatalf("getting channel 2 failed: %s", err) } queue := "test-pubsub" if _, err = sub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) } ch, err := sub.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Could not sub: %s", err) } fixture := Publishing{ Body: make([]byte, 1e4+1000), } if _, err = pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) } err = pub.PublishWithContext(context.TODO(), "", queue, false, false, fixture) if err != nil { t.Fatalf("Could not publish big body") } select { case msg := <-ch: if !bytes.Equal(msg.Body, fixture.Body) { t.Fatalf("Consumed big body didn't match") } case <-time.After(200 * time.Millisecond): t.Fatalf("Timeout on receive") } } } func TestIntegrationGetOk(t *testing.T) { if c := integrationConnection(t, "getok"); c != nil { defer c.Close() queue := "test.get-ok" ch, _ := c.Channel() if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) } if err := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("ok")}); err != nil { t.Fatalf("Failed to publish: %s", err) } msg, ok, err := ch.Get(queue, false) if err != nil { t.Fatalf("Failed get: %v", err) } if !ok { t.Fatalf("Get on a queued message did not find the message") } if string(msg.Body) != "ok" { t.Fatalf("Get did not get the correct message") } } } func TestIntegrationGetEmpty(t *testing.T) { if c := integrationConnection(t, "getok"); c != nil { defer c.Close() queue := "test.get-ok" ch, _ := c.Channel() if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) } _, ok, err := ch.Get(queue, false) if err != nil { t.Fatalf("Failed get: %v", err) } if !ok { t.Fatalf("Get on a queued message retrieved a message when it shouldn't have") } } } func TestIntegrationTxCommit(t *testing.T) { if c := integrationConnection(t, "txcommit"); c != nil { defer c.Close() queue := "test.tx.commit" ch, _ := c.Channel() if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) } if err := ch.Tx(); err != nil { t.Fatalf("tx.select failed") } if err := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("ok")}); err != nil { t.Fatalf("Failed to publish: %s", err) } if err := ch.TxCommit(); err != nil { t.Fatalf("tx.commit failed") } msg, ok, err := ch.Get(queue, false) if err != nil || !ok { t.Fatalf("Failed get: %v", err) } if string(msg.Body) != "ok" { t.Fatalf("Get did not get the correct message from the transaction") } } } func TestIntegrationTxRollback(t *testing.T) { if c := integrationConnection(t, "txrollback"); c != nil { defer c.Close() queue := "test.tx.rollback" ch, _ := c.Channel() if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Failed to declare: %s", err) } if err := ch.Tx(); err != nil { t.Fatalf("tx.select failed") } if err := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("ok")}); err != nil { t.Fatalf("Failed to publish: %s", err) } if err := ch.TxRollback(); err != nil { t.Fatalf("tx.rollback failed") } _, ok, err := ch.Get(queue, false) if err != nil { t.Fatalf("Failed get: %v", err) } if ok { t.Fatalf("message was published when it should have been rolled back") } } } func TestIntegrationReturn(t *testing.T) { if c, ch := integrationQueue(t, "return"); c != nil { defer c.Close() ret := make(chan Return, 1) ch.NotifyReturn(ret) // mandatory publish to an exchange without a binding should be returned if err := ch.PublishWithContext(context.TODO(), "", "return-without-binding", true, false, Publishing{Body: []byte("mandatory")}); err != nil { t.Fatalf("Failed to publish: %s", err) } select { case res := <-ret: if string(res.Body) != "mandatory" { t.Fatalf("expected return of the same message") } if res.ReplyCode != NoRoute { t.Fatalf("expected no consumers reply code on the Return result, got: %v", res.ReplyCode) } case <-time.After(200 * time.Millisecond): t.Fatalf("no return was received within 200ms") } } } func TestIntegrationCancel(t *testing.T) { queue := "cancel" consumerTag := "test.cancel" if c, ch := integrationQueue(t, queue); c != nil { defer c.Close() cancels := ch.NotifyCancel(make(chan string, 1)) consumeErr := make(chan error, 1) deleteErr := make(chan error, 1) go func() { if _, err := ch.Consume(queue, consumerTag, false, false, false, false, nil); err != nil { consumeErr <- err } if _, err := ch.QueueDelete(queue, false, false, false); err != nil { deleteErr <- err } }() select { case err := <-consumeErr: t.Fatalf("cannot consume from %q to test NotifyCancel: %v", queue, err) case err := <-deleteErr: t.Fatalf("cannot delete integration queue: %v", err) case tag := <-cancels: if want, got := consumerTag, tag; want != got { t.Fatalf("expected to be notified of deleted queue with consumer tag, got: %q", got) } case <-time.After(200 * time.Millisecond): t.Fatalf("expected to be notified of deleted queue with 200ms") } } } func TestIntegrationConfirm(t *testing.T) { if c, ch := integrationQueue(t, "confirm"); c != nil { defer c.Close() confirms := ch.NotifyPublish(make(chan Confirmation, 1)) if err := ch.Confirm(false); err != nil { t.Fatalf("could not confirm") } if err := ch.PublishWithContext(context.TODO(), "", "confirm", false, false, Publishing{Body: []byte("confirm")}); err != nil { t.Fatalf("Failed to publish: %s", err) } select { case confirmed := <-confirms: if confirmed.DeliveryTag != 1 { t.Fatalf("expected ack starting with delivery tag of 1") } case <-time.After(200 * time.Millisecond): t.Fatalf("no ack was received within 200ms") } } } // https://github.com/streadway/amqp/issues/61 func TestRoundTripAllFieldValueTypes61(t *testing.T) { if conn := integrationConnection(t, "issue61"); conn != nil { defer conn.Close() timestamp := time.Unix(100000000, 0) headers := Table{ "A": []interface{}{ []interface{}{"nested array", int32(3)}, Decimal{2, 1}, Table{"S": "nested table in array"}, int32(2 << 20), string("array string"), timestamp, nil, byte(2), int8(-2), float64(2.64), float32(2.32), int64(2 << 60), int16(2 << 10), bool(true), []byte{'b', '2'}, }, "D": Decimal{1, 1}, "F": Table{"S": "nested table in table"}, "I": int32(1 << 20), "S": string("string"), "T": timestamp, "V": nil, "B": byte(1), "b": int8(-1), "d": float64(1.64), "f": float32(1.32), "l": int64(1 << 60), "s": int16(1 << 10), "t": bool(true), "x": []byte{'b', '1'}, } queue := "test.issue61-roundtrip" ch, _ := conn.Channel() if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Could not declare") } msgs, err := ch.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Could not consume") } err = ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("ignored"), Headers: headers}) if err != nil { t.Fatalf("Could not publish: %v", err) } msg, ok := <-msgs if !ok { t.Fatalf("Channel closed prematurely likely due to publish exception") } for k, v := range headers { if !reflect.DeepEqual(v, msg.Headers[k]) { t.Fatalf("Round trip header not the same for key %q: expected: %#v, got %#v", k, v, msg.Headers[k]) } } } } // Declares a queue with the x-message-ttl extension to exercise integer // serialization. // // Relates to https://github.com/streadway/amqp/issues/60 func TestDeclareArgsXMessageTTL(t *testing.T) { if conn := integrationConnection(t, "declareTTL"); conn != nil { defer conn.Close() ch, _ := conn.Channel() args := Table{"x-message-ttl": int32(9000000)} // should not drop the connection if _, err := ch.QueueDeclare("declareWithTTL", false, true, false, false, args); err != nil { t.Fatalf("cannot declare with TTL: got: %v", err) } } } // Sets up the topology where rejected messages will be forwarded // to a fanout exchange, with a single queue bound. // // Relates to https://github.com/streadway/amqp/issues/56 func TestDeclareArgsRejectToDeadLetterQueue(t *testing.T) { if conn := integrationConnection(t, "declareArgs"); conn != nil { defer conn.Close() ex, q := "declareArgs", "declareArgs-deliveries" dlex, dlq := ex+"-dead-letter", q+"-dead-letter" ch, _ := conn.Channel() if err := ch.ExchangeDeclare(ex, "fanout", false, true, false, false, nil); err != nil { t.Fatalf("cannot declare %v: got: %v", ex, err) } if err := ch.ExchangeDeclare(dlex, "fanout", false, true, false, false, nil); err != nil { t.Fatalf("cannot declare %v: got: %v", dlex, err) } if _, err := ch.QueueDeclare(dlq, false, true, false, false, nil); err != nil { t.Fatalf("cannot declare %v: got: %v", dlq, err) } if err := ch.QueueBind(dlq, "#", dlex, false, nil); err != nil { t.Fatalf("cannot bind %v to %v: got: %v", dlq, dlex, err) } if _, err := ch.QueueDeclare(q, false, true, false, false, Table{ "x-dead-letter-exchange": dlex, }); err != nil { t.Fatalf("cannot declare %v with dlq %v: got: %v", q, dlex, err) } if err := ch.QueueBind(q, "#", ex, false, nil); err != nil { t.Fatalf("cannot bind %v: got: %v", ex, err) } fails, err := ch.Consume(q, "", false, false, false, false, nil) if err != nil { t.Fatalf("cannot consume %v: got: %v", q, err) } // Reject everything consumed rejectErrs := make(chan error, len(fails)) go func() { for d := range fails { if err := d.Reject(false); err != nil { rejectErrs <- err } } }() // Publish the 'poison' if err := ch.PublishWithContext(context.TODO(), ex, q, true, false, Publishing{Body: []byte("ignored")}); err != nil { t.Fatalf("publishing failed") } // spin-get until message arrives at the dead-letter queue with a // synchronous parse to exercise the array field (x-death) set by the // server relating to issue-56 for i := 0; i < 10; i++ { d, got, err := ch.Get(dlq, false) if !got && err == nil { continue } else if err != nil { t.Fatalf("expected success in parsing reject, got: %v", err) } else { // pass if we've parsed an array if v, ok := d.Headers["x-death"]; ok { if _, ok := v.([]interface{}); ok { return } } t.Fatalf("array field x-death expected in the headers, got: %v (%T)", d.Headers, d.Headers["x-death"]) } } t.Fatalf("expected dead-letter after 10 get attempts") } } // https://github.com/streadway/amqp/issues/48 func TestDeadlockConsumerIssue48(t *testing.T) { if conn := integrationConnection(t, "issue48"); conn != nil { defer conn.Close() deadline := make(chan bool) go func() { select { case <-time.After(5 * time.Second): panic("expected to receive 2 deliveries while in an RPC, got a deadlock") case <-deadline: // pass } }() ch, err := conn.Channel() if err != nil { t.Fatalf("got error on channel.open: %v", err) } queue := "test-issue48" if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("expected to declare a queue: %v", err) } if err := ch.Confirm(false); err != nil { t.Fatalf("got error on confirm: %v", err) } confirms := ch.NotifyPublish(make(chan Confirmation, 2)) for i := 0; i < cap(confirms); i++ { // Fill the queue with some new or remaining publishings if err := ch.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("")}); err != nil { t.Fatalf("error publishing: %v", err) } } for i := 0; i < cap(confirms); i++ { // Wait for them to land on the queue, so they'll be delivered on consume <-confirms } // Consuming should send them all on the wire msgs, err := ch.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("got error on consume: %v", err) } // We pop one off the chan, the other is on the wire <-msgs // Opening a new channel (any RPC) while another delivery is on the wire if _, err := conn.Channel(); err != nil { t.Fatalf("got error on consume: %v", err) } // We pop the next off the chan <-msgs deadline <- true } } // https://github.com/streadway/amqp/issues/46 func TestRepeatedChannelExceptionWithPublishAndMaxProcsIssue46(t *testing.T) { conn := integrationConnection(t, "issue46") if conn == nil { t.Fatal("conn is nil") } t.Cleanup(func() { conn.Close() }) for i := 0; i < 100; i++ { if conn.IsClosed() { t.Fatal("conn is closed") } ch, channelOpenError := conn.Channel() if channelOpenError != nil { t.Fatalf("error opening channel: %d error: %+v", i, channelOpenError) } for j := 0; j < 100; j++ { if ch.IsClosed() { if j == 0 { t.Fatal("channel should not be closed") } break } err := ch.PublishWithContext(context.TODO(), "not-existing-exchange", "some-key", false, false, Publishing{Body: []byte("some-data")}) if err != nil { if publishError, ok := err.(*Error); !ok || publishError.Code != 504 { t.Fatalf("expected channel only exception i: %d j: %d error: %+v", i, j, publishError) } } } } } // https://github.com/streadway/amqp/issues/43 func TestChannelExceptionWithCloseIssue43(t *testing.T) { conn := integrationConnection(t, "issue43") if conn != nil { t.Cleanup(func() { conn.Close() }) go func() { for err := range conn.NotifyClose(make(chan *Error)) { t.Log(err.Error()) } }() c1, err := conn.Channel() if err != nil { t.Fatalf("failed to create channel, got: %v", err) } go func() { for err := range c1.NotifyClose(make(chan *Error)) { t.Log("Channel1 Close: " + err.Error()) } }() c2, err := conn.Channel() if err != nil { t.Fatalf("failed to create channel, got: %v", err) } go func() { for err := range c2.NotifyClose(make(chan *Error)) { t.Log("Channel2 Close: " + err.Error()) } }() // Cause an asynchronous channel exception causing the server // to send a "channel.close" method either before or after the next // asynchronous method. err = c1.PublishWithContext(context.TODO(), "nonexisting-exchange", "", false, false, Publishing{}) if err != nil { t.Fatalf("failed to publish, got: %v", err) } // Receive or send the channel close method, the channel shuts down // but this expects a channel.close-ok to be received. c1.Close() // This ensures that the 2nd channel is unaffected by the channel exception // on channel 1. err = c2.ExchangeDeclare("test-channel-still-exists", "direct", false, true, false, false, nil) if err != nil { t.Fatalf("failed to declare exchange, got: %v", err) } } } // https://github.com/streadway/amqp/issues/7 func TestCorruptedMessageIssue7(t *testing.T) { messageCount := 1024 c1 := integrationConnection(t, "") c2 := integrationConnection(t, "") if c1 != nil && c2 != nil { defer c1.Close() defer c2.Close() pub, err := c1.Channel() if err != nil { t.Fatalf("Cannot create Channel") } sub, err := c2.Channel() if err != nil { t.Fatalf("Cannot create Channel") } queue := "test-corrupted-message-regression" if _, err := pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Cannot declare") } if _, err := sub.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("Cannot declare") } msgs, err := sub.Consume(queue, "", false, false, false, false, nil) if err != nil { t.Fatalf("Cannot consume") } for i := 0; i < messageCount; i++ { err := pub.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{ Body: generateCrc32Random(t, 7*i), }) if err != nil { t.Fatalf("Failed to publish") } } for i := 0; i < messageCount; i++ { select { case msg := <-msgs: assertMessageCrc32(t, msg.Body, fmt.Sprintf("missed match at %d", i)) case <-time.After(200 * time.Millisecond): t.Fatalf("Timeout on recv") } } } } // https://github.com/streadway/amqp/issues/136 func TestChannelCounterShouldNotPanicIssue136(t *testing.T) { if c := integrationConnection(t, "issue136"); c != nil { defer c.Close() var wg sync.WaitGroup // exceeds 65535 channels errs := make(chan error, 8*10000*2) for i := 0; i < 8; i++ { wg.Add(1) go func() { for j := 0; j < 10000; j++ { ch, err := c.Channel() if err != nil { errs <- err } if err := ch.Close(); err != nil { errs <- err } } wg.Done() }() } wg.Wait() close(errs) for err := range errs { if err != nil { t.Fatalf("failed to create or close channel, got: %v", err) } } } } func TestExchangeDeclarePrecondition(t *testing.T) { c1 := integrationConnection(t, "exchange-double-declare") c2 := integrationConnection(t, "exchange-double-declare-cleanup") if c1 != nil && c2 != nil { defer c1.Close() defer c2.Close() ch, err := c1.Channel() if err != nil { t.Fatalf("Create channel") } exchange := "test-mismatched-redeclare" err = ch.ExchangeDeclare( exchange, "direct", // exchangeType false, // durable true, // auto-delete false, // internal false, // noWait nil, // arguments ) if err != nil { t.Fatalf("Could not initially declare exchange") } err = ch.ExchangeDeclare( exchange, "direct", true, // different durability true, false, false, nil, ) if err == nil { t.Fatalf("Expected to fail a redeclare with different durability, didn't receive an error") } else { declareErr := err.(*Error) if declareErr.Code != PreconditionFailed { t.Fatalf("Expected precondition error") } if !declareErr.Recover { t.Fatalf("Expected to be able to recover") } } ch2, _ := c2.Channel() if err = ch2.ExchangeDelete(exchange, false, false); err != nil { t.Fatalf("Could not delete exchange: %v", err) } } } func TestRabbitMQQueueTTLGet(t *testing.T) { if c := integrationRabbitMQ(t, "ttl"); c != nil { defer c.Close() queue := "test.rabbitmq-message-ttl" channel, err := c.Channel() if err != nil { t.Fatalf("channel: %v", err) } if _, err = channel.QueueDeclare( queue, false, true, false, false, Table{"x-message-ttl": int32(100)}, // in ms ); err != nil { t.Fatalf("queue declare: %s", err) } if err := channel.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("ttl")}); err != nil { t.Fatalf("error publishing: %v", err) } time.Sleep(200 * time.Millisecond) _, ok, err := channel.Get(queue, false) if ok { t.Fatalf("Expected the message to expire in 100ms, it didn't expire after 200ms") } if err != nil { t.Fatalf("Failed to get on ttl queue") } } } func TestRabbitMQQueueNackMultipleRequeue(t *testing.T) { if c := integrationRabbitMQ(t, "nack"); c != nil { defer c.Close() if c.isCapable("basic.nack") { queue := "test.rabbitmq-basic-nack" channel, err := c.Channel() if err != nil { t.Fatalf("channel: %v", err) } if _, err = channel.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("queue declare: %s", err) } if err := channel.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("1")}); err != nil { t.Fatalf("error publishing: %v", err) } if err := channel.PublishWithContext(context.TODO(), "", queue, false, false, Publishing{Body: []byte("2")}); err != nil { t.Fatalf("error publishing: %v", err) } m1, ok, err := channel.Get(queue, false) if !ok || err != nil || m1.Body[0] != '1' { t.Fatalf("could not get message %v", m1) } m2, ok, err := channel.Get(queue, false) if !ok || err != nil || m2.Body[0] != '2' { t.Fatalf("could not get message %v", m2) } if err := m2.Nack(true, true); err != nil { t.Fatalf("nack error: %v", err) } m1, ok, err = channel.Get(queue, false) if !ok || err != nil || m1.Body[0] != '1' { t.Fatalf("could not get message %v", m1) } m2, ok, err = channel.Get(queue, false) if !ok || err != nil || m2.Body[0] != '2' { t.Fatalf("could not get message %v", m2) } } } } func TestConsumerCancelNotification(t *testing.T) { c := integrationConnection(t, "consumer cancel notification") if c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("got error on channel.open: %v", err) } queue := "test-consumer-cancel-notification" if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { t.Fatalf("expected to declare a queue: %v", err) } if _, err := ch.Consume(queue, "", false, false, false, false, nil); err != nil { t.Fatalf("basic.consume failed") } // consumer cancel notification channel ccnChan := make(chan string, 1) ch.NotifyCancel(ccnChan) if _, err := ch.QueueDelete(queue, false, false, true); err != nil { t.Fatalf("queue.delete failed: %s", err) } select { case <-ccnChan: // do nothing case <-time.After(time.Second * 10): t.Fatalf("basic.cancel wasn't received") } // we don't close ccnChan because channel shutdown // does it } } func TestConcurrentChannelAndConnectionClose(t *testing.T) { c := integrationConnection(t, "concurrent channel and connection test") if c != nil { ch, err := c.Channel() if err != nil { t.Fatalf("got error on channel.open: %v", err) } var wg sync.WaitGroup wg.Add(2) starter := make(chan struct{}) go func() { defer wg.Done() <-starter c.Close() }() go func() { defer wg.Done() <-starter ch.Close() }() close(starter) wg.Wait() } } func TestIntegrationGetNextPublishSeqNo(t *testing.T) { if c := integrationConnection(t, "GetNextPublishSeqNo"); c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("channel: %v", err) } if err = ch.Confirm(false); err != nil { t.Fatalf("could not confirm") } ex := "test-get-next-pub" if err = ch.ExchangeDeclare(ex, "direct", false, false, false, false, nil); err != nil { t.Fatalf("cannot declare %v: got: %v", ex, err) } n := ch.GetNextPublishSeqNo() if n != 1 { t.Fatalf("wrong next publish seqence number before any publish, expected: %d, got: %d", 1, n) } if err := ch.PublishWithContext(context.TODO(), "test-get-next-pub-seq", "", false, false, Publishing{}); err != nil { t.Fatalf("publish error: %v", err) } n = ch.GetNextPublishSeqNo() if n != 2 { t.Fatalf("wrong next publish seqence number after 1 publishing, expected: %d, got: %d", 2, n) } } } func TestIntegrationGetNextPublishSeqNoRace(t *testing.T) { if c := integrationConnection(t, "GetNextPublishSeqNoRace"); c != nil { defer c.Close() ch, err := c.Channel() if err != nil { t.Fatalf("channel: %v", err) } if err = ch.Confirm(false); err != nil { t.Fatalf("could not confirm") } ex := "test-get-next-pub" if err = ch.ExchangeDeclare(ex, "direct", false, false, false, false, nil); err != nil { t.Fatalf("cannot declare %v: got: %v", ex, err) } n := ch.GetNextPublishSeqNo() if n != 1 { t.Fatalf("wrong next publish seqence number before any publish, expected: %d, got: %d", 1, n) } wg := sync.WaitGroup{} fail := false wg.Add(2) go func() { defer wg.Done() _ = ch.GetNextPublishSeqNo() }() go func() { defer wg.Done() if err := ch.PublishWithContext(context.TODO(), "test-get-next-pub-seq", "", false, false, Publishing{}); err != nil { t.Logf("publish error: %v", err) fail = true } }() wg.Wait() if fail { t.FailNow() } n = ch.GetNextPublishSeqNo() if n != 2 { t.Fatalf("wrong next publish seqence number after 15 publishing, expected: %d, got: %d", 2, n) } } } // https://github.com/rabbitmq/amqp091-go/pull/44 func TestShouldNotWaitAfterConnectionClosedIssue44(t *testing.T) { conn := integrationConnection(t, "TestShouldNotWaitAfterConnectionClosedIssue44") ch, err := conn.Channel() if err != nil { t.Fatalf("channel error: %v", err) } err = ch.Confirm(false) if err != nil { t.Fatalf("confirm error: %v", err) } closed := conn.NotifyClose(make(chan *Error, 1)) go func() { <-closed }() confirm, err := ch.PublishWithDeferredConfirmWithContext(context.TODO(), "test-issue44", "issue44", false, false, Publishing{Body: []byte("abc")}) if err != nil { t.Fatalf("PublishWithDeferredConfirm error: %v", err) } ch.Close() conn.Close() ack := confirm.Wait() if ack != false { t.Fatalf("ack returned should be false %v", ack) } } /* * Support for integration tests */ // Returns a connection to the AMQP if the AMQP_URL environment // variable is set and a connection can be established. func integrationConnection(t *testing.T, name string) *Connection { conf := defaultConfig() if conf.Properties == nil { conf.Properties = make(Table) } conf.Properties.SetClientConnectionName(name) conn, err := DialConfig(amqpURL, conf) if err != nil { t.Fatalf("cannot dial integration server. Is the rabbitmq-server service running? %s", err) return nil } return conn } // Returns a connection, channel and declares a queue when the AMQP_URL is in the environment func integrationQueue(t *testing.T, name string) (*Connection, *Channel) { if conn := integrationConnection(t, name); conn != nil { if channel, err := conn.Channel(); err == nil { if _, err = channel.QueueDeclare(name, false, true, false, false, nil); err == nil { return conn, channel } } } return nil, nil } func integrationQueueDelete(t *testing.T, c *Channel, queue string) { if c, err := c.QueueDelete(queue, false, false, false); err != nil { t.Fatalf("queue deletion failed, c: %d, err: %v", c, err) } } // Delegates to integrationConnection and only returns a connection if the // product is RabbitMQ func integrationRabbitMQ(t *testing.T, name string) *Connection { if conn := integrationConnection(t, name); conn != nil { if server, ok := conn.Properties["product"]; ok && server == "RabbitMQ" { return conn } } return nil } func assertConsumeBody(t *testing.T, messages <-chan Delivery, want []byte) (msg *Delivery) { select { case got := <-messages: if !bytes.Equal(want, got.Body) { t.Fatalf("Message body does not match want: %v, got: %v, for: %+v", want, got.Body, got) } msg = &got case <-time.After(200 * time.Millisecond): t.Fatalf("Timeout waiting for %v", want) } return msg } // https://github.com/rabbitmq/amqp091-go/issues/11 func TestShouldNotWaitAfterConnectionClosedNewChannelCreatedIssue11(t *testing.T) { conn := integrationConnection(t, "TestShouldNotWaitAfterConnectionClosedNewChannelCreatedIssue11") ch, err := conn.Channel() if err != nil { t.Fatalf("channel error: %v", err) } conn.NotifyClose(make(chan *Error, 1)) _, err = ch.PublishWithDeferredConfirmWithContext(context.TODO(), "issue11", "issue11", false, false, Publishing{Body: []byte("abc")}) if err != nil { t.Fatalf("PublishWithDeferredConfirm error: %v", err) } ch.Close() conn.Close() _, err = conn.Channel() if err == nil { t.Fatalf("Opening a channel from a closed connection should not block but returning an error %v", err) } } // Pulls out the CRC and verifies the remaining content against the CRC func assertMessageCrc32(t *testing.T, msg []byte, assert string) { size := binary.BigEndian.Uint32(msg[:4]) crc := crc32.NewIEEE() crc.Write(msg[8:]) if binary.BigEndian.Uint32(msg[4:8]) != crc.Sum32() { t.Fatalf("Message does not match CRC: %s", assert) } if int(size) != len(msg)-8 { t.Fatalf("Message does not match size, should=%d, is=%d: %s", size, len(msg)-8, assert) } } // Creates a random body size with a leading 32-bit CRC in network byte order // that verifies the remaining slice func generateCrc32Random(t *testing.T, size int) []byte { msg := make([]byte, size+8) if _, err := io.ReadFull(devrand.Reader, msg); err != nil { t.Fatalf("could not get random data: %+v", err) } crc := crc32.NewIEEE() crc.Write(msg[8:]) binary.BigEndian.PutUint32(msg[0:4], uint32(size)) binary.BigEndian.PutUint32(msg[4:8], crc.Sum32()) return msg } func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } golang-github-rabbitmq-amqp091-go-1.10.0/log.go000066400000000000000000000011011462444370000210150ustar00rootroot00000000000000// Copyright (c) 2022 VMware, Inc. or its affiliates. All Rights Reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 type Logging interface { Printf(format string, v ...interface{}) } var Logger Logging = NullLogger{} // Enables logging using a custom Logging instance. Note that this is // not thread safe and should be called at application start func SetLogger(logger Logging) { Logger = logger } type NullLogger struct { } func (l NullLogger) Printf(format string, v ...interface{}) { } golang-github-rabbitmq-amqp091-go-1.10.0/rabbitmq-confs/000077500000000000000000000000001462444370000226235ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/rabbitmq-confs/tls/000077500000000000000000000000001462444370000234255ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/rabbitmq-confs/tls/90-tls.conf000066400000000000000000000004031462444370000253210ustar00rootroot00000000000000listeners.ssl.default = 5671 ssl_options.cacertfile = /certs/cacert.pem ssl_options.certfile = /certs/cert.pem ssl_options.keyfile = /certs/key.pem ssl_options.depth = 2 ssl_options.verify = verify_none ssl_options.fail_if_no_peer_cert = false golang-github-rabbitmq-amqp091-go-1.10.0/read.go000066400000000000000000000222541462444370000211630ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "bytes" "encoding/binary" "errors" "io" "time" ) /* ReadFrame reads a frame from an input stream and returns an interface that can be cast into one of the following: methodFrame PropertiesFrame bodyFrame heartbeatFrame 2.3.5 frame Details All frames consist of a header (7 octets), a payload of arbitrary size, and a 'frame-end' octet that detects malformed frames: 0 1 3 7 size+7 size+8 +------+---------+-------------+ +------------+ +-----------+ | type | channel | size | | payload | | frame-end | +------+---------+-------------+ +------------+ +-----------+ octet short long size octets octet To read a frame, we: 1. Read the header and check the frame type and channel. 2. Depending on the frame type, we read the payload and process it. 3. Read the frame end octet. In realistic implementations where performance is a concern, we would use “read-ahead buffering” or “gathering reads” to avoid doing three separate system calls to read a frame. */ func (r *reader) ReadFrame() (frame frame, err error) { var scratch [7]byte if _, err = io.ReadFull(r.r, scratch[:7]); err != nil { return } typ := scratch[0] channel := binary.BigEndian.Uint16(scratch[1:3]) size := binary.BigEndian.Uint32(scratch[3:7]) switch typ { case frameMethod: if frame, err = r.parseMethodFrame(channel, size); err != nil { return } case frameHeader: if frame, err = r.parseHeaderFrame(channel, size); err != nil { return } case frameBody: if frame, err = r.parseBodyFrame(channel, size); err != nil { return nil, err } case frameHeartbeat: if frame, err = r.parseHeartbeatFrame(channel, size); err != nil { return } default: return nil, ErrFrame } if _, err = io.ReadFull(r.r, scratch[:1]); err != nil { return nil, err } if scratch[0] != frameEnd { return nil, ErrFrame } return } func readShortstr(r io.Reader) (v string, err error) { var length uint8 if err = binary.Read(r, binary.BigEndian, &length); err != nil { return } bytes := make([]byte, length) if _, err = io.ReadFull(r, bytes); err != nil { return } return string(bytes), nil } func readLongstr(r io.Reader) (v string, err error) { var length uint32 if err = binary.Read(r, binary.BigEndian, &length); err != nil { return } // slices can't be longer than max int32 value if length > (^uint32(0) >> 1) { return } bytes := make([]byte, length) if _, err = io.ReadFull(r, bytes); err != nil { return } return string(bytes), nil } func readDecimal(r io.Reader) (v Decimal, err error) { if err = binary.Read(r, binary.BigEndian, &v.Scale); err != nil { return } if err = binary.Read(r, binary.BigEndian, &v.Value); err != nil { return } return } func readTimestamp(r io.Reader) (v time.Time, err error) { var sec int64 if err = binary.Read(r, binary.BigEndian, &sec); err != nil { return } return time.Unix(sec, 0), nil } /* 'A': []interface{} 'D': Decimal 'F': Table 'I': int32 'S': string 'T': time.Time 'V': nil 'b': int8 'B': byte 'd': float64 'f': float32 'l': int64 's': int16 't': bool 'x': []byte */ func readField(r io.Reader) (v interface{}, err error) { var typ byte if err = binary.Read(r, binary.BigEndian, &typ); err != nil { return } switch typ { case 't': var value uint8 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value != 0, nil case 'B': var value [1]byte if _, err = io.ReadFull(r, value[0:1]); err != nil { return } return value[0], nil case 'b': var value int8 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value, nil case 's': var value int16 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value, nil case 'I': var value int32 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value, nil case 'l': var value int64 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value, nil case 'f': var value float32 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value, nil case 'd': var value float64 if err = binary.Read(r, binary.BigEndian, &value); err != nil { return } return value, nil case 'D': return readDecimal(r) case 'S': return readLongstr(r) case 'A': return readArray(r) case 'T': return readTimestamp(r) case 'F': return readTable(r) case 'x': var len int32 if err = binary.Read(r, binary.BigEndian, &len); err != nil { return nil, err } value := make([]byte, len) if _, err = io.ReadFull(r, value); err != nil { return nil, err } return value, err case 'V': return nil, nil } return nil, ErrSyntax } /* Field tables are long strings that contain packed name-value pairs. The name-value pairs are encoded as short string defining the name, and octet defining the values type and then the value itself. The valid field types for tables are an extension of the native integer, bit, string, and timestamp types, and are shown in the grammar. Multi-octet integer fields are always held in network byte order. */ func readTable(r io.Reader) (table Table, err error) { var nested bytes.Buffer var str string if str, err = readLongstr(r); err != nil { return } nested.Write([]byte(str)) table = make(Table) for nested.Len() > 0 { var key string var value interface{} if key, err = readShortstr(&nested); err != nil { return } if value, err = readField(&nested); err != nil { return } table[key] = value } return } func readArray(r io.Reader) ([]interface{}, error) { var ( size uint32 err error ) if err = binary.Read(r, binary.BigEndian, &size); err != nil { return nil, err } var ( lim = &io.LimitedReader{R: r, N: int64(size)} arr []interface{} field interface{} ) for { if field, err = readField(lim); err != nil { if err == io.EOF { break } return nil, err } arr = append(arr, field) } return arr, nil } // Checks if this bit mask matches the flags bitset func hasProperty(mask uint16, prop int) bool { return int(mask)&prop > 0 } func (r *reader) parseHeaderFrame(channel uint16, size uint32) (frame frame, err error) { hf := &headerFrame{ ChannelId: channel, } if err = binary.Read(r.r, binary.BigEndian, &hf.ClassId); err != nil { return } if err = binary.Read(r.r, binary.BigEndian, &hf.weight); err != nil { return } if err = binary.Read(r.r, binary.BigEndian, &hf.Size); err != nil { return } var flags uint16 if err = binary.Read(r.r, binary.BigEndian, &flags); err != nil { return } if hasProperty(flags, flagContentType) { if hf.Properties.ContentType, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagContentEncoding) { if hf.Properties.ContentEncoding, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagHeaders) { if hf.Properties.Headers, err = readTable(r.r); err != nil { return } } if hasProperty(flags, flagDeliveryMode) { if err = binary.Read(r.r, binary.BigEndian, &hf.Properties.DeliveryMode); err != nil { return } } if hasProperty(flags, flagPriority) { if err = binary.Read(r.r, binary.BigEndian, &hf.Properties.Priority); err != nil { return } } if hasProperty(flags, flagCorrelationId) { if hf.Properties.CorrelationId, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagReplyTo) { if hf.Properties.ReplyTo, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagExpiration) { if hf.Properties.Expiration, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagMessageId) { if hf.Properties.MessageId, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagTimestamp) { if hf.Properties.Timestamp, err = readTimestamp(r.r); err != nil { return } } if hasProperty(flags, flagType) { if hf.Properties.Type, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagUserId) { if hf.Properties.UserId, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagAppId) { if hf.Properties.AppId, err = readShortstr(r.r); err != nil { return } } if hasProperty(flags, flagReserved1) { if hf.Properties.reserved1, err = readShortstr(r.r); err != nil { return } } return hf, nil } func (r *reader) parseBodyFrame(channel uint16, size uint32) (frame frame, err error) { bf := &bodyFrame{ ChannelId: channel, Body: make([]byte, size), } if _, err = io.ReadFull(r.r, bf.Body); err != nil { return nil, err } return bf, nil } var errHeartbeatPayload = errors.New("Heartbeats should not have a payload") func (r *reader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) { hf := &heartbeatFrame{ ChannelId: channel, } if size > 0 { return nil, errHeartbeatPayload } return hf, nil } golang-github-rabbitmq-amqp091-go-1.10.0/read_test.go000066400000000000000000000014031462444370000222130ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "strings" "testing" ) func TestGoFuzzCrashers(t *testing.T) { if testing.Short() { t.Skip("excessive allocation") } testData := []string{ "\b000000", "\x02\x16\x10�[��\t\xbdui�" + "\x10\x01\x00\xff\xbf\xef\xbfサn\x99\x00\x10r", "\x0300\x00\x00\x00\x040000", } for idx, testStr := range testData { r := reader{strings.NewReader(testStr)} frame, err := r.ReadFrame() if err != nil && frame != nil { t.Errorf("%d. frame is not nil: %#v err = %v", idx, frame, err) } } } golang-github-rabbitmq-amqp091-go-1.10.0/reconnect_test.go000066400000000000000000000062611462444370000232670ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091_test import ( "context" "fmt" "os" amqp "github.com/rabbitmq/amqp091-go" ) // Every connection should declare the topology they expect func setup(url, queue string) (*amqp.Connection, *amqp.Channel, error) { conn, err := amqp.Dial(url) if err != nil { return nil, nil, err } ch, err := conn.Channel() if err != nil { return nil, nil, err } if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { return nil, nil, err } return conn, ch, nil } func consume(url, queue string) (*amqp.Connection, <-chan amqp.Delivery, error) { conn, ch, err := setup(url, queue) if err != nil { return nil, nil, err } // Indicate we only want 1 message to acknowledge at a time. if err := ch.Qos(1, 0, false); err != nil { return nil, nil, err } // Exclusive consumer deliveries, err := ch.Consume(queue, "", false, true, false, false, nil) return conn, deliveries, err } func ExampleConnection_reconnect() { if url := os.Getenv("AMQP_URL"); url != "" { queue := "example.reconnect" // The connection/channel for publishing to interleave the ingress messages // between reconnects, shares the same topology as the consumer. If we rather // sent all messages up front, the first consumer would receive every message. // We would rather show how the messages are not lost between reconnects. con, pub, err := setup(url, queue) if err != nil { fmt.Println("err publisher setup:", err) return } defer con.Close() // Purge the queue from the publisher side to establish initial state if _, err := pub.QueuePurge(queue, false); err != nil { fmt.Println("err purge:", err) return } // Reconnect simulation, should be for { ... } in production for i := 1; i <= 3; i++ { fmt.Println("connect") conn, deliveries, err := consume(url, queue) if err != nil { fmt.Println("err consume:", err) return } // Simulate a producer on a different connection showing that consumers // continue where they were left off after each reconnect. if err := pub.PublishWithContext(context.TODO(), "", queue, false, false, amqp.Publishing{ Body: []byte(fmt.Sprintf("%d", i)), }); err != nil { fmt.Println("err publish:", err) return } // Simulates a consumer that when the range finishes, will setup a new // session and begin ranging over the deliveries again. for msg := range deliveries { fmt.Println(string(msg.Body)) if e := msg.Ack(false); e != nil { fmt.Println("ack error: ", e) } // Simulate an error like a server restart, loss of route or operator // intervention that results in the connection terminating go conn.Close() } } } else { // pass with expected output when not running in an integration // environment. fmt.Println("connect") fmt.Println("1") fmt.Println("connect") fmt.Println("2") fmt.Println("connect") fmt.Println("3") } // Output: // connect // 1 // connect // 2 // connect // 3 } golang-github-rabbitmq-amqp091-go-1.10.0/return.go000066400000000000000000000043661462444370000215730ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "time" ) // Return captures a flattened struct of fields returned by the server when a // Publishing is unable to be delivered either due to the `mandatory` flag set // and no route found, or `immediate` flag set and no free consumer. type Return struct { ReplyCode uint16 // reason ReplyText string // description Exchange string // basic.publish exchange RoutingKey string // basic.publish routing key // Properties ContentType string // MIME content type ContentEncoding string // MIME content encoding Headers Table // Application or header exchange table DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2) Priority uint8 // queue implementation use - 0 to 9 CorrelationId string // application use - correlation identifier ReplyTo string // application use - address to to reply to (ex: RPC) Expiration string // implementation use - message expiration spec MessageId string // application use - message identifier Timestamp time.Time // application use - message timestamp Type string // application use - message type name UserId string // application use - creating user id AppId string // application use - creating application Body []byte } func newReturn(msg basicReturn) *Return { props, body := msg.getContent() return &Return{ ReplyCode: msg.ReplyCode, ReplyText: msg.ReplyText, Exchange: msg.Exchange, RoutingKey: msg.RoutingKey, Headers: props.Headers, ContentType: props.ContentType, ContentEncoding: props.ContentEncoding, DeliveryMode: props.DeliveryMode, Priority: props.Priority, CorrelationId: props.CorrelationId, ReplyTo: props.ReplyTo, Expiration: props.Expiration, MessageId: props.MessageId, Timestamp: props.Timestamp, Type: props.Type, UserId: props.UserId, AppId: props.AppId, Body: body, } } golang-github-rabbitmq-amqp091-go-1.10.0/shared_test.go000066400000000000000000000015461462444370000225560ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "io" "testing" ) type pipe struct { r *io.PipeReader w *io.PipeWriter } func (p pipe) Read(b []byte) (int, error) { return p.r.Read(b) } func (p pipe) Write(b []byte) (int, error) { return p.w.Write(b) } func (p pipe) Close() error { p.r.Close() p.w.Close() return nil } type logIO struct { t *testing.T prefix string proxy io.ReadWriteCloser } func (log *logIO) Read(p []byte) (n int, err error) { return log.proxy.Read(p) } func (log *logIO) Write(p []byte) (n int, err error) { return log.proxy.Write(p) } func (log *logIO) Close() (err error) { return log.proxy.Close() } golang-github-rabbitmq-amqp091-go-1.10.0/spec/000077500000000000000000000000001462444370000206465ustar00rootroot00000000000000golang-github-rabbitmq-amqp091-go-1.10.0/spec/amqp0-9-1.stripped.extended.xml000066400000000000000000000555141462444370000263540ustar00rootroot00000000000000 golang-github-rabbitmq-amqp091-go-1.10.0/spec/gen.go000066400000000000000000000323001462444370000217440ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build ignore // +build ignore package main import ( "bytes" "encoding/xml" "errors" "fmt" "io" "log" "os" "regexp" "strings" "text/template" ) var ( ErrUnknownType = errors.New("unknown field type in gen") ErrUnknownDomain = errors.New("unknown domain type in gen") ) var amqpTypeToNative = map[string]string{ "bit": "bool", "octet": "byte", "shortshort": "uint8", "short": "uint16", "long": "uint32", "longlong": "uint64", "timestamp": "time.Time", "table": "Table", "shortstr": "string", "longstr": "string", } type Rule struct { Name string `xml:"name,attr"` Docs []string `xml:"doc"` } type Doc struct { Type string `xml:"type,attr"` Body string `xml:",innerxml"` } type Chassis struct { Name string `xml:"name,attr"` Implement string `xml:"implement,attr"` } type Assert struct { Check string `xml:"check,attr"` Value string `xml:"value,attr"` Method string `xml:"method,attr"` } type Field struct { Name string `xml:"name,attr"` Domain string `xml:"domain,attr"` Type string `xml:"type,attr"` Label string `xml:"label,attr"` Reserved bool `xml:"reserved,attr"` Docs []Doc `xml:"doc"` Asserts []Assert `xml:"assert"` } type Response struct { Name string `xml:"name,attr"` } type Method struct { Name string `xml:"name,attr"` Response Response `xml:"response"` Synchronous bool `xml:"synchronous,attr"` Content bool `xml:"content,attr"` Index string `xml:"index,attr"` Label string `xml:"label,attr"` Docs []Doc `xml:"doc"` Rules []Rule `xml:"rule"` Fields []Field `xml:"field"` Chassis []Chassis `xml:"chassis"` } type Class struct { Name string `xml:"name,attr"` Handler string `xml:"handler,attr"` Index string `xml:"index,attr"` Label string `xml:"label,attr"` Docs []Doc `xml:"doc"` Methods []Method `xml:"method"` Chassis []Chassis `xml:"chassis"` } type Domain struct { Name string `xml:"name,attr"` Type string `xml:"type,attr"` Label string `xml:"label,attr"` Rules []Rule `xml:"rule"` Docs []Doc `xml:"doc"` } type Constant struct { Name string `xml:"name,attr"` Value int `xml:"value,attr"` Class string `xml:"class,attr"` Doc string `xml:"doc"` } type Amqp struct { Major int `xml:"major,attr"` Minor int `xml:"minor,attr"` Port int `xml:"port,attr"` Comment string `xml:"comment,attr"` Constants []Constant `xml:"constant"` Domains []Domain `xml:"domain"` Classes []Class `xml:"class"` } type renderer struct { Root Amqp bitcounter int } type fieldset struct { AmqpType string NativeType string Fields []Field *renderer } var ( helpers = template.FuncMap{ "public": public, "private": private, "clean": clean, } packageTemplate = template.Must(template.New("package").Funcs(helpers).Parse(` // Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* GENERATED FILE - DO NOT EDIT */ /* Rebuild from the spec/gen.go tool */ {{with .Root}} package amqp091 import ( "fmt" "encoding/binary" "io" ) // Error codes that can be sent from the server during a connection or // channel exception or used by the client to indicate a class of error like // ErrCredentials. The text of the error is likely more interesting than // these constants. const ( {{range $c := .Constants}} {{if $c.IsError}}{{.Name | public}}{{else}}{{.Name | private}}{{end}} = {{.Value}}{{end}} ) func isSoftExceptionCode(code int) bool { switch code { {{range $c := .Constants}} {{if $c.IsSoftError}} case {{$c.Value}}: return true {{end}}{{end}} } return false } {{range .Classes}} {{$class := .}} {{range .Methods}} {{$method := .}} {{$struct := $.StructName $class.Name $method.Name}} {{if .Docs}}/* {{range .Docs}} {{.Body | clean}} {{end}} */{{end}} type {{$struct}} struct { {{range .Fields}} {{$.FieldName .}} {{$.FieldType . | $.NativeType}} {{if .Label}}// {{.Label}}{{end}}{{end}} {{if .Content}}Properties properties Body []byte{{end}} } func (msg *{{$struct}}) id() (uint16, uint16) { return {{$class.Index}}, {{$method.Index}} } func (msg *{{$struct}}) wait() (bool) { return {{.Synchronous}}{{if $.HasField "NoWait" .}} && !msg.NoWait{{end}} } {{if .Content}} func (msg *{{$struct}}) getContent() (properties, []byte) { return msg.Properties, msg.Body } func (msg *{{$struct}}) setContent(props properties, body []byte) { msg.Properties, msg.Body = props, body } {{end}} func (msg *{{$struct}}) write(w io.Writer) (err error) { {{if $.HasType "bit" $method}}var bits byte{{end}} {{.Fields | $.Fieldsets | $.Partial "enc-"}} return } func (msg *{{$struct}}) read(r io.Reader) (err error) { {{if $.HasType "bit" $method}}var bits byte{{end}} {{.Fields | $.Fieldsets | $.Partial "dec-"}} return } {{end}} {{end}} func (r *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) { mf := &methodFrame { ChannelId: channel, } if err = binary.Read(r.r, binary.BigEndian, &mf.ClassId); err != nil { return } if err = binary.Read(r.r, binary.BigEndian, &mf.MethodId); err != nil { return } switch mf.ClassId { {{range .Classes}} {{$class := .}} case {{.Index}}: // {{.Name}} switch mf.MethodId { {{range .Methods}} case {{.Index}}: // {{$class.Name}} {{.Name}} // fmt.Println("NextMethod: class:{{$class.Index}} method:{{.Index}}") method := &{{$.StructName $class.Name .Name}}{} if err = method.read(r.r); err != nil { return } mf.Method = method {{end}} default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } {{end}} default: return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId) } return mf, nil } {{end}} {{define "enc-bit"}} {{range $off, $field := .Fields}} if msg.{{$field | $.FieldName}} { bits |= 1 << {{$off}} } {{end}} if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } {{end}} {{define "enc-octet"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-shortshort"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-short"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-long"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-longlong"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-timestamp"}} {{range .Fields}} if err = writeTimestamp(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-shortstr"}} {{range .Fields}} if err = writeShortstr(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-longstr"}} {{range .Fields}} if err = writeLongstr(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-table"}} {{range .Fields}} if err = writeTable(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-bit"}} if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } {{range $off, $field := .Fields}} msg.{{$field | $.FieldName}} = (bits & (1 << {{$off}}) > 0) {{end}} {{end}} {{define "dec-octet"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-shortshort"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-short"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-long"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-longlong"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-timestamp"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readTimestamp(r); err != nil { return } {{end}} {{end}} {{define "dec-shortstr"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readShortstr(r); err != nil { return } {{end}} {{end}} {{define "dec-longstr"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readLongstr(r); err != nil { return } {{end}} {{end}} {{define "dec-table"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readTable(r); err != nil { return } {{end}} {{end}} `)) ) func (c *Constant) IsError() bool { return strings.Contains(c.Class, "error") } func (c *Constant) IsSoftError() bool { return c.Class == "soft-error" } func (renderer *renderer) Partial(prefix string, fields []fieldset) (s string, err error) { var buf bytes.Buffer for _, set := range fields { name := prefix + set.AmqpType t := packageTemplate.Lookup(name) if t == nil { return "", errors.New(fmt.Sprintf("Missing template: %s", name)) } if err = t.Execute(&buf, set); err != nil { return } } return string(buf.Bytes()), nil } // Groups the fields so that the right encoder/decoder can be called func (renderer *renderer) Fieldsets(fields []Field) (f []fieldset, err error) { if len(fields) > 0 { for _, field := range fields { cur := fieldset{} cur.AmqpType, err = renderer.FieldType(field) if err != nil { return } cur.NativeType, err = renderer.NativeType(cur.AmqpType) if err != nil { return } cur.Fields = append(cur.Fields, field) f = append(f, cur) } i, j := 0, 1 for j < len(f) { if f[i].AmqpType == f[j].AmqpType { f[i].Fields = append(f[i].Fields, f[j].Fields...) } else { i++ f[i] = f[j] } j++ } return f[:i+1], nil } return } func (renderer *renderer) HasType(typ string, method Method) bool { for _, f := range method.Fields { name, _ := renderer.FieldType(f) if name == typ { return true } } return false } func (renderer *renderer) HasField(field string, method Method) bool { for _, f := range method.Fields { name := renderer.FieldName(f) if name == field { return true } } return false } func (renderer *renderer) Domain(field Field) (domain Domain, err error) { for _, domain = range renderer.Root.Domains { if field.Domain == domain.Name { return } } return domain, nil // return domain, ErrUnknownDomain } func (renderer *renderer) FieldName(field Field) (t string) { t = public(field.Name) if field.Reserved { t = strings.ToLower(t) } return } func (renderer *renderer) FieldType(field Field) (t string, err error) { t = field.Type if t == "" { var domain Domain domain, err = renderer.Domain(field) if err != nil { return "", err } t = domain.Type } return } func (renderer *renderer) NativeType(amqpType string) (t string, err error) { if t, ok := amqpTypeToNative[amqpType]; ok { return t, nil } return "", ErrUnknownType } func (renderer *renderer) Tag(d Domain) string { label := "`" label += `domain:"` + d.Name + `"` if len(d.Type) > 0 { label += `,type:"` + d.Type + `"` } label += "`" return label } func (renderer *renderer) StructName(parts ...string) string { return parts[0] + public(parts[1:]...) } func clean(body string) (res string) { return strings.Replace(body, "\r", "", -1) } func private(parts ...string) string { return export(regexp.MustCompile(`[-_]\w`), parts...) } func public(parts ...string) string { return export(regexp.MustCompile(`^\w|[-_]\w`), parts...) } func export(delim *regexp.Regexp, parts ...string) (res string) { for _, in := range parts { res += delim.ReplaceAllStringFunc(in, func(match string) string { switch len(match) { case 1: return strings.ToUpper(match) case 2: return strings.ToUpper(match[1:]) } panic("unreachable") }) } return } func main() { var r renderer spec, err := io.ReadAll(os.Stdin) if err != nil { log.Fatalln("Please pass spec on stdin", err) } err = xml.Unmarshal(spec, &r.Root) if err != nil { log.Fatalln("Could not parse XML:", err) } if err = packageTemplate.Execute(os.Stdout, &r); err != nil { log.Fatalln("Generate error: ", err) } } golang-github-rabbitmq-amqp091-go-1.10.0/spec091.go000066400000000000000000001665001462444370000214370ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* GENERATED FILE - DO NOT EDIT */ /* Rebuild from the spec/gen.go tool */ package amqp091 import ( "encoding/binary" "fmt" "io" ) // Error codes that can be sent from the server during a connection or // channel exception or used by the client to indicate a class of error like // ErrCredentials. The text of the error is likely more interesting than // these constants. const ( frameMethod = 1 frameHeader = 2 frameBody = 3 frameHeartbeat = 8 frameMinSize = 4096 frameEnd = 206 replySuccess = 200 ContentTooLarge = 311 NoRoute = 312 NoConsumers = 313 ConnectionForced = 320 InvalidPath = 402 AccessRefused = 403 NotFound = 404 ResourceLocked = 405 PreconditionFailed = 406 FrameError = 501 SyntaxError = 502 CommandInvalid = 503 ChannelError = 504 UnexpectedFrame = 505 ResourceError = 506 NotAllowed = 530 NotImplemented = 540 InternalError = 541 ) func isSoftExceptionCode(code int) bool { switch code { case 311: return true case 312: return true case 313: return true case 403: return true case 404: return true case 405: return true case 406: return true } return false } type connectionStart struct { VersionMajor byte VersionMinor byte ServerProperties Table Mechanisms string Locales string } func (msg *connectionStart) id() (uint16, uint16) { return 10, 10 } func (msg *connectionStart) wait() bool { return true } func (msg *connectionStart) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.VersionMajor); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.VersionMinor); err != nil { return } if err = writeTable(w, msg.ServerProperties); err != nil { return } if err = writeLongstr(w, msg.Mechanisms); err != nil { return } if err = writeLongstr(w, msg.Locales); err != nil { return } return } func (msg *connectionStart) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.VersionMajor); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.VersionMinor); err != nil { return } if msg.ServerProperties, err = readTable(r); err != nil { return } if msg.Mechanisms, err = readLongstr(r); err != nil { return } if msg.Locales, err = readLongstr(r); err != nil { return } return } type connectionStartOk struct { ClientProperties Table Mechanism string Response string Locale string } func (msg *connectionStartOk) id() (uint16, uint16) { return 10, 11 } func (msg *connectionStartOk) wait() bool { return true } func (msg *connectionStartOk) write(w io.Writer) (err error) { if err = writeTable(w, msg.ClientProperties); err != nil { return } if err = writeShortstr(w, msg.Mechanism); err != nil { return } if err = writeLongstr(w, msg.Response); err != nil { return } if err = writeShortstr(w, msg.Locale); err != nil { return } return } func (msg *connectionStartOk) read(r io.Reader) (err error) { if msg.ClientProperties, err = readTable(r); err != nil { return } if msg.Mechanism, err = readShortstr(r); err != nil { return } if msg.Response, err = readLongstr(r); err != nil { return } if msg.Locale, err = readShortstr(r); err != nil { return } return } type connectionSecure struct { Challenge string } func (msg *connectionSecure) id() (uint16, uint16) { return 10, 20 } func (msg *connectionSecure) wait() bool { return true } func (msg *connectionSecure) write(w io.Writer) (err error) { if err = writeLongstr(w, msg.Challenge); err != nil { return } return } func (msg *connectionSecure) read(r io.Reader) (err error) { if msg.Challenge, err = readLongstr(r); err != nil { return } return } type connectionSecureOk struct { Response string } func (msg *connectionSecureOk) id() (uint16, uint16) { return 10, 21 } func (msg *connectionSecureOk) wait() bool { return true } func (msg *connectionSecureOk) write(w io.Writer) (err error) { if err = writeLongstr(w, msg.Response); err != nil { return } return } func (msg *connectionSecureOk) read(r io.Reader) (err error) { if msg.Response, err = readLongstr(r); err != nil { return } return } type connectionTune struct { ChannelMax uint16 FrameMax uint32 Heartbeat uint16 } func (msg *connectionTune) id() (uint16, uint16) { return 10, 30 } func (msg *connectionTune) wait() bool { return true } func (msg *connectionTune) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.ChannelMax); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.FrameMax); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.Heartbeat); err != nil { return } return } func (msg *connectionTune) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.ChannelMax); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.FrameMax); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.Heartbeat); err != nil { return } return } type connectionTuneOk struct { ChannelMax uint16 FrameMax uint32 Heartbeat uint16 } func (msg *connectionTuneOk) id() (uint16, uint16) { return 10, 31 } func (msg *connectionTuneOk) wait() bool { return true } func (msg *connectionTuneOk) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.ChannelMax); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.FrameMax); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.Heartbeat); err != nil { return } return } func (msg *connectionTuneOk) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.ChannelMax); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.FrameMax); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.Heartbeat); err != nil { return } return } type connectionOpen struct { VirtualHost string reserved1 string reserved2 bool } func (msg *connectionOpen) id() (uint16, uint16) { return 10, 40 } func (msg *connectionOpen) wait() bool { return true } func (msg *connectionOpen) write(w io.Writer) (err error) { var bits byte if err = writeShortstr(w, msg.VirtualHost); err != nil { return } if err = writeShortstr(w, msg.reserved1); err != nil { return } if msg.reserved2 { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *connectionOpen) read(r io.Reader) (err error) { var bits byte if msg.VirtualHost, err = readShortstr(r); err != nil { return } if msg.reserved1, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.reserved2 = (bits&(1<<0) > 0) return } type connectionOpenOk struct { reserved1 string } func (msg *connectionOpenOk) id() (uint16, uint16) { return 10, 41 } func (msg *connectionOpenOk) wait() bool { return true } func (msg *connectionOpenOk) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.reserved1); err != nil { return } return } func (msg *connectionOpenOk) read(r io.Reader) (err error) { if msg.reserved1, err = readShortstr(r); err != nil { return } return } type connectionClose struct { ReplyCode uint16 ReplyText string ClassId uint16 MethodId uint16 } func (msg *connectionClose) id() (uint16, uint16) { return 10, 50 } func (msg *connectionClose) wait() bool { return true } func (msg *connectionClose) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.ReplyCode); err != nil { return } if err = writeShortstr(w, msg.ReplyText); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.ClassId); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.MethodId); err != nil { return } return } func (msg *connectionClose) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.ReplyCode); err != nil { return } if msg.ReplyText, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.ClassId); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.MethodId); err != nil { return } return } type connectionCloseOk struct { } func (msg *connectionCloseOk) id() (uint16, uint16) { return 10, 51 } func (msg *connectionCloseOk) wait() bool { return true } func (msg *connectionCloseOk) write(w io.Writer) (err error) { return } func (msg *connectionCloseOk) read(r io.Reader) (err error) { return } type connectionBlocked struct { Reason string } func (msg *connectionBlocked) id() (uint16, uint16) { return 10, 60 } func (msg *connectionBlocked) wait() bool { return false } func (msg *connectionBlocked) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.Reason); err != nil { return } return } func (msg *connectionBlocked) read(r io.Reader) (err error) { if msg.Reason, err = readShortstr(r); err != nil { return } return } type connectionUnblocked struct { } func (msg *connectionUnblocked) id() (uint16, uint16) { return 10, 61 } func (msg *connectionUnblocked) wait() bool { return false } func (msg *connectionUnblocked) write(w io.Writer) (err error) { return } func (msg *connectionUnblocked) read(r io.Reader) (err error) { return } type connectionUpdateSecret struct { NewSecret string Reason string } func (msg *connectionUpdateSecret) id() (uint16, uint16) { return 10, 70 } func (msg *connectionUpdateSecret) wait() bool { return true } func (msg *connectionUpdateSecret) write(w io.Writer) (err error) { if err = writeLongstr(w, msg.NewSecret); err != nil { return } if err = writeShortstr(w, msg.Reason); err != nil { return } return } func (msg *connectionUpdateSecret) read(r io.Reader) (err error) { if msg.NewSecret, err = readLongstr(r); err != nil { return } if msg.Reason, err = readShortstr(r); err != nil { return } return } type connectionUpdateSecretOk struct { } func (msg *connectionUpdateSecretOk) id() (uint16, uint16) { return 10, 71 } func (msg *connectionUpdateSecretOk) wait() bool { return true } func (msg *connectionUpdateSecretOk) write(w io.Writer) (err error) { return } func (msg *connectionUpdateSecretOk) read(r io.Reader) (err error) { return } type channelOpen struct { reserved1 string } func (msg *channelOpen) id() (uint16, uint16) { return 20, 10 } func (msg *channelOpen) wait() bool { return true } func (msg *channelOpen) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.reserved1); err != nil { return } return } func (msg *channelOpen) read(r io.Reader) (err error) { if msg.reserved1, err = readShortstr(r); err != nil { return } return } type channelOpenOk struct { reserved1 string } func (msg *channelOpenOk) id() (uint16, uint16) { return 20, 11 } func (msg *channelOpenOk) wait() bool { return true } func (msg *channelOpenOk) write(w io.Writer) (err error) { if err = writeLongstr(w, msg.reserved1); err != nil { return } return } func (msg *channelOpenOk) read(r io.Reader) (err error) { if msg.reserved1, err = readLongstr(r); err != nil { return } return } type channelFlow struct { Active bool } func (msg *channelFlow) id() (uint16, uint16) { return 20, 20 } func (msg *channelFlow) wait() bool { return true } func (msg *channelFlow) write(w io.Writer) (err error) { var bits byte if msg.Active { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *channelFlow) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Active = (bits&(1<<0) > 0) return } type channelFlowOk struct { Active bool } func (msg *channelFlowOk) id() (uint16, uint16) { return 20, 21 } func (msg *channelFlowOk) wait() bool { return false } func (msg *channelFlowOk) write(w io.Writer) (err error) { var bits byte if msg.Active { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *channelFlowOk) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Active = (bits&(1<<0) > 0) return } type channelClose struct { ReplyCode uint16 ReplyText string ClassId uint16 MethodId uint16 } func (msg *channelClose) id() (uint16, uint16) { return 20, 40 } func (msg *channelClose) wait() bool { return true } func (msg *channelClose) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.ReplyCode); err != nil { return } if err = writeShortstr(w, msg.ReplyText); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.ClassId); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.MethodId); err != nil { return } return } func (msg *channelClose) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.ReplyCode); err != nil { return } if msg.ReplyText, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.ClassId); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.MethodId); err != nil { return } return } type channelCloseOk struct { } func (msg *channelCloseOk) id() (uint16, uint16) { return 20, 41 } func (msg *channelCloseOk) wait() bool { return true } func (msg *channelCloseOk) write(w io.Writer) (err error) { return } func (msg *channelCloseOk) read(r io.Reader) (err error) { return } type exchangeDeclare struct { reserved1 uint16 Exchange string Type string Passive bool Durable bool AutoDelete bool Internal bool NoWait bool Arguments Table } func (msg *exchangeDeclare) id() (uint16, uint16) { return 40, 10 } func (msg *exchangeDeclare) wait() bool { return true && !msg.NoWait } func (msg *exchangeDeclare) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.Type); err != nil { return } if msg.Passive { bits |= 1 << 0 } if msg.Durable { bits |= 1 << 1 } if msg.AutoDelete { bits |= 1 << 2 } if msg.Internal { bits |= 1 << 3 } if msg.NoWait { bits |= 1 << 4 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *exchangeDeclare) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.Type, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Passive = (bits&(1<<0) > 0) msg.Durable = (bits&(1<<1) > 0) msg.AutoDelete = (bits&(1<<2) > 0) msg.Internal = (bits&(1<<3) > 0) msg.NoWait = (bits&(1<<4) > 0) if msg.Arguments, err = readTable(r); err != nil { return } return } type exchangeDeclareOk struct { } func (msg *exchangeDeclareOk) id() (uint16, uint16) { return 40, 11 } func (msg *exchangeDeclareOk) wait() bool { return true } func (msg *exchangeDeclareOk) write(w io.Writer) (err error) { return } func (msg *exchangeDeclareOk) read(r io.Reader) (err error) { return } type exchangeDelete struct { reserved1 uint16 Exchange string IfUnused bool NoWait bool } func (msg *exchangeDelete) id() (uint16, uint16) { return 40, 20 } func (msg *exchangeDelete) wait() bool { return true && !msg.NoWait } func (msg *exchangeDelete) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if msg.IfUnused { bits |= 1 << 0 } if msg.NoWait { bits |= 1 << 1 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *exchangeDelete) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Exchange, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.IfUnused = (bits&(1<<0) > 0) msg.NoWait = (bits&(1<<1) > 0) return } type exchangeDeleteOk struct { } func (msg *exchangeDeleteOk) id() (uint16, uint16) { return 40, 21 } func (msg *exchangeDeleteOk) wait() bool { return true } func (msg *exchangeDeleteOk) write(w io.Writer) (err error) { return } func (msg *exchangeDeleteOk) read(r io.Reader) (err error) { return } type exchangeBind struct { reserved1 uint16 Destination string Source string RoutingKey string NoWait bool Arguments Table } func (msg *exchangeBind) id() (uint16, uint16) { return 40, 30 } func (msg *exchangeBind) wait() bool { return true && !msg.NoWait } func (msg *exchangeBind) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Destination); err != nil { return } if err = writeShortstr(w, msg.Source); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } if msg.NoWait { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *exchangeBind) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Destination, err = readShortstr(r); err != nil { return } if msg.Source, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoWait = (bits&(1<<0) > 0) if msg.Arguments, err = readTable(r); err != nil { return } return } type exchangeBindOk struct { } func (msg *exchangeBindOk) id() (uint16, uint16) { return 40, 31 } func (msg *exchangeBindOk) wait() bool { return true } func (msg *exchangeBindOk) write(w io.Writer) (err error) { return } func (msg *exchangeBindOk) read(r io.Reader) (err error) { return } type exchangeUnbind struct { reserved1 uint16 Destination string Source string RoutingKey string NoWait bool Arguments Table } func (msg *exchangeUnbind) id() (uint16, uint16) { return 40, 40 } func (msg *exchangeUnbind) wait() bool { return true && !msg.NoWait } func (msg *exchangeUnbind) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Destination); err != nil { return } if err = writeShortstr(w, msg.Source); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } if msg.NoWait { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *exchangeUnbind) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Destination, err = readShortstr(r); err != nil { return } if msg.Source, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoWait = (bits&(1<<0) > 0) if msg.Arguments, err = readTable(r); err != nil { return } return } type exchangeUnbindOk struct { } func (msg *exchangeUnbindOk) id() (uint16, uint16) { return 40, 51 } func (msg *exchangeUnbindOk) wait() bool { return true } func (msg *exchangeUnbindOk) write(w io.Writer) (err error) { return } func (msg *exchangeUnbindOk) read(r io.Reader) (err error) { return } type queueDeclare struct { reserved1 uint16 Queue string Passive bool Durable bool Exclusive bool AutoDelete bool NoWait bool Arguments Table } func (msg *queueDeclare) id() (uint16, uint16) { return 50, 10 } func (msg *queueDeclare) wait() bool { return true && !msg.NoWait } func (msg *queueDeclare) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if msg.Passive { bits |= 1 << 0 } if msg.Durable { bits |= 1 << 1 } if msg.Exclusive { bits |= 1 << 2 } if msg.AutoDelete { bits |= 1 << 3 } if msg.NoWait { bits |= 1 << 4 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *queueDeclare) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Passive = (bits&(1<<0) > 0) msg.Durable = (bits&(1<<1) > 0) msg.Exclusive = (bits&(1<<2) > 0) msg.AutoDelete = (bits&(1<<3) > 0) msg.NoWait = (bits&(1<<4) > 0) if msg.Arguments, err = readTable(r); err != nil { return } return } type queueDeclareOk struct { Queue string MessageCount uint32 ConsumerCount uint32 } func (msg *queueDeclareOk) id() (uint16, uint16) { return 50, 11 } func (msg *queueDeclareOk) wait() bool { return true } func (msg *queueDeclareOk) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.Queue); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.MessageCount); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.ConsumerCount); err != nil { return } return } func (msg *queueDeclareOk) read(r io.Reader) (err error) { if msg.Queue, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.MessageCount); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.ConsumerCount); err != nil { return } return } type queueBind struct { reserved1 uint16 Queue string Exchange string RoutingKey string NoWait bool Arguments Table } func (msg *queueBind) id() (uint16, uint16) { return 50, 20 } func (msg *queueBind) wait() bool { return true && !msg.NoWait } func (msg *queueBind) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } if msg.NoWait { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *queueBind) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoWait = (bits&(1<<0) > 0) if msg.Arguments, err = readTable(r); err != nil { return } return } type queueBindOk struct { } func (msg *queueBindOk) id() (uint16, uint16) { return 50, 21 } func (msg *queueBindOk) wait() bool { return true } func (msg *queueBindOk) write(w io.Writer) (err error) { return } func (msg *queueBindOk) read(r io.Reader) (err error) { return } type queueUnbind struct { reserved1 uint16 Queue string Exchange string RoutingKey string Arguments Table } func (msg *queueUnbind) id() (uint16, uint16) { return 50, 50 } func (msg *queueUnbind) wait() bool { return true } func (msg *queueUnbind) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *queueUnbind) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } if msg.Arguments, err = readTable(r); err != nil { return } return } type queueUnbindOk struct { } func (msg *queueUnbindOk) id() (uint16, uint16) { return 50, 51 } func (msg *queueUnbindOk) wait() bool { return true } func (msg *queueUnbindOk) write(w io.Writer) (err error) { return } func (msg *queueUnbindOk) read(r io.Reader) (err error) { return } type queuePurge struct { reserved1 uint16 Queue string NoWait bool } func (msg *queuePurge) id() (uint16, uint16) { return 50, 30 } func (msg *queuePurge) wait() bool { return true && !msg.NoWait } func (msg *queuePurge) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if msg.NoWait { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *queuePurge) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoWait = (bits&(1<<0) > 0) return } type queuePurgeOk struct { MessageCount uint32 } func (msg *queuePurgeOk) id() (uint16, uint16) { return 50, 31 } func (msg *queuePurgeOk) wait() bool { return true } func (msg *queuePurgeOk) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.MessageCount); err != nil { return } return } func (msg *queuePurgeOk) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.MessageCount); err != nil { return } return } type queueDelete struct { reserved1 uint16 Queue string IfUnused bool IfEmpty bool NoWait bool } func (msg *queueDelete) id() (uint16, uint16) { return 50, 40 } func (msg *queueDelete) wait() bool { return true && !msg.NoWait } func (msg *queueDelete) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if msg.IfUnused { bits |= 1 << 0 } if msg.IfEmpty { bits |= 1 << 1 } if msg.NoWait { bits |= 1 << 2 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *queueDelete) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.IfUnused = (bits&(1<<0) > 0) msg.IfEmpty = (bits&(1<<1) > 0) msg.NoWait = (bits&(1<<2) > 0) return } type queueDeleteOk struct { MessageCount uint32 } func (msg *queueDeleteOk) id() (uint16, uint16) { return 50, 41 } func (msg *queueDeleteOk) wait() bool { return true } func (msg *queueDeleteOk) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.MessageCount); err != nil { return } return } func (msg *queueDeleteOk) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.MessageCount); err != nil { return } return } type basicQos struct { PrefetchSize uint32 PrefetchCount uint16 Global bool } func (msg *basicQos) id() (uint16, uint16) { return 60, 10 } func (msg *basicQos) wait() bool { return true } func (msg *basicQos) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.PrefetchSize); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.PrefetchCount); err != nil { return } if msg.Global { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicQos) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.PrefetchSize); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.PrefetchCount); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Global = (bits&(1<<0) > 0) return } type basicQosOk struct { } func (msg *basicQosOk) id() (uint16, uint16) { return 60, 11 } func (msg *basicQosOk) wait() bool { return true } func (msg *basicQosOk) write(w io.Writer) (err error) { return } func (msg *basicQosOk) read(r io.Reader) (err error) { return } type basicConsume struct { reserved1 uint16 Queue string ConsumerTag string NoLocal bool NoAck bool Exclusive bool NoWait bool Arguments Table } func (msg *basicConsume) id() (uint16, uint16) { return 60, 20 } func (msg *basicConsume) wait() bool { return true && !msg.NoWait } func (msg *basicConsume) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if err = writeShortstr(w, msg.ConsumerTag); err != nil { return } if msg.NoLocal { bits |= 1 << 0 } if msg.NoAck { bits |= 1 << 1 } if msg.Exclusive { bits |= 1 << 2 } if msg.NoWait { bits |= 1 << 3 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeTable(w, msg.Arguments); err != nil { return } return } func (msg *basicConsume) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if msg.ConsumerTag, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoLocal = (bits&(1<<0) > 0) msg.NoAck = (bits&(1<<1) > 0) msg.Exclusive = (bits&(1<<2) > 0) msg.NoWait = (bits&(1<<3) > 0) if msg.Arguments, err = readTable(r); err != nil { return } return } type basicConsumeOk struct { ConsumerTag string } func (msg *basicConsumeOk) id() (uint16, uint16) { return 60, 21 } func (msg *basicConsumeOk) wait() bool { return true } func (msg *basicConsumeOk) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.ConsumerTag); err != nil { return } return } func (msg *basicConsumeOk) read(r io.Reader) (err error) { if msg.ConsumerTag, err = readShortstr(r); err != nil { return } return } type basicCancel struct { ConsumerTag string NoWait bool } func (msg *basicCancel) id() (uint16, uint16) { return 60, 30 } func (msg *basicCancel) wait() bool { return true && !msg.NoWait } func (msg *basicCancel) write(w io.Writer) (err error) { var bits byte if err = writeShortstr(w, msg.ConsumerTag); err != nil { return } if msg.NoWait { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicCancel) read(r io.Reader) (err error) { var bits byte if msg.ConsumerTag, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoWait = (bits&(1<<0) > 0) return } type basicCancelOk struct { ConsumerTag string } func (msg *basicCancelOk) id() (uint16, uint16) { return 60, 31 } func (msg *basicCancelOk) wait() bool { return true } func (msg *basicCancelOk) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.ConsumerTag); err != nil { return } return } func (msg *basicCancelOk) read(r io.Reader) (err error) { if msg.ConsumerTag, err = readShortstr(r); err != nil { return } return } type basicPublish struct { reserved1 uint16 Exchange string RoutingKey string Mandatory bool Immediate bool Properties properties Body []byte } func (msg *basicPublish) id() (uint16, uint16) { return 60, 40 } func (msg *basicPublish) wait() bool { return false } func (msg *basicPublish) getContent() (properties, []byte) { return msg.Properties, msg.Body } func (msg *basicPublish) setContent(props properties, body []byte) { msg.Properties, msg.Body = props, body } func (msg *basicPublish) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } if msg.Mandatory { bits |= 1 << 0 } if msg.Immediate { bits |= 1 << 1 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicPublish) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Mandatory = (bits&(1<<0) > 0) msg.Immediate = (bits&(1<<1) > 0) return } type basicReturn struct { ReplyCode uint16 ReplyText string Exchange string RoutingKey string Properties properties Body []byte } func (msg *basicReturn) id() (uint16, uint16) { return 60, 50 } func (msg *basicReturn) wait() bool { return false } func (msg *basicReturn) getContent() (properties, []byte) { return msg.Properties, msg.Body } func (msg *basicReturn) setContent(props properties, body []byte) { msg.Properties, msg.Body = props, body } func (msg *basicReturn) write(w io.Writer) (err error) { if err = binary.Write(w, binary.BigEndian, msg.ReplyCode); err != nil { return } if err = writeShortstr(w, msg.ReplyText); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } return } func (msg *basicReturn) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &msg.ReplyCode); err != nil { return } if msg.ReplyText, err = readShortstr(r); err != nil { return } if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } return } type basicDeliver struct { ConsumerTag string DeliveryTag uint64 Redelivered bool Exchange string RoutingKey string Properties properties Body []byte } func (msg *basicDeliver) id() (uint16, uint16) { return 60, 60 } func (msg *basicDeliver) wait() bool { return false } func (msg *basicDeliver) getContent() (properties, []byte) { return msg.Properties, msg.Body } func (msg *basicDeliver) setContent(props properties, body []byte) { msg.Properties, msg.Body = props, body } func (msg *basicDeliver) write(w io.Writer) (err error) { var bits byte if err = writeShortstr(w, msg.ConsumerTag); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.DeliveryTag); err != nil { return } if msg.Redelivered { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } return } func (msg *basicDeliver) read(r io.Reader) (err error) { var bits byte if msg.ConsumerTag, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.DeliveryTag); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Redelivered = (bits&(1<<0) > 0) if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } return } type basicGet struct { reserved1 uint16 Queue string NoAck bool } func (msg *basicGet) id() (uint16, uint16) { return 60, 70 } func (msg *basicGet) wait() bool { return true } func (msg *basicGet) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.reserved1); err != nil { return } if err = writeShortstr(w, msg.Queue); err != nil { return } if msg.NoAck { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicGet) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.reserved1); err != nil { return } if msg.Queue, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.NoAck = (bits&(1<<0) > 0) return } type basicGetOk struct { DeliveryTag uint64 Redelivered bool Exchange string RoutingKey string MessageCount uint32 Properties properties Body []byte } func (msg *basicGetOk) id() (uint16, uint16) { return 60, 71 } func (msg *basicGetOk) wait() bool { return true } func (msg *basicGetOk) getContent() (properties, []byte) { return msg.Properties, msg.Body } func (msg *basicGetOk) setContent(props properties, body []byte) { msg.Properties, msg.Body = props, body } func (msg *basicGetOk) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.DeliveryTag); err != nil { return } if msg.Redelivered { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } if err = writeShortstr(w, msg.Exchange); err != nil { return } if err = writeShortstr(w, msg.RoutingKey); err != nil { return } if err = binary.Write(w, binary.BigEndian, msg.MessageCount); err != nil { return } return } func (msg *basicGetOk) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.DeliveryTag); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Redelivered = (bits&(1<<0) > 0) if msg.Exchange, err = readShortstr(r); err != nil { return } if msg.RoutingKey, err = readShortstr(r); err != nil { return } if err = binary.Read(r, binary.BigEndian, &msg.MessageCount); err != nil { return } return } type basicGetEmpty struct { reserved1 string } func (msg *basicGetEmpty) id() (uint16, uint16) { return 60, 72 } func (msg *basicGetEmpty) wait() bool { return true } func (msg *basicGetEmpty) write(w io.Writer) (err error) { if err = writeShortstr(w, msg.reserved1); err != nil { return } return } func (msg *basicGetEmpty) read(r io.Reader) (err error) { if msg.reserved1, err = readShortstr(r); err != nil { return } return } type basicAck struct { DeliveryTag uint64 Multiple bool } func (msg *basicAck) id() (uint16, uint16) { return 60, 80 } func (msg *basicAck) wait() bool { return false } func (msg *basicAck) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.DeliveryTag); err != nil { return } if msg.Multiple { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicAck) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.DeliveryTag); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Multiple = (bits&(1<<0) > 0) return } type basicReject struct { DeliveryTag uint64 Requeue bool } func (msg *basicReject) id() (uint16, uint16) { return 60, 90 } func (msg *basicReject) wait() bool { return false } func (msg *basicReject) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.DeliveryTag); err != nil { return } if msg.Requeue { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicReject) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.DeliveryTag); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Requeue = (bits&(1<<0) > 0) return } type basicRecoverAsync struct { Requeue bool } func (msg *basicRecoverAsync) id() (uint16, uint16) { return 60, 100 } func (msg *basicRecoverAsync) wait() bool { return false } func (msg *basicRecoverAsync) write(w io.Writer) (err error) { var bits byte if msg.Requeue { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicRecoverAsync) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Requeue = (bits&(1<<0) > 0) return } type basicRecover struct { Requeue bool } func (msg *basicRecover) id() (uint16, uint16) { return 60, 110 } func (msg *basicRecover) wait() bool { return true } func (msg *basicRecover) write(w io.Writer) (err error) { var bits byte if msg.Requeue { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicRecover) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Requeue = (bits&(1<<0) > 0) return } type basicRecoverOk struct { } func (msg *basicRecoverOk) id() (uint16, uint16) { return 60, 111 } func (msg *basicRecoverOk) wait() bool { return true } func (msg *basicRecoverOk) write(w io.Writer) (err error) { return } func (msg *basicRecoverOk) read(r io.Reader) (err error) { return } type basicNack struct { DeliveryTag uint64 Multiple bool Requeue bool } func (msg *basicNack) id() (uint16, uint16) { return 60, 120 } func (msg *basicNack) wait() bool { return false } func (msg *basicNack) write(w io.Writer) (err error) { var bits byte if err = binary.Write(w, binary.BigEndian, msg.DeliveryTag); err != nil { return } if msg.Multiple { bits |= 1 << 0 } if msg.Requeue { bits |= 1 << 1 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *basicNack) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &msg.DeliveryTag); err != nil { return } if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Multiple = (bits&(1<<0) > 0) msg.Requeue = (bits&(1<<1) > 0) return } type txSelect struct { } func (msg *txSelect) id() (uint16, uint16) { return 90, 10 } func (msg *txSelect) wait() bool { return true } func (msg *txSelect) write(w io.Writer) (err error) { return } func (msg *txSelect) read(r io.Reader) (err error) { return } type txSelectOk struct { } func (msg *txSelectOk) id() (uint16, uint16) { return 90, 11 } func (msg *txSelectOk) wait() bool { return true } func (msg *txSelectOk) write(w io.Writer) (err error) { return } func (msg *txSelectOk) read(r io.Reader) (err error) { return } type txCommit struct { } func (msg *txCommit) id() (uint16, uint16) { return 90, 20 } func (msg *txCommit) wait() bool { return true } func (msg *txCommit) write(w io.Writer) (err error) { return } func (msg *txCommit) read(r io.Reader) (err error) { return } type txCommitOk struct { } func (msg *txCommitOk) id() (uint16, uint16) { return 90, 21 } func (msg *txCommitOk) wait() bool { return true } func (msg *txCommitOk) write(w io.Writer) (err error) { return } func (msg *txCommitOk) read(r io.Reader) (err error) { return } type txRollback struct { } func (msg *txRollback) id() (uint16, uint16) { return 90, 30 } func (msg *txRollback) wait() bool { return true } func (msg *txRollback) write(w io.Writer) (err error) { return } func (msg *txRollback) read(r io.Reader) (err error) { return } type txRollbackOk struct { } func (msg *txRollbackOk) id() (uint16, uint16) { return 90, 31 } func (msg *txRollbackOk) wait() bool { return true } func (msg *txRollbackOk) write(w io.Writer) (err error) { return } func (msg *txRollbackOk) read(r io.Reader) (err error) { return } type confirmSelect struct { Nowait bool } func (msg *confirmSelect) id() (uint16, uint16) { return 85, 10 } func (msg *confirmSelect) wait() bool { return true } func (msg *confirmSelect) write(w io.Writer) (err error) { var bits byte if msg.Nowait { bits |= 1 << 0 } if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } return } func (msg *confirmSelect) read(r io.Reader) (err error) { var bits byte if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } msg.Nowait = (bits&(1<<0) > 0) return } type confirmSelectOk struct { } func (msg *confirmSelectOk) id() (uint16, uint16) { return 85, 11 } func (msg *confirmSelectOk) wait() bool { return true } func (msg *confirmSelectOk) write(w io.Writer) (err error) { return } func (msg *confirmSelectOk) read(r io.Reader) (err error) { return } func (r *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) { mf := &methodFrame{ ChannelId: channel, } if err = binary.Read(r.r, binary.BigEndian, &mf.ClassId); err != nil { return } if err = binary.Read(r.r, binary.BigEndian, &mf.MethodId); err != nil { return } switch mf.ClassId { case 10: // connection switch mf.MethodId { case 10: // connection start // fmt.Println("NextMethod: class:10 method:10") method := &connectionStart{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // connection start-ok // fmt.Println("NextMethod: class:10 method:11") method := &connectionStartOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 20: // connection secure // fmt.Println("NextMethod: class:10 method:20") method := &connectionSecure{} if err = method.read(r.r); err != nil { return } mf.Method = method case 21: // connection secure-ok // fmt.Println("NextMethod: class:10 method:21") method := &connectionSecureOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 30: // connection tune // fmt.Println("NextMethod: class:10 method:30") method := &connectionTune{} if err = method.read(r.r); err != nil { return } mf.Method = method case 31: // connection tune-ok // fmt.Println("NextMethod: class:10 method:31") method := &connectionTuneOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 40: // connection open // fmt.Println("NextMethod: class:10 method:40") method := &connectionOpen{} if err = method.read(r.r); err != nil { return } mf.Method = method case 41: // connection open-ok // fmt.Println("NextMethod: class:10 method:41") method := &connectionOpenOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 50: // connection close // fmt.Println("NextMethod: class:10 method:50") method := &connectionClose{} if err = method.read(r.r); err != nil { return } mf.Method = method case 51: // connection close-ok // fmt.Println("NextMethod: class:10 method:51") method := &connectionCloseOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 60: // connection blocked // fmt.Println("NextMethod: class:10 method:60") method := &connectionBlocked{} if err = method.read(r.r); err != nil { return } mf.Method = method case 61: // connection unblocked // fmt.Println("NextMethod: class:10 method:61") method := &connectionUnblocked{} if err = method.read(r.r); err != nil { return } mf.Method = method case 70: // connection update-secret // fmt.Println("NextMethod: class:10 method:70") method := &connectionUpdateSecret{} if err = method.read(r.r); err != nil { return } mf.Method = method case 71: // connection update-secret-ok // fmt.Println("NextMethod: class:10 method:71") method := &connectionUpdateSecretOk{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } case 20: // channel switch mf.MethodId { case 10: // channel open // fmt.Println("NextMethod: class:20 method:10") method := &channelOpen{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // channel open-ok // fmt.Println("NextMethod: class:20 method:11") method := &channelOpenOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 20: // channel flow // fmt.Println("NextMethod: class:20 method:20") method := &channelFlow{} if err = method.read(r.r); err != nil { return } mf.Method = method case 21: // channel flow-ok // fmt.Println("NextMethod: class:20 method:21") method := &channelFlowOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 40: // channel close // fmt.Println("NextMethod: class:20 method:40") method := &channelClose{} if err = method.read(r.r); err != nil { return } mf.Method = method case 41: // channel close-ok // fmt.Println("NextMethod: class:20 method:41") method := &channelCloseOk{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } case 40: // exchange switch mf.MethodId { case 10: // exchange declare // fmt.Println("NextMethod: class:40 method:10") method := &exchangeDeclare{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // exchange declare-ok // fmt.Println("NextMethod: class:40 method:11") method := &exchangeDeclareOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 20: // exchange delete // fmt.Println("NextMethod: class:40 method:20") method := &exchangeDelete{} if err = method.read(r.r); err != nil { return } mf.Method = method case 21: // exchange delete-ok // fmt.Println("NextMethod: class:40 method:21") method := &exchangeDeleteOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 30: // exchange bind // fmt.Println("NextMethod: class:40 method:30") method := &exchangeBind{} if err = method.read(r.r); err != nil { return } mf.Method = method case 31: // exchange bind-ok // fmt.Println("NextMethod: class:40 method:31") method := &exchangeBindOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 40: // exchange unbind // fmt.Println("NextMethod: class:40 method:40") method := &exchangeUnbind{} if err = method.read(r.r); err != nil { return } mf.Method = method case 51: // exchange unbind-ok // fmt.Println("NextMethod: class:40 method:51") method := &exchangeUnbindOk{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } case 50: // queue switch mf.MethodId { case 10: // queue declare // fmt.Println("NextMethod: class:50 method:10") method := &queueDeclare{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // queue declare-ok // fmt.Println("NextMethod: class:50 method:11") method := &queueDeclareOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 20: // queue bind // fmt.Println("NextMethod: class:50 method:20") method := &queueBind{} if err = method.read(r.r); err != nil { return } mf.Method = method case 21: // queue bind-ok // fmt.Println("NextMethod: class:50 method:21") method := &queueBindOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 50: // queue unbind // fmt.Println("NextMethod: class:50 method:50") method := &queueUnbind{} if err = method.read(r.r); err != nil { return } mf.Method = method case 51: // queue unbind-ok // fmt.Println("NextMethod: class:50 method:51") method := &queueUnbindOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 30: // queue purge // fmt.Println("NextMethod: class:50 method:30") method := &queuePurge{} if err = method.read(r.r); err != nil { return } mf.Method = method case 31: // queue purge-ok // fmt.Println("NextMethod: class:50 method:31") method := &queuePurgeOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 40: // queue delete // fmt.Println("NextMethod: class:50 method:40") method := &queueDelete{} if err = method.read(r.r); err != nil { return } mf.Method = method case 41: // queue delete-ok // fmt.Println("NextMethod: class:50 method:41") method := &queueDeleteOk{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } case 60: // basic switch mf.MethodId { case 10: // basic qos // fmt.Println("NextMethod: class:60 method:10") method := &basicQos{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // basic qos-ok // fmt.Println("NextMethod: class:60 method:11") method := &basicQosOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 20: // basic consume // fmt.Println("NextMethod: class:60 method:20") method := &basicConsume{} if err = method.read(r.r); err != nil { return } mf.Method = method case 21: // basic consume-ok // fmt.Println("NextMethod: class:60 method:21") method := &basicConsumeOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 30: // basic cancel // fmt.Println("NextMethod: class:60 method:30") method := &basicCancel{} if err = method.read(r.r); err != nil { return } mf.Method = method case 31: // basic cancel-ok // fmt.Println("NextMethod: class:60 method:31") method := &basicCancelOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 40: // basic publish // fmt.Println("NextMethod: class:60 method:40") method := &basicPublish{} if err = method.read(r.r); err != nil { return } mf.Method = method case 50: // basic return // fmt.Println("NextMethod: class:60 method:50") method := &basicReturn{} if err = method.read(r.r); err != nil { return } mf.Method = method case 60: // basic deliver // fmt.Println("NextMethod: class:60 method:60") method := &basicDeliver{} if err = method.read(r.r); err != nil { return } mf.Method = method case 70: // basic get // fmt.Println("NextMethod: class:60 method:70") method := &basicGet{} if err = method.read(r.r); err != nil { return } mf.Method = method case 71: // basic get-ok // fmt.Println("NextMethod: class:60 method:71") method := &basicGetOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 72: // basic get-empty // fmt.Println("NextMethod: class:60 method:72") method := &basicGetEmpty{} if err = method.read(r.r); err != nil { return } mf.Method = method case 80: // basic ack // fmt.Println("NextMethod: class:60 method:80") method := &basicAck{} if err = method.read(r.r); err != nil { return } mf.Method = method case 90: // basic reject // fmt.Println("NextMethod: class:60 method:90") method := &basicReject{} if err = method.read(r.r); err != nil { return } mf.Method = method case 100: // basic recover-async // fmt.Println("NextMethod: class:60 method:100") method := &basicRecoverAsync{} if err = method.read(r.r); err != nil { return } mf.Method = method case 110: // basic recover // fmt.Println("NextMethod: class:60 method:110") method := &basicRecover{} if err = method.read(r.r); err != nil { return } mf.Method = method case 111: // basic recover-ok // fmt.Println("NextMethod: class:60 method:111") method := &basicRecoverOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 120: // basic nack // fmt.Println("NextMethod: class:60 method:120") method := &basicNack{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } case 90: // tx switch mf.MethodId { case 10: // tx select // fmt.Println("NextMethod: class:90 method:10") method := &txSelect{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // tx select-ok // fmt.Println("NextMethod: class:90 method:11") method := &txSelectOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 20: // tx commit // fmt.Println("NextMethod: class:90 method:20") method := &txCommit{} if err = method.read(r.r); err != nil { return } mf.Method = method case 21: // tx commit-ok // fmt.Println("NextMethod: class:90 method:21") method := &txCommitOk{} if err = method.read(r.r); err != nil { return } mf.Method = method case 30: // tx rollback // fmt.Println("NextMethod: class:90 method:30") method := &txRollback{} if err = method.read(r.r); err != nil { return } mf.Method = method case 31: // tx rollback-ok // fmt.Println("NextMethod: class:90 method:31") method := &txRollbackOk{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } case 85: // confirm switch mf.MethodId { case 10: // confirm select // fmt.Println("NextMethod: class:85 method:10") method := &confirmSelect{} if err = method.read(r.r); err != nil { return } mf.Method = method case 11: // confirm select-ok // fmt.Println("NextMethod: class:85 method:11") method := &confirmSelectOk{} if err = method.read(r.r); err != nil { return } mf.Method = method default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } default: return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId) } return mf, nil } golang-github-rabbitmq-amqp091-go-1.10.0/tls_test.go000066400000000000000000000234631462444370000221140ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "crypto/tls" "crypto/x509" "errors" "fmt" "net" "testing" "time" ) func tlsServerConfig(t *testing.T) *tls.Config { cfg := new(tls.Config) cfg.ClientCAs = x509.NewCertPool() cfg.ClientCAs.AppendCertsFromPEM([]byte(caCert)) cert, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) if err != nil { t.Fatalf("TLS server config error: %+v", err) } cfg.Certificates = append(cfg.Certificates, cert) cfg.ClientAuth = tls.RequireAndVerifyClientCert return cfg } func tlsClientConfig(t *testing.T) *tls.Config { cfg := new(tls.Config) cfg.RootCAs = x509.NewCertPool() cfg.RootCAs.AppendCertsFromPEM([]byte(caCert)) cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) if err != nil { t.Fatalf("TLS client config error: %+v", err) } cfg.Certificates = append(cfg.Certificates, cert) return cfg } type tlsServer struct { net.Listener URL string Config *tls.Config Sessions chan *server } // Captures the header for each accepted connection func (s *tlsServer) Serve(t *testing.T) { for { c, err := s.Accept() if err != nil { return } s.Sessions <- newServer(t, c, c) } } func startTLSServer(t *testing.T, cfg *tls.Config) tlsServer { l, err := tls.Listen("tcp", "127.0.0.1:3456", cfg) if err != nil { t.Fatalf("TLS server Listen error: %+v", err) } s := tlsServer{ Listener: l, Config: cfg, URL: fmt.Sprintf("amqps://%s/", l.Addr().String()), Sessions: make(chan *server), } go s.Serve(t) return s } func TestTlsConfigFromUriPushdownServerNameIndication(t *testing.T) { uri := "amqps://user:pass@example.com:5671?server_name_indication=another-hostname.com" parsedUri, err := ParseURI(uri) if err != nil { t.Fatalf("expected to parse URI successfully, got error: %s", err) } tlsConf, err := tlsConfigFromURI(parsedUri) if err != nil { t.Fatalf("expected tlsConfigFromURI to succeed, got error: %s", err) } const expectedServerName = "another-hostname.com" if tlsConf.ServerName != expectedServerName { t.Fatalf("expected tlsConf server name to equal Uri servername: want %s, got %s", expectedServerName, tlsConf.ServerName) } } // Tests opening a connection of a TLS enabled socket server func TestTLSHandshake(t *testing.T) { srv := startTLSServer(t, tlsServerConfig(t)) defer srv.Close() success := make(chan bool) errs := make(chan error, 3) go func() { select { case <-time.After(10 * time.Millisecond): errs <- errors.New("server timeout waiting for TLS handshake from client") case session := <-srv.Sessions: session.connectionOpen() session.connectionClose() session.S.Close() } }() go func() { c, err := DialTLS(srv.URL, tlsClientConfig(t)) if err != nil { errs <- fmt.Errorf("expected to open a TLS connection, got err: %v", err) return } defer c.Close() if st := c.ConnectionState(); !st.HandshakeComplete { errs <- fmt.Errorf("expected to complete a TLS handshake, TLS connection state: %+v", st) } success <- true }() select { case err := <-errs: t.Fatalf("TLS server saw error: %+v", err) case <-success: return } } const caCert = ` -----BEGIN CERTIFICATE----- MIIC0TCCAbmgAwIBAgIUW418AvO6YD2WD5X/coo9geXvauEwDQYJKoZIhvcNAQEL BQAwEzERMA8GA1UEAwwITXlUZXN0Q0EwHhcNMjIwNDA0MDMxNjIxWhcNMzIwNDAx MDMxNjIxWjATMREwDwYDVQQDDAhNeVRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAOU1K7mS1z7U9mdDaxpmaj/JwDjtIquqGv1IE4hi9RU/jkse 2GYUuOf5Viu7LMaKCS4I5qwyXT/yPUviw5b1bjEviNURCmQScvXKJg9/P31JlZPc RBV6Hnrku7nZeJcDfiAo8YDA3QPdr1uhTjjnIo4x2SYJfKDgBvsLfxXETUkFsECQ uuBLl3VL8jeU8y9wAg0y4gmcOZUyKPlcr9dsmDKftzkas+Zd4JR6e327U+VvVxv4 hDMQGxx/yirKxMwH2+pwkYOWwOTb1FGi8iVjdAwgjlKsLqUR9eSN2AXW/v41E79h Sb9uEGeIfscGOrgVfVtVRZWNPDzM1/DpYSpsP2ECAwEAAaMdMBswDAYDVR0TBAUw AwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAJWlbsQqPQPX/lOF CUnta6OHpt6OlPnN1lUhvbNQVpt0KdmuHQKwSyGiq6fVDt//2RcxPwCYO9AKUZZn /T48+4haGFEKLRVQrDX7/CQoGbHwWbbgcNpJiQ3XPhGvUrLy6VoahAYK81D23XT0 b4TobCYY+8ny5qhyQgBZ7Jme2jDk0MXt8yjZFGcyA0fRy54ql1AxIXdx5/yvq2CI nLGEZQLsYhz/r+4gooIkLOIFD2JKoW9p56T52XiRxmz0tAs4WFW38Z6VrzB48Lpq dBH0sqzECTVQmX8er1taHa+Tg29tuXyqNkBn0tB9qq7G24SDaQFqpW7+J1kNnAY2 KEcj4Ic= -----END CERTIFICATE----- ` const serverCert = ` -----BEGIN CERTIFICATE----- MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhNeVRl c3RDQTAeFw0yMjA0MDQwMzE2MjFaFw0zMjA0MDEwMzE2MjFaMCUxEjAQBgNVBAMM CTEyNy4wLjAuMTEPMA0GA1UECgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEArH6lT2bGHiGLaWh+eXLXeAcbXq2bGDyeS1zsRGT/+D6YBzFo 7FAUMbN8iJtjWNBlRRdVyiUJn5TUYHrXdCeoAs5JrdzNefax+3toDu6I+nJG4dO3 nbZ9zo8q7uBZtCi/Oph28CtwQxMjNuf3pHlSSKZG5/br4w6HhgSnuLBxyxpr3CrU 2kdHZGIGjX4sEGJ/aDCKIUem/jEXp5i1n13+p5QmPfZc6KX2+sUmN6todqlZpsEV WZAYLXshHJvViLMTrCJQ3KfqoF7o4PrwQJoQ3jlNct2ZF68H0Iz/bT/VQfPS+4CL t71xHhU/mYn+kIt1w0APhCMMkYPOhijmGlDAbwIDAQABo0AwPjAJBgNVHRMEAjAA MAsGA1UdDwQEAwIFIDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/ AAABMA0GCSqGSIb3DQEBCwUAA4IBAQA8dM07fsOfxU7oVWQN3Aey0g8/wc2eL0ZY oxxvKJqZjOSamNEiwb+lTTmUGg78wiIpFK4ChpPbMpURcRVrxTJuWRg4qagFWn18 RGNqNVnucFSOPe3e+zZqnjWc22PDbBt4B3agdpJsHq4hpdOdJreAh/7MMrWyFMZI PZ5WFjThFpeQw+vArYUq6BMD3H86G++gfWlSv0yPxWG+Q44+tfZSyiiM6k0+p3nw 1k0eBIA+BibcZ+dQu0m2Vq7XKxfOeN67LY8aElGNSRmH/YAyo5mSrrvzbbZKpr4/ cn4f79jDcBEtbMeCav0eHXzSwWWpteZvdLOm6rxnHd3jngRPqRj+ -----END CERTIFICATE----- ` const serverKey = ` -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEArH6lT2bGHiGLaWh+eXLXeAcbXq2bGDyeS1zsRGT/+D6YBzFo 7FAUMbN8iJtjWNBlRRdVyiUJn5TUYHrXdCeoAs5JrdzNefax+3toDu6I+nJG4dO3 nbZ9zo8q7uBZtCi/Oph28CtwQxMjNuf3pHlSSKZG5/br4w6HhgSnuLBxyxpr3CrU 2kdHZGIGjX4sEGJ/aDCKIUem/jEXp5i1n13+p5QmPfZc6KX2+sUmN6todqlZpsEV WZAYLXshHJvViLMTrCJQ3KfqoF7o4PrwQJoQ3jlNct2ZF68H0Iz/bT/VQfPS+4CL t71xHhU/mYn+kIt1w0APhCMMkYPOhijmGlDAbwIDAQABAoIBAQCFfEw5MfNHBfZ4 z+Bv46tSu00262oGS4LEF1jPZMmhNe84QchMd3vpKljI7lbnN/3mhbRiBl94Gxhu wSFSRg4CfdkOrrxkEcCSOGHCjF18UksAH3MMnVimLKywxvUkMhQqKCqCmVr6zSiH KOO/aBOBHQvqHm9U+r1tvNR+XCzzWmz0OavKquXhdTbLyHWeR7rS4mn53Up0hQIe la8PzWpw+mcNz00UbeeK84tUro4+wGV6aFHh23F+RqpXqW8VNnBS0zVtqYc7x7a8 WGcxlLt1hWeiFgEJUuui2i1YxyHDb3IyhGckVG+lSzTpgjl9Pisl7jjlVBRAkiWw fRJfWuyBAoGBAOERpoRSpXCBum3T7ZwcD3pWHgNY1za9zqsgZayumlhqvxmia77o HvXyroj+8DTUZ+NiKn0HnF8Hni/2fhAWs9XPlELM9BENQP5kcJzcBSalUOa23C6V Iba30BB8I1v8AhYLdCFdpio+aQnd1S4VH4sTRoQjsspOSesxDWa1snJ/AoGBAMQz VadEoBR8ZHsZjks7JaU2G1h2tdU3hfynD8DPq0SFnbS3NhVphmYAdfbOrea9ilmQ AyO/TJD6xmK7IELWYNtS4DCqAaQKxfOsS8wF97lJ1/XKDNQND8PfbVwzLF8NcbMT G15Vq9dkJpiGBe0YYxdjtPtwM+FSriK37YIyidoRAoGAQqGBFKeLBvXBBYa6T38X LfaUyBTjEfe7WXor36WJWCeyD5rAHzKFB/ciqLgg0OMZJn4HaiB4sMGGmVh2FblC 4Eel8ujOUMYFucpudGHGvJwwiT0VjkzkQD3GwTqfFTpUO8aESOR6rwLvAdbEp/Hk 9r1sIO6Ynb/zrkdFWmTsQW0CgYBvXrhjH2hC2K1s1v/XonZnBoSVPaVPp5nN5cLi br9IQRRZLZpsox7gLajIdV9vV+39kurFUuSSc1dDWfchGXGXbb7GwOn3hQoCnK3V 3RlWOx10bsHDaLqnM99u87lfJ1GAFft2G+lUdYwXDhS1Fh/Beh6Uj4dTgsxH9uHC AxAPEQKBgCkOBQBfPg4mZdYKAaNoRvNhJr1XdruQnFsBCdvCm2x8DAkNGme7Ax8H QYB+jH1eC/uInPUnW3kh7KPqgdGVNbYeZGT1FwuwoJEubR6+rBiRm3HsjBJlR+Tv RK8SwjKS0ZkCb8eujkyBakXdF2tAY9Du8HODID+CuuxhGD1YsF2q -----END RSA PRIVATE KEY----- ` const clientCert = ` -----BEGIN CERTIFICATE----- MIIC4jCCAcqgAwIBAgIBAjANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhNeVRl c3RDQTAeFw0yMjA0MDQwMzE2MjFaFw0zMjA0MDEwMzE2MjFaMCUxEjAQBgNVBAMM CTEyNy4wLjAuMTEPMA0GA1UECgwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAtL3BQX/ubTjO+m4WMDe7OiPquHkyE/G8zHNoFXH/pjcf0iYq lBWD8+bmoPxzOy2RtRawZIR5ZtHkPH8YZues3nwnnUPYQfUGlqlSc4TOyO7vn0Eq ufJZCVF3+DWTHaV41K20/cpLuCs308KKVt7XosDmj4iwc1t7NcjS8TFI4/SpBIYg TzII+RzFXhT3FFXuo5ZkOoti0IyUXxALX/Ba+ubR9vrJC4BS7VtoSQQ/uRr5x9MB /yZWUDTcOOJNjh1mKe+9vEFAjCgRFOwaZzph38Av/L2e4uPTsjTX6MsMQZK1nl5h 03HoQZ3FRJLVKTHVszihIODgNQ5ReM3YBxrqiwIDAQABoy8wLTAJBgNVHRMEAjAA MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsF AAOCAQEAxMJjHzZ6BiSwzieH+Ujz70fU+qa4xdoY8tt32gnBJt9d+dsgjaAIeGZR veAA3Ak0aM+1zoCGc2792oEv7gOz946brbkJLNC5e43ZlDFHYbj1OYcMtxL4CCyW 2QImY/CgoXfVkth1SSRl+wGEwMlS8XuriczwU4Sl/faJHE+mwwJNlNBNKjv1GoUb 0T3lnfuUNoXzH1SstR/6+/eD0CNvTsTvkC9eXJULt2V93jTFNGlM6KcVvF5E7BDm iSYtaQ8vtPF64LKM+dF3ymzJDoT6EyYU6t1X5RPQ6fQTRbCvZ74NsWoqWzA4VB20 rgNKRkWBHGxKqNMrF1YQvm2hWxw6pA== -----END CERTIFICATE----- ` const clientKey = ` -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAtL3BQX/ubTjO+m4WMDe7OiPquHkyE/G8zHNoFXH/pjcf0iYq lBWD8+bmoPxzOy2RtRawZIR5ZtHkPH8YZues3nwnnUPYQfUGlqlSc4TOyO7vn0Eq ufJZCVF3+DWTHaV41K20/cpLuCs308KKVt7XosDmj4iwc1t7NcjS8TFI4/SpBIYg TzII+RzFXhT3FFXuo5ZkOoti0IyUXxALX/Ba+ubR9vrJC4BS7VtoSQQ/uRr5x9MB /yZWUDTcOOJNjh1mKe+9vEFAjCgRFOwaZzph38Av/L2e4uPTsjTX6MsMQZK1nl5h 03HoQZ3FRJLVKTHVszihIODgNQ5ReM3YBxrqiwIDAQABAoIBAQCmfc2R2pj1P8lZ 4yLJU+1CB2fmeq3otVvnMcAFUTfgExNa8BF0y8T7Xg3A6gvzzWxVVgsy7N0wG9SU 7ba6xFr3r4KGWcLSLzXcfykWhJY/fep51vvWwinGbaeHm0JjotQFheYdisXpZtZM WP46O5iDshIw0gdInFKJHu9BgtbUNDRAAI9xsmDcITSXf7ZblH5CCLJ6QLFn1Olg O11k6ABYmIxKSXfLylFDzUxfnGhXkrv8/sQwj3B7XxBYVoAF24uw2ufZ+qIlcxUy m/T/IU1lOLNmfWwDk2+t7zslOpXAEZtrni7u2GF54wIbMr/Z0unGC1B0AUXDwp8y MNODQdcBAoGBAOe+p5neqgoUL8kBlRvp/dp5kIsVPNBN7+vbVYVzeIEBNixfYe9S 0dixQHiJr29sWn/4yC2KkPQewyDVBf7iNXlcXfkjbEjU0cZaMJao4sKki/N/GF64 IpC8HRM1BSvaSnrJZtZ6aWljpvWRa8bGRdQWb397ufChNpstYEALMMZLAoGBAMeo hGZJcWIEtl1oJIHsKFKf7jwftFidzyy2ShVLX52n3HkbCYMdwg5DIjQ6Vj9GMy8E AvrcQduhLw4otdPL+X270xX8WKbxRAvlDnGfmOX1+z/lJy2OLi/HpZeUyM6Oe/Uy ijaGTKOqRSi9xpJOdDR17e/fcYKzvPejNxZcDMTBAoGAeH/VLBfweI8oja8J9lrE CX7eXsNrPLDZyNziaiKxjPqxTX9HMCbzQGZiLIsDMr+3iwU0KSH830LDmWXK2U6M GY+iuXHm0zP949Jvo1crmaPvtWvnoxDBwFpgD+Woy7WUtqXUmD9MYmVToiq8TL45 /t6vmS0fcPSSrTt56bMn6GMCgYEAvRDLL8FkaRllR9aSm6VyGavxAWZUdYYa5ZBJ XxjdFoIauWPtAghv9umDvklv2sMzPNZjrAJfKwfbc2EBrep9+56dKTipCo11jn39 y4MCWuEwZzUsgGsfOYepO31dGpy6rVqKn09Vy7Y1f3sWSv2X9QWnp3rEFqz1yNr6 E2ZfgQECgYA943tAbNFmAZW3WdS82cpM+HXOkKuYkPfFsBq/eoKoi1p0WLtueQZ3 cC39vheaWdRJPI/dKjhgifY6pBcVLzGIf9gD6VKVbIrcx6U3uMULEQb3GqvdIOtU d5WpU0bwFMa+vYfrlAjngXlDW/tGqK8ietb6n+xW15M1w4mrEFcAng== -----END RSA PRIVATE KEY----- ` golang-github-rabbitmq-amqp091-go-1.10.0/types.go000066400000000000000000000507341462444370000214200ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "fmt" "io" "time" ) // DefaultExchange is the default direct exchange that binds every queue by its // name. Applications can route to a queue using the queue name as routing key. const DefaultExchange = "" // Constants for standard AMQP 0-9-1 exchange types. const ( ExchangeDirect = "direct" ExchangeFanout = "fanout" ExchangeTopic = "topic" ExchangeHeaders = "headers" ) var ( // ErrClosed is returned when the channel or connection is not open ErrClosed = &Error{Code: ChannelError, Reason: "channel/connection is not open"} // ErrChannelMax is returned when Connection.Channel has been called enough // times that all channel IDs have been exhausted in the client or the // server. ErrChannelMax = &Error{Code: ChannelError, Reason: "channel id space exhausted"} // ErrSASL is returned from Dial when the authentication mechanism could not // be negotiated. ErrSASL = &Error{Code: AccessRefused, Reason: "SASL could not negotiate a shared mechanism"} // ErrCredentials is returned when the authenticated client is not authorized // to any vhost. ErrCredentials = &Error{Code: AccessRefused, Reason: "username or password not allowed"} // ErrVhost is returned when the authenticated user is not permitted to // access the requested Vhost. ErrVhost = &Error{Code: AccessRefused, Reason: "no access to this vhost"} // ErrSyntax is hard protocol error, indicating an unsupported protocol, // implementation or encoding. ErrSyntax = &Error{Code: SyntaxError, Reason: "invalid field or value inside of a frame"} // ErrFrame is returned when the protocol frame cannot be read from the // server, indicating an unsupported protocol or unsupported frame type. ErrFrame = &Error{Code: FrameError, Reason: "frame could not be parsed"} // ErrCommandInvalid is returned when the server sends an unexpected response // to this requested message type. This indicates a bug in this client. ErrCommandInvalid = &Error{Code: CommandInvalid, Reason: "unexpected command received"} // ErrUnexpectedFrame is returned when something other than a method or // heartbeat frame is delivered to the Connection, indicating a bug in the // client. ErrUnexpectedFrame = &Error{Code: UnexpectedFrame, Reason: "unexpected frame received"} // ErrFieldType is returned when writing a message containing a Go type unsupported by AMQP. ErrFieldType = &Error{Code: SyntaxError, Reason: "unsupported table field type"} ) // internal errors used inside the library var ( errInvalidTypeAssertion = &Error{Code: InternalError, Reason: "type assertion unsuccessful", Server: false, Recover: true} ) // Error captures the code and reason a channel or connection has been closed // by the server. type Error struct { Code int // constant code from the specification Reason string // description of the error Server bool // true when initiated from the server, false when from this library Recover bool // true when this error can be recovered by retrying later or with different parameters } func newError(code uint16, text string) *Error { return &Error{ Code: int(code), Reason: text, Recover: isSoftExceptionCode(int(code)), Server: true, } } func (e Error) Error() string { return fmt.Sprintf("Exception (%d) Reason: %q", e.Code, e.Reason) } // Used by header frames to capture routing and header information type properties struct { ContentType string // MIME content type ContentEncoding string // MIME content encoding Headers Table // Application or header exchange table DeliveryMode uint8 // queue implementation use - Transient (1) or Persistent (2) Priority uint8 // queue implementation use - 0 to 9 CorrelationId string // application use - correlation identifier ReplyTo string // application use - address to to reply to (ex: RPC) Expiration string // implementation use - message expiration spec MessageId string // application use - message identifier Timestamp time.Time // application use - message timestamp Type string // application use - message type name UserId string // application use - creating user id AppId string // application use - creating application reserved1 string // was cluster-id - process for buffer consumption } // DeliveryMode. Transient means higher throughput but messages will not be // restored on broker restart. The delivery mode of publishings is unrelated // to the durability of the queues they reside on. Transient messages will // not be restored to durable queues, persistent messages will be restored to // durable queues and lost on non-durable queues during server restart. // // This remains typed as uint8 to match Publishing.DeliveryMode. Other // delivery modes specific to custom queue implementations are not enumerated // here. const ( Transient uint8 = 1 Persistent uint8 = 2 ) // The property flags are an array of bits that indicate the presence or // absence of each property value in sequence. The bits are ordered from most // high to low - bit 15 indicates the first property. const ( flagContentType = 0x8000 flagContentEncoding = 0x4000 flagHeaders = 0x2000 flagDeliveryMode = 0x1000 flagPriority = 0x0800 flagCorrelationId = 0x0400 flagReplyTo = 0x0200 flagExpiration = 0x0100 flagMessageId = 0x0080 flagTimestamp = 0x0040 flagType = 0x0020 flagUserId = 0x0010 flagAppId = 0x0008 flagReserved1 = 0x0004 ) // Expiration. These constants can be used to set a messages expiration TTL. // They should be viewed as a clarification of the expiration functionality in // messages and their usage is not enforced by this pkg. // // The server requires a string value that is interpreted by the server as // milliseconds. If no value is set, which translates to the nil value of // string, the message will never expire by itself. This does not influence queue // configured TTL configurations. const ( NeverExpire string = "" // empty value means never expire ImmediatelyExpire string = "0" // 0 means immediately expire ) // Queue captures the current server state of the queue on the server returned // from Channel.QueueDeclare or Channel.QueueInspect. type Queue struct { Name string // server confirmed or generated name Messages int // count of messages not awaiting acknowledgment Consumers int // number of consumers receiving deliveries } // Publishing captures the client message sent to the server. The fields // outside of the Headers table included in this struct mirror the underlying // fields in the content frame. They use native types for convenience and // efficiency. type Publishing struct { // Application or exchange specific fields, // the headers exchange will inspect this field. Headers Table // Properties ContentType string // MIME content type ContentEncoding string // MIME content encoding DeliveryMode uint8 // Transient (0 or 1) or Persistent (2) Priority uint8 // 0 to 9 CorrelationId string // correlation identifier ReplyTo string // address to to reply to (ex: RPC) // Expiration represents the message TTL in milliseconds. A value of "0" // indicates that the message will immediately expire if the message arrives // at its destination and the message is not directly handled by a consumer // that currently has the capacatity to do so. If you wish the message to // not expire on its own, set this value to any ttl value, empty string or // use the corresponding constants NeverExpire and ImmediatelyExpire. This // does not influence queue configured TTL values. Expiration string MessageId string // message identifier Timestamp time.Time // message timestamp Type string // message type name UserId string // creating user id - ex: "guest" AppId string // creating application id // The application specific payload of the message Body []byte } // Blocking notifies the server's TCP flow control of the Connection. When a // server hits a memory or disk alarm it will block all connections until the // resources are reclaimed. Use NotifyBlock on the Connection to receive these // events. type Blocking struct { Active bool // TCP pushback active/inactive on server Reason string // Server reason for activation } // DeferredConfirmation represents a future publisher confirm for a message. It // allows users to directly correlate a publishing to a confirmation. These are // returned from PublishWithDeferredConfirm on Channels. type DeferredConfirmation struct { DeliveryTag uint64 done chan struct{} ack bool } // Confirmation notifies the acknowledgment or negative acknowledgement of a // publishing identified by its delivery tag. Use NotifyPublish on the Channel // to consume these events. type Confirmation struct { DeliveryTag uint64 // A 1 based counter of publishings from when the channel was put in Confirm mode Ack bool // True when the server successfully received the publishing } // Decimal matches the AMQP decimal type. Scale is the number of decimal // digits Scale == 2, Value == 12345, Decimal == 123.45 type Decimal struct { Scale uint8 Value int32 } // Most common queue argument keys in queue declaration. For a comprehensive list // of queue arguments, visit [RabbitMQ Queue docs]. // // [QueueTypeArg] queue argument is used to declare quorum and stream queues. // Accepted values are [QueueTypeClassic] (default), [QueueTypeQuorum] and // [QueueTypeStream]. [Quorum Queues] accept (almost) all queue arguments as their // Classic Queues counterparts. Check [feature comparison] docs for more // information. // // Queues can define their [max length] using [QueueMaxLenArg] and // [QueueMaxLenBytesArg] queue arguments. Overflow behaviour is set using // [QueueOverflowArg]. Accepted values are [QueueOverflowDropHead] (default), // [QueueOverflowRejectPublish] and [QueueOverflowRejectPublishDLX]. // // [Queue TTL] can be defined using [QueueTTLArg]. That is, the time-to-live for an // unused queue. [Queue Message TTL] can be defined using [QueueMessageTTLArg]. // This will set a time-to-live for messages in the queue. // // [Stream retention] can be configured using [StreamMaxLenBytesArg], to set the // maximum size of the stream. Please note that stream queues always keep, at // least, one segment. [Stream retention] can also be set using [StreamMaxAgeArg], // to set time-based retention. Values are string with unit suffix. Valid // suffixes are Y, M, D, h, m, s. E.g. "7D" for one week. The maximum segment // size can be set using [StreamMaxSegmentSizeBytesArg]. The default value is // 500_000_000 bytes ~= 500 megabytes // // Starting with RabbitMQ 3.12, consumer timeout can be configured as a queue // argument. This is the timeout for a consumer to acknowledge a message. The // value is the time in milliseconds. The timeout is evaluated periodically, // at one minute intervals. Values lower than one minute are not supported. // See the [consumer timeout] guide for more information. // // [Single Active Consumer] on quorum and classic queues can be configured // using [SingleActiveConsumerArg]. This argument expects a boolean value. It is // false by default. // // [RabbitMQ Queue docs]: https://rabbitmq.com/queues.html // [Stream retention]: https://rabbitmq.com/streams.html#retention // [max length]: https://rabbitmq.com/maxlength.html // [Queue TTL]: https://rabbitmq.com/ttl.html#queue-ttl // [Queue Message TTL]: https://rabbitmq.com/ttl.html#per-queue-message-ttl // [Quorum Queues]: https://rabbitmq.com/quorum-queues.html // [feature comparison]: https://rabbitmq.com/quorum-queues.html#feature-comparison // [consumer timeout]: https://rabbitmq.com/consumers.html#acknowledgement-timeout // [Single Active Consumer]: https://rabbitmq.com/consumers.html#single-active-consumer const ( QueueTypeArg = "x-queue-type" QueueMaxLenArg = "x-max-length" QueueMaxLenBytesArg = "x-max-length-bytes" StreamMaxLenBytesArg = "x-max-length-bytes" QueueOverflowArg = "x-overflow" QueueMessageTTLArg = "x-message-ttl" QueueTTLArg = "x-expires" StreamMaxAgeArg = "x-max-age" StreamMaxSegmentSizeBytesArg = "x-stream-max-segment-size-bytes" // QueueVersionArg declares the Classic Queue version to use. Expects an integer, either 1 or 2. QueueVersionArg = "x-queue-version" // ConsumerTimeoutArg is available in RabbitMQ 3.12+ as a queue argument. ConsumerTimeoutArg = "x-consumer-timeout" SingleActiveConsumerArg = "x-single-active-consumer" ) // Values for queue arguments. Use as values for queue arguments during queue declaration. // The following argument table will create a classic queue, with max length set to 100 messages, // and a queue TTL of 30 minutes. // // args := amqp.Table{ // amqp.QueueTypeArg: QueueTypeClassic, // amqp.QueueMaxLenArg: 100, // amqp.QueueTTLArg: 1800000, // } // // Refer to [Channel.QueueDeclare] for more examples. const ( QueueTypeClassic = "classic" QueueTypeQuorum = "quorum" QueueTypeStream = "stream" QueueOverflowDropHead = "drop-head" QueueOverflowRejectPublish = "reject-publish" QueueOverflowRejectPublishDLX = "reject-publish-dlx" ) // Table stores user supplied fields of the following types: // // bool // byte // int8 // float32 // float64 // int // int16 // int32 // int64 // nil // string // time.Time // amqp.Decimal // amqp.Table // []byte // []interface{} - containing above types // // Functions taking a table will immediately fail when the table contains a // value of an unsupported type. // // The caller must be specific in which precision of integer it wishes to // encode. // // Use a type assertion when reading values from a table for type conversion. // // RabbitMQ expects int32 for integer values. type Table map[string]interface{} func validateField(f interface{}) error { switch fv := f.(type) { case nil, bool, byte, int8, int, int16, int32, int64, float32, float64, string, []byte, Decimal, time.Time: return nil case []interface{}: for _, v := range fv { if err := validateField(v); err != nil { return fmt.Errorf("in array %s", err) } } return nil case Table: for k, v := range fv { if err := validateField(v); err != nil { return fmt.Errorf("table field %q %s", k, err) } } return nil } return fmt.Errorf("value %T not supported", f) } // Validate returns and error if any Go types in the table are incompatible with AMQP types. func (t Table) Validate() error { return validateField(t) } // Sets the connection name property. This property can be used in // amqp.Config to set a custom connection name during amqp.DialConfig(). This // can be helpful to identify specific connections in RabbitMQ, for debugging or // tracing purposes. func (t Table) SetClientConnectionName(connName string) { t["connection_name"] = connName } type message interface { id() (uint16, uint16) wait() bool read(io.Reader) error write(io.Writer) error } type messageWithContent interface { message getContent() (properties, []byte) setContent(properties, []byte) } /* The base interface implemented as: 2.3.5 frame Details All frames consist of a header (7 octets), a payload of arbitrary size, and a 'frame-end' octet that detects malformed frames: 0 1 3 7 size+7 size+8 +------+---------+-------------+ +------------+ +-----------+ | type | channel | size | | payload | | frame-end | +------+---------+-------------+ +------------+ +-----------+ octet short long size octets octet To read a frame, we: 1. Read the header and check the frame type and channel. 2. Depending on the frame type, we read the payload and process it. 3. Read the frame end octet. In realistic implementations where performance is a concern, we would use “read-ahead buffering” or “gathering reads” to avoid doing three separate system calls to read a frame. */ type frame interface { write(io.Writer) error channel() uint16 } /* Perform any updates on the channel immediately after the frame is decoded while the connection mutex is held. */ func updateChannel(f frame, channel *Channel) { if mf, isMethodFrame := f.(*methodFrame); isMethodFrame { if _, isChannelClose := mf.Method.(*channelClose); isChannelClose { channel.setClosed() } } } type reader struct { r io.Reader } type writer struct { w io.Writer } // Implements the frame interface for Connection RPC type protocolHeader struct{} func (protocolHeader) write(w io.Writer) error { _, err := w.Write([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1}) return err } func (protocolHeader) channel() uint16 { panic("only valid as initial handshake") } /* Method frames carry the high-level protocol commands (which we call "methods"). One method frame carries one command. The method frame payload has this format: 0 2 4 +----------+-----------+-------------- - - | class-id | method-id | arguments... +----------+-----------+-------------- - - short short ... To process a method frame, we: 1. Read the method frame payload. 2. Unpack it into a structure. A given method always has the same structure, so we can unpack the method rapidly. 3. Check that the method is allowed in the current context. 4. Check that the method arguments are valid. 5. Execute the method. Method frame bodies are constructed as a list of AMQP data fields (bits, integers, strings and string tables). The marshalling code is trivially generated directly from the protocol specifications, and can be very rapid. */ type methodFrame struct { ChannelId uint16 ClassId uint16 MethodId uint16 Method message } func (f *methodFrame) channel() uint16 { return f.ChannelId } /* Heartbeating is a technique designed to undo one of TCP/IP's features, namely its ability to recover from a broken physical connection by closing only after a quite long time-out. In some scenarios we need to know very rapidly if a peer is disconnected or not responding for other reasons (e.g. it is looping). Since heartbeating can be done at a low level, we implement this as a special type of frame that peers exchange at the transport level, rather than as a class method. */ type heartbeatFrame struct { ChannelId uint16 } func (f *heartbeatFrame) channel() uint16 { return f.ChannelId } /* Certain methods (such as Basic.Publish, Basic.Deliver, etc.) are formally defined as carrying content. When a peer sends such a method frame, it always follows it with a content header and zero or more content body frames. A content header frame has this format: 0 2 4 12 14 +----------+--------+-----------+----------------+------------- - - | class-id | weight | body size | property flags | property list... +----------+--------+-----------+----------------+------------- - - short short long long short remainder... We place content body in distinct frames (rather than including it in the method) so that AMQP may support "zero copy" techniques in which content is never marshalled or encoded. We place the content properties in their own frame so that recipients can selectively discard contents they do not want to process */ type headerFrame struct { ChannelId uint16 ClassId uint16 weight uint16 Size uint64 Properties properties } func (f *headerFrame) channel() uint16 { return f.ChannelId } /* Content is the application data we carry from client-to-client via the AMQP server. Content is, roughly speaking, a set of properties plus a binary data part. The set of allowed properties are defined by the Basic class, and these form the "content header frame". The data can be any size, and MAY be broken into several (or many) chunks, each forming a "content body frame". Looking at the frames for a specific channel, as they pass on the wire, we might see something like this: [method] [method] [header] [body] [body] [method] ... */ type bodyFrame struct { ChannelId uint16 Body []byte } func (f *bodyFrame) channel() uint16 { return f.ChannelId } type heartbeatDuration struct { value time.Duration hasValue bool } func newHeartbeatDurationFromSeconds(s int) heartbeatDuration { v := time.Duration(s) * time.Second return heartbeatDuration{ value: v, hasValue: true, } } golang-github-rabbitmq-amqp091-go-1.10.0/types_test.go000066400000000000000000000034011462444370000224440ustar00rootroot00000000000000package amqp091 import ( "testing" "time" ) func TestNewError(t *testing.T) { testCases := []struct { code uint16 text string expectedServer bool }{ // Just three basics samples {404, "Not Found", true}, {500, "Internal Server Error", true}, {403, "Forbidden", true}, } for _, tc := range testCases { err := newError(tc.code, tc.text) if err.Code != int(tc.code) { t.Errorf("expected Code %d, got %d", tc.code, err.Code) } if err.Reason != tc.text { t.Errorf("expected Reason %s, got %s", tc.text, err.Reason) } if err.Server != tc.expectedServer { t.Errorf("expected Server to be %v", tc.expectedServer) } } } func TestValidateField(t *testing.T) { // Test case for simple types simpleTypes := []interface{}{ nil, true, byte(1), int8(1), 10, int16(10), int32(10), int64(10), float32(1.0), float64(1.0), "string", []byte("byte slice"), Decimal{Scale: 2, Value: 12345}, time.Now(), } for _, v := range simpleTypes { if err := validateField(v); err != nil { t.Errorf("validateField failed for simple type %T: %s", v, err) } } // Test case for []interface{} sliceTypes := []interface{}{ "string", 10, float64(1.0), Decimal{Scale: 2, Value: 12345}, } if err := validateField(sliceTypes); err != nil { t.Errorf("validateField failed for []interface{}: %s", err) } // Test case for Table tableType := Table{ "key1": "value1", "key2": 10, "key3": []interface{}{"nested string", 20}, } if err := validateField(tableType); err != nil { t.Errorf("validateField failed for Table: %s", err) } // Test case for unsupported type unsupportedType := struct{}{} if err := validateField(unsupportedType); err == nil { t.Error("validateField should fail for unsupported type but it didn't") } } golang-github-rabbitmq-amqp091-go-1.10.0/uri.go000066400000000000000000000152651462444370000210530ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "errors" "fmt" "net" "net/url" "strconv" "strings" ) var errURIScheme = errors.New("AMQP scheme must be either 'amqp://' or 'amqps://'") var errURIWhitespace = errors.New("URI must not contain whitespace") var schemePorts = map[string]int{ "amqp": 5672, "amqps": 5671, } var defaultURI = URI{ Scheme: "amqp", Host: "localhost", Port: 5672, Username: "guest", Password: "guest", Vhost: "/", } // URI represents a parsed AMQP URI string. type URI struct { Scheme string Host string Port int Username string Password string Vhost string CertFile string // client TLS auth - path to certificate (PEM) CACertFile string // client TLS auth - path to CA certificate (PEM) KeyFile string // client TLS auth - path to private key (PEM) ServerName string // client TLS auth - server name AuthMechanism []string Heartbeat heartbeatDuration ConnectionTimeout int ChannelMax uint16 } // ParseURI attempts to parse the given AMQP URI according to the spec. // See http://www.rabbitmq.com/uri-spec.html. // // Default values for the fields are: // // Scheme: amqp // Host: localhost // Port: 5672 // Username: guest // Password: guest // Vhost: / // // Supports TLS query parameters. See https://www.rabbitmq.com/uri-query-parameters.html // // certfile: // keyfile: // cacertfile: // server_name_indication: // auth_mechanism: // heartbeat: // connection_timeout: // channel_max: // // If cacertfile is not provided, system CA certificates will be used. // Mutual TLS (client auth) will be enabled only in case keyfile AND certfile provided. // // If Config.TLSClientConfig is set, TLS parameters from URI will be ignored. func ParseURI(uri string) (URI, error) { builder := defaultURI if strings.Contains(uri, " ") { return builder, errURIWhitespace } u, err := url.Parse(uri) if err != nil { return builder, err } defaultPort, okScheme := schemePorts[u.Scheme] if okScheme { builder.Scheme = u.Scheme } else { return builder, errURIScheme } host := u.Hostname() port := u.Port() if host != "" { builder.Host = host } if port != "" { port32, err := strconv.ParseInt(port, 10, 32) if err != nil { return builder, err } builder.Port = int(port32) } else { builder.Port = defaultPort } if u.User != nil { builder.Username = u.User.Username() if password, ok := u.User.Password(); ok { builder.Password = password } } if u.Path != "" { if strings.HasPrefix(u.Path, "/") { if u.Host == "" && strings.HasPrefix(u.Path, "///") { // net/url doesn't handle local context authorities and leaves that up // to the scheme handler. In our case, we translate amqp:/// into the // default host and whatever the vhost should be if len(u.Path) > 3 { builder.Vhost = u.Path[3:] } } else if len(u.Path) > 1 { builder.Vhost = u.Path[1:] } } else { builder.Vhost = u.Path } } // see https://www.rabbitmq.com/uri-query-parameters.html params := u.Query() builder.CertFile = params.Get("certfile") builder.KeyFile = params.Get("keyfile") builder.CACertFile = params.Get("cacertfile") builder.ServerName = params.Get("server_name_indication") builder.AuthMechanism = params["auth_mechanism"] if params.Has("heartbeat") { value, err := strconv.Atoi(params.Get("heartbeat")) if err != nil { return builder, fmt.Errorf("heartbeat is not an integer: %v", err) } builder.Heartbeat = newHeartbeatDurationFromSeconds(value) } if params.Has("connection_timeout") { value, err := strconv.Atoi(params.Get("connection_timeout")) if err != nil { return builder, fmt.Errorf("connection_timeout is not an integer: %v", err) } builder.ConnectionTimeout = value } if params.Has("channel_max") { value, err := strconv.ParseUint(params.Get("channel_max"), 10, 16) if err != nil { return builder, fmt.Errorf("connection_timeout is not an integer: %v", err) } builder.ChannelMax = uint16(value) } return builder, nil } // PlainAuth returns a PlainAuth structure based on the parsed URI's // Username and Password fields. func (uri URI) PlainAuth() *PlainAuth { return &PlainAuth{ Username: uri.Username, Password: uri.Password, } } // AMQPlainAuth returns a PlainAuth structure based on the parsed URI's // Username and Password fields. func (uri URI) AMQPlainAuth() *AMQPlainAuth { return &AMQPlainAuth{ Username: uri.Username, Password: uri.Password, } } func (uri URI) String() string { authority, err := url.Parse("") if err != nil { return err.Error() } authority.Scheme = uri.Scheme if uri.Username != defaultURI.Username || uri.Password != defaultURI.Password { authority.User = url.User(uri.Username) if uri.Password != defaultURI.Password { authority.User = url.UserPassword(uri.Username, uri.Password) } } if defaultPort, found := schemePorts[uri.Scheme]; !found || defaultPort != uri.Port { authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) } else { // JoinHostPort() automatically add brackets to the host if it's // an IPv6 address. // // If not port is specified, JoinHostPort() return an IP address in the // form of "[::1]:", so we use TrimSuffix() to remove the extra ":". authority.Host = strings.TrimSuffix(net.JoinHostPort(uri.Host, ""), ":") } if uri.Vhost != defaultURI.Vhost { // Make sure net/url does not double escape, e.g. // "%2F" does not become "%252F". authority.Path = uri.Vhost authority.RawPath = url.QueryEscape(uri.Vhost) } else { authority.Path = "/" } if uri.CertFile != "" || uri.KeyFile != "" || uri.CACertFile != "" || uri.ServerName != "" { rawQuery := strings.Builder{} if uri.CertFile != "" { rawQuery.WriteString("certfile=") rawQuery.WriteString(uri.CertFile) rawQuery.WriteRune('&') } if uri.KeyFile != "" { rawQuery.WriteString("keyfile=") rawQuery.WriteString(uri.KeyFile) rawQuery.WriteRune('&') } if uri.CACertFile != "" { rawQuery.WriteString("cacertfile=") rawQuery.WriteString(uri.CACertFile) rawQuery.WriteRune('&') } if uri.ServerName != "" { rawQuery.WriteString("server_name_indication=") rawQuery.WriteString(uri.ServerName) } authority.RawQuery = rawQuery.String() } return authority.String() } golang-github-rabbitmq-amqp091-go-1.10.0/uri_test.go000066400000000000000000000252131462444370000221040ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "reflect" "testing" "time" ) // Test matrix defined on http://www.rabbitmq.com/uri-spec.html type testURI struct { url string username string password string host string port int vhost string canon string } var uriTests = []testURI{ { url: "amqp://user:pass@host:10000/vhost", username: "user", password: "pass", host: "host", port: 10000, vhost: "vhost", canon: "amqp://user:pass@host:10000/vhost", }, { url: "amqp://", username: defaultURI.Username, password: defaultURI.Password, host: defaultURI.Host, port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://localhost/", }, { url: "amqp://:@/", username: "", password: "", host: defaultURI.Host, port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://:@localhost/", }, { url: "amqp://user@", username: "user", password: defaultURI.Password, host: defaultURI.Host, port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://user@localhost/", }, { url: "amqp://user:pass@", username: "user", password: "pass", host: defaultURI.Host, port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://user:pass@localhost/", }, { url: "amqp://guest:pass@", username: "guest", password: "pass", host: defaultURI.Host, port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://guest:pass@localhost/", }, { url: "amqp://host", username: defaultURI.Username, password: defaultURI.Password, host: "host", port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://host/", }, { url: "amqp://:10000", username: defaultURI.Username, password: defaultURI.Password, host: defaultURI.Host, port: 10000, vhost: defaultURI.Vhost, canon: "amqp://localhost:10000/", }, { url: "amqp:///vhost", username: defaultURI.Username, password: defaultURI.Password, host: defaultURI.Host, port: defaultURI.Port, vhost: "vhost", canon: "amqp://localhost/vhost", }, { url: "amqp://host/", username: defaultURI.Username, password: defaultURI.Password, host: "host", port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://host/", }, { url: "amqp://host/%2F", username: defaultURI.Username, password: defaultURI.Password, host: "host", port: defaultURI.Port, vhost: "/", canon: "amqp://host/", }, { url: "amqp://host/%2F%2F", username: defaultURI.Username, password: defaultURI.Password, host: "host", port: defaultURI.Port, vhost: "//", canon: "amqp://host/%2F%2F", }, { url: "amqp://host/%2Fslash%2F", username: defaultURI.Username, password: defaultURI.Password, host: "host", port: defaultURI.Port, vhost: "/slash/", canon: "amqp://host/%2Fslash%2F", }, { url: "amqp://192.168.1.1:1000/", username: defaultURI.Username, password: defaultURI.Password, host: "192.168.1.1", port: 1000, vhost: defaultURI.Vhost, canon: "amqp://192.168.1.1:1000/", }, { url: "amqp://[::1]", username: defaultURI.Username, password: defaultURI.Password, host: "::1", port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://[::1]/", }, { url: "amqp://[::1]:1000", username: defaultURI.Username, password: defaultURI.Password, host: "::1", port: 1000, vhost: defaultURI.Vhost, canon: "amqp://[::1]:1000/", }, { url: "amqp://[fe80::1]", username: defaultURI.Username, password: defaultURI.Password, host: "fe80::1", port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://[fe80::1]/", }, { url: "amqp://[fe80::1]", username: defaultURI.Username, password: defaultURI.Password, host: "fe80::1", port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://[fe80::1]/", }, { url: "amqp://[fe80::1%25en0]", username: defaultURI.Username, password: defaultURI.Password, host: "fe80::1%en0", port: defaultURI.Port, vhost: defaultURI.Vhost, canon: "amqp://[fe80::1%25en0]/", }, { url: "amqp://[fe80::1]:5671", username: defaultURI.Username, password: defaultURI.Password, host: "fe80::1", port: 5671, vhost: defaultURI.Vhost, canon: "amqp://[fe80::1]:5671/", }, { url: "amqps:///", username: defaultURI.Username, password: defaultURI.Password, host: defaultURI.Host, port: schemePorts["amqps"], vhost: defaultURI.Vhost, canon: "amqps://localhost/", }, { url: "amqps://host:1000/", username: defaultURI.Username, password: defaultURI.Password, host: "host", port: 1000, vhost: defaultURI.Vhost, canon: "amqps://host:1000/", }, } func TestURISpec(t *testing.T) { for _, test := range uriTests { u, err := ParseURI(test.url) if err != nil { t.Fatal("Could not parse spec URI: ", test.url, " err: ", err) } if test.username != u.Username { t.Error("For: ", test.url, " usernames do not match. want: ", test.username, " got: ", u.Username) } if test.password != u.Password { t.Error("For: ", test.url, " passwords do not match. want: ", test.password, " got: ", u.Password) } if test.host != u.Host { t.Error("For: ", test.url, " hosts do not match. want: ", test.host, " got: ", u.Host) } if test.port != u.Port { t.Error("For: ", test.url, " ports do not match. want: ", test.port, " got: ", u.Port) } if test.vhost != u.Vhost { t.Error("For: ", test.url, " vhosts do not match. want: ", test.vhost, " got: ", u.Vhost) } if test.canon != u.String() { t.Error("For: ", test.url, " canonical string does not match. want: ", test.canon, " got: ", u.String()) } } } func TestURIUnknownScheme(t *testing.T) { if _, err := ParseURI("http://example.com/"); err == nil { t.Fatal("Expected error when parsing non-amqp scheme") } } func TestURIScheme(t *testing.T) { if _, err := ParseURI("amqp://example.com/"); err != nil { t.Fatalf("Expected to parse amqp scheme, got %v", err) } if _, err := ParseURI("amqps://example.com/"); err != nil { t.Fatalf("Expected to parse amqps scheme, got %v", err) } } func TestURIWhitespace(t *testing.T) { if _, err := ParseURI("amqp://admin:PASSWORD@rabbitmq-service/ -http_port=8080"); err == nil { t.Fatal("Expected to fail if URI contains whitespace") } } func TestURIDefaults(t *testing.T) { url := "amqp://" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.String() != "amqp://localhost/" { t.Fatal("Defaults not encoded properly got:", uri.String()) } } func TestURIComplete(t *testing.T) { url := "amqp://bob:dobbs@foo.bar:5678/private" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.String() != url { t.Fatal("Defaults not encoded properly want:", url, " got:", uri.String()) } } func TestURIDefaultPortAmqpNotIncluded(t *testing.T) { url := "amqp://foo.bar:5672/" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.String() != "amqp://foo.bar/" { t.Fatal("Defaults not encoded properly got:", uri.String()) } } func TestURIDefaultPortAmqp(t *testing.T) { url := "amqp://foo.bar/" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.Port != 5672 { t.Fatal("Default port not correct for amqp, got:", uri.Port) } } func TestURIDefaultPortAmqpsNotIncludedInString(t *testing.T) { url := "amqps://foo.bar:5671/" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.String() != "amqps://foo.bar/" { t.Fatal("Defaults not encoded properly got:", uri.String()) } } func TestURIDefaultPortAmqps(t *testing.T) { url := "amqps://foo.bar/" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.Port != 5671 { t.Fatal("Default port not correct for amqps, got:", uri.Port) } } func TestURITLSConfig(t *testing.T) { url := "amqps://foo.bar/?certfile=/foo/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82/cert.pem&keyfile=/foo/%E4%BD%A0%E5%A5%BD/key.pem&cacertfile=C:%5Ccerts%5Cca.pem&server_name_indication=example.com" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if uri.CertFile != "/foo/привет/cert.pem" { t.Fatal("Certfile not set") } if uri.CACertFile != "C:\\certs\\ca.pem" { t.Fatal("CA not set") } if uri.KeyFile != "/foo/你好/key.pem" { t.Fatal("Key not set") } if uri.ServerName != "example.com" { t.Fatal("Server name not set") } } func TestURIParameters(t *testing.T) { url := "amqps://foo.bar/?auth_mechanism=plain&auth_mechanism=amqpplain&heartbeat=2&connection_timeout=5000&channel_max=8" uri, err := ParseURI(url) if err != nil { t.Fatal("Could not parse") } if !reflect.DeepEqual(uri.AuthMechanism, []string{"plain", "amqpplain"}) { t.Fatal("AuthMechanism not set") } if !uri.Heartbeat.hasValue { t.Fatal("Heartbeat not set") } if uri.Heartbeat.value != time.Duration(2)*time.Second { t.Fatal("Heartbeat not set") } if uri.ConnectionTimeout != 5000 { t.Fatal("ConnectionTimeout not set") } if uri.ChannelMax != 8 { t.Fatal("ChannelMax name not set") } } func TestURI_ParseUriToString(t *testing.T) { tests := []struct { name string uri string want string }{ {name: "virtual host is set", uri: "amqp://example.com/foobar", want: "amqp://example.com/foobar"}, {name: "non-default port", uri: "amqp://foo.bar:1234/example", want: "amqp://foo.bar:1234/example"}, { name: "TLS with URI parameters", uri: "amqps://some-host.com/foobar?certfile=/foo/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82/cert.pem&keyfile=/foo/%E4%BD%A0%E5%A5%BD/key.pem&cacertfile=C:%5Ccerts%5Cca.pem&server_name_indication=example.com", want: "amqps://some-host.com/foobar?certfile=/foo/привет/cert.pem&keyfile=/foo/你好/key.pem&cacertfile=C:\\certs\\ca.pem&server_name_indication=example.com", }, {name: "only server name indication", uri: "amqps://foo.bar?server_name_indication=example.com", want: "amqps://foo.bar/?server_name_indication=example.com"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { amqpUri, err := ParseURI(tt.uri) if err != nil { t.Errorf("ParseURI() error = %v", err) } if got := amqpUri.String(); got != tt.want { t.Errorf("String() = %v, want %v", got, tt.want) } }) } } golang-github-rabbitmq-amqp091-go-1.10.0/write.go000066400000000000000000000210631462444370000213770ustar00rootroot00000000000000// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package amqp091 import ( "bufio" "bytes" "encoding/binary" "errors" "io" "math" "time" ) func (w *writer) WriteFrameNoFlush(frame frame) (err error) { err = frame.write(w.w) return } func (w *writer) WriteFrame(frame frame) (err error) { if err = frame.write(w.w); err != nil { return } if buf, ok := w.w.(*bufio.Writer); ok { err = buf.Flush() } return } func (f *methodFrame) write(w io.Writer) (err error) { var payload bytes.Buffer if f.Method == nil { return errors.New("malformed frame: missing method") } class, method := f.Method.id() if err = binary.Write(&payload, binary.BigEndian, class); err != nil { return } if err = binary.Write(&payload, binary.BigEndian, method); err != nil { return } if err = f.Method.write(&payload); err != nil { return } return writeFrame(w, frameMethod, f.ChannelId, payload.Bytes()) } // Heartbeat // // Payload is empty func (f *heartbeatFrame) write(w io.Writer) (err error) { return writeFrame(w, frameHeartbeat, f.ChannelId, []byte{}) } // CONTENT HEADER // 0 2 4 12 14 // +----------+--------+-----------+----------------+------------- - - // | class-id | weight | body size | property flags | property list... // +----------+--------+-----------+----------------+------------- - - // // short short long long short remainder... func (f *headerFrame) write(w io.Writer) (err error) { var payload bytes.Buffer if err = binary.Write(&payload, binary.BigEndian, f.ClassId); err != nil { return } if err = binary.Write(&payload, binary.BigEndian, f.weight); err != nil { return } if err = binary.Write(&payload, binary.BigEndian, f.Size); err != nil { return } // First pass will build the mask to be serialized, second pass will serialize // each of the fields that appear in the mask. var mask uint16 if len(f.Properties.ContentType) > 0 { mask = mask | flagContentType } if len(f.Properties.ContentEncoding) > 0 { mask = mask | flagContentEncoding } if f.Properties.Headers != nil && len(f.Properties.Headers) > 0 { mask = mask | flagHeaders } if f.Properties.DeliveryMode > 0 { mask = mask | flagDeliveryMode } if f.Properties.Priority > 0 { mask = mask | flagPriority } if len(f.Properties.CorrelationId) > 0 { mask = mask | flagCorrelationId } if len(f.Properties.ReplyTo) > 0 { mask = mask | flagReplyTo } if len(f.Properties.Expiration) > 0 { mask = mask | flagExpiration } if len(f.Properties.MessageId) > 0 { mask = mask | flagMessageId } if !f.Properties.Timestamp.IsZero() { mask = mask | flagTimestamp } if len(f.Properties.Type) > 0 { mask = mask | flagType } if len(f.Properties.UserId) > 0 { mask = mask | flagUserId } if len(f.Properties.AppId) > 0 { mask = mask | flagAppId } if err = binary.Write(&payload, binary.BigEndian, mask); err != nil { return } if hasProperty(mask, flagContentType) { if err = writeShortstr(&payload, f.Properties.ContentType); err != nil { return } } if hasProperty(mask, flagContentEncoding) { if err = writeShortstr(&payload, f.Properties.ContentEncoding); err != nil { return } } if hasProperty(mask, flagHeaders) { if err = writeTable(&payload, f.Properties.Headers); err != nil { return } } if hasProperty(mask, flagDeliveryMode) { if err = binary.Write(&payload, binary.BigEndian, f.Properties.DeliveryMode); err != nil { return } } if hasProperty(mask, flagPriority) { if err = binary.Write(&payload, binary.BigEndian, f.Properties.Priority); err != nil { return } } if hasProperty(mask, flagCorrelationId) { if err = writeShortstr(&payload, f.Properties.CorrelationId); err != nil { return } } if hasProperty(mask, flagReplyTo) { if err = writeShortstr(&payload, f.Properties.ReplyTo); err != nil { return } } if hasProperty(mask, flagExpiration) { if err = writeShortstr(&payload, f.Properties.Expiration); err != nil { return } } if hasProperty(mask, flagMessageId) { if err = writeShortstr(&payload, f.Properties.MessageId); err != nil { return } } if hasProperty(mask, flagTimestamp) { if err = binary.Write(&payload, binary.BigEndian, uint64(f.Properties.Timestamp.Unix())); err != nil { return } } if hasProperty(mask, flagType) { if err = writeShortstr(&payload, f.Properties.Type); err != nil { return } } if hasProperty(mask, flagUserId) { if err = writeShortstr(&payload, f.Properties.UserId); err != nil { return } } if hasProperty(mask, flagAppId) { if err = writeShortstr(&payload, f.Properties.AppId); err != nil { return } } return writeFrame(w, frameHeader, f.ChannelId, payload.Bytes()) } // Body // // Payload is one byterange from the full body who's size is declared in the // Header frame func (f *bodyFrame) write(w io.Writer) (err error) { return writeFrame(w, frameBody, f.ChannelId, f.Body) } func writeFrame(w io.Writer, typ uint8, channel uint16, payload []byte) (err error) { end := []byte{frameEnd} size := uint(len(payload)) _, err = w.Write([]byte{ typ, byte((channel & 0xff00) >> 8), byte((channel & 0x00ff) >> 0), byte((size & 0xff000000) >> 24), byte((size & 0x00ff0000) >> 16), byte((size & 0x0000ff00) >> 8), byte((size & 0x000000ff) >> 0), }) if err != nil { return } if _, err = w.Write(payload); err != nil { return } if _, err = w.Write(end); err != nil { return } return } func writeShortstr(w io.Writer, s string) (err error) { b := []byte(s) var length = uint8(len(b)) if err = binary.Write(w, binary.BigEndian, length); err != nil { return } if _, err = w.Write(b[:length]); err != nil { return } return } func writeLongstr(w io.Writer, s string) (err error) { b := []byte(s) var length = uint32(len(b)) if err = binary.Write(w, binary.BigEndian, length); err != nil { return } if _, err = w.Write(b[:length]); err != nil { return } return } /* 'A': []interface{} 'D': Decimal 'F': Table 'I': int32 'S': string 'T': time.Time 'V': nil 'b': int8 'B': byte 'd': float64 'f': float32 'l': int64 's': int16 't': bool 'x': []byte */ func writeField(w io.Writer, value interface{}) (err error) { var buf [9]byte var enc []byte switch v := value.(type) { case bool: buf[0] = 't' if v { buf[1] = byte(1) } else { buf[1] = byte(0) } enc = buf[:2] case byte: buf[0] = 'B' buf[1] = v enc = buf[:2] case int8: buf[0] = 'b' buf[1] = uint8(v) enc = buf[:2] case int16: buf[0] = 's' binary.BigEndian.PutUint16(buf[1:3], uint16(v)) enc = buf[:3] case int: buf[0] = 'I' binary.BigEndian.PutUint32(buf[1:5], uint32(v)) enc = buf[:5] case int32: buf[0] = 'I' binary.BigEndian.PutUint32(buf[1:5], uint32(v)) enc = buf[:5] case int64: buf[0] = 'l' binary.BigEndian.PutUint64(buf[1:9], uint64(v)) enc = buf[:9] case float32: buf[0] = 'f' binary.BigEndian.PutUint32(buf[1:5], math.Float32bits(v)) enc = buf[:5] case float64: buf[0] = 'd' binary.BigEndian.PutUint64(buf[1:9], math.Float64bits(v)) enc = buf[:9] case Decimal: buf[0] = 'D' buf[1] = v.Scale binary.BigEndian.PutUint32(buf[2:6], uint32(v.Value)) enc = buf[:6] case string: buf[0] = 'S' binary.BigEndian.PutUint32(buf[1:5], uint32(len(v))) enc = append(buf[:5], []byte(v)...) case []interface{}: // field-array buf[0] = 'A' sec := new(bytes.Buffer) for _, val := range v { if err = writeField(sec, val); err != nil { return } } binary.BigEndian.PutUint32(buf[1:5], uint32(sec.Len())) if _, err = w.Write(buf[:5]); err != nil { return } if _, err = w.Write(sec.Bytes()); err != nil { return } return case time.Time: buf[0] = 'T' binary.BigEndian.PutUint64(buf[1:9], uint64(v.Unix())) enc = buf[:9] case Table: if _, err = w.Write([]byte{'F'}); err != nil { return } return writeTable(w, v) case []byte: buf[0] = 'x' binary.BigEndian.PutUint32(buf[1:5], uint32(len(v))) if _, err = w.Write(buf[0:5]); err != nil { return } if _, err = w.Write(v); err != nil { return } return case nil: buf[0] = 'V' enc = buf[:1] default: return ErrFieldType } _, err = w.Write(enc) return } func writeTable(w io.Writer, table Table) (err error) { var buf bytes.Buffer for key, val := range table { if err = writeShortstr(&buf, key); err != nil { return } if err = writeField(&buf, val); err != nil { return } } return writeLongstr(w, buf.String()) }