Skip to content

Commit f7fd285

Browse files
committed
Expand Pingora gateway protocol examples
1 parent 1ef1f20 commit f7fd285

10 files changed

Lines changed: 1815 additions & 238 deletions

File tree

‎Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ edition = "2024"
55
license = "Apache-2.0"
66

77
[dependencies]
8-
pd-host-function = { path = "../rustscript/pd-vm/pd-host-function" }
8+
pd-host-function = { path = "../rustscript/pd-host-function" }
99
pingora = "0.8.1"
10-
vm = { package = "pd-vm", path = "../rustscript/pd-vm", default-features = false, features = ["runtime"] }
10+
vm = { package = "pd-vm", path = "../rustscript", default-features = false, features = ["runtime"] }
1111

1212
[dev-dependencies]
1313
pretty_assertions = "1"

‎README.md‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,30 @@ Standalone Pingora integration demo for `pd-vm` / RustScript.
44

55
## What it proves
66

7-
A Pingora gateway can keep the framework and proxy code compiled while moving request/response policy into RustScript:
7+
A Pingora gateway can keep the framework and proxy code compiled while moving request, transport, protocol, and response policy into RustScript:
88

9-
- live `pingora::http::RequestHeader` reads: `pingora::request::method`, `pingora::request::path`, `pingora::request::header`
10-
- live Pingora mutations: `pingora::request::insert_header`, `pingora::response::set_status`, `pingora::response::insert_header`
11-
- host functions are exported with `#[pd_host_function(name = "pingora::...")]`; the `.rss` names and namespaces match the bound host names exactly
9+
- live `pingora::http::RequestHeader` reads and request mutation
10+
- HTTP request/response/exchange host functions compatible with the `pd-edge` examples
11+
- proxy stream functions for native forwarding, bridged forwarding, and exchange-backed streams
12+
- TCP, UDP, TLS termination, WebSocket, and WebRTC-style host functions for protocol policy demos
13+
- host function names are bound in the same namespaces used by the `.rss` scripts: `http`, `proxy`, `tcp`, `udp`, `tls`, `websocket`, `webrtc`, `rate_limit`, and compatibility `pingora::*` names
1214

1315
This does not fork or patch Pingora. It depends on the upstream `pingora` crate plus local `pd-vm` / `pd-host-function` paths only.
1416

17+
## Scripts
18+
19+
| script | focus |
20+
|---|---|
21+
| `scripts/gateway_policy.rss` | direct Pingora request/response policy |
22+
| `scripts/http_proxy.rss` | HTTP exchange, streaming body reads, proxy forwarding, rate limit decision |
23+
| `scripts/tls_termination.rss` | raw transport prelude, TLS handshake, downstream HTTP attach |
24+
| `scripts/websocket_proxy.rss` | WebSocket upstream configuration, message round trip, stream bridge |
25+
| `scripts/transport_matrix.rss` | TCP, TLS, UDP, and WebRTC-style host calls in one policy |
26+
1527
## Run
1628

1729
```bash
1830
cargo test --tests --jobs 4
1931
cargo run --example decision
32+
cargo run --example protocols
2033
```
21-
22-
## Script
23-
24-
See `scripts/gateway_policy.rss`.

‎examples/protocols.rs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use pingora::http::RequestHeader;
2+
use rustscript_pingora_gateway_policy::ScriptedGatewayPolicy;
3+
4+
fn run(label: &str, script: &str, path: &[u8], headers: &[(&str, &str)]) {
5+
let policy = ScriptedGatewayPolicy::from_source(script).expect("script should compile");
6+
let mut request = RequestHeader::build("GET", path, None).expect("request should build");
7+
for (name, value) in headers {
8+
request
9+
.insert_header((*name).to_string(), (*value).to_string())
10+
.expect("header should insert");
11+
}
12+
let result = policy
13+
.evaluate(&mut request)
14+
.expect("script should evaluate");
15+
println!(
16+
"{label}: status={} body={} proxy_events={}",
17+
result.response.status.as_u16(),
18+
result.body,
19+
result.proxy_events.len()
20+
);
21+
}
22+
23+
fn main() {
24+
run(
25+
"http_proxy",
26+
include_str!("../scripts/http_proxy.rss"),
27+
b"/proxy?mode=stream",
28+
&[("x-stream", "1"), ("x-client-id", "paid")],
29+
);
30+
run(
31+
"tls_termination",
32+
include_str!("../scripts/tls_termination.rss"),
33+
b"/terminate?x=1",
34+
&[("x-downstream-scheme", "tcp")],
35+
);
36+
run(
37+
"websocket_proxy",
38+
include_str!("../scripts/websocket_proxy.rss"),
39+
b"/ws",
40+
&[("x-ws-message", "hello-ws")],
41+
);
42+
run(
43+
"transport_matrix",
44+
include_str!("../scripts/transport_matrix.rss"),
45+
b"/transport",
46+
&[],
47+
);
48+
}

