Skip to content

Commit

Permalink
Add basic webclient
Browse files Browse the repository at this point in the history
Webclient is able to let user login and saves session string in cookies.
  • Loading branch information
sheyderich committed Sep 11, 2015
1 parent 1a24fe8 commit c27677f
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 52 deletions.
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ byteorder = "0.3"
bincode = "0.4.0"
docopt = "0.6"
rustc-serialize = "0.3"
nickel = "*"
plugin = "*"
typemap = "*"
hyper = "*"
cookie = "0.1"
url = "*"

[dependencies.server]
path = "server"

[dependencies.iron]
version = "*"
218 changes: 169 additions & 49 deletions src/webclient/main.rs
Original file line number Diff line number Diff line change
@@ -1,88 +1,208 @@
extern crate iron;
#[macro_use]
extern crate nickel;
extern crate plugin;
extern crate typemap;
extern crate hyper;
extern crate uosql;
extern crate rustc_serialize;
extern crate cookie;
extern crate url;

use iron::prelude::*;
use iron::status;
use uosql::Connection;
use std::io::Read;
use uosql::Error;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex};
use plugin::Extensible;
use hyper::header::{Cookie, SetCookie};
use nickel::{Nickel, HttpRouter};
use cookie::Cookie as CookiePair;
use hyper::method::Method;
use url::form_urlencoded as urlencode;

// Dummy key for typemap
struct ConnKey;
impl typemap::Key for ConnKey {
type Value = Arc<Mutex<Connection>>;
}

#[derive(Debug)]
struct Login {
user : String,
password: String
}

