109 lines
3.6 KiB
Rust
109 lines
3.6 KiB
Rust
use http::HeaderMap;
|
|
use rust_proxy::{
|
|
acl::{routing_path, Acl, Action, Rule as AclRule},
|
|
config::{compile, demo_config, ValidationPolicy},
|
|
headers::{apply as apply_headers, HeaderRules, Operation, Rule as HeaderRule},
|
|
vhost::{LocationResponse, PathRoute},
|
|
};
|
|
|
|
fn policy() -> ValidationPolicy {
|
|
ValidationPolicy {
|
|
tls_addr: None,
|
|
cert_dir: None,
|
|
data_dir: None,
|
|
proxy_addr: "127.0.0.1:8080".parse().unwrap(),
|
|
admin_addr: "127.0.0.1:9090".parse().unwrap(),
|
|
}
|
|
}
|
|
|
|
fn local_route() -> PathRoute {
|
|
PathRoute {
|
|
id: "local-status".into(),
|
|
path_prefix: "/status".into(),
|
|
response: Some(LocationResponse {
|
|
status: 200,
|
|
content_type: "text/plain; charset=utf-8".into(),
|
|
body: "ok".into(),
|
|
}),
|
|
..PathRoute::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn percent_encoded_spaces_are_valid_request_paths() {
|
|
assert_eq!(
|
|
routing_path("/Ubuntu%2014.04%20LTS/").unwrap(),
|
|
"/Ubuntu 14.04 LTS/"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_slashes_route_normally_but_reach_upstream_unchanged() {
|
|
let raw_target = "/api//v1/items";
|
|
let routing_target = routing_path(raw_target).unwrap();
|
|
assert_eq!(routing_target, "/api/v1/items");
|
|
|
|
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
|
|
config.vhosts[0].routes = vec![PathRoute {
|
|
id: "api".into(),
|
|
path_prefix: "/api".into(),
|
|
..PathRoute::default()
|
|
}];
|
|
let snapshot = compile(config, 1, &policy()).unwrap();
|
|
let selected = snapshot.select("localhost", &routing_target).unwrap();
|
|
assert_eq!(selected.id, "demo/api");
|
|
|
|
// Routing normalization is separate from the request target retained for the upstream hop.
|
|
assert_eq!(raw_target, "/api//v1/items");
|
|
}
|
|
|
|
#[test]
|
|
fn local_location_response_uses_response_rules_without_opening_backend() {
|
|
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
|
|
let mut route = local_route();
|
|
route.headers = HeaderRules {
|
|
request: vec![],
|
|
response: vec![HeaderRule {
|
|
op: Operation::Set,
|
|
name: "x-local-response".into(),
|
|
value: Some("yes".into()),
|
|
}],
|
|
};
|
|
config.vhosts[0].routes = vec![route];
|
|
|
|
let snapshot = compile(config, 1, &policy()).unwrap();
|
|
let selected = snapshot.select("localhost", "/status").unwrap();
|
|
let response = selected.response.as_ref().expect("route must remain local");
|
|
assert_eq!(response.status, 200);
|
|
assert_eq!(response.body, "ok");
|
|
|
|
let mut headers = HeaderMap::new();
|
|
apply_headers(&mut headers, &selected.headers.response);
|
|
assert_eq!(headers.get("x-local-response").unwrap(), "yes");
|
|
|
|
// The proxy checks route.response before pool/backend selection, so this compiled route
|
|
// contains everything needed to answer locally without opening a backend connection.
|
|
assert!(selected.response.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn local_location_response_still_respects_route_acl() {
|
|
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
|
|
let mut route = local_route();
|
|
route.acl = Some(Acl {
|
|
default: Action::Deny,
|
|
rules: vec![AclRule {
|
|
network: "10.0.0.0/8".into(),
|
|
action: Action::Allow,
|
|
}],
|
|
});
|
|
config.vhosts[0].routes = vec![route];
|
|
|
|
let snapshot = compile(config, 1, &policy()).unwrap();
|
|
let selected = snapshot.select("localhost", "/status").unwrap();
|
|
let acl = selected.acl.as_ref().expect("route ACL must be compiled");
|
|
assert!(acl.allows("10.1.2.3".parse().unwrap()));
|
|
assert!(!acl.allows("192.0.2.1".parse().unwrap()));
|
|
assert!(selected.response.is_some());
|
|
}
|