-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
383 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
envoyfilter/target |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,66 @@ | ||
Envoy filter code for gateway | ||
# Envoy filter code for gateway | ||
|
||
## Add toolchain | ||
|
||
```sh | ||
$ rustup target add wasm32-wasi | ||
``` | ||
|
||
## Building | ||
|
||
```sh | ||
$ cargo build --target wasm32-wasi --release | ||
``` | ||
|
||
## Using in Envoy | ||
|
||
This example can be run with [`docker compose`](https://docs.docker.com/compose/install/) | ||
and has a matching Envoy configuration. | ||
|
||
```sh | ||
$ docker compose up | ||
``` | ||
|
||
## Examples | ||
|
||
### Direct response. | ||
|
||
Send HTTP request to `localhost:10000/hello`: | ||
|
||
```sh | ||
$ curl localhost:10000/hello | ||
``` | ||
|
||
Expected response: | ||
|
||
```console | ||
HTTP/1.1 200 OK | ||
content-length: 40 | ||
content-type: text/plain | ||
custom-header: katanemo filter | ||
date: Wed, 10 Jul 2024 16:59:43 GMT | ||
server: envoy | ||
``` | ||
|
||
### Inline call. | ||
|
||
Send HTTP request to `localhost:10000/inline`: | ||
|
||
```sh | ||
$ curl localhost:10000/hello | ||
{ | ||
"headers": { | ||
"Accept": "*/*", | ||
"Host": "localhost", | ||
"User-Agent": "curl/7.81.0", | ||
"X-Amzn-Trace-Id": "Root=1-637c4767-6e31776a0b407a0219b5b570", | ||
"X-Envoy-Expected-Rq-Timeout-Ms": "15000" | ||
} | ||
} | ||
``` | ||
|
||
Expected Envoy logs: | ||
|
||
```console | ||
[...] wasm log http_auth_random: Access granted. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
services: | ||
envoy: | ||
image: envoyproxy/envoy:v1.30-latest | ||
hostname: envoy | ||
ports: | ||
- "10000:10000" | ||
volumes: | ||
- ./envoy.yaml:/etc/envoy/envoy.yaml | ||
- ./target/wasm32-wasi/release:/etc/envoy/proxy-wasm-plugins | ||
networks: | ||
- envoymesh | ||
networks: | ||
envoymesh: {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
static_resources: | ||
listeners: | ||
address: | ||
socket_address: | ||
address: 0.0.0.0 | ||
port_value: 10000 | ||
filter_chains: | ||
- filters: | ||
- name: envoy.filters.network.http_connection_manager | ||
typed_config: | ||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager | ||
stat_prefix: ingress_http | ||
codec_type: AUTO | ||
route_config: | ||
name: local_routes | ||
virtual_hosts: | ||
- name: local_service | ||
domains: | ||
- "*" | ||
routes: | ||
- match: | ||
prefix: "/inline" | ||
route: | ||
cluster: httpbin | ||
- match: | ||
prefix: "/" | ||
direct_response: | ||
status: 200 | ||
body: | ||
inline_string: "Inspect the HTTP header: custom-header.\n" | ||
http_filters: | ||
- name: envoy.filters.http.wasm | ||
typed_config: | ||
"@type": type.googleapis.com/udpa.type.v1.TypedStruct | ||
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm | ||
value: | ||
config: | ||
name: "http_config" | ||
configuration: | ||
"@type": "type.googleapis.com/google.protobuf.StringValue" | ||
value: katanemo filter | ||
vm_config: | ||
runtime: "envoy.wasm.runtime.v8" | ||
code: | ||
local: | ||
filename: "/etc/envoy/proxy-wasm-plugins/intelligent_prompt_gateway.wasm" | ||
- name: envoy.filters.http.router | ||
typed_config: | ||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router | ||
|
||
clusters: | ||
- name: httpbin | ||
connect_timeout: 5s | ||
type: STRICT_DNS | ||
lb_policy: ROUND_ROBIN | ||
load_assignment: | ||
cluster_name: httpbin | ||
endpoints: | ||
- lb_endpoints: | ||
- endpoint: | ||
address: | ||
socket_address: | ||
address: httpbin.org | ||
port_value: 80 | ||
hostname: "httpbin.org" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
Oops, something went wrong.