fedimovies/src/main.rs
Rafael Caricio 1e40a42524
Support database connection via SSL
This is required to use managed Postgres databases. It is necessary to
use SSL connection to the remote host as the connection goes through the
open internet.
2023-04-26 12:07:36 +02:00

170 lines
7.1 KiB
Rust

use actix_cors::Cors;
use actix_web::{
dev::Service, http::Method, middleware::Logger as ActixLogger, web, App, HttpResponse,
HttpServer,
};
use tokio::sync::Mutex;
use fedimovies::activitypub::views as activitypub;
use fedimovies::atom::views::atom_scope;
use fedimovies::http::{
create_auth_error_handler, create_default_headers_middleware, json_error_handler,
};
use fedimovies::job_queue::scheduler;
use fedimovies::logger::configure_logger;
use fedimovies::mastodon_api::accounts::views::account_api_scope;
use fedimovies::mastodon_api::apps::views::application_api_scope;
use fedimovies::mastodon_api::custom_emojis::views::custom_emoji_api_scope;
use fedimovies::mastodon_api::directory::views::directory_api_scope;
use fedimovies::mastodon_api::instance::views::instance_api_scope;
use fedimovies::mastodon_api::markers::views::marker_api_scope;
use fedimovies::mastodon_api::media::views::media_api_scope;
use fedimovies::mastodon_api::notifications::views::notification_api_scope;
use fedimovies::mastodon_api::oauth::views::oauth_api_scope;
use fedimovies::mastodon_api::search::views::search_api_scope;
use fedimovies::mastodon_api::settings::views::settings_api_scope;
use fedimovies::mastodon_api::statuses::views::status_api_scope;
use fedimovies::mastodon_api::subscriptions::views::subscription_api_scope;
use fedimovies::mastodon_api::timelines::views::timeline_api_scope;
use fedimovies::nodeinfo::views as nodeinfo;
use fedimovies::web_client::views as web_client;
use fedimovies::webfinger::views as webfinger;
use fedimovies_config::{parse_config, Environment, REEF_VERSION};
use fedimovies_models::database::migrate::apply_migrations;
use fedimovies_models::database::{create_pool, get_database_client};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let (config, config_warnings) = parse_config();
configure_logger(config.log_level);
log::info!("config loaded from {}", config.config_path);
for warning in config_warnings {
log::warn!("{}", warning);
}
// https://wiki.postgresql.org/wiki/Number_Of_Database_Connections
let db_pool_size = num_cpus::get() * 2;
let db_pool = create_pool(
&config.database_url,
config.tls_ca_file.as_ref().map(|s| s.as_path()),
db_pool_size,
);
let mut db_client = get_database_client(&db_pool).await.unwrap();
apply_migrations(&mut db_client).await;
if !config.media_dir().exists() {
std::fs::create_dir(config.media_dir()).expect("failed to create media directory");
};
std::mem::drop(db_client);
log::info!(
"app initialized; version {}, environment = '{:?}'",
REEF_VERSION,
config.environment,
);
scheduler::run(config.clone(), db_pool.clone());
log::info!("scheduler started");
let num_workers = std::cmp::max(num_cpus::get(), 4);
let http_socket_addr = format!("{}:{}", config.http_host, config.http_port,);
// Mutex is used to make server process incoming activities sequentially
let inbox_mutex = web::Data::new(Mutex::new(()));
let http_server = HttpServer::new(move || {
let cors_config = match config.environment {
Environment::Development => Cors::permissive(),
Environment::Production => {
let mut cors_config = Cors::default();
for origin in config.http_cors_allowlist.iter() {
cors_config = cors_config.allowed_origin(origin);
}
cors_config
.allowed_origin(&config.instance_url())
.allowed_origin_fn(|origin, req_head| {
req_head.method == Method::GET
|| origin.as_bytes().starts_with(b"http://localhost:")
})
.allow_any_method()
.allow_any_header()
.expose_any_header()
}
};
let payload_size_limit = 2 * config.limits.media.file_size_limit;
let mut app = App::new()
.wrap(cors_config)
.wrap(ActixLogger::new("%r : %s : %{r}a"))
.wrap_fn(|req, srv| {
// Always log server errors (500-599)
let fut = srv.call(req);
async {
let res = fut.await?;
if let Some(error) = res.response().error() {
if error.as_response_error().status_code().is_server_error() {
log::warn!(
"{} {} : {}",
res.request().method(),
res.request().path(),
error,
);
};
};
Ok(res)
}
})
.wrap(create_auth_error_handler())
.wrap(create_default_headers_middleware())
.app_data(web::PayloadConfig::default().limit(payload_size_limit))
.app_data(
web::JsonConfig::default()
.limit(payload_size_limit)
.error_handler(json_error_handler),
)
.app_data(web::Data::new(config.clone()))
.app_data(web::Data::new(db_pool.clone()))
.app_data(web::Data::clone(&inbox_mutex))
.service(actix_files::Files::new("/media", config.media_dir()))
.service(oauth_api_scope())
.service(account_api_scope())
.service(application_api_scope())
.service(custom_emoji_api_scope())
.service(directory_api_scope())
.service(instance_api_scope())
.service(marker_api_scope())
.service(media_api_scope())
.service(notification_api_scope())
.service(search_api_scope())
.service(settings_api_scope())
.service(status_api_scope())
.service(subscription_api_scope())
.service(timeline_api_scope())
.service(webfinger::webfinger_view)
.service(activitypub::actor_scope())
.service(activitypub::instance_actor_scope())
.service(activitypub::object_view)
.service(activitypub::emoji_view)
.service(activitypub::tag_view)
.service(atom_scope())
.service(nodeinfo::get_nodeinfo_jrd)
.service(nodeinfo::get_nodeinfo_2_0)
.service(nodeinfo::get_nodeinfo_2_1)
.service(web_client::profile_page_redirect())
.service(web_client::profile_acct_page_redirect())
.service(web_client::post_page_redirect())
.service(
// Fallback for well-known paths
web::resource("/.well-known/{path}").to(HttpResponse::NotFound),
);
if let Some(ref web_client_dir) = config.web_client_dir {
app = app.service(web_client::static_service(web_client_dir));
};
app
});
log::info!("listening on {}", http_socket_addr);
http_server
.workers(num_workers)
.bind(http_socket_addr)?
.run()
.await
}