Repository navigation
add ConnectOptions::with_websocket_config for MqttClient (#165) - #210
Conversation
…ade request (LabOverWire#165) WebSocketTransport::connect built the upgrade request from a fixed set of headers and always offered Sec-WebSocket-Protocol: mqtt, so with_header, with_subprotocol(s) and with_user_agent never reached the server. Request construction moves to WebSocketConfig::build_handshake_request, which starts from tungstenite's into_client_request (fixing a Host header that dropped non-default ports) and adds the configured subprotocols, user agent and custom headers. Invalid, reserved or case-duplicated headers and non-token subprotocols are rejected before dialing. WebSocketConfig's Debug no longer prints header values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LabOverWire#165) Content-Length, Transfer-Encoding and Sec-WebSocket-Accept are now rejected as custom handshake headers alongside the existing reserved names. Custom headers are validated and sent in name order, so the request and the reported error do not depend on HashMap iteration. The integration test checks Host against the exact bound address, and the CHANGELOG calls out the changes that can affect existing configurations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erWire#190) With server-certificate verification on, WebSocketTransport::connect used tokio-tungstenite's own client config built from the platform roots, so a configured CA, client certificate, use_system_roots and ALPN protocols were ignored. With verification off, the client certificate and ALPN were dropped. The rustls client config construction moves from TlsTransport to a public TlsConfig::client_config, and a wss:// connection with a tls_config now passes that config to tokio-tungstenite in both cases. Without a tls_config, the platform roots are still used. The WSS module's duplicate NoVerifier is replaced by the shared NoVerification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S config over wss (LabOverWire#190) with_tls_auto parsed the URL's host and port as a socket address, so it and the with_ca_cert_* and with_client_auth_* methods that call it failed for any host name. A wss:// connection takes its server and certificate name from the URL, so a host name now gets the unspecified address with the URL's port. MqttClient's wss:// path built its WebSocketConfig from the URL alone and ignored the TLS configuration stored by set_tls_config; it now applies it as connect_tls does. New tests cover a host name URL, the URL host being verified rather than TlsConfig::hostname, and MqttClient presenting its stored client certificate, and two rejection tests now assert UnknownIssuer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…headers (LabOverWire#165) Review follow-up. The configured subprotocols are now followed by "mqtt" when the list does not include it, as MQTT-6.0.0-3 requires: the broker in this crate only selects a subprotocol the client offers, so a config preferring mqttv5.0 failed with "Server sent no subprotocol". A subprotocol listed twice is rejected (RFC 6455 §4.1), and TE, Trailer, Keep-Alive, Proxy-Connection and Expect join the reserved headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
LabOverWire#190) Review follow-up. With a tls_config and use_system_roots, wss:// now loads the platform's root certificates through rustls-native-certs, as tokio-tungstenite already did without a tls_config, instead of the bundled webpki-roots, so a CA installed on the machine keeps working for an ALPN-only config or MqttClient::set_tls_config(cert, key, None). TlsTransport keeps webpki-roots; roots are only loaded when the server certificate is verified. MqttClient's wss:// path no longer offers the stored config's ALPN protocols, which select MQTT directly over TLS; a WebSocketConfig's own ALPN is still offered, and the CHANGELOG notes that a server advertising only http/1.1 rejects an MQTT one. TlsConfig::addr and with_tls_auto document the 0.0.0.0 placeholder, and the private key is copied with clone_key() rather than an expect. The wss test server moves to a shared module for the new SSL_CERT_FILE test binary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#165) MqttClient built each connection's WebSocketConfig from the URL alone, so its ws:// and wss:// connections could not send custom handshake headers, subprotocols or a user agent. ConnectOptions::with_websocket_config stores a WebSocketConfig that the client reads on every connection attempt, including automatic reconnects, with the URL replaced by the address passed to connect. For wss://, its TLS configuration, when set, is used in place of the stored one; insecure_tls still disables verification. WebSocketConfig now implements Clone, and is held behind an Arc so the connect futures stay small. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
fabracht
left a comment
There was a problem hiding this comment.
Thanks, Bryan, this is what I had in mind. I ran an MqttClient with with_websocket_config against our ws listener, with a placeholder URL, mqttv5.0 and a custom header, and it connects, subscribes and gets its message. Your two tests fail without the client/inner.rs change, as you said.
I made the last few changes myself so we can get it in:
- Merged main into your branch for the conflicts #189 and #191 left behind. Your code is unchanged.
ConnectOptionshas public fields and no#[non_exhaustive], so the new field breaks code that builds it with a struct literal. I checked with a small crate outside the workspace: a literal that compiles against main fails here withmissing field websocket_config, and only whentransport-websocketis on. I added a Breaking entry for it to the CHANGELOG, like the one forresume_existing_sessionin 0.41.0, and it will go out in a minor release.- We run clippy with
-W clippy::pedantic, and with the biggerConnectOptionsit flagged nine test calls as large futures, inquic_multistream_integration.rs,broker_quic_integration.rsandclean_start_prebind_subscribe.rs. They're wrapped inBox::pinnow. parse_websocket_urlhad landed betweenis_http_tokenand its doc comment, so I moved the comment back.
Nothing left for you to do here, merging once CI passes.
|
Thanks Bryan, this is merged now. We added one thing before merging. The client follows a CONNACK redirect (UseAnotherServer or ServerMoved) on its own, and the redirected connection was reusing the WebSocket config, so the custom headers (an Authorization header, for example) went to whatever server the redirect named. Now only the address passed to The red Rust CI run on your fork came from a timing test of ours that counted backend setup time as lock time. That's fixed on main in #216 and has nothing to do with your change. It'll go out in the next release. |
Part of #165, as agreed there: a static WebSocket configuration on
ConnectOptions, applied on every connect and reconnect. The request modifier will come separately.Builds on #189 and #191: the headers only reach the wire with #189, and this edits the client's
wss://path that #191 rewrote. Until those merge, the diff includes their commits; the change here is the last commit.Changes
ConnectOptions::with_websocket_config(WebSocketConfig), stored asOption<Arc<WebSocketConfig>>(behindtransport-websocket). AnArckeeps the connect futures under clippy'slarge_futureslimit.MqttClientreads it on every connection attempt.connect_websocketandconnect_websocket_secureread it from the client's current options, so automatic reconnects use it too. The URL is replaced by the address passed toconnect(crate-privateWebSocketConfig::with_url, which validates the scheme likenew), and the headers, subprotocols, user agent and timeout are kept.WebSocketConfigis used as given, except thatset_insecure_tls(true)still disables verification. Without one, the stored config fromset_tls_config/connect_with_tlsapplies as in use WebSocketConfig's TLS configuration for wss:// connections (#190) #191.WebSocketConfigimplementsClone. ItsDebugstill redacts header values, andConnectOptions'Debugnow includes it.Tests
tests/client_websocket_config.rs:websocket_config_is_sent_on_connect_and_on_reconnect. A loopback ws:// server captures each upgrade request, answers CONNECT with a CONNACK, then closes the connection so the client reconnects. Both requests carry the configuredAuthorization,mqttv5.0, mqttandUser-Agent, andHostis the address passed toconnectrather than the config's placeholder URL.websocket_config_tls_is_used_for_wss. With no stored TLS config, the client only completes the wss handshake if theWebSocketConfig's CA is used.Both fail without the
client/inner.rschange.Locally, on the pinned 1.98.0 toolchain, fmt, clippy (workspace, wasm, and the lib with no features or a single feature),
cargo doc -D warningsand the doctests pass. The full suite passes apart from the same machine-only failures noted on #189 and #191. Fork CI: bryan-ashby#3.🤖 Generated with Claude Code