@@ -16,19 +16,14 @@ pub struct ScriptedGatewayPolicy {
1616impl ScriptedGatewayPolicy {
1717 pub fn from_source ( source : impl Into < String > ) -> Result < Self , String > {
1818 let source = source. into ( ) ;
19- run_string ( & wrap_request_source ( & source, "GET" , "/" , "" ) ) ?;
19+ let request = RequestHeader :: build ( "GET" , b"/" , None )
20+ . map_err ( |err| format ! ( "failed to build Pingora request: {err}" ) ) ?;
21+ run_string ( & wrap_request_source ( & source, & request) , & request) ?;
2022 Ok ( Self { source } )
2123 }
2224
2325 pub fn evaluate_request ( & self , request : & RequestHeader ) -> Result < GatewayDecision , String > {
24- let method = request. method . as_str ( ) ;
25- let path = String :: from_utf8_lossy ( request. raw_path ( ) ) ;
26- let tier = request
27- . headers
28- . get ( "x-user-tier" )
29- . and_then ( |value| value. to_str ( ) . ok ( ) )
30- . unwrap_or ( "" ) ;
31- let output = run_string ( & wrap_request_source ( & self . source , method, & path, tier) ) ?;
26+ let output = run_string ( & wrap_request_source ( & self . source , request) , request) ?;
3227 parse_decision ( & output)
3328 }
3429
@@ -69,23 +64,57 @@ fn parse_decision(output: &str) -> Result<GatewayDecision, String> {
6964 Err ( format ! ( "unknown gateway decision '{output}'" ) )
7065}
7166
72- fn wrap_request_source ( policy : & str , method : & str , path : & str , tier : & str ) -> String {
67+ fn wrap_request_source ( policy : & str , request : & RequestHeader ) -> String {
68+ let method = request. method . as_str ( ) ;
69+ let path = String :: from_utf8_lossy ( request. raw_path ( ) ) ;
7370 format ! (
74- "let method = {};\n let path = {};\n let tier = {}; \ n {}" ,
71+ "let method = {};\n let path = {};\n {}" ,
7572 rss_string( method) ,
76- rss_string( path) ,
77- rss_string( tier) ,
73+ rss_string( & path) ,
7874 policy
7975 )
8076}
8177
82- fn run_string ( source : & str ) -> Result < String , String > {
83- match run_value ( source) ? {
78+ fn run_string ( source : & str , request : & RequestHeader ) -> Result < String , String > {
79+ match run_value ( source, request ) ? {
8480 Value :: String ( value) => Ok ( value. as_str ( ) . to_string ( ) ) ,
8581 other => Err ( format ! ( "script returned {other:?}; expected string" ) ) ,
8682 }
8783}
8884
85+ #[ derive( Debug , Clone ) ]
86+ struct PingoraRequestSnapshot {
87+ method : String ,
88+ headers : Vec < ( String , String ) > ,
89+ }
90+
91+ impl PingoraRequestSnapshot {
92+ fn from_request ( request : & RequestHeader ) -> Self {
93+ let headers = request
94+ . headers
95+ . iter ( )
96+ . filter_map ( |( name, value) | {
97+ Some ( (
98+ name. as_str ( ) . to_ascii_lowercase ( ) ,
99+ value. to_str ( ) . ok ( ) ?. to_string ( ) ,
100+ ) )
101+ } )
102+ . collect ( ) ;
103+ Self {
104+ method : request. method . as_str ( ) . to_string ( ) ,
105+ headers,
106+ }
107+ }
108+
109+ fn header ( & self , name : & str ) -> String {
110+ let needle = name. to_ascii_lowercase ( ) ;
111+ self . headers
112+ . iter ( )
113+ . find_map ( |( header_name, value) | ( header_name == & needle) . then ( || value. clone ( ) ) )
114+ . unwrap_or_default ( )
115+ }
116+ }
117+
89118struct GatewayDecisionHost ;
90119
91120impl HostFunction for GatewayDecisionHost {
@@ -99,10 +128,51 @@ impl HostFunction for GatewayDecisionHost {
99128 }
100129}
101130
102- fn run_value ( source : & str ) -> Result < Value , String > {
131+ struct PingoraHeaderHost {
132+ request : PingoraRequestSnapshot ,
133+ }
134+
135+ impl HostFunction for PingoraHeaderHost {
136+ fn call ( & mut self , _vm : & mut Vm , args : & [ Value ] ) -> Result < CallOutcome , VmError > {
137+ match args {
138+ [ Value :: String ( name) ] => Ok ( CallOutcome :: Return ( CallReturn :: one ( Value :: string (
139+ self . request . header ( name. as_str ( ) ) ,
140+ ) ) ) ) ,
141+ _ => Err ( VmError :: TypeMismatch ( "header name string" ) ) ,
142+ }
143+ }
144+ }
145+
146+ struct PingoraMethodIsHost {
147+ request : PingoraRequestSnapshot ,
148+ }
149+
150+ impl HostFunction for PingoraMethodIsHost {
151+ fn call ( & mut self , _vm : & mut Vm , args : & [ Value ] ) -> Result < CallOutcome , VmError > {
152+ match args {
153+ [ Value :: String ( expected) ] => Ok ( CallOutcome :: Return ( CallReturn :: one ( Value :: Bool (
154+ self . request . method == expected. as_str ( ) ,
155+ ) ) ) ) ,
156+ _ => Err ( VmError :: TypeMismatch ( "method string" ) ) ,
157+ }
158+ }
159+ }
160+
161+ fn run_value ( source : & str , request : & RequestHeader ) -> Result < Value , String > {
103162 let compiled = compile_source ( source) . map_err ( |err| err. to_string ( ) ) ?;
163+ let snapshot = PingoraRequestSnapshot :: from_request ( request) ;
104164 let mut vm = Vm :: new ( compiled. program ) ;
105165 vm. bind_function ( "gateway_decision" , Box :: new ( GatewayDecisionHost ) ) ;
166+ vm. bind_function (
167+ "pingora_header" ,
168+ Box :: new ( PingoraHeaderHost {
169+ request : snapshot. clone ( ) ,
170+ } ) ,
171+ ) ;
172+ vm. bind_function (
173+ "pingora_method_is" ,
174+ Box :: new ( PingoraMethodIsHost { request : snapshot } ) ,
175+ ) ;
106176 let status = vm. run ( ) . map_err ( |err| err. to_string ( ) ) ?;
107177 if status != VmStatus :: Halted {
108178 return Err ( format ! ( "script did not halt: {status:?}" ) ) ;
0 commit comments