gdritter repos httputils / a35954d
[REBASE ME} Getty Ritter 8 years ago
4 changed file(s) with 189 addition(s) and 39 deletion(s). Collapse all Expand all
3131 mode: how to forward the request; defaults to "http"
3232 host: which host to forward to; defaults to "localhost"
3333 port: which port to forward to; defaults to "80"
34 path: which path to forward to; defaults to "/dev/null"
34 conf: which path to forward to; defaults to "/dev/null"
3535 ~~~
3636
3737 These are interpreted as follows:
4545 `http`, then Aloysius will forward the HTTP request to the server
4646 listening on the host `host` and the port `port`. If the mode is
4747 `aloys`, then Aloysius will recursively check the configuration
48 directory at `path`.
48 directory at `conf`.
4949
5050 ## Example Setups
5151
7272 > # make ${U}.example.com forward to the user's aloys configuration
7373 > echo "${U}.example.com" >/var/run/aloys/user-${U}/domain
7474 > echo "aloys" >/var/run/aloys/user-${U}/mode
75 > echo "${HOMEDIR}/aloys" >/var/run/aloys/user-${U}/path
75 > echo "${HOMEDIR}/aloys" >/var/run/aloys/user-${U}/conf
7676 > done
7777 $ aloysius /var/run/aloys
7878 ~~~
2020 :host => read_field(dir, "host", "localhost"),
2121 :port => read_field(dir, "port", "80"),
2222 :mode => read_field(dir, "mode", "http"),
23 :path => read_field(dir, "path", "/dev/null"),
2324 }]
2425 end
2526
5556 def find_server(env, paths)
5657 paths.each do |s|
5758 if match(s[:path], env['PATH_INFO']) and match(s[:domain], env['SERVER_NAME']) then
58 return [ 200, { 'Content-Type' => 'text/html'},
59 [ "Forwarding to #{s[:mode]}://#{s[:host]}:#{s[:port]}" ] ]
59 if s[:mode] == "http" then
60 return [ 200, { 'Content-Type' => 'text/html'},
61 [ "Forwarding to #{s[:mode]}://#{s[:host]}:#{s[:port]}" ] ]
62 elsif s[:mode] == "aloys" then
63 return [ 200, { 'Content-Type' => 'text/html'}
64 [ "Deferring to aloys dir at #{s[:path]}" ] ]
65 end
6066 end
6167 end
6268 return [ 500, { 'Content-Type' => 'text/html' },
11 extern crate hyper;
22
3 // use std::io::Write;
4
53 use hyper::Server;
6 use hyper::server::{Request,Response};
4 use hyper::server::{Handler,Request,Response};
75 use hyper::net::Fresh;
86
9 #[derive(Debug, Clone)]
10 struct Delegate {
11 name: String,
7 use std::env::{args,current_dir,set_current_dir};
8 use std::fs::{File,read_dir};
9 use std::io::prelude::Read;
10 use std::iter::Iterator;
11 use std::path::Path;
12
13 mod matching;
14
15 #[derive(Debug)]
16 enum AloysError {
17 IOError(std::io::Error),
18 HyperError(hyper::error::Error),
19 }
20
21 impl From<std::io::Error> for AloysError {
22 fn from(e: std::io::Error) -> AloysError {
23 AloysError::IOError(e)
24 }
25 }
26
27 impl From<hyper::error::Error> for AloysError {
28 fn from(e: hyper::error::Error) -> AloysError {
29 AloysError::HyperError(e)
30 }
1231 }
1332
1433 #[derive(Debug, Clone)]
15 struct PathSpec {
16 forPath: Option<String>,
17 forDomain: Option<String>,
18 delegateTo: Delegate,
34 enum Delegate {
35 Forward(HTTPForward),
36 Defer(Routes),
1937 }
2038
21 fn find_delegate(req: Request, delegates: Vec<PathSpec>) -> Option<Delegate> {
22 for d in delegates.iter() {
23 if let Some(ref domain) = d.forDomain {
24 }
25 if let Some(ref path) = d.forPath {
39 #[derive(Debug, Clone)]
40 struct HTTPForward {
41 domn: String,
42 port: u32,
43 }
44
45 #[derive(Debug, Clone)]
46 struct RouteSpec {
47 for_path: Option<String>,
48 for_domain: Option<String>,
49 delegate_to: Delegate,
50 }
51
52 #[derive(Debug, Clone)]
53 struct Routes {
54 routes: Vec<RouteSpec>,
55 }
56
57 fn with_file_contents<A, F>(path: &Path, default: A, callback: F) -> A
58 where F: FnOnce(String) -> A + 'static {
59 match File::open(path) {
60 Ok(mut f) => {
61 let mut s = String::new();
62 match f.read_to_string(&mut s) {
63 Ok(_) => callback(s),
64 Err(_) => default,
65 }
66 },
67 Err(_) => default,
68 }
69 }
70
71 impl RouteSpec {
72 fn from_path(dir: &Path) -> Result<RouteSpec,AloysError> {
73 let path = with_file_contents(&dir.join("path"), None, |s| Some(s));
74 let domain = with_file_contents(&dir.join("domain"), None, |s| Some(s));
75 let mode = with_file_contents(&dir.join("mode"), "http", |s| s);
76 if mode == "http" {
77 } else if mode == "alyos" {
78 let fwd = with_file_contents(&dir.join("conf"),
79 Path::new(&"/dev/null"),
80 |s| Path::new(&s));
81 let rts = try!(Routes::load_paths(fwd));
82 Ok(RouteSpec { for_path: path,
83 for_domain: domain,
84 delegate_to: Delegate::
85 } else {
2686
2787 }
28 return Some(d.delegateTo.clone());
88 // let forPath = match File::open(dir.join("path")) {
89 // Ok(f) => {};
90 // Err
91 // };
92 Ok(RouteSpec { for_path: None,
93 for_domain: None,
94 delegate_to: Delegate::Defer(Routes { routes: vec![] }),
95 })
2996 }
30 return None;
3197 }
3298
33 fn dispatch(req: Request, res: Response<Fresh>, delegates: Vec<PathSpec>) {
34 println!("{:?}", req.headers);
35 res.send(b"Unhandled request.").unwrap();
99 impl Routes {
100 fn load_paths(dir: &Path) -> Result<Routes,AloysError> {
101 let mut routes = vec![];
102 for entry in try!(read_dir(dir)) {
103 let entry_path = try!(entry).path();
104 routes.push(try!(RouteSpec::from_path(entry_path.as_path())));
105 }
106 Ok(Routes { routes: routes })
107 }
108
109 fn dispatch(&self, req: &Request, res: Response<Fresh>) {
110 match self.find_delegate(&req) {
111 Some(delegate) => delegate.run(req, res),
112 None => {
113 let _ = res.send(b"[no matching handler]");
114 },
115 }
116 }
117
118 fn find_delegate(&self, req: &Request) -> Option<&Delegate> {
119 for d in self.routes.iter() {
120 if let Some(ref d) = d.for_domain {
121 println!("Matching against {:?}", d);
122 }
123 if let Some(ref p) = d.for_path {
124 println!("Matching against {:?}", p);
125 }
126 return Some(&d.delegate_to);
127 }
128 return None;
129 }
36130 }
37131
38 fn hello(req: Request, res: Response<Fresh>) {
39 res.send(b"Unhandled request.").unwrap();
132 impl Handler for Routes {
133 fn handle(&self, req: Request, res: Response<Fresh>) {
134 self.dispatch(&req, res);
135 }
136 }
137
138 impl Delegate {
139 fn run(&self, req: &Request, res: Response<Fresh>) {
140 match self {
141 &Delegate::Forward(_) => {
142 let _ = res.send(b"[matched by me!]");
143 },
144 &Delegate::Defer(ref rts) => {
145 rts.dispatch(req, res);
146 },
147 }
148 }
149 }
150
151
152 fn run_server() -> Result<(), AloysError> {
153 if let Some(target_dir) = args().next() {
154 println!("switching to directory {:}", target_dir);
155 try!(set_current_dir(Path::new(&target_dir)));
156 }
157 let cwd = try!(current_dir());
158 let paths = try!(Routes::load_paths(cwd.as_path()));
159 let srv = try!(Server::http("127.0.0.1:8080"));
160 let _ = srv.handle(paths);
161 Ok(())
40162 }
41163
42164 fn main() {
43 let sample: Vec<PathSpec> = vec![
44 PathSpec { forPath: None,
45 forDomain: None,
46 delegateTo: Delegate { name: "default".to_owned() } } ];
47 match Server::http("127.0.0.1:8080") {
48 Ok(s) => {
49 let _ = s.handle(hello);
50 }
51 Err(e) => {
52 println!("Unable to run server: {:?}", e);
53 }
54 }
165 match run_server() {
166 Ok(_) => (),
167 Err(e) => println!("Got error: {:?}", e),
168 };
55169 }
1 pub fn do_match(spec_str: &str, string_str: &str) -> bool {
2 let spec = spec_str.as_bytes();
3 let string = string_str.as_bytes();
4 let mut stack = vec![(0, 0)];
5 while let Some((i, j)) = stack.pop() {
6 if i >= spec.len() && j >= spec.len() {
7 return true;
8 } else if i >= spec.len() || j >= spec.len() {
9 continue;
10 } else if spec[i] == 0x2a {
11 stack.push((i, j+1));
12 stack.push((i+1, j+1));
13 } else if spec[i] == string[j] {
14 stack.push((i+1, j+1));
15 }
16 }
17 false
18 }
19
20 #[test]
21 fn test_match() {
22 assert!(do_match("foo", "foo"));
23 assert!(do_match("*oo", "foo"));
24 assert!(do_match("f*o", "foo"));
25 assert!(do_match("fo*", "foo"));
26 assert!(do_match("f*", "foo"));
27 assert!(do_match("*o", "foo"));
28 assert!(do_match("*", "foo"));
29 assert!(!do_match("foo", "bar"));
30 }