summaryrefslogtreecommitdiffstats
path: root/third_party/rust/viaduct/README.md
blob: fd6adf51714a0a8eb14e58d9f5320c73e1b00777 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 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](https://github.com/mozilla-mobile/android-components/tree/master/components/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 explicitely 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`.