feat(eventhubs): support AMQP-over-WebSockets transport - #4596
feat(eventhubs): support AMQP-over-WebSockets transport#4596Johnathan W (j7nw4r) wants to merge 25 commits into
Conversation
3ebc11b to
114e08b
Compare
|
Rebased onto main and re-pushed. Heads up on one thing I had to touch that isn't really about WebSockets, Heath Stewart (@heaths). To see the unreleased Also folded in two fixes the rebase surfaced: a test in Still no live round-trip over |
|
Validated this live against a real namespace, so the transport is no longer only unit-tested. I added an Still outstanding is a run from a network that actually blocks 5671 outbound, which is the case this feature exists for. I do not have one handy. |
7b3b20a to
8f3c093
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds AMQP-over-WebSockets support for Event Hubs, enabling connections over port 443.
Changes:
- Public API: adds transport selection to AMQP and Event Hubs clients.
- Implements secure WebSocket transport in the fe2o3 backend.
- Adds documentation, tests, example, and dependencies.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
Cargo.toml |
Adds WebSocket dependency. |
Cargo.lock |
Updates resolved dependencies. |
sdk/core/azure_core_amqp/Cargo.toml |
Enables WebSocket support. |
sdk/core/azure_core_amqp/CHANGELOG.md |
Documents the AMQP API. |
sdk/core/azure_core_amqp/src/lib.rs |
Exports AmqpTransport. |
sdk/core/azure_core_amqp/src/connection.rs |
Adds transport configuration. |
sdk/core/azure_core_amqp/src/fe2o3/connection.rs |
Opens AMQP WebSocket streams. |
sdk/core/azure_core_amqp/src/fe2o3/error.rs |
Maps WebSocket errors. |
sdk/eventhubs/azure_messaging_eventhubs/Cargo.toml |
Uses unreleased local dependencies. |
sdk/eventhubs/azure_messaging_eventhubs/CHANGELOG.md |
Documents transport selection. |
sdk/eventhubs/azure_messaging_eventhubs/examples/eventhubs_websocket_transport.rs |
Demonstrates WebSocket transport. |
sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs |
Updates test construction. |
sdk/eventhubs/azure_messaging_eventhubs/src/common/recoverable/connection.rs |
Propagates transport during recovery. |
sdk/eventhubs/azure_messaging_eventhubs/src/consumer/mod.rs |
Adds consumer transport selection. |
sdk/eventhubs/azure_messaging_eventhubs/src/event_processor/processor.rs |
Documents inherited transport. |
sdk/eventhubs/azure_messaging_eventhubs/src/models/mod.rs |
Adds TransportType. |
sdk/eventhubs/azure_messaging_eventhubs/src/producer/mod.rs |
Adds producer transport selection. |
sdk/eventhubs/azure_messaging_eventhubs_checkpointstore_blob/Cargo.toml |
Aligns local dependency versions. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
sdk/core/azure_core_amqp/Cargo.toml:47
- The
fe2o3_amqpfeature still enablesfe2o3-amqp-wswithout any TLS backend, butconnection.rsunconditionally callsconnect_tls_with_config, which is absent unless a WebSocket TLS feature is selected. As confirmed in the prior thread,cargo check -p azure_core_amqp --no-default-features --features fe2o3_amqptherefore fails, although this supported feature combination compiled before the change. Please either model shared TLS backend features for both fe2o3 crates and gate the WebSocket arm, or otherwise ensure this feature combination remains compilable.
"dep:fe2o3-amqp-ws",
Add a runnable sample that opens a producer and a consumer with TransportType::AmqpWebSocket, sends a tagged event, and reads it back. It mirrors the connection-string sample, so the two can be run side by side to compare transports.
Add an IPv6 regression test for the WebSocket address builder, register fe2o3-amqp-ws in the crate spelling dictionary in both the underscore and the hyphen form, and record the AmqpConnectionOptions field addition under Breaking Changes.
Add the native_tls and rustls features, which select the TLS backend for both fe2o3-amqp and fe2o3-amqp-ws. The default feature selects native_tls. The WebSocket transport needs one of them, because fe2o3-amqp-ws gates its TLS entry point behind its own features. Without a backend the crate now compiles and the transport returns an error at run time. Before this change, "--no-default-features --features fe2o3_amqp" did not compile. Mark AmqpConnectionOptions as non_exhaustive and add with_* methods for each field, so later fields are additive. Update the Event Hubs call site to use the new methods.
The rustls backend of fe2o3-amqp depends on ring, and deny.toml bans that crate, so "cargo deny --all-features check bans" failed. Keep native_tls as the only TLS backend and gate the WebSocket transport on it. See issue Azure#4189 for the ring work.
Extract RecoverableConnection::connection_options so a test can assert the transport reaches the options given to AmqpConnection::open. A test on the constructor alone passed even when create_connection dropped the call. Route each builder open path through one transport() helper and test it, which covers the connection-string path too. Correct the CHANGELOG example: "..Default::default()" does not build a non_exhaustive struct from another crate. Show the with_ method instead.
Document on AmqpTransport::WebSocket, on with_transport, and on the Event Hubs TransportType::AmqpWebSocket that the variant needs a TLS backend. A build with default-features = false can select the variant, but the connection then returns an error when it opens.
The Event Hubs crate depends on azure_core_amqp with its default features, so native_tls is always on and the WebSocket variant works in every build of this crate. The previous text described a configuration the manifest does not produce. Also qualify the azure_core_amqp text: the run-time error needs the fe2o3_amqp feature, since the no-op backend panics.
The receiver attach test that came in with Azure#4807 calls RecoverableConnection::new with the previous six arguments, so it stopped compiling after this branch added the transport argument. Pass the default transport.
…mple The open method takes the fully qualified namespace and uses it as the AMQP host, so the short name in the TransportType example does not resolve.
The run-time error needs the fe2o3_amqp feature. Without a backend feature the no-op connection takes over, and its open calls unimplemented!, so it panics.
`AmqpConnectionOptions` returns to a plain struct with public fields. The `#[non_exhaustive]` marker and the `with_` methods are gone, so a struct literal with `..Default::default()` builds it again. The repo allows the `constructible_struct_adds_field` semver lint for this reason. AMQP over WebSockets is now a feature of this crate, not a TLS selection. `websockets_rustls` and `websockets_native_tls` each turn on `fe2o3-amqp-ws` and forward one of its TLS backends. `default` selects `websockets_rustls`, which is rustls with the aws-lc-rs provider, the same stack `azure_core` selects for HTTP. The direct `rustls` dependency selects that provider, because `fe2o3-amqp-ws` takes rustls without default features. The `fe2o3_amqp` feature no longer pulls in `fe2o3-amqp-ws`, so a build with that feature alone compiles. The TCP transport keeps `fe2o3-amqp/native-tls`, which `default` selected before this change: the rustls backend of `fe2o3-amqp` 0.14 pulls in `ring`, which `deny.toml` bans (Azure#4189).
`models::TransportType` duplicated `azure_core_amqp::AmqpTransport`, which this crate already re-exports types from. The enum and its `From` implementation are gone. `models` re-exports `AmqpTransport` beside `AmqpMessage` and `AmqpValue`, and the builder method is `with_transport`, which takes that type.
The WebSocket transport had two features, `websockets_rustls` and `websockets_native_tls`. Each one turned on the transport and one TLS stack in a single step, so a consumer could not select the TLS stack that the rest of the application uses. Give the transport the base plus modifier shape that `azure_core` uses for HTTP, where `reqwest` is the base and `reqwest_rustls` adds the stack. Name the features after the backend crate `fe2o3-amqp-ws`, which is the rule that `reqwest` and the existing `fe2o3_amqp` both follow. `fe2o3_amqp_ws` turns on the transport code, and `fe2o3_amqp_ws_rustls` and `fe2o3_amqp_ws_native_tls` each add one stack on top of it. Add `fe2o3_amqp_native_tls` for the TLS stack of the TCP transport. `default` named `fe2o3-amqp/native-tls` inline before, and `fe2o3-amqp` is only a dev-dependency of `azure_messaging_eventhubs`, so that crate could not forward it. Without the named feature, an application that turned off the default features to select a WebSocket stack also lost the TLS stack of the TCP transport. Forward all five features from `azure_messaging_eventhubs`, in the same way that `azure_core` forwards the `reqwest` TLS features of `typespec_client_core`. That crate is the one an application depends on, so the split was not reachable without the forward. Keep the features additive, which the Cargo reference asks for. A build can hold both WebSocket stacks; `fe2o3-amqp-ws` then uses native-tls, and the connection now logs a warning that names the stack in use.
The rebase onto main brought in new tests that build a `RecoverableConnection` or a `ProducerClient`. Both constructors take the transport, so each new call site needs the argument. The tests do not exercise the transport, so they pass the default, which is TCP.
`fe2o3_amqp_ws_native_tls` was a parity feature: it named a second TLS stack that this crate does not otherwise use. `azure_core` has no such feature. It offers `reqwest` as the base and `reqwest_rustls` for the stack it ships, and `samples/list_blobs_native_tls` selects another stack with a direct `reqwest` dependency and Cargo feature unification. Follow that pattern. Keep `fe2o3_amqp_ws` as the base and `fe2o3_amqp_ws_rustls` for the stack this repository ships. Gate the transport code on the base feature alone, which is what makes unification work: an application can now name `fe2o3_amqp_ws`, add `fe2o3-amqp-ws` with `native-tls`, and get the transport on that stack. The old gate read this crate's own TLS features, so the same manifest compiled the transport out and returned an error at run time. One stack must be selected somewhere in the graph, because `fe2o3-amqp-ws` puts `connect_tls_with_config` behind its own TLS features. `fe2o3_amqp_ws` on its own therefore does not build. `reqwest` differs here, since it compiles with no TLS feature at all. Add `tokio-tungstenite` to the crate dictionary, in both spellings, beside the other entries.
`fe2o3-amqp` 0.16 builds its rustls backend on aws-lc-rs, where 0.14 built it on `ring`, which `deny.toml` bans. That was the only reason this crate named native-tls for AMQP framed on TCP. Update the `fe2o3-amqp` family from 0.14 to 0.16, which needs no source change, and replace `fe2o3_amqp_native_tls` with `fe2o3_amqp_rustls`. The `default` feature selects it, so both the TCP and the WebSocket transport now run on rustls with the aws-lc-rs provider, the stack that the rest of `sdk/core` uses. There is no native-tls feature. `fe2o3-amqp` accepts one TLS stack and reports `TlsConnectorNotFound` at run time when both are on, so a pair of features that cannot both be on would break `--all-features`. A consumer that wants another stack turns off the default features and takes a direct dependency on `fe2o3-amqp` with the stack they want, which is the pattern `samples/list_blobs_native_tls` shows for `reqwest`. The `azure_core_amqp` dependency of `azure_messaging_eventhubs` now sets `default-features = false`, in the same shape as `azure_core`, so that turning the default features off can drop the rustls stack. Closes Azure#4189.
`fe2o3_amqp_ws` is the base feature and names no TLS stack, but the transport called `WebSocketStream::connect_tls_with_config`, which `fe2o3-amqp-ws` puts behind its own TLS features. A build that named `fe2o3_amqp_ws` alone therefore failed with E0599, so the advertised base feature did not compile. Call `connect_with_config` instead. It carries no feature gate, and it reaches the same `tokio_tungstenite` connect path with no connector, which is what the previous call passed. The behavior is unchanged when a TLS stack is selected, and a build that selects none now reports `TlsFeatureNotEnabled` when the connection opens. This matches `reqwest`, which also compiles with no TLS feature and fails at run time, and it needs no TLS modifier feature of its own. Update the feature comment, the `AmqpTransport::WebSocket` doc, and the changelog, which all said the base feature could not build.
`RecoverableConnection::new` takes a `transport` argument on this branch, and `ConsumerClientOptions` carries a `transport` field. PR Azure#4933 landed on main after this branch forked and added a unit test and a builder call that use the earlier shapes. Git merges the two changes without a textual conflict, so the branch built on its own while the pipeline, which builds the merge with main, failed with E0061, E0063, and E0277. Pass the default transport at both new call sites.
The `docs.rs` metadata named `fe2o3_amqp`, `fe2o3_amqp_ws`, and `fe2o3_amqp_ws_rustls`, so docs.rs built the crate without the TLS stack for AMQP framed on TCP. Add `fe2o3_amqp_rustls`. The list now holds every feature that `default` selects. It leaves out `test`, which is an internal helper, and `ffi`, which does not build on its own (Azure#4978). `azure_core` names its features the same way.
The comment said the receiver and the stream hold references to the consumer's connection, so the caller must release them before the consumer closes. That overstates the requirement. `ConsumerClient::close` takes `&self` on the connection manager and drains the receiver cache, so it detaches the receiver link on its own. A receiver that outlives its client then reports the closed client instead of opening a second connection (Azure#4931). The order of `receiver.close()` and `consumer.close()` is free. The one order the caller must keep is the stream before the receiver. `stream_events` borrows `&self` and `close` takes `self` by value, so the compiler enforces it. The block above already covers that.
Two entries under Features Added are breaking. Restore the section and move them. The `AmqpConnectionOptions::transport` field lands on a struct that is not `#[non_exhaustive]`, so an existing struct literal that names every field no longer compiles. The `default` feature swap from `fe2o3-amqp/native-tls` to `fe2o3_amqp_rustls` changes the trust anchors. `fe2o3-amqp` builds its rustls root store from `webpki-roots`, and native-tls read the root store of the operating system. The same swap reaches `azure_messaging_eventhubs` through its forwarded features, so its changelog gets the matching entry.
An explicit port on `custom_endpoint` carries into the `wss://` address, so a proxy named as `amqps://proxy:5671` dials 5671 and not 443. The .NET Azure SDK does the same. `AmqpClient` builds its `ConnectionEndpoint` from the host and the explicit port of `CustomEndpointAddress`, and `CreateTransportSettingsForWebSockets` carries that port into the WebSocket URI. A proxy that accepts WebSockets on its own port stays reachable, so the behavior stands. Document the rule on `AmqpConnectionOptions::custom_endpoint` and on the `with_custom_endpoint` builders, and pin the AMQP-port case with a test.
The `rustls` feature of `fe2o3-amqp` 0.16 fills its root store from `webpki-roots` only, which is a compiled-in copy of the Mozilla root set. The connection called `open()` with no connector, so AMQP framed directly on TCP took that default and stopped reading the trust store of the operating system. A broker behind a private or an enterprise certificate authority would then fail the handshake, even when the operating system trusts that authority. Supply a connector built on `rustls-platform-verifier` instead. The TCP transport now trusts the same roots as `reqwest/rustls` on the HTTP side, and it agrees with the WebSocket transport, which already reads the same store through native roots. Use `rustls_connector`, because it needs the `rustls` feature only, where `tls_connector` also needs `native-tls` to be off. The connector-typed builder hands `alt_tls_estab` to the same transport call as the default path, so `alt_tls_establishment(true)` keeps its behavior. Add a test that builds the connector, which catches a missing crypto provider at test time instead of at connect time.
0b02452 to
ff03820
Compare
Josue Gomez (SwayGom)
left a comment
There was a problem hiding this comment.
Approving — the design is right and the implementation is careful. Keeping the AMQP hostname separate from the socket target on the WebSocket path is the detail that matters most here, and it is correct. Splitting the base transport feature from the TLS stack feature (fe2o3_amqp_ws / fe2o3_amqp_ws_rustls) also matches the existing reqwest / reqwest_rustls shape in this repo.
Two things I would like tracked, neither of which I think should hold the PR:
1. The two transports do not use the same TLS trust mechanism.
The TCP path builds its connector from rustls-platform-verifier, which delegates chain verification to the operating system. The WebSocket path goes through fe2o3-amqp-ws/rustls-tls-native-roots, which only seeds a rustls root store and then verifies in rustls. Those are not equivalent: they can differ on revocation, cross-signed intermediates, and enterprise policy. The PR description says the WebSocket path "reads the same store through native roots", which reads as stronger than what the code actually gives us (the CHANGELOG is more careful).
The practical risk is a customer who switches to WebSockets to get around a blocked 5671 and then hits a TLS failure that looks unrelated to the transport change. Worth an issue to either align the two paths or document the difference explicitly.
2. The AmqpConnectionOptions breaking change is still a breaking change.
The CHANGELOG is honest about this: the struct is not #[non_exhaustive], so an exhaustive struct literal no longer compiles, and the constructible_struct_adds_field semver lint is allowlisted rather than satisfied. That trade also applies to every future field on this struct, not just transport. If the intent is for field additions to be non-breaking going forward, #[non_exhaustive] is the way to get that from the type system instead of from documentation.
Smaller notes, take or leave:
Err(...)?; unreachable!()in the#[cfg(not(feature = "fe2o3_amqp_ws"))]arm would read better as a plainreturn Err(...).platform_verifier_connector_buildsassertsis_ok(), but the failure mode its comment describes is a panic, so the test aborts rather than failing on the case it documents.- The deliberate runtime error for
AmqpTransport::WebSocketwithout thefe2o3_amqp_wsfeature is the right API call, but there is no test covering it.
Note that CI is currently red on all six Build Test jobs plus Build Analyze, and there is an open changes-requested review, so this still needs both resolved before it can merge.
|
Closing this for #5031, which carries the same commits on a branch in this repo. #4928 moved cargo onto the Azure Artifacts feed, and that feed wants a token even to read. Fork PRs do not get one, so the build authenticates as the anonymous user and the feed answers 401 before cargo resolves anything. That fails all six jobs before anything compiles. Every fork build in this repo has hit it since August 6. Moving the branch here is the only way I get a pipeline that runs. Same code, rebased on current main. Heath Stewart (@heaths), the open threads did not come across, so I will carry them to #5031 and answer them there. Larry Osterman (@LarryOsterman) and Josue Gomez (@SwayGom), you will need to re-approve on the new one. |
Pull request was closed
Summary
azure_messaging_eventhubsconnected over AMQP on TCP/TLS only (port 5671). No option selected AMQP-over-WebSockets (wss://, port 443). Some networks block the native AMQP ports (5671 and 5672). Clients on those networks had no path to Event Hubs. The .NET, Java, and Python Azure SDKs all offer this option. (#3601)This change adds a transport option, and it keeps the TLS stack a separate choice. Both parts are additive. No existing caller breaks, and
Tcpstays the default.Motivation
The AMQP WebSocket binding sends an AMQP connection over a WebSocket on port 443. The service treats that connection as equal to a 5671 connection. The client builder and the AMQP backend fixed the scheme to
amqps://and the transport to TCP, so no public option reached the WebSocket binding. Some firewalls block every outbound port except 443. Clients behind those firewalls cannot reach Event Hubs without a transport selector.A WebSocket connection also needs a TLS stack. Most applications already link one stack. A second stack adds binary size, and it adds a second trust store to configure.
azure_corekeeps the transport and the stack apart for HTTP, wherereqwestis the base feature andreqwest_rustlsadds the stack. A consumer of the AMQP crates expects the same shape.The TLS stack also controls which root certificates the client trusts. A change of stack must keep the anchors that the previous stack used, or a broker behind a private certificate authority stops working.
Changes
azure_core_amqpadds the publicAmqpTransport { Tcp, WebSocket }enum and anAmqpConnectionOptions::transportfield.AmqpConnectionOptionsstays a plain options bag with public fields, so..Default::default()still builds it. Theopen()function of thefe2o3backend branches on the transport. TheWebSocketarm connects afe2o3_amqp_ws::WebSocketStream, and it gives the stream toConnection::builder().open_with_stream(...). The AMQPhostnamestays the real service host, so AMQP link addressing does not change. SASL ANONYMOUS and CBS authorization stay the same.WebSocketarm callsconnect_with_config, notconnect_tls_with_config.fe2o3-amqp-wsputsconnect_tls_with_configbehind its own TLS features, and a call to it would keepfe2o3_amqp_wsfrom building on its own.connect_with_configcarries no such gate. It uses whichever stack the features offe2o3-amqp-wsselect, and the connection reportsTlsFeatureNotEnabledwhen the graph selects none.fe2o3_amqpis the base backend.fe2o3_amqp_rustlsadds the TLS stack for AMQP framed on TCP.fe2o3_amqp_wsturns on the WebSocket transport code and thefe2o3-amqp-wsdependency, and it names no TLS stack.fe2o3_amqp_ws_rustlsadds the stack for the WebSocket transport.defaultselects all four. Each stack is rustls with the aws-lc-rs provider, which is the stack that the rest ofsdk/coreuses.fe2o3_amqp_rustlsbuilds onrustls-platform-verifier. The default connector offe2o3-amqpfills its root store fromwebpki-roots, which is a compiled-in copy of the Mozilla root set, and it ignores the trust store of the operating system. native-tls read that store before this change, so the default would drop the roots that an operator installs, and a broker behind a private or an enterprise certificate authority would fail the handshake. The platform verifier reads the trust store of the operating system on every supported target, through Security.framework on Apple, through CryptoAPI on Windows, and throughrustls-native-certselsewhere. The TCP transport therefore keeps the anchors it had, and it agrees withreqwest/rustlson the HTTP side and with the WebSocket transport, which reads the same store through native roots.webpki-rootsstill arrives as a dependency, because therustlsfeature offe2o3-amqpalways names it, and nothing reads it.rustls_connector, not through thetls_connectoralias.rustls_connectorneeds therustlsfeature only, where the alias also needsnative-tlsto be off, so the call survives a consumer that unifies both features. The connector-typed builder keeps its ownopen, and that path handsalt_tls_estabto the same transport call as the default path, soalt_tls_establishment(true)keeps its behavior.fe2o3-amqpaccepts one TLS stack only, and it reportsTlsConnectorNotFoundat run time when both are on. Two features that cannot both be on would break--all-features. To build on another stack, turn off the default features, name the base features, and take a direct dependency onfe2o3-amqpandfe2o3-amqp-wswith the stack you want. Cargo unifies the features, and nothing pulls rustls in.samples/list_blobs_native_tlsshows the same pattern forreqwest. The feature comments record this recipe.fe2o3_amqp_rustlsandfe2o3_amqp_ws_rustlseach take a directrustlsdependency. That dependency only selects the crypto provider, and this crate names one rustls type, which is theClientConfigthat the connector needs. Thetokio-tungstenitechain underfe2o3-amqp-wstakes rustls withdefault-features = falseand turns on no provider feature, soClientConfig::builder()panics when no process-level default is installed. The TCP connector calls the same builder and needs the same guarantee. The default features ofrustlsgive aws-lc-rs, std, and tls12, anddeny.tomlbansring, so this repository cannot reach that panic. An application that unifies a second provider into the graph must callCryptoProvider::install_default()first.fe2o3-amqpfamily from 0.14 to 0.16, and it addsfe2o3-amqp-ws0.16,rustls0.23,rustls-platform-verifier0.7, andtokio-rustls0.26. The rustls backend offe2o3-amqp0.14 was built onring, whichdeny.tomlbans. Version 0.16 uses aws-lc-rs instead, so this change closes Cannot use aws-lc-rs for AMQP #4189.reqwestalready resolvesrustls-platform-verifierandtokio-rustlsthrough its ownrustlsfeature, so the graph gains no new package, andCargo.lockgains two dependency edges only.azure_messaging_eventhubsforwards all four features.azure_coreforwards thereqwestTLS features oftypespec_client_corein the same way. An application depends on the Event Hubs crate, so the split does not reach the application without the forward.azure_messaging_eventhubsre-exportsAmqpTransportasmodels::AmqpTransport, next to the otherazure_core_amqptypes that this crate already re-exports. It addswith_transport(...)on the producer builder and the consumer builder. The transport reaches the connection on all four construction paths, which areopenandopen_with_connection_stringon each of the two builders.EventProcessortakes the transport from theConsumerClientthatbuildreceives. It therefore runs over WebSockets when that client selects them. The documentation onbuildshows this with an example.wss://{host}[:{port}]/$servicebus/websocket/. The other Azure SDKs use the same binding path. A custom endpoint redirects the socket, and the AMQPhostnamestays the real service host.eventhubs_websocket_transportsample opens a producer and a consumer over WebSockets, sends a tagged event, and reads the event back.docs.rsmetadata names all four features, so docs.rs documents both transports together with their TLS stacks.azure_messaging_eventhubsneeds the unreleasedAmqpTransport. It therefore takesazure_core_amqpandazure_coreas localpathplusversiondependencies, and it takes the local dev-dependencies aspathonly. This follows the policy inAGENTS.md.azure_messaging_eventhubs_checkpointstore_blobgets the same treatment. Without those lines, the registry build ofazure_storage_blobpins the previousazure_core, and two copies of the crate land in the graph. Two copies break the trait bounds that cross between the two crates. These lines return toworkspace = truewhenazure_core1.2.0 publishes.Related work
#4873 adds
BufferedProducerClient, which gives the crate a third public builder.BufferedProducerClientBuilderforwardsapplication_id,retry_options, andcustom_endpointtoProducerClient::builder(). It needs the same forward for the transport that this change adds. Without that one method, a buffered producer cannot use AMQP over WebSockets. Neither change blocks the other. The second change to merge adds the method.Both changes edit
producer/mod.rs,common/recoverable/connection.rs, and the changelog. The second change to merge therefore needs a small rebase. #4895 also editscommon/recoverable/connection.rs.Test plan
Five feature combinations of
azure_core_amqpbuild, and each build passes--no-default-features. The feature sets arefe2o3_amqp;fe2o3_amqpwithfe2o3_amqp_rustls;fe2o3_amqpwithfe2o3_amqp_ws;fe2o3_amqpwithfe2o3_amqp_wsandfe2o3_amqp_ws_rustls; and all four together. The default build passes, and--all-features --all-targetspasses. The base-only build namesfe2o3_amqp_wswith no TLS modifier, which is the combination that must compile without a stack.The unit tests pass.
azure_core_amqpruns 122 tests.azure_messaging_eventhubsruns 148 tests, and it ignores 14 live tests. 51 doc tests pass, 4 in the first crate and 47 in the second. The tests cover the WebSocket address construction, and they include an IPv6 case. They also cover the transport that reachesRecoverableConnection, the options thatAmqpConnection::openreceives, and the builder helper that everyopenpath reads. One test builds the TCP connector, which reports a missing crypto provider at test time instead of at connect time.cargo fmt --checkpasses, andcargo clippy --all-features --all-targetspasses. cspell passes against the repository configuration.cargo treeshows noringin the default graph.A live run against an Event Hubs namespace validates both transports. Three connection-string tests passed over TCP, which are
test_round_trip_connection_string,send_eventdata_with_connection_string, andconsumer_open_with_connection_string. A temporary test then opened a producer on each transport, read the hub properties, and sent an event. Both transports passed. Theeventhubs_websocket_transportsample opened a producer and a consumer withAmqpTransport::WebSocket, sent a tagged event, and read the event back. The process held one established socket during that run, to port 443. Theeventhubs_connection_stringsample keeps the default transport, and it ran against the same namespace as a control. That process held its socket on port 5671.A negative control proves that the supplied connector governs the handshake. The service certificate chains to a public authority, and both root sets carry that authority, so a passing test alone does not separate the new connector from the default connector. A temporary build replaced the platform verifier with an empty root store. The live round trip then failed with
invalid peer certificate: UnknownIssuer, and it passed again after the revert.A run from a network that blocks 5671 outbound is still open. A broker behind a private certificate authority has the negative control only, because the test namespace uses a public authority. An application that selects another TLS stack has build coverage only.