//! Actix-web based HTTP server for the publication server. //! //! Here we deal with booting and setup, and once active deal with parsing //! arguments and routing of requests, typically handing off to the //! daemon::api::endpoints functions for processing and responding. use std::convert::Infallible; use std::io; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use futures::future::ok; use futures::task::SpawnExt; use futures::Future; use futures::TryFutureExt; use hyper; use hyper::server::conn::{AddrIncoming, Http}; use hyper::service::{make_service_fn, service_fn}; use tokio::net::TcpListener; use tokio_proto::TcpServer; use crate::commons::error::Error; use crate::daemon::config::Config; use crate::daemon::endpoints; use crate::daemon::endpoints::*; use crate::daemon::http::{tls, tls_keys}; use crate::daemon::http::{HttpResponse, Request}; use crate::daemon::krillserver::KrillServer; //------------ AppServer ----------------------------------------------------- #[derive(Clone)] pub struct State(Arc>); impl State { pub fn read(&self) -> RwLockReadGuard { self.0.read().unwrap() } pub fn write(&self) -> RwLockWriteGuard { self.0.write().unwrap() } } pub async fn start(config: Config) -> Result<(), Error> { let state = { let krill = KrillServer::build(&config)?; State(Arc::new(RwLock::new(krill))) }; let service = make_service_fn(move |_| { let mut state = state.clone(); async move { Ok::<_, Infallible>(service_fn(move |req: hyper::Request| { let mut state = state.clone(); async move { map_requests(req, state) } })) } }); tls_keys::create_key_cert_if_needed(&config.data_dir) .map_err(|e| Error::HttpsSetup(format!("{}", e)))?; let mut server_config_builder = tls::TlsConfigBuilder::new() .cert_path(tls_keys::cert_file_path(&config.data_dir)) .key_path(tls_keys::key_file_path(&config.data_dir)); let server_config = server_config_builder.build().unwrap(); let acceptor = tls::TlsAcceptor::new( server_config, AddrIncoming::bind(&config.socket_addr()).unwrap(), ); let server = hyper::Server::builder(acceptor) .serve(service) .map_err(|e| eprintln!("Server error: {}", e)); if let Err(_) = server.await { eprintln!("Krill failed to start"); ::std::process::exit(1); } Ok(()) } fn map_requests( req: hyper::Request, mut state: State, ) -> Result, Error> { let req = Request::new(req, state); debug!("Got request for: {}", req.path.full()); health(req) .or_else(metrics) .or_else(stats) .or_else(api) .or_else(not_found) .map_err(|_| Error::custom("should have received not found response"))? .res() // // let post_limit_api = config.post_limit_api; // let post_limit_rfc8181 = config.post_limit_rfc8181; // let post_limit_rfc6492 = config.post_limit_rfc6492; // HttpServer::new(move || { // .wrap(middleware::Logger::default()) // // API end-points // .service( // scope("/api/v1") // .data(web::JsonConfig::default().limit(post_limit_api)) // // Let the UI check if it's authorized // .route("/authorized", get().to(api_authorized)) // // Repositories and their publishers (both embedded and remote) // .route("/publishers", get().to(list_pbl)) // .route("/publishers", post().to(add_pbl)) // .route("/publishers/{handle}", get().to(show_pbl)) // .route("/publishers/{handle}", delete().to(remove_pbl)) // .route( // "/publishers/{handle}/response.xml", // get().to(repository_response_xml), // ) // .route( // "/publishers/{handle}/response.json", // get().to(repository_response_json), // ) // .route("/publishers/stale/{seconds}", get().to(stale_publishers)) // // CAs (both embedded and remote) // .route("/cas", post().to(ca_init)) // .route("/cas", get().to(cas)) // .route("/cas/issues", get().to(endpoints::all_ca_issues)) // .route("/cas/issues/{ca}", get().to(endpoints::ca_issues)) // .route("/cas/{ca}", get().to(ca_info)) // .route("/cas/{ca}/id", post().to(ca_regenerate_id)) // .route("/cas/{ca}/history", get().to(ca_history)) // .route("/cas/{ca}/child_request.xml", get().to(ca_child_req_xml)) // .route("/cas/{ca}/child_request.json", get().to(ca_child_req_json)) // .route("/cas/{ca}/repo", get().to(ca_repo_details)) // .route("/cas/{ca}/repo/state", get().to(ca_repo_state)) // .route( // "/cas/{ca}/repo/request.json", // get().to(ca_publisher_req_json), // ) // .route("/cas/{ca}/repo/request.xml", get().to(ca_publisher_req_xml)) // .route("/cas/{ca}/repo", post().to(ca_repo_update)) // .route("/cas/{ca}/parents", post().to(ca_add_parent)) // .route( // "/cas/{ca}/parents-xml/{parent}", // post().to(ca_add_parent_xml), // ) // .route("/cas/{ca}/parents/{parent}", get().to(ca_my_parent_contact)) // .route("/cas/{ca}/parents/{parent}", post().to(ca_update_parent)) // .route("/cas/{ca}/parents/{parent}", delete().to(ca_remove_parent)) // .route("/cas/{ca}/children", post().to(ca_add_child)) // .route( // "/cas/{ca}/children/{child}/contact", // get().to(ca_parent_contact), // ) // .route( // "/cas/{ca}/children/{child}/parent_response.json", // get().to(ca_parent_res_json), // ) // .route( // "/cas/{ca}/children/{child}/parent_response.xml", // get().to(ca_parent_res_xml), // ) // .route("/cas/{ca}/children/{child}", get().to(ca_show_child)) // .route("/cas/{ca}/children/{child}", post().to(ca_child_update)) // .route("/cas/{ca}/children/{child}", delete().to(ca_child_remove)) // .route("/cas/{ca}/keys/roll_init", post().to(ca_kr_init)) // .route("/cas/{ca}/keys/roll_activate", post().to(ca_kr_activate)) // .route("/cas/{ca}/routes", post().to(ca_routes_update)) // .route("/cas/{ca}/routes", get().to(ca_routes_show)) // // Republish ALL CAs // .route("/cas/republish_all", post().to(republish_all)) // // Force resyncing of all CAs at repo servers // .route("/cas/resync_all", post().to(resync_all)) // // Force refresh of ALL CA certificates // .route("/cas/refresh_all", post().to(refresh_all)) // // Methods that are not found should return a bad request and some explanation // .default_service(web::route().to(api_bad_request)), // ) // // Publication Protocol (RFC 8181) // .service( // Resource::new("/rfc8181/{handle}") // .data(web::PayloadConfig::default().limit(post_limit_rfc8181)) // .route(post().to(rfc8181)), // ) // // Uo-Down Protocol (RFC 6492) // .service( // Resource::new("/rfc6492/{handle}") // .data(web::PayloadConfig::default().limit(post_limit_rfc6492)) // .route(post().to(rfc6492)), // ) // // Public TA related methods // .route("/ta/ta.tal", get().to(tal)) // .route("/ta/ta.cer", get().to(ta_cer)) // // RRDP repository // .route("/rrdp/{path:.*}", get().to(serve_rrdp_files)) // // UI // .route( // "/", // get().to(|| { // HttpResponse::Found() // .header("location", "/index.html") // .finish() // }), // ) // .add_statics() // // Catch all (not found or not allowed) // .default_service( // // 404 for GET request // web::resource("") // .route(web::get().to(not_found)) // // all requests that are not `GET` // .route( // web::route() // .guard(guard::Not(guard::Get())) // .to(HttpResponse::MethodNotAllowed), // ), // ) // }) } //------------ Tests --------------------------------------------------------- // Tested in tests/integration_test.rs #[cfg(test)] mod tests { use std::path::PathBuf; use std::time::Duration; use tokio::time::{delay_for, timeout}; use crate::commons::util::{httpclient, test}; use super::*; #[tokio::test] async fn hello_world() { let dir = test::sub_dir(&PathBuf::from("work")); let server_conf = { // Use a data dir for the storage let data_dir = test::sub_dir(&dir); Config::test(&data_dir) }; tokio::spawn(super::start(server_conf)); assert!(crate::daemon::test::server_ready().await); } }