higress
higress copied to clipboard
feat: implements Promise for dispatch callback
â… . Describe what this PR did
In the current envoy WASM plugins, if we need IO request such as HTTP/GRPC/Redis, we must register request to envoy event loop and assigned a token via such as dispatch_http_call
and WASM plugin should yield current request lifetime using Action::Pause
. When the IO request completed, the envoy will callback to WASM plugin via on_http_call_response
, and plugin should dispatch response using token (or ignored if single IO request). If we want to share something between dispatch_http_call
and on_http_call_response
, we must share them in plugin context fields, it's not a suitable scope.
Here is an example from proxy-wasm-rust-sdk
impl HttpContext for HttpAuthRandom {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
self.dispatch_http_call(
"httpbin",
vec![
(":method", "GET"),
(":path", "/bytes/1"),
(":authority", "httpbin.org"),
],
None,
vec![],
Duration::from_secs(1),
)
.unwrap();
Action::Pause
}
fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
self.set_http_response_header("Powered-By", Some("proxy-wasm"));
Action::Continue
}
}
impl Context for HttpAuthRandom {
fn on_http_call_response(&mut self, _: u32, _: usize, body_size: usize, _: usize) {
if let Some(body) = self.get_http_call_response_body(0, body_size) {
if !body.is_empty() && body[0] % 2 == 0 {
info!("Access granted.");
self.resume_http_request();
return;
}
}
info!("Access forbidden.");
self.send_http_response(
403,
vec![("Powered-By", "proxy-wasm")],
Some(b"Access forbidden.\n"),
);
}
}
So there is three major problem we need to resolve:
- How to easier dispatch request and callback response via token.
- How to share something between dispatch request and callback response with smaller scope.
- How to make code more fluid instead of spreading logic in different places
In Rust async programming, normally we use async/await
for IO request, but in envoy WASM plugin, there is no executor to poll future. A suitable solution is providing JavaScript style Promise (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). With Promise, we could write all logic in single function
impl HttpContext for HttpAuthRandom {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
let token = self.dispatch_http_call(
"httpbin",
vec![
(":method", "GET"),
(":path", "/bytes/1"),
(":authority", "httpbin.org"),
],
None,
vec![],
Duration::from_secs(1),
)
.unwrap();
let promise = Promise::new();
// make relation between promise and request (token)
promise.then(|(_, _, body_size, _)| {
if let Some(body) = hostcalls::get_http_call_response_body(0, body_size) {
if !body.is_empty() && body[0] % 2 == 0 {
info!("Access granted.");
hostcalls::resume_http_request();
return;
}
}
info!("Access forbidden.");
hostcalls::send_http_response(
403,
vec![("Powered-By", "proxy-wasm")],
Some(b"Access forbidden.\n"),
);
})
Action::Pause
}
fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
self.set_http_response_header("Powered-By", Some("proxy-wasm"));
Action::Continue
}
}
It seems more fluid then writing in callback of on_http_call_response
, but there is no executor for promise to trigger state transferring. We can use on_http_call_response
as trigger simply
struct HttpAuthRandom {
promise: Rc<Promise<(u32, usize, usize, usize)>>
}
impl HttpContext for HttpAuthRandom {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
// ...
// make relation between promise and request (token)
self.promise = promise.clone();
// ...
}
}
impl Context for HttpAuthRandom {
fn on_http_call_response(&mut self, _token_id: u32, _num_headers: usize, _body_size: usize, _num_trailers: usize) {
self.promise.fulfill((_token_id, _num_headers, _body_size, _num_trailers))
}
}
As for making relationship between multi tokens and promises, we could just using HashMap
with insert/remove
(maybe it should be embed in SDK but not belongs to this PR)
struct HttpAuthRandom {
m: HashMap<u32, Rc<Promise<(u32, usize, usize, usize)>>
}
impl HttpContext for HttpAuthRandom {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
// ...
// make relation between promise and request (token)
self.m.insert(token, promise.clone());
// ...
}
}
impl Context for HttpAuthRandom {
fn on_http_call_response(&mut self, _token_id: u32, _num_headers: usize, _body_size: usize, _num_trailers: usize) {
let promise = self.m.remove(token);
promise.fulfill((_token_id, _num_headers, _body_size, _num_trailers))
}
}