Skip to content

PR 4721: Schema-serde RpcV2Cbor client sends requests to / instead of the RPC operation route #4801

Description

@drganjoo

Summary

When the schema-based client generator is enabled for smithy.protocols#rpcv2Cbor, a generated client can serialize a valid CBOR body but send the request to /.

The generated RPC v2 CBOR server expects the canonical route:

/service/PokemonService/operation/GetServerStatistics

The server therefore returns 404.

The legacy per-shape client generated from the same model sends the canonical route and successfully calls the same server. This isolates the problem to the schema-based client request path. It is not a general aws-smithy-cbor codec or server interoperability problem.

Environment

This was reproduced from PR #4721 at commit 043eae3c7.

git clone https://github.com/smithy-lang/smithy-rs.git
cd smithy-rs
gh pr checkout 4721
git checkout 043eae3c7

Reproduction branch

A complete reproduction, including the generated SDKs, adapted Pokémon server, and rpc-v2-cbor-cli, is available at: https://github.com/smithy-lang/smithy-rs/tree/fahadzub/4721-error

Change the Pokémon model to RPC v2 CBOR

codegen-core/common-test-models/pokemon.smithy

Replace the RestJson1 service protocol with RPC v2 CBOR and leave the service body unchanged:

-use aws.protocols#restJson1
+use smithy.protocols#rpcv2Cbor

-@restJson1
+@rpcv2Cbor

The existing operation-level @http traits can remain in the model. RPC v2 CBOR ignores those bindings and uses its canonical RPC route. Their presence is important to this reproduction because the schema request generator currently treats them as a reason to pass an empty endpoint to the runtime protocol.

Generate the client and server SDKs

cd examples
make codegen

The generated SDKs are copied to:

  • examples/pokemon-service-client
  • examples/pokemon-service-server-sdk

Required server example adaptations

1. Import runtime body types directly

The newly generated server SDK does not re-export ByteStream or SdkBody from pokemon_service_server_sdk::types.

Add this dependency to:

examples/pokemon-service-common/Cargo.toml

aws-smithy-types = { path = "../../rust-runtime/aws-smithy-types" }

Update the imports in:

examples/pokemon-service-common/src/lib.rs

use aws_smithy_types::{body::SdkBody, byte_stream::ByteStream};

use pokemon_service_server_sdk::{
    error, input,
    model::{self, CapturingPayload},
    output,
    server::Extension,
    types::Blob,
};

2. Buffer the radio response into Blob

Replace the return construction in stream_pokemon_radio with:

let data = ByteStream::new(result.into_body())
    .collect()
    .await
    .expect("failed to read radio stream")
    .into_bytes();

output::StreamPokemonRadioOutput {
    data: Blob::from_maybe_shared(data),
}

Confirm the server builds:

cd examples
cargo check --quiet -p pokemon-service-common
cargo check --quiet -p pokemon-service

Add the real RPC v2 CBOR CLI

Add the direct runtime protocol dependency to:

examples/pokemon-service-client-usage/Cargo.toml

aws-smithy-cbor = { path = "../../rust-runtime/aws-smithy-cbor" }

Create:

examples/pokemon-service-client-usage/examples/rpc-v2-cbor-cli.rs

use aws_smithy_cbor::protocol::RpcV2CborProtocol;

#[tokio::main]
async fn main() {
    let config = pokemon_service_client::Config::builder()
        .endpoint_url("http://localhost:13734")
        .protocol(RpcV2CborProtocol::new())
        .build();
    let client = pokemon_service_client::Client::from_conf(config);

    let result = client.get_server_statistics().send().await;
    println!("result={result:#?}");
}

Confirm the CLI builds:

cd examples
cargo check -p pokemon-service-client-usage  --example rpc-v2-cbor-cli

Start the server

Run the server in the first terminal:

cd examples
cargo run --bin pokemon-service

Leave this process running on 127.0.0.1:13734.

Invoke the server

Run the CLI in a second terminal:

cd examples
cargo run --quiet \
  -p pokemon-service-client-usage \
  --example rpc-v2-cbor-cli

Actual result

The request reaches the server, but the server returns 404:

result=Err(
    ServiceError(
        ServiceError {
            source: Unhandled(...),
            raw: Response {
                status: StatusCode(
                    404,
                ),
                headers: {
                    "content-type": "application/cbor",
                    "content-length": "0",
                    ...
                },
                ...
            },
        },
    ),
)

Expected result

result=Ok(
    GetServerStatisticsOutput {
        calls_count: 0,
    },
)

The exact count can differ if the server has already handled requests.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions