application-services/components/viaduct
Lina Butler d4272d9230 all: Fix remaining lint suggestions from Clippy 0.1.79. 2024-07-11 00:03:23 +00:00
..
android Use a Gradle version catalog for managing dependencies 2024-02-21 19:07:00 +00:00
ios Build iOS Rust code as an XCFramework. (#4396) 2021-08-20 12:44:56 -10:00
src all: Fix remaining lint suggestions from Clippy 0.1.79. 2024-07-11 00:03:23 +00:00
Cargo.toml Upgrade prost to 0.12 2023-09-13 18:54:00 +00:00
README.md Typos 2024-05-24 20:17:12 +00:00

README.md

Viaduct

Viaduct is our HTTP request library, which can make requests either via a rust-based (reqwest) networking stack (used on iOS and for local desktop use, for tests and the like), or using a stack that calls a function passed into it over the FFI (on android).

For usage info, you can run cargo +nightly doc -p viaduct (the +nightly is optional, however some intra-doc links require it), it has several examples.

Android/FFI Backend overview

On Android, the backend works as follows:

  1. During megazord initialization, we are passed a Lazy<Client> (Client comes from the concept-fetch android component, and Lazy is from the Kotlin stdlib).

    • It also sets a flag that indicates that even if the FFI backend never gets fully initialized (e.g. with a callback), we should error rather than use the reqwest backend (which should not be compiled in, however we've had trouble ensuring this in the past, although at this point we have checks in CI to ensure it is not present).
  2. At this point, a JNA Callback instance is created and passed into Rust.

    • This serves to proxy the request made by Rust to the Client.
    • The Callback instance is never allowed to be GCed.
    • To Rust, it's just a extern "C" function pointer that get's stored in an atomic variable and never can be unset.
  3. When Rust makes a request:

    1. We serialize the request info into a protobuf record
    2. This record is passed into the function pointer we should have by this point (erroring if it has not been set yet).
    3. The callback (on the Java side now) deserializes the protobuf record, converts it to a concept-fetch Request instance, and passes it to the client.
    4. The response (or error) is then converted into a protobuf record. The java code then asks Rust for a buffer big enough to hold the serialized response (or error).
    5. The response is written to the buffer, and returned to Rust.
    6. Rust then decodes the protobuf, and converts it to a viaduct::Response object that it returns to the caller.

Some notes:

  • This "request flow" is entirely synchronous, simplifying the implementation considerably.

  • Cookies are explicitly not supported at the moment, adding them would require a separate security review.

  • Generally, this is the way the FFI backend is expected to work on any platform, but for concreteness (and because it's the only one currently using the FFI backend), we explained it for Android.

  • Most of the code in viaduct is defining a ergonomic HTTP facade, and is unrelated to this (or to the reqwest backend). This code is more or less entirely (in the Kotlin layer and) in src/backend/ffi.rs.