Add initial scaffolding

Signed-off-by: José Ulises Niño Rivera <junr03@users.noreply.github.com>
This commit is contained in:
José Ulises Niño Rivera 2024-07-10 10:06:02 -07:00
parent 71eccbe2eb
commit cd2a1db493
8 changed files with 376 additions and 18 deletions

114
envoyfilter/src/lib.rs Normal file
View file

@ -0,0 +1,114 @@
use log::info;
use std::time::Duration;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Trace);
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
Box::new(HttpHeaderRoot {
header_content: String::new(),
})
});
}}
struct HttpHeader {
context_id: u32,
header_content: String,
}
// HttpContext is the trait that allows the Rust code to interact with HTTP objects.
impl HttpContext for HttpHeader {
// Envoy's HTTP model is event driven. The WASM ABI has given implementors events to hook onto
// the lifecycle of the http request and response.
fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
// Example of reading the HTTP headers on the incoming request
for (name, value) in &self.get_http_request_headers() {
info!("#{} -> {}: {}", self.context_id, name, value);
}
// Example logic of branching based on a request header.
match self.get_http_request_header(":path") {
// If the path header is present and the path is /inline
Some(path) if path == "/inline" => {
// Dispatch an HTTP call inline. This is the model that we will use for the LLM routing host.
self.dispatch_http_call(
"httpbin",
vec![
(":method", "GET"),
(":path", "/bytes/1"),
(":authority", "httpbin.org"),
],
None,
vec![],
Duration::from_secs(5),
)
.unwrap();
// Pause the filter until the out of band HTTP response arrives.
Action::Pause
}
// Otherwise let the HTTP request continue.
_ => Action::Continue,
}
}
fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
// Note that the filter can add custom headers. In this case the header is coming from a config value.
self.add_http_response_header("custom-header", self.header_content.as_str());
Action::Continue
}
}
impl Context for HttpHeader {
// Note that the event driven model continues here from the return of the on_http_request_headers above.
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.");
// This call allows the filter to continue operating on the HTTP request sent by the user.
// In Katanemo's use case the call would continue after the LLM host has responded with routing
// decisions.
self.resume_http_request();
return;
}
}
info!("Access forbidden.");
// This is an example of short-circuiting the http request and sending back a response to the client.
// i.e there was never an external HTTP request made. This could be used for example if the user prompt requires
// more information before it can be sent out to a third party API.
self.send_http_response(
403,
vec![("Powered-By", "Katanemo")],
Some(b"Access forbidden.\n"),
);
}
}
struct HttpHeaderRoot {
header_content: String,
}
impl Context for HttpHeaderRoot {}
// RootContext allows the Rust code to reach into the Envoy Config
impl RootContext for HttpHeaderRoot {
fn on_configure(&mut self, _: usize) -> bool {
if let Some(config_bytes) = self.get_plugin_configuration() {
self.header_content = String::from_utf8(config_bytes).unwrap()
}
true
}
fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
Some(Box::new(HttpHeader {
context_id,
header_content: self.header_content.clone(),
}))
}
fn get_type(&self) -> Option<ContextType> {
Some(ContextType::HttpContext)
}
}

View file

@ -1,17 +0,0 @@
fn main() {
println!("Hello, world!\nMy favourite number is {}", some_fn());
}
fn some_fn() -> i32 {
42
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn some_fn_is_42() {
assert_eq!(some_fn(), 42);
}
}