‎scripts/http_proxy.rss‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use http;
2+
use proxy;
3+
use rate_limit;
4+
5+
let path: string = http::request::get_path();
6+
let client: string = http::request::get_header("x-client-id");
7+
8+
if !rate_limit::allow(client, 100, 60) {
9+
http::response::set_status(429);
10+
http::response::set_header("x-rustscript-action", "rate-limited");
11+
http::response::set_body("rate limited");
12+
} else {
13+
let exchange: int = http::exchange::default_upstream();
14+
http::exchange::set_target(exchange, "127.0.0.1", 18080);
15+
http::exchange::set_path(exchange, path);
16+
http::exchange::set_header(exchange, "x-forwarded-by", "rustscript-pingora");
17+
18+
if http::request::get_header("x-stream") != "" {
19+
http::exchange::send(exchange);
20+
http::response::set_status(http::exchange::get_status(exchange));
21+
http::response::set_header("content-type", http::exchange::get_header(exchange, "content-type"));
22+
let mut body = "";
23+
while !http::exchange::body::eof(exchange) {
24+
body = body + http::exchange::body::next_chunk(exchange, 8);
25+
}
26+
http::response::set_body(body);
27+
http::response::set_header("x-rustscript-action", "streaming-proxy");
28+
} else {
29+
let downstream: int = proxy::stream::downstream();
30+
let upstream: int = proxy::stream::exchange(exchange);
31+
let status: string = proxy::forward(downstream, upstream, 16);
32+
http::response::set_status(200);
33+
http::response::set_header("x-proxy-status", status);
34+
http::response::set_header("x-rustscript-action", "native-proxy");
35+
}
36+
}

‎scripts/tls_termination.rss‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use http;
2+
use runtime;
3+
use tcp;
4+
use tls;
5+
6+
let downstream: int = tcp::stream::downstream();
7+
let scheme: string = http::request::get_scheme();
8+
9+
if scheme == "tcp" {
10+
let prefix: string = tcp::stream::peek(downstream, 4);
11+
if prefix == "GET " || prefix == "POST" || prefix == "HEAD" {
12+
http::downstream::attach_transport();
13+
http::response::set_header("x-termination", "plain-http");
14+
} else {
15+
let session: int = tls::session::from_socket(downstream);
16+
tls::session::set_alpn(session, "h2,http/1.1");
17+
tls::session::set_sni(session, "gateway.local");
18+
if tls::session::handshake(session) {
19+
http::downstream::attach_transport();
20+
http::response::set_header("x-termination", "tls");
21+
http::response::set_header("x-tls-alpn", tls::session::get_alpn(session));
22+
http::response::set_header("x-tls-peer", tls::session::get_peer_name(session));
23+
} else {
24+
tcp::stream::close(downstream);
25+
runtime::exit();
26+
}
27+
}
28+
}
29+
30+
http::response::set_status(201);
31+
http::response::set_body(http::request::get_method() + "|" + http::request::get_path_with_query());

‎scripts/transport_matrix.rss‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use http;
2+
use tcp;
3+
use tls;
4+
use udp;
5+
use webrtc;
6+
7+
let tcp_stream: int = tcp::stream::new();
8+
tcp::stream::set_target(tcp_stream, "127.0.0.1", 19000);
9+
tcp::stream::connect(tcp_stream);
10+
tcp::stream::write(tcp_stream, "hello-tcp");
11+
12+
let session: int = tls::session::from_socket(tcp_stream);
13+
tls::session::set_verify(session, false);
14+
tls::session::set_alpn(session, "edge/1");
15+
tls::session::handshake(session);
16+
17+
let socket: int = udp::socket::new();
18+
udp::socket::set_target(socket, "127.0.0.1", 19001);
19+
udp::socket::send_text(socket, "hello-udp");
20+
let udp_reply: string = udp::socket::recv_text(socket, 64);
21+
22+
let rtc: int = webrtc::connection::new();
23+
webrtc::connection::set_ice_servers(rtc, "stun:localhost");
24+
webrtc::connection::set_data_channel_label(rtc, "policy");
25+
webrtc::connection::connect(rtc);
26+
webrtc::connection::send_text(rtc, "hello-rtc");
27+
28+
http::response::set_status(200);
29+
http::response::set_header("x-tcp-phase", tcp::stream::get_phase(tcp_stream));
30+
http::response::set_header("x-tls-phase", tls::session::get_phase(session));
31+
http::response::set_header("x-tls-alpn", tls::session::get_alpn(session));
32+
http::response::set_header("x-udp-reply", udp_reply);
33+
http::response::set_header("x-webrtc-phase", webrtc::connection::get_phase(rtc));
34+
http::response::set_body("transport matrix ok");

‎scripts/websocket_proxy.rss‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use http;
2+
use proxy;
3+
use websocket;
4+
5+
let downstream: int = websocket::connection::downstream();
6+
http::response::set_header("x-downstream-ws", websocket::connection::get_phase(downstream));
7+
8+
let upstream: int = websocket::connection::default_upstream();
9+
websocket::connection::set_target(upstream, "127.0.0.1", 18091);
10+
websocket::connection::set_path(upstream, "/echo");
11+
websocket::connection::set_subprotocols(upstream, "chat,superchat");
12+
websocket::connection::connect(upstream);
13+
14+
let mut message: string = http::request::get_header("x-ws-message");
15+
if message == "" {
16+
message = "pingora-websocket";
17+
}
18+
websocket::connection::send_text(upstream, message);
19+
let reply: string = websocket::connection::read_text(upstream);
20+
21+
let down_stream: int = proxy::stream::downstream();
22+
let up_stream: int = proxy::stream::from_websocket_binary(upstream);
23+
let proxy_status: string = proxy::bridge(down_stream, up_stream, 8);
24+
25+
http::response::set_status(200);
26+
http::response::set_header("x-ws-protocol", websocket::connection::get_subprotocol(upstream));
27+
http::response::set_header("x-proxy-status", proxy_status);
28+
http::response::set_body(reply);
29+
websocket::connection::close(upstream, 1000, "complete");
30+
http::response::set_header("x-ws-phase", websocket::connection::get_phase(upstream));

0 commit comments

Comments
 (0)