Skip to content

Commit

Permalink
Fairy bridge demo
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bendk committed Jun 21, 2024
1 parent a8c15ba commit d1d6654
Show file tree
Hide file tree
Showing 21 changed files with 1,448 additions and 6 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ commands:
- run: sudo apt-get install python tcl
- run: sudo apt-get install python3-venv
- run: sudo apt-get install libclang-dev
- run: sudo apt-get install libssl-dev
- run:
name: Install NSS build system dependencies
command: sudo apt-get install ninja-build gyp zlib1g-dev pip
Expand Down
83 changes: 78 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"components/as-ohttp-client",
"components/autofill",
"components/crashtest",
"components/fairy-bridge",
"components/fxa-client",
"components/logins",
"components/nimbus",
Expand Down
24 changes: 24 additions & 0 deletions components/fairy-bridge/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "fairy-bridge"
version = "0.1.0"
edition = "2021"

[features]
default = []
backend-reqwest = ["dep:reqwest"]
backend-c = ["dep:oneshot"]

[dependencies]
async-trait = "0.1"
oneshot = { version = "0.1", optional = true }
pollster = "0.3.0"
serde = "1"
serde_json = "1"
thiserror = "1"
tokio = { version = "1", features = ["rt-multi-thread"] }
uniffi = { workspace = true }
url = "2.2"
reqwest = { version = "0.11.23", optional = true }

[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
55 changes: 55 additions & 0 deletions components/fairy-bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Fairy Bridge

Fairy Bridge is an HTTP request bridge library that allows requests to be made using various
backends, including:

- The builtin reqwest backend
- Custom Rust backends
- Custom backends written in the foreign language

The plan for this is:
- iOS will use the reqwest backend
- Android will use a custom backend in Kotlin using fetch
(https://github.com/mozilla-mobile/firefox-android/tree/35ce01367157440f9e9daa4ed48a8022af80c8f2/android-components/components/concept/fetch)
- Desktop will use a custom backend in Rust that hooks into necko

## Sync / Async

The backends are implemented using async code, but there's also the option to block on a request.
This means `fairy-bridge` can be used in both sync and async contexts.

## Cookies / State

Cookies and state are outside the scope of this library. Any such functionality is the responsibility of the consumer.

## Name

`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.

![Picture of the Fairy Bridge](http://www.naturalarches.org/big9_files/FairyBridge1680.jpg)

# Backends

## Reqwest

- Handle requests using the Rust [reqwest library](https://docs.rs/reqwest/latest/reqwest/).
- This backend creates a tokio thread to execute the requests.
- Call `fairy_bridge::init_backend_reqwest` to select this backend.

## Foreign code

- The foreign code can implement a backend themselves by implementing the `fairy_bridge::Backend` trait.
- Pass an instance of the object that implements the trait to `fairy_bridge::init_backend` to select this backend.

## C / C++ code

- A backend can also be implemented in C / C++ code
- Include the `c-backend-include/fairy_bridge.h` file.
- Implement the `fairy_bridge_backend_c_send_request` function.
- Call `fairy_bridge::init_backend_c` to select this backend (from the bindings language, not C).
- See `examples/fairy-bridge-demo` for a code example.

## (Coming soon) Necko backend

- The geckoview `libxul` library comes with a Necko-based c backend.
- Link to `libxul` and call `fairy_bridge::init_backend_c` to select this backend.
100 changes: 100 additions & 0 deletions components/fairy-bridge/c-backend-include/fairy_bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include <cstdint>

namespace fairy_bridge {

struct BackendSettings {
uint32_t timeout;
uint32_t connect_timeout;
uint32_t redirect_limit;
};


enum class Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
};

struct Header {
const char* key;
const char* value;
};

struct Request {
Method method;
const char* url;
Header* headers;
size_t header_count;
const char* body;
size_t body_length;
};

/**
* Opaque HTTP result type
*/
struct Result;

/**
* Calls used to build up a Result.
*
* Strings are passed as (const char*, size_t), pairs since this is often easier for backends to work with.
*/
extern "C" {
void fairy_bridge_result_set_url(Result* result, const char* url, size_t length);
void fairy_bridge_result_set_status_code(Result* result, uint16_t code);
void fairy_bridge_result_add_header(Result* result, const char* key, size_t key_length, const char* value, size_t value_length);
void fairy_bridge_result_extend_body(Result* result, const char* data, size_t length);
}

/**
* Complete a result
*
* Call this after the result has been successfully built using the previous methods. This
* consumes the result pointer and it should not be used again by the backend.
*/
extern "C" {
void fairy_bridge_result_complete(Result* result);
}

/**
* Complete a result with an error
*
* This causes an error to be returned for the result. Any previous builder calls will be
* ignored. This consumes the result pointer and it should not be used again by the backend.
*/
extern "C" {
void fairy_bridge_result_complete_error(Result* result, const char* message, size_t length);
}

} // namespace fairy_bridge

/**
* Backend API
*
* This must be implemented by the backend code.
*/
extern "C" {
/**
* Initialize the backend. This is called once at startup.
*/
void fairy_bridge_backend_c_init(fairy_bridge::BackendSettings settings);

/**
* Perform a rquest
*
* The backend should schedule the request to be performed in a separate thread.
*
* The result is initially empty. It should be built up and completed by the
* `fairy_bridge_result_*` functions.
*
* `request` and `result` are valid until `fairy_bridge_result_complete` or
* `fairy_bridge_result_complete_error` is called. After that they should not be used.
*/
void fairy_bridge_backend_c_send_request(fairy_bridge::Request* request, fairy_bridge::Result* result);
}
38 changes: 38 additions & 0 deletions components/fairy-bridge/src/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::{FairyBridgeError, Request, Response};
use std::sync::Arc;

/// Settings for a backend instance
///
/// Backend constructions should input this in order to configure themselves
///
/// Repr(C) so we can pass it to the C backend
#[derive(Debug, uniffi::Record)]
#[repr(C)]
pub struct BackendSettings {
// Connection timeout in ms (0 indicates no timeout).
#[uniffi(default = 0)]
pub connect_timeout: u32,
// Timeout for the entire request in ms (0 indicates no timeout).
#[uniffi(default = 0)]
pub timeout: u32,
// Maximum amount of redirects to follow (0 means redirects are not allowed)
#[uniffi(default = 10)]
pub redirect_limit: u32,
}

#[uniffi::export(with_foreign)]
#[async_trait::async_trait]
pub trait Backend: Send + Sync {
async fn send_request(self: Arc<Self>, request: Request) -> Result<Response, FairyBridgeError>;
}

#[uniffi::export]
pub fn init_backend(backend: Arc<dyn Backend>) -> Result<(), FairyBridgeError> {
crate::REGISTERED_BACKEND
.set(backend)
.map_err(|_| FairyBridgeError::BackendAlreadyInitialized)
}
Loading

0 comments on commit d1d6654

Please sign in to comment.