extern crate chrono; extern crate dotenv; extern crate getrandom; extern crate base64; use std::net::SocketAddr; use std::convert::Infallible; // our main dispatcher basically never fails hence why we use this use std::env; use tokio; use hyper::{ self, Server, Response, Request, Body, Method, StatusCode, service::{make_service_fn, service_fn} }; use mysql_async::Pool; use dotenv::dotenv; mod auth; use auth::AuthReason; mod routes; mod invites; mod channels; mod members; mod messages; mod http_params; mod perms; mod db_types; async fn route_dispatcher(pool: &Pool, resp: &mut Response, meth: &Method, path: &str, params: serde_json::Value) { // At some point we should have some way of hiding this obnoxious complexity use routes::resolve_dynamic_route; println!("{}: {}", meth, path); match (meth, path) { (&Method::GET, routes::INVITE_JOIN) => { if let Err(_) = invites::route_join_invite_code(pool, resp, params).await { *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } }, (&Method::GET, routes::INVITE_CREATE) => { if let Err(_) = invites::create_invite(pool, resp).await { *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } }, (&Method::GET, routes::CHANNELS_LIST) => channels::list_channels(pool, resp).await, (&Method::POST, routes::CHANNELS_CREATE) => channels::create_channel(pool, resp, params).await, (&Method::POST, routes::CHANNELS_DELETE) => channels::delete_channel(pool, resp, params).await, (&Method::POST, routes::MESSAGE_SEND) => messages::send_message(pool, resp, params).await, _ => { // We attempt dynamic routes as fallback for a few reasons // 1. theres less of these than there are the static routes // 2. because of the above and that this is wholly more expensive than static routse // we can justify putting in tons of branches since we're likely to: // far jump here, lose cache, and still be be network bound // Computatinoal bounds are really of no concern with this api since // we're not doing any heavy calculations at any point if let Some(route) = resolve_dynamic_route(path) { *resp.status_mut() = StatusCode::OK; println!("Static part: {}", route.base); println!("Dynamic part: {}", route.dynamic); } else { println!("NOT FOUND: {}: {}", meth, path); *resp.status_mut() = StatusCode::NOT_FOUND } } } } async fn main_responder(request: Request) -> Result, hyper::Error>{ use AuthReason::*; let mut response = Response::new(Body::empty()); let (parts, mut body) = request.into_parts(); let method = parts.method; let path = parts.uri.path(); let params_res = http_params::parse_params(&mut body).await; if let Ok(params) = params_res { let pool = Pool::new(&env::var("DATABASE_URL").unwrap()); if let Ok(auth_result) = auth::wall_entry(path, &pool, ¶ms).await { // Deal with permissions errors at this point match auth_result { OpenAuth | Good => route_dispatcher(&pool, &mut response, &method, path, params).await, NoKey => { println!("AUTH: NoKey/BadKey"); *response.status_mut() = StatusCode::UNAUTHORIZED }, } } else { *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } } else { println!("PARSER: Parameter parsing failed"); *response.status_mut() = StatusCode::BAD_REQUEST; } Ok(response) } async fn shutdown_signal() { tokio::signal::ctrl_c() .await .expect("Failed to capture ctrl-c signal"); } #[tokio::main] async fn main() { dotenv().ok(); println!("Servering on localhost:8888"); let addr = SocketAddr::from(([127,0,0,1], 8888)); let service = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(main_responder)) }); let server = Server::bind(&addr).serve(service); let graceful_shutdown = server.with_graceful_shutdown(shutdown_signal()); if let Err(e) = graceful_shutdown.await { eprintln!("Server shutdown error: {}", e); } }