Skip to content

Commit 5f60e78

Browse files
committed
Fairy bridge demo
This shows off a potential viaduct replacement that uses new UniFFI features. Check out `components/fairy-bridge/README.md` and `examples/fairy-bridge-demo/README.md` for details. Execute `examples/fairy-bridge-demo/run-demo.py` to test it out yourself. The UniFFI features are still a WIP. This is currently using a branch in my repo. The current plan for getting these into UniFFI main is: - Get the `0.26.0` release out the door - Merge PR #1818 into `main` - Merge my `async-trait-interfaces` branch into main (probably using a few smaller PRs) There is a C++ backend using libcurl. I think that necko shouldn't be any harder than this.
1 parent a8c15ba commit 5f60e78

File tree

20 files changed

+1447
-5
lines changed

20 files changed

+1447
-5
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ commands:
107107
- run: sudo apt-get install python tcl
108108
- run: sudo apt-get install python3-venv
109109
- run: sudo apt-get install libclang-dev
110+
- run: sudo apt-get install libssl-dev
110111
- run:
111112
name: Install NSS build system dependencies
112113
command: sudo apt-get install ninja-build gyp zlib1g-dev pip

Cargo.lock

Lines changed: 78 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"components/as-ohttp-client",
77
"components/autofill",
88
"components/crashtest",
9+
"components/fairy-bridge",
910
"components/fxa-client",
1011
"components/logins",
1112
"components/nimbus",

components/fairy-bridge/Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "fairy-bridge"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[features]
7+
default = []
8+
backend-reqwest = ["dep:reqwest"]
9+
backend-c = ["dep:oneshot"]
10+
11+
[dependencies]
12+
async-trait = "0.1"
13+
oneshot = { version = "0.1", optional = true }
14+
pollster = "0.3.0"
15+
serde = "1"
16+
serde_json = "1"
17+
thiserror = "1"
18+
tokio = { version = "1", features = ["rt-multi-thread"] }
19+
uniffi = { workspace = true }
20+
url = "2.2"
21+
reqwest = { version = "0.11.23", optional = true }
22+
23+
[build-dependencies]
24+
uniffi = { workspace = true, features = ["build"] }

components/fairy-bridge/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Fairy Bridge
2+
3+
Fairy Bridge is an HTTP request bridge library that allows requests to be made using various
4+
backends, including:
5+
6+
- The builtin reqwest backend
7+
- Custom Rust backends
8+
- Custom backends written in the foreign language
9+
10+
The plan for this is:
11+
- iOS will use the reqwest backend
12+
- Android will use a custom backend in Kotlin using fetch
13+
(https://github.com/mozilla-mobile/firefox-android/tree/35ce01367157440f9e9daa4ed48a8022af80c8f2/android-components/components/concept/fetch)
14+
- Desktop will use a custom backend in Rust that hooks into necko
15+
16+
## Sync / Async
17+
18+
The backends are implemented using async code, but there's also the option to block on a request.
19+
This means `fairy-bridge` can be used in both sync and async contexts.
20+
21+
## Cookies / State
22+
23+
Cookies and state are outside the scope of this library. Any such functionality is the responsibility of the consumer.
24+
25+
## Name
26+
27+
`fairy-bridge` is named after the Fairy Bridge (Xian Ren Qiao) -- the largest known natural bridge in the world, located in northwestern Guangxi Province, China.
28+
29+
![Picture of the Fairy Bridge](http://www.naturalarches.org/big9_files/FairyBridge1680.jpg)
30+
31+
# Backends
32+
33+
## Reqwest
34+
35+
- Handle requests using the Rust [reqwest library](https://docs.rs/reqwest/latest/reqwest/).
36+
- This backend creates a tokio thread to execute the requests.
37+
- Call `fairy_bridge::init_backend_reqwest` to select this backend.
38+
39+
## Foreign code
40+
41+
- The foreign code can implement a backend themselves by implementing the `fairy_bridge::Backend` trait.
42+
- Pass an instance of the object that implements the trait to `fairy_bridge::init_backend` to select this backend.
43+
44+
## C / C++ code
45+
46+
- A backend can also be implemented in C / C++ code
47+
- Include the `c-backend-include/fairy_bridge.h` file.
48+
- Implement the `fairy_bridge_backend_c_send_request` function.
49+
- Call `fairy_bridge::init_backend_c` to select this backend (from the bindings language, not C).
50+
- See `examples/fairy-bridge-demo` for a code example.
51+
52+
## (Coming soon) Necko backend
53+
54+
- The geckoview `libxul` library comes with a Necko-based c backend.
55+
- Link to `libxul` and call `fairy_bridge::init_backend_c` to select this backend.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#include <cstdint>
2+
3+
namespace fairy_bridge {
4+
5+
struct BackendSettings {
6+
uint32_t timeout;
7+
uint32_t connect_timeout;
8+
uint32_t redirect_limit;
9+
};
10+
11+
12+
enum class Method {
13+
Get,
14+
Head,
15+
Post,
16+
Put,
17+
Delete,
18+
Connect,
19+
Options,
20+
Trace,
21+
Patch,
22+
};
23+
24+
struct Header {
25+
const char* key;
26+
const char* value;
27+
};
28+
29+
struct Request {
30+
Method method;
31+
const char* url;
32+
Header* headers;
33+
size_t header_count;
34+
const char* body;
35+
size_t body_length;
36+
};
37+
38+
/**
39+
* Opaque HTTP result type
40+
*/
41+
struct Result;
42+
43+
/**
44+
* Calls used to build up a Result.
45+
*
46+
* Strings are passed as (const char*, size_t), pairs since this is often easier for backends to work with.
47+
*/
48+
extern "C" {
49+
void fairy_bridge_result_set_url(Result* result, const char* url, size_t length);
50+
void fairy_bridge_result_set_status_code(Result* result, uint16_t code);
51+
void fairy_bridge_result_add_header(Result* result, const char* key, size_t key_length, const char* value, size_t value_length);
52+
void fairy_bridge_result_extend_body(Result* result, const char* data, size_t length);
53+
}
54+
55+
/**
56+
* Complete a result
57+
*
58+
* Call this after the result has been successfully built using the previous methods. This
59+
* consumes the result pointer and it should not be used again by the backend.
60+
*/
61+
extern "C" {
62+
void fairy_bridge_result_complete(Result* result);
63+
}
64+
65+
/**
66+
* Complete a result with an error
67+
*
68+
* This causes an error to be returned for the result. Any previous builder calls will be
69+
* ignored. This consumes the result pointer and it should not be used again by the backend.
70+
*/
71+
extern "C" {
72+
void fairy_bridge_result_complete_error(Result* result, const char* message, size_t length);
73+
}
74+
75+
} // namespace fairy_bridge
76+
77+
/**
78+
* Backend API
79+
*
80+
* This must be implemented by the backend code.
81+
*/
82+
extern "C" {
83+
/**
84+
* Initialize the backend. This is called once at startup.
85+
*/
86+
void fairy_bridge_backend_c_init(fairy_bridge::BackendSettings settings);
87+
88+
/**
89+
* Perform a rquest
90+
*
91+
* The backend should schedule the request to be performed in a separate thread.
92+
*
93+
* The result is initially empty. It should be built up and completed by the
94+
* `fairy_bridge_result_*` functions.
95+
*
96+
* `request` and `result` are valid until `fairy_bridge_result_complete` or
97+
* `fairy_bridge_result_complete_error` is called. After that they should not be used.
98+
*/
99+
void fairy_bridge_backend_c_send_request(fairy_bridge::Request* request, fairy_bridge::Result* result);
100+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use crate::{FairyBridgeError, Request, Response};
6+
use std::sync::Arc;
7+
8+
/// Settings for a backend instance
9+
///
10+
/// Backend constructions should input this in order to configure themselves
11+
///
12+
/// Repr(C) so we can pass it to the C backend
13+
#[derive(Debug, uniffi::Record)]
14+
#[repr(C)]
15+
pub struct BackendSettings {
16+
// Connection timeout in ms (0 indicates no timeout).
17+
#[uniffi(default = 0)]
18+
pub connect_timeout: u32,
19+
// Timeout for the entire request in ms (0 indicates no timeout).
20+
#[uniffi(default = 0)]
21+
pub timeout: u32,
22+
// Maximum amount of redirects to follow (0 means redirects are not allowed)
23+
#[uniffi(default = 10)]
24+
pub redirect_limit: u32,
25+
}
26+
27+
#[uniffi::export(with_foreign)]
28+
#[async_trait::async_trait]
29+
pub trait Backend: Send + Sync {
30+
async fn send_request(self: Arc<Self>, request: Request) -> Result<Response, FairyBridgeError>;
31+
}
32+
33+
#[uniffi::export]
34+
pub fn init_backend(backend: Arc<dyn Backend>) -> Result<(), FairyBridgeError> {
35+
crate::REGISTERED_BACKEND
36+
.set(backend)
37+
.map_err(|_| FairyBridgeError::BackendAlreadyInitialized)
38+
}

0 commit comments

Comments
 (0)