/// Web based client
fn main() {

// Key Value pairs: User + connections
// Every user has one connection
let map: HashMap<String, Connection> = HashMap::new();
let ptr = Arc::new(Mutex::new(map));
let mut server = Nickel::new();
let map: HashMap<String, Arc<Mutex<Connection>>>= HashMap::new();
let map = Arc::new(Mutex::new(map));
let map2 = map.clone();

// Cookie managing
server.utilize(middleware! { |req, res|

// If login data has been posted, continue
if req.origin.method == Method::Post {
return Ok(nickel::Action::Continue(res));
}

// Look for session string in Cookies
let sess = match req.origin.headers.get::<Cookie>() {
// If no Cookie found, go to Login
None => {
let m = HashMap::<i8, i8>::new();
return res.render("src/webclient/templates/login.tpl", &m);
}
// If there is a Cookie, eat it
// (or find the matching UosqlDB-Cookie and extract session string)
Some(cs) => {
if let Some(sess) = cs.to_cookie_jar(&[1u8]).find("UosqlDB") {
sess.value
// There is a cookie, but it is not ours :'(
// Return to Login
} else {
let m = HashMap::<i8, i8>::new();
return res.render("src/webclient/templates/login.tpl", &m);
}
},
};

// We have a session string and look for the matching connection in
// our Session-Connection map
let guard = map.lock().unwrap();
match guard.get(&sess) {
// No matching session: Old cookie
None => {
let mut data = HashMap::new();
data.insert("err_msg", "Invalid Session");
return res.render("src/webclient/templates/login.tpl", &data);
}
// There is a connection, we are logged in, we can enter the site!
Some(con) => {
req.extensions_mut().insert::<ConnKey>(con.clone());
return Ok(nickel::Action::Continue(res));
}
}
});

// Login managing
server.post("/login", middleware! { |req, mut res|

// Read the post data
let mut login_data = String::new();
let read = req.origin.read_to_string(&mut login_data).unwrap();

// Not sufficiently filled in, return to Login with error msg
if read < 15 {
let mut data = HashMap::new();
data.insert("err_msg", "No data given");
return res.render("src/webclient/templates/login.tpl", &data);
}

// Extract login data from Post string
let pairs = urlencode::parse(login_data.as_bytes());
let username = pairs.iter().find(|e| e.0 == "user").map(|e| e.1.clone());
let password = pairs.iter().find(|e| e.0 == "password").map(|e| e.1.clone());

// If eihter username or password are empty, return to Login page
if username.is_none() || password.is_none() {
let mut data = HashMap::new();
data.insert("err_msg", "Not all required fields given");
return res.render("src/webclient/templates/login.tpl", &data);
}

// build Login struct
let login = Login {
user: username.unwrap(),
password: password.unwrap()
};

// Generate new session string
let sess_str = login.user.clone(); // Dummy

let mut chain = Chain::new(move |_: &mut Request| {
let own = ptr.clone();
// Locked until guard dies
let mut guard = own.lock().unwrap();
// Try connect to db server
// Insert connection and session string into hashmap
let mut guard = map2.lock().unwrap();

//==================== TODO Read user data input somehow ==========================//
// Get login data
let username = "Peter".to_string();
// DUMMY DATA
let connection = "127.0.0.1".to_string();
let port = 4242;
let password = "Bob".to_string();

// Get connection of user or build new connection and insert it into
// hashmap
let con = match guard.deref_mut().entry(username.clone()) {
Entry::Occupied(o) => o.into_mut(),
// create new connections
match guard.deref_mut().entry(sess_str.clone()) {
Entry::Occupied(_) => {},
Entry::Vacant(v) => {
let cres = Connection::connect(connection, port,
username, password);
login.user.clone(), login.password.clone());
match cres {
Err(e) => {
match e {
let errstr = match e {
// Connection error handling
// TO DO: Wait for Display/Debug
Error::AddrParse(_) => {
return Ok(Response::with((iron::status::Ok,
format!("Could not connect to specified server."))))
"Could not connect to specified server."
},
Error::Io(_) => {
return Ok(Response::with((iron::status::Ok,
format!("Connection failure. Try again later."))))
"Connection failure. Try again later."
},
Error::Decode(_) => {
return Ok(Response::with((iron::status::Ok,
format!("Could not read data from server."))))
"Could not readfsdfd data from server."
},
Error::Encode(_) => {
return Ok(Response::with((iron::status::Ok,
format!("Could not send data to server."))))
"Could not send data to server."
},
Error::UnexpectedPkg(e) => {
return Ok(Response::with((iron::status::Ok,
format!("{}", e.to_string()))))
Error::UnexpectedPkg(_) => {
"Unexpected Package."
},
Error::Auth(e) => {
return Ok(Response::with((iron::status::Ok,
format!("{}", e.to_string()))))
Error::Auth(_) => {
"Authentication failed."
},
}
};
let mut data = HashMap::new();
data.insert("err", errstr);
return res.render("src/webclient/templates/error.tpl", &data);
}
Ok(c) => v.insert(c),
Ok(c) => {
v.insert(Arc::new(Mutex::new(c)));
},
}
}
};

// Msg to print to web
let msg = format!("Connected (version {}) to {}:{}\n{}\n",
con.get_version(), con.get_ip(),
con.get_port(), con.get_message());
// Set a Cookie with the session string as its value
// sess_str is set to a value here, so we can safely unwrap
let keks = CookiePair::new("UosqlDB".to_owned(), sess_str.clone());
res.headers_mut().set(SetCookie(vec![keks]));

Ok(Response::with((iron::status::Ok, msg)))
// Redirect to the greeting page
*res.status_mut() = nickel::status::StatusCode::Found;
res.headers_mut().set_raw("location", vec![b"/".to_vec()]);
return res.send("");
});
// Build webclient on localhost 3000
Iron::new(chain).http("localhost:3000").unwrap();
}

/*
Get data input from user via webclient and
process it
// Greeting page
server.get("/", middleware! { |req, response|

// Look for connection
let con = req.extensions().get::<ConnKey>().unwrap().lock().unwrap();

*/
// Current display with short welcome message
let version = con.get_version().to_string();
let port = con.get_port().to_string();
let mut data = HashMap::new();

data.insert("name", con.get_username());
data.insert("version", &version);
data.insert("bind", con.get_ip());
data.insert("port", &port);
data.insert("msg", con.get_message());
return response.render("src/webclient/templates/hello.tpl", &data);
});


server.listen("127.0.0.1:6767");
}
7 changes: 7 additions & 0 deletions src/webclient/templates/error.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<body>
<h1>
Error: {{ err }}
</h1>
</body>
</html>
15 changes: 15 additions & 0 deletions src/webclient/templates/hello.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<html>
<body style = "background-color:lightgrey">
<form style = "text-align:right">
<button type="button" id = "logout"> Logout </button>
</form>
<h1 style = "text-align:center">
Hello {{ name }}!
</h1>
<h4 style = "text-align:center; font-family:courier">
Connected (version : {{ version }}) to {{ bind }} : {{ port }} <br>
{{ msg }}
</h4>

</body>
</html>
19 changes: 19 additions & 0 deletions src/webclient/templates/login.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<html>
<body style = "background-color: lightgrey">
<h1 style = "text-align:center">
Login
</h1>
<p style="text-align:center">
{{ err_msg }}
</p>
<p>
<form style = "text-align:center" method = "post" action="/login">
<label for ="user"> Username:</label>
<input type = "text" name="user" id="user" required><br>
<label for ="password"> Password:</label>
<input type = "password" name="password" id="password" required><br>
<input type = "submit" value="Login">
</form>
</p>
</body>
</html>
7 changes: 7 additions & 0 deletions src/webclient/templates/success.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<body>
<h1>
Success!
</h1>
</body>
</html>

0 comments on commit c27677f

Please sign in to comment.