Skip to main content

BidiStream

Struct BidiStream 

Source
pub struct BidiStream<B, Req, RespView> { /* private fields */ }
Expand description

A bidirectional streaming RPC in progress.

Returned from call_bidi_stream. Provides a send/close_send/message API modeled on connect-go’s BidiStreamForClient.

§Half-duplex vs full-duplex

The Connect spec supports both. Half-duplex (send all, then receive all) works on HTTP/1.1 and HTTP/2. Full-duplex (interleaved send/receive) requires HTTP/2. This type does not distinguish — it’s the caller’s responsibility to respect the protocol in use. On HTTP/1.1, calling message() before close_send() will block until the request body is complete.

To drive the two sides from separate tasks, split the stream into independently owned halves with into_split().

§Cancellation

Dropping the BidiStream cancels the call: any in-flight initialization task is aborted, which resets the underlying transport stream. Request messages accepted by send() but not yet transmitted may never reach the server — a caller that needs the request delivered must drive the call to completion via message() before dropping. Cancelling an individual message() future is safe and resumable — see message().

§Example

let mut stream = call_bidi_stream(&transport, &config, "svc", "method", CallOptions::default()).await?;
stream.send(request1).await?;
stream.send(request2).await?;
stream.close_send();
// `Ok(None)` means a clean end; a failed RPC surfaces as `Err`,
// so `?` is the complete error handling.
while let Some(msg) = stream.message().await? {
    println!("got: {msg:?}");
}

Implementations§

Source§

impl<B, Req, RespView> BidiStream<B, Req, RespView>

Source

pub fn into_split(self) -> (BidiSendHalf<Req>, BidiRecvHalf<B, RespView>)

Split the stream into independently owned send and receive halves, so the two sides can be driven from separate tasks (full duplex).

Interleaved, response-dependent use — receiving an answer before sending the next message — requires an HTTP/2 transport, exactly as with an unsplit stream: on HTTP/1.1 no response arrives until the request body is complete, so a task waiting on the other half’s progress deadlocks. Prefer moving each half into its own spawned task (as below) over storing them in named struct fields — the halves’ full type parameters include the transport body type, which task-local inference names for you.

The halves are plain moves of the stream’s two sides — no locking is added — and there is no way to reassemble them. Semantics carried by each half:

  • Dropping the BidiSendHalf (or calling close_send()) ends the request body cleanly; the RPC continues until the receive half finishes.
  • Dropping the BidiRecvHalf cancels the RPC — as when dropping a whole BidiStream — after which sends on the other half fail.
  • When send() fails because the server closed the stream, the server’s error is retrieved from the receive half via message().
§Example
let (mut send, mut recv) = stream.into_split();
let reader = tokio::spawn(async move {
    while let Some(msg) = recv.message().await? {
        println!("got: {msg:?}");
    }
    Ok::<_, connectrpc::ConnectError>(())
});
for req in requests {
    send.send(req).await?;
}
send.close_send();
reader.await.expect("reader task")?;
Source§

impl<B, Req, RespView> BidiStream<B, Req, RespView>
where B: Body<Data = Bytes> + Send + Unpin, B::Error: Display, Req: Message + JsonSerialize, RespView: MessageView<'static> + Send, RespView::Owned: Message + JsonDeserialize,

Source

pub async fn send(&mut self, msg: Req) -> Result<(), ConnectError>

Send a request message.

§Errors

See BidiSendHalf::send for the error contract.

Source

pub fn close_send(&mut self)

Close the send side of the stream. Idempotent. See BidiSendHalf::close_send.

Source

pub async fn message<M>( &mut self, ) -> Result<Option<StreamMessage<M>>, ConnectError>
where B: 'static, RespView: MessageView<'static, Owned = M> + 'static, M: HasMessageView<View<'static> = RespView>,

Receive the next response message.

§Errors

See BidiRecvHalf::message for the full contract.

Source

pub fn headers(&self) -> Option<&HeaderMap>

Response headers. See BidiRecvHalf::headers.

Source

pub fn trailers(&self) -> Option<&HeaderMap>

Trailing metadata. See BidiRecvHalf::trailers.

Source

pub fn error(&self) -> Option<&ConnectError>

Terminal error that ended the stream, if any. See BidiRecvHalf::error.

Trait Implementations§

Source§

impl<B, Req, RespView> Debug for BidiStream<B, Req, RespView>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<B, Req, RespView> !RefUnwindSafe for BidiStream<B, Req, RespView>

§

impl<B, Req, RespView> !UnwindSafe for BidiStream<B, Req, RespView>

§

impl<B, Req, RespView> Freeze for BidiStream<B, Req, RespView>

§

impl<B, Req, RespView> Send for BidiStream<B, Req, RespView>
where Req: Send, B: Send, RespView: Send,

§

impl<B, Req, RespView> Sync for BidiStream<B, Req, RespView>
where Req: Sync, B: Send + Sync, RespView: Sync + Send,

§

impl<B, Req, RespView> Unpin for BidiStream<B, Req, RespView>
where Req: Unpin,

§

impl<B, Req, RespView> UnsafeUnpin for BidiStream<B, Req, RespView>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more