freechat/server/src/main.rs

128 lines
4.4 KiB
Rust

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<Body>, 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<Body>) -> Result<Response<Body>, 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, &params).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);
}